lib/TMD.PsExec.ps1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
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." # } |