Public/Invoke-AutomateCommand.ps1
function Invoke-AutomateCommand { <# .SYNOPSIS Will issue a command against a given machine and return the results. .DESCRIPTION Will issue a command against a given machine and return the results. .PARAMETER ComputerID The ComputerID for the machine you wish to connect to. ComputerIDs can be provided via the pipeline. IE - Get-AutomateComputer -ComputerID 5 | Invoke-AutomateCommand -Powershell -Command "Get-Service" .PARAMETER Command The command you wish to issue to the machine. .PARAMETER TimeOut The amount of time in seconds to wait for the command results. The default is 30 seconds. .PARAMETER BatchSize Number of computers to invoke commands in parallel at a time. .PARAMETER ResultPropertyName String containing the name of the member you would like to add to the input pipeline object that will hold the result of this command .OUTPUTS The output of the Command provided. .NOTES Version: 1.0 Author: Darren White Creation Date: 2020-07-09 Purpose/Change: Initial script development .EXAMPLE Get-AutomateComputer -ComputerID 5 | Invoke-AutomateCommand -Powershell -Command "Get-Service" Will execute PowerShell command "Get-Service" on the computer. .EXAMPLE Invoke-AutomateCommand -ComputerID @(3,4,5,6,7,8) -Command 'hostname' -OfflineAction Skip Will return the hostnames of the online machines. .EXAMPLE $Results = Get-AutomateComputer -ClientName "Contoso" | Invoke-AutomateCommand -ResultPropertyName "OfficePlatform" -PowerShell -Command { Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name Platform } -PassthroughObjects $Results | select ComputerName, OfficePlatform .EXAMPLE Invoke-AutomateCommand -ComputerID $ComputerID -CommandID 123 -Timeout 600000 Tells the remote agent to resend system inventory. #> [CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName = 'ExecuteCommand')] param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [int[]]$ComputerID, [Parameter(ParameterSetName = 'ExecuteCommand', Mandatory = $True)] [Parameter(ParameterSetName = 'PassthroughObjects')] [string]$Command, [Parameter(ParameterSetName = 'ExecuteCommand')] [Parameter(ParameterSetName = 'PassthroughObjects')] [string]$WorkingDirectory="%WINDIR%\Temp", [Parameter(ParameterSetName = 'ExecuteCommand')] [Parameter(ParameterSetName = 'PassthroughObjects')] [switch]$PowerShell, [Parameter(ParameterSetName = 'CommandID', Mandatory = $True)] [int]$CommandID=2, [Parameter(ParameterSetName = 'CommandID')] $CommandParameters='', [int]$TimeOut = 30000, [Parameter(ParameterSetName = 'ExecuteCommand')] [Parameter(ParameterSetName = 'PassthroughObjects')] [ValidateSet('Wait', 'Queue', 'Skip')] $OfflineAction = 'Wait', [ValidateRange(1, 100)] [int]$BatchSize = 20, [switch]$PassthroughObjects, [string]$ResultPropertyName = 'Output' ) Begin { $ProgressPreference='SilentlyContinue' $URI = "cwa/api/v1/Computers" Write-Debug "Selected parameterset $($PSCmdlet.ParameterSetName)" If ($PSCmdlet.ParameterSetName -eq 'CommandID') { $CommandBody=$CommandParameters } Else { If ($PowerShell) { $Command=$Command -Replace '"','\"' $FormattedCommand="powershell.exe!!! -NonInteractive -Command ""`$WorkingDirectory=[System.Environment]::ExpandEnvironmentVariables('$WorkingDirectory'); Set-Location -Path `$WorkingDirectory -ErrorAction Stop; $Command""" } Else { $FormattedCommand="cmd.exe!!! /c ""CD /D ""$WorkingDirectory"" && $Command""" } $CommandBody = $FormattedCommand } $ResultObjects = @{} $ComputerCollection = {}.Invoke() Write-Debug "CommandID $($CommandID) will be used to submit the following command`n$($CommandBody)" } Process { If ($PassthroughObjects) { $ObjectsIn=$_ Foreach ($xObject in $ObjectsIn) { If ($xObject -and $xObject.ComputerID) { [string]$Computer=$xObject.ComputerID If (!($ResultObjects.ContainsKey($Computer))) { $ResultObjects.Add($Computer, [pscustomobject]@{ComputerID = $Computer }) } Else {Write-Warning "ComputerID $Computer has already been added. Skipping"} $Null = $ComputerCollection.Add($xObject) } Else {Write-Warning "Input Object is missing ComputerID property"} } } Else { Foreach ($Computer in $ComputerId) { If ($Computer.ComputerID) {$Computer=$Computer.ComputerID} [string]$Computer="$($Computer)" [pscustomobject]@{ComputerID = $Computer} | ForEach-Object { If (!($ResultObjects.ContainsKey($Computer))) { $ResultObjects.Add($Computer, $_) } Else {Write-Warning "ComputerID $Computer has already been added. Skipping"} $Null = $ComputerCollection.Add($_) } } } } End { Function New-ReturnObject { param([object]$InputObject, [object]$Result, [bool]$IsSuccess, [string]$PropertyName) $InputObject | Add-Member -NotePropertyName $PropertyName -NotePropertyValue $Result -Force $InputObject | Add-Member -NotePropertyName 'IsSuccess' -NotePropertyValue $IsSuccess -Force $InputObject } $ProcessComputers=@($ResultObjects.Keys) $RemainingComputers={}.Invoke() $AddComputers={}.Invoke() $ComputerIndex=0 Do { While (($AddComputers.Count+$RemainingComputers.Count) -lt $BatchSize -and $ComputerIndex -lt $ProcessComputers.Count) { $AddComputers.Add($ProcessComputers[$ComputerIndex]) $ComputerIndex++ } If ($AddComputers.Count -gt 0) { If ( $PSCmdlet.ShouldProcess($AddComputers, "Submit Command to Agents") ) { Foreach ($tmpComputerID IN $AddComputers) { # Issue command $ExecuteCommand=@{ 'ComputerId' = $tmpComputerID 'Command'=@{'Id'="$($CommandID)"} 'Parameters'=@($CommandBody) } $Arguments = @{ 'URI'="/${URI}/${tmpComputerID}/CommandExecute" 'ContentType'="application/json" 'Body'=$ExecuteCommand|ConvertTo-Json -Depth 100 -Compress 'Method'='POST' } Write-Debug "Calling Invoke-AutomateAPIMaster with Arguments $(ConvertTo-JSON -InputObject $Arguments -Depth 100 -Compress)" Try { $Result = Invoke-AutomateAPIMaster -Arguments $Arguments If ($Result.content){ $Result = $Result.content | ConvertFrom-Json } If ($Result -and $Result.ID) { $ResultObjects[$tmpComputerID] = New-ReturnObject -InputObject $ResultObjects[$tmpComputerID] -Result "$($Result.Id)" -PropertyName 'CmdID' -IsSuccess $false $EventDate = Get-Date $TimeOutDateTime = $EventDate.AddMilliseconds($TimeOut+3000) $ResultObjects[$tmpComputerID] = New-ReturnObject -InputObject $ResultObjects[$tmpComputerID] -Result $TimeOutDateTime -PropertyName '__CommandTimeout' -IsSuccess $false $Null = $RemainingComputers.Add($tmpComputerID) } } Catch { Write-Error "$(($_.ErrorDetails | ConvertFrom-Json).message)" return } } } $AddComputers.Clear() } If ($RemainingComputers.Count -gt 0) { Start-Sleep -Seconds 5 $WaitingComputers=@($RemainingComputers.GetEnumerator()) Foreach ($tmpComputerID IN $WaitingComputers) { If ( $PSCmdlet.ShouldProcess($ResultObjects[$tmpComputerID].CmdID, "Checking Command Result") ) { Try { $RequestParameters = @{ 'endpoint'="Computers/${tmpComputerID}/CommandExecute" 'IDs'=$ResultObjects[$tmpComputerID].CmdID } $CommandExpired=($ResultObjects[$tmpComputerID].__CommandTimeout -le (Get-Date)) Write-Debug "Submitting: $(ConvertTo-JSON -InputObject $RequestParameters -Depth 10 -compress)" $cmdResult = Get-AutomateAPIGeneric @RequestParameters Write-Debug "Response: $(ConvertTo-JSON -InputObject $cmdResult -Depth 10 -compress)" If ($cmdResult -and @('Failed','Success') -contains $cmdResult.Status) { $Output = $cmdResult.Output.Trim() $ResultObjects[$tmpComputerID] = New-ReturnObject -InputObject $ResultObjects[$tmpComputerID] -Result $Output -PropertyName $ResultPropertyName -IsSuccess $($cmdResult.Status -eq 'Success') $Null = $RemainingComputers.Remove($tmpComputerID) } ElseIf ($CommandExpired) { $ResultObjects[$tmpComputerID] = New-ReturnObject -InputObject $ResultObjects[$tmpComputerID] -Result "Command timed out" -PropertyName $ResultPropertyName -IsSuccess $False $Null = $RemainingComputers.Remove($tmpComputerID) } } Catch { Write-Error "$(($_.ErrorDetails | ConvertFrom-Json).message)" } } } } } Until ($ComputerIndex -eq $ProcessComputers.Count -and $RemainingComputers.Count -eq 0) If ($ComputerCollection.Count -eq 1 -and !($PassthroughObjects)) { $ResultObjects.Values | Select-Object -ExpandProperty "$ResultPropertyName" -ErrorAction SilentlyContinue } ElseIf (!($PassthroughObjects)) { $ComputerCollection | Select-Object -Property @{n=$ResultPropertyName;e={If ($_.ComputerID) {[string]$ComputerID=$_.ComputerID} Else {[string]$ComputerID=$_}; Write-Debug "Inserting results for ComputerID $($ComputerID)"; If ($ResultObjects.ContainsKey($ComputerID)) {$ResultObjects[$ComputerID]|Select-Object -Property * -ExcludeProperty __CommandTimeout} Else {"Results for ComputerID $($ComputerID) were not found"} }} | Select-Object -ExpandProperty "$ResultPropertyName" } Else { $ComputerCollection | Select-Object -ExcludeProperty $ResultPropertyName -Property *,@{n=$ResultPropertyName;e={If ($_.ComputerID) {[string]$ComputerID=$_.ComputerID} Else {[string]$ComputerID=$_}; Write-Debug "Inserting results for ComputerID $($ComputerID)"; If ($ResultObjects.ContainsKey($ComputerID)) {$ResultObjects[$ComputerID]|Select-Object -Property * -ExcludeProperty ComputerID,__CommandTimeout} Else {"Results for ComputerID $($ComputerID) were not found"} }} } } } |