lib/TMD.PsExec.ps1
Function Invoke-TMDPsExec { param( [CmdletBinding()] [Parameter(mandatory = $true)][String]$ComputerName, [Parameter(mandatory = $true)][pscredential]$Credential, [Parameter(mandatory = $false)][String]$Command, [Parameter(mandatory = $false)]$CommandArguments, [Parameter(mandatory = $false)]$Script, [Parameter(mandatory = $false)][Switch]$Console, [Parameter(mandatory = $false)][Switch]$PassThru, [Parameter(mandatory = $false)][string]$CommandDelimiter = ';', [Parameter(mandatory = $false)][String]$PsSuitePath = 'C:\TMConsole\SysinternalsSuite' ) Begin { ## Test if PsExec is installed $PsExecPath = Join-Path $PsSuitePath 'psexec.exe' $PsExeFileExists = Test-Path -Path $PsExecPath if(-Not $PsExeFileExists){ Throw "PSExec is not installed at $($PsSuitePath). Install there, or use the -PsSuitePath Parameter to define the copy to use." } ## A Script may have been provided, convert it into a single line command. if ($Script -and -not $Command) { $Commands = [System.Text.StringBuilder]::new() $Script.Values | ForEach-Object { [void]$Commands.Append($_) [void]$Commands.Append($CommandDelimiter) } $Command = $Commands.ToString() } } Process { # Make sure this is Windows if (-not $isWindows) { Throw "Running PsExec Commands are only supported when running Console on Windows." } ## Create an ArgumentList for the PsExec Command $PsExecArguments = @() $PsExecArguments += "\\$ComputerName" $PsExecArguments += '-s' $PsExecArguments += '-h' $PsExecArguments += '-u' $PsExecArguments += $Credential.UserName $PsExecArguments += '-p' $PsExecArguments += '"' + $Credential.GetNetworkCredential().Password + '"' $PsExecArguments += '-r TMD-PsExec' $PsExecArguments += '-nobanner' $PsExecArguments += '-accepteula' $PsExecArguments += $Command $PsExecArguments += $CommandArguments ## StartProcess Options $PsExecProcessSplat = @{ FilePath = $PsExecPath ArgumentList = $PsExecArguments NoNewWindow = $True Wait = $True ErrorAction = 'SilentlyContinue' WarningAction = 'SilentlyContinue' } # $PsExecArguments = [System.Text.StringBuilder]::new() # $PsExecArguments.Append(" \\$ComputerName") | Out-Null # $PsExecArguments.Append( '-s') | Out-Null # $PsExecArguments.Append( '-h') | Out-Null # $PsExecArguments.Append( '-u ') | Out-Null # $PsExecArguments.Append($Credential.UserName) | Out-Null # $PsExecArguments.Append(' -p') | Out-Null # $PsExecArguments.Append(' "' + $Credential.GetNetworkCredential().Password + '"') | Out-Null # $PsExecArguments.Append(' -r TMD-PsExec') | Out-Null # $PsExecArguments.Append(' -nobanner') | Out-Null # $PsExecArguments.Append(' -accepteula') | Out-Null # $PsExecArguments.Append($Command) | Out-Null # $PsExecArguments.Append('') | Out-Null # $PsExecArguments.Append($CommandArguments) | Out-Null # $PsExecArgumentString = "{0}" -f $PsExecArguments.ToString() ## PsExec output's Its StdOut on the StdErr channel so the inside SSH content can be delivered to StdOut ## This requires altering the Error Action Preference to Continue (so the output can be red as well as saved) $ExistingErrorAction = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' ## Starting the process may involve Writing to the console, or not. if ($Console) { ## Start process with all output collected to StdOut and to Write Host (Start-Process @PsExecProcessSplat) 2>&1 | Where-Object { ($_.Exception.Message -ne "") ` -and ($_.Exception.Message -ne "End of keyboard-interactive prompts from server") } | Tee-Object -Variable PsExecOutput | Write-Host | Out-Null } else { ## Don't use Write-Host, but Out-Null Start-Process @PsExecProcessSplat | Where-Object { ($_.Exception.Message -ne "Keyboard-interactive authentication prompts from server:") ` -and ($_.Exception.Message -ne "End of keyboard-interactive prompts from server") } | Tee-Object -Variable PsExecOutput | Out-Null } ## Return Error Preference Setting $ErrorActionPreference = $ExistingErrorAction } End { ## Return the Session Standard Output if ($PassThru) { return $PsExecOutput } } } ## Process.Start Constructor Method for starting exe ## Create a Process object (and a fuction level cache) and use Std<Out|Err> handlers + rediretion ## Create a Cache to return only appropriate objects back # $ProcessOutputCache = @{ # StdOut = @() # StdErr = @() # } ## Construct a new ProcessStartInfo object # $psi = New-Object System.Diagnostics.ProcessStartInfo # $psi.CreateNoWindow = $true # # $psi.UseShellExecute = $false # # $psi.RedirectStandardOutput = $true # # $psi.RedirectStandardError = $true # $psi.UseShellExecute = $true # $psi.FileName = $PsExec # $psi.Arguments = $PsExecArguments # ## Invoke the Process Invocation using the StartInfo object # $Process = New-Object System.Diagnostics.Process # $Process.StartInfo = $psi # ## Define and Register Standard Output Handler # $StdOutHandler = { # param([Object]$sender, [DataReceivedEventArgs]$e) # if ($ProcessOutputCache.StdOut -notcontains $e) { # Write-Host $e # $ProcessOutputCache.StdOut += $e # } # } # Register-ObjectEvent -InputObject $Process -Action $StdOutHandler -EventName 'OutputDataReceived' # | Out-Null # ## Define and Register Standard Error Handler # $StdErrHandler = { # param([Object]$sender, [DataReceivedEventArgs]$e) # if ($ProcessOutputCache.StdErr -notcontains $e) { # Write-Host $e # $ProcessOutputCache.StdErr += $e # } # } # Register-ObjectEvent -InputObject $Process -Action $StdErrHandler -EventName 'ErrorDataReceived' # | Out-Null ## Start the process, but don't return anything # [void]$Process.Start() ## Invoke Output Handlers # $Process.BeginOutputReadLine() # $Process.BeginErrorReadLine() ## Wait until the process exits before returning control # $Process.WaitForExit() ## Return Error Handling to the state it as in before this function ran ## PsExec exits with 'code 0.' as it's last line upon success. # $LastLine = $FullStdOut | Select-Object -Last 1 # if ($LastLine.SubString($LastLine.length - 7, 7) -ne 'code 0.') { # Write-Progress -Id 30 -ParentId 0 -Activity 'Installation Complete' -PercentComplete 100 -Completed # Throw "PsExec Script Exited abnormally. Please review the log." # } |