u/PowerShellStunnah

179 post karma · 1,317 comment karma · joined Jan 2015

PowerShellStunnah

You've already leaked the credentials to the endpoint, you should consider them compromised.

I would take a different approach:

  • Client script requests a set of temporary local admin credentials from a JEA endpoint
  • JEA endpoint is authorized to fetch password from AD (if using LAPS) or a key vault
  • JEA endpoint schedules expiration of the password
  • Client script proceeds to elevate using the credentials provided

See this talk from last year for a better overview of this approach (use case is exactly the same - end user need temporary local admin)

PowerShellStunnah

Use JEA to handle the actual call, allow domain members to connect to the JEA endpoint

PowerShellStunnah

Use Get-ScheduledTask to retrieve all sceduled tasks. The Principal property will hold an object describing the logon user, and Triggers will contain the triggers from which you can infer scheduling information

PowerShellStunnah

That's because whoever designed Export-Csv didn't take advantage of parameter validation to ensure Path is correctly passed. It's simply a choice on the part of the author.

You could choose to do that in a script function as well:

function Export-Something
{
    param(
        [string]$Path
    )

    if([string]::IsNullOrEmpty($Path)){ throw "Path is empty" }
}

I'd strongly recommend NEVER doing that - as you've found it messes with the user's expectations - but it's still valid code :)

PowerShellStunnah

Start by converting the json to an object using ConvertFrom-Json:

$object = @'
{
"server1": {
    "description": "my server",
    "ip": "1.1.1.1",
    "vcpu": 4,
    "ram": 8,
    "disks": "60,25"
},
"server2": {
    "description": "my server 2",
    "ip": "1.1.1.2",
    "vcpu": 8,
    "ram": 16,
    "disks": "60,25"
}
}
'@ |ConvertFrom-Json

Now we just need to get the values of each property from that object:

$values = $object.psobject.Properties.Value

... and that's it :)

PowerShellStunnah

Here it's mandatory to supply an argument to the -LogName parameter - notice it's the only one not contained in []

PowerShellStunnah

For PowerShell, specifically, see the about_Command_Syntax help file

PowerShellStunnah

Yes!

The name inside of <> is the type of the argument. In .NET (and therefore, by extension, in PowerShell), a type name like someType[] means "an array of [someType]".

So in the example above, you're expected to pass a single string as a Name argument, but you can specify multiple strings as the Component argument:

Command -Name "here's a single string" -Component @("her'es","an","array","of","them")
PowerShellStunnah

Or, the other way around: -exclude is only ever valid if -subtree has been specified :)

PowerShellStunnah

This is not specific to PowerShell (dsquery.exe is an executable from way back), but generally it goes:

[-switchName <argument>]  # This switch is OPTIONAL
[-switchName] <argument>  # Providing an argument is mandatory, but you don't need to specify the switch name explicitly
{-s1 | -s2 | -s3}         # These are mutually exclusive switches - you can only specify ONE of them
PowerShellStunnah

The expression 'lockdownNormal' -Or 'lockdownStrict' evaluates to [bool], which is then assigned to $curLockdownMode

Use:

if($curLockdownMode -in 'lockdownNormal','lockdownStrict'){...}
PowerShellStunnah

Reminds me of this one I wrote a few years ago, utilizing runspaces instead of jobs: https://gist.github.com/IISResetMe/daabee94cfe80aa4c0d3a937ae034d0b

  • it attempts to resolve the hostname like ping -a
PowerShellStunnah

You've left a [Parameter] (instead of [Parameter()]) somewhere in the param block

PowerShellStunnah

And some more gravy so you can really soak em

PowerShellStunnah

What is the purpose of skipping the first line of the XML file?

$xml | Select-Object -skip 1
PowerShellStunnah

I'd create an array to hold the input values and keep track of how many we've got that way:

$values = @()
$x = 1
do{
  $values += $(Read-Host "Enter value $x") -as [int]
  $x++
}while($values.Count -lt 5)

return ($values |Measure-Object -Sum).Sum
PowerShellStunnah

Two immediate observations:

  1. Writing data to disk and reading it back all the time is going to be slow, not to mention completely unnecessary.
  2. You're going to have a hard time making consistent improvements with all that repeated code all over the place.

I'd personally start by re-writing it to a point where you only need to change one line of code at a time to test/measure further optimizations you might make, and then of course fix #1:

```powershell $ErrorActionPreference = 'Continue'

Fetch mac address from file like before

$macaddress = Get-Content "$env:APPDATA\DeviceTracker\merakidevicefinder.txt" $justthegammac = $macaddress -replace "([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])", '$1$2:$3$4:$5$6:$7$8:$9$10:$11$12'

Set up API key, TLS settings etc. like before

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $APIKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' $headers = @{ 'Content-Type' = 'application/json' 'X-Cisco-Meraki-API-Key' = $APIKey }

Let's store the sites and their IDs in an ordered dictionary.

Keeping track of the sites/networks like this will allow us

to reuse the same code over and over again for each entry

$Networks = [ordered]@{ Site11 = 'xxxxxxxxxxxxxxxxxxxxx' Site6 = 'xxxxxxxxxxxxxxxxxxxxx' Site12 = 'xxxxxxxxxxxxxxxxxxxxx' Site4 = 'xxxxxxxxxxxxxxxxxxxxx' Site8 = 'xxxxxxxxxxxxxxxxxxxxx' Site5 = 'xxxxxxxxxxxxxxxxxxxxx' Site10 = 'xxxxxxxxxxxxxxxxxxxxx' Site1 = 'xxxxxxxxxxxxxxxxxxxxx' Site3 = 'xxxxxxxxxxxxxxxxxxxxx' Site7 = 'xxxxxxxxxxxxxxxxxxxxx' Site9 = 'xxxxxxxxxxxxxxxxxxxxx' Site2 = 'xxxxxxxxxxxxxxxxxxxxx' }

$APDetails = foreach($site in $Networks.GetEnumerator()){ $SiteName = $site.Key $NetworkID = $site.Value

try {
    $deviceInfo = Invoke-RestMethod -Method Get -Uri "https://api.meraki.com/api/v0/networks/$($NetworkID)/clients/$($justthegammac)/" -Headers $headers -ErrorAction Stop
}
catch {
    Write-Host "No $SiteName" -ForegroundColor Red
    Write-Host $_.Exception.Response.StatusCode.value__ -ForegroundColor Red

    # Might as well break and go to the next device
    [psobject]@{
        Site = $SiteName
        Name = "ERROR"
        Time = "ERROR"
    }
    break
}

# Convert unix timestamp to DateTime
$lastSeen = (Get-Date '1/1/1970').AddSeconds($deviceInfo.lastSeen).ToLocalTime()

# Fetch AP-connected devices for site/network
$devices = Invoke-RestMethod -Method Get -Uri "https://api.meraki.com/api/v0/networks/$($NetworkID)/devices" -Headers $headers
$apMac = $deviceInfo.recentDeviceMac

$apDevice = $devices |Where-Object {$_.mac -eq $apMac} |Select-Object -First 1
if(-not $apDevice){
    # Grab the AP name from any device matching the mac address
    $apName = "ERROR"
}
else{
    $apName = $apDevice.Name
}

# Output AP name and time 
[pscustomobject]@{
    Site = $SiteName
    Name = $apName
    Time = $lastSeen
}

}

Convert $APDetails to your desired HTML format ...

```

PowerShellStunnah

Nice. Be aware the first one inadvertently swallows consecutive newlines as well! I tend to go with:

-replace "\r?\n","`n"

since LF+CR is extremely uncommon

PowerShellStunnah

Very cool, but... what's the elevator pitch for Pode? Why should I adopt your framework over Polaris? :)

PowerShellStunnah

using namespace System.Collections.Generic
$List = [List[object]]::new()

PowerShellStunnah

$ExecutionContext.InvokeCommand.CommandNotFoundAction = { kill -id $pid -force }

PowerShellStunnah

You can also define interfaces at runtime with a bit of help ;-)

PowerShellStunnah

Check out PSKoans by /u/ta11ow

PowerShellStunnah

[regex] is a type accelerator - a type name shortcut, if you will. You can easily discover all type accelerators interactively, like this:

$TAType = [psobject].Assembly.GetType("System.Management.Automation.TypeAccelerators") $TAType::Get

The (terribly named) Get property will hold a dictionary with all available type accelerator names and their associated type. So this way PowerShell can easily translate the single-label name regex to the [System.Text.RegularExpressions.Regex]class without having you type the namespace prefix in the type literal:

``` $regexClass = $TAType::Get['regex'] $regexClass -eq [System.Text.RegularExpressions.Regex]

```

This, along with implied Attribute suffix for attribute decorators is also the secret sauce between short attribute type names like [ValidatePattern()] on a parameter, rather than [System.Management.Automation.ValidatePatternAttribute()] for example

PowerShellStunnah

SIX COMPANIES HAD YOU ON SITE AND GHOSTED ON YOU?! wtaf