u/PowerShellStunnah
PowerShellStunnah
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:
- Writing data to disk and reading it back all the time is going to be slow, not to mention completely unnecessary.
- 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
[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
You've already leaked the credentials to the endpoint, you should consider them compromised.
I would take a different approach:
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)