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."
# }