Is there a bug in PowerShell's Start-Process command when accessing the StandardError and StandardOutput properties?
If I run the following I get no output:
$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait
$process.StandardOutput
$process.StandardError
But if I redirect the output to a file I get the expected result:
$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt
That's how Start-Process was designed for some reason. Here's a way to get it without sending to file:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
In the code given in the question, I think that reading the ExitCode property of the initiation variable should work.
$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait
$process.ExitCode
Note that (as in your example) you need to add the -PassThru and -Wait parameters (this caught me out for a while).
IMPORTANT:
We have been using the function as provided above by LPG.
However, this contains a bug you might encounter when you start a process that generates a lot of output. Due to this you might end up with a deadlock when using this function. Instead use the adapted version below:
Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
Try {
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $commandPath
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = $commandArguments
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
[pscustomobject]#{
commandTitle = $commandTitle
stdout = $p.StandardOutput.ReadToEnd()
stderr = $p.StandardError.ReadToEnd()
ExitCode = $p.ExitCode
}
$p.WaitForExit()
}
Catch {
exit
}
}
Further information on this issue can be found at MSDN:
A deadlock condition can result if the parent process calls p.WaitForExit before p.StandardError.ReadToEnd and the child process writes enough text to fill the redirected stream. The parent process would wait indefinitely for the child process to exit. The child process would wait indefinitely for the parent to read from the full StandardError stream.
I also had this issue and ended up using Andy's code to create a function to clean things up when multiple commands need to be run.
It'll return stderr, stdout, and exit codes as objects. One thing to note: the function won't accept .\ in the path; full paths must be used.
Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $commandPath
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = $commandArguments
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
[pscustomobject]#{
commandTitle = $commandTitle
stdout = $p.StandardOutput.ReadToEnd()
stderr = $p.StandardError.ReadToEnd()
ExitCode = $p.ExitCode
}
}
Here's how to use it:
$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"
I really had troubles with those examples from Andy Arismendi and from LPG. You should always use:
$stdout = $p.StandardOutput.ReadToEnd()
before calling
$p.WaitForExit()
A full example is:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$p.WaitForExit()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
Here's a kludgy way to get the output from another powershell process (serialized):
start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'
import-clixml out.xml
Let me emphasize -nonewwindow to get the standardoutput and standarderror, at least on the local screen:
start-process -wait cmd '/c dir' -nonewwindow
Volume in drive C is Windows
Volume Serial Number is 2AC6-626F
Directory of C:\users\me\foo
11/24/2022 11:40 AM <DIR> .
11/24/2022 11:40 AM <DIR> ..
11/24/2022 11:40 AM 330 file.json
1 File(s) 330 bytes
2 Dir(s) 25,042,915,328 bytes free
start-process -wait cmd '/c dir foo' -nonewwindow
Volume in drive C is Windows
Volume Serial Number is 2AC6-626F
Directory of C:\users\me\foo
File Not Found
Here's what I cooked up based on the examples posted by others on this thread. This version will hide the console window and provided options for output display.
function Invoke-Process {
[CmdletBinding(SupportsShouldProcess)]
param
(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$FilePath,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$ArgumentList,
[ValidateSet("Full","StdOut","StdErr","ExitCode","None")]
[string]$DisplayLevel
)
$ErrorActionPreference = 'Stop'
try {
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $FilePath
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.WindowStyle = 'Hidden'
$pinfo.CreateNoWindow = $true
$pinfo.Arguments = $ArgumentList
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$result = [pscustomobject]#{
Title = ($MyInvocation.MyCommand).Name
Command = $FilePath
Arguments = $ArgumentList
StdOut = $p.StandardOutput.ReadToEnd()
StdErr = $p.StandardError.ReadToEnd()
ExitCode = $p.ExitCode
}
$p.WaitForExit()
if (-not([string]::IsNullOrEmpty($DisplayLevel))) {
switch($DisplayLevel) {
"Full" { return $result; break }
"StdOut" { return $result.StdOut; break }
"StdErr" { return $result.StdErr; break }
"ExitCode" { return $result.ExitCode; break }
}
}
}
catch {
exit
}
}
Example: Invoke-Process -FilePath "FQPN" -ArgumentList "ARGS" -DisplayLevel Full
To get both stdout and stderr, I use:
Function GetProgramOutput([string]$exe, [string]$arguments)
{
$process = New-Object -TypeName System.Diagnostics.Process
$process.StartInfo.FileName = $exe
$process.StartInfo.Arguments = $arguments
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardError = $true
$process.Start()
$output = $process.StandardOutput.ReadToEnd()
$err = $process.StandardError.ReadToEnd()
$process.WaitForExit()
$output
$err
}
$exe = "cmd"
$arguments = '/c echo hello 1>&2' #this writes 'hello' to stderr
$runResult = (GetProgramOutput $exe $arguments)
$stdout = $runResult[-2]
$stderr = $runResult[-1]
[System.Console]::WriteLine("Standard out: " + $stdout)
[System.Console]::WriteLine("Standard error: " + $stderr)
Improved Answer - as long as you're OK with Start-Job instead of Start-Process
It turns out that the STDOUT and STDERR are accumulated in string arrays $job.ChildJobs[0].Output and $job.ChildJobs[0].Erroras the script runs. So you can poll these values and write them out periodically. Somewhat of a hack maybe, but it works.
It's not a stream though, so you have to manually keep track of the starting index into the array.
This code is simpler than my original answer, and at the end you have the entire STDOUT in $job.ChildJobs[0].Output. And as a little bonus for this demo, the calling script is PS7 and the background job is PS5.
$scriptBlock = {
Param ([int]$param1, [int]$param2)
$PSVersionTable
Start-Sleep -Seconds 1
$param1 + $param2
}
$parameters = #{
ScriptBlock = $scriptBlock
ArgumentList = 1, 2
PSVersion = 5.1 # <-- remove this line for PS7
}
$timeoutSec = 5
$job = Start-Job #parameters
$job.ChildJobs[0].Output
$index = $job.ChildJobs[0].Output.Count
while ($job.JobStateInfo.State -eq [System.Management.Automation.JobState]::Running) {
Start-Sleep -Milliseconds 200
$job.ChildJobs[0].Output[$index]
$index = $job.ChildJobs[0].Output.Count
if (([DateTime]::Now - $job.PSBeginTime).TotalSeconds -gt $timeoutSec) {
throw "Job timed out."
}
}
As pointed out, my original answer can interleave the output. This is a limitation of event handling in PowerShell. It's not a fixable problem.
Original Answer, don't use - just leaving it here for interest
If there's a timeout, ReadToEnd() is not an option. You could do some fancy looping, but IMO the 'cleanest' way to do this is to ignore the streams. Hook the OutputDataReceived/ErrorDataReceived events instead, collecting the output. This approach also avoids the threading issues mentioned by others.
This is straightforward in C#, but it's tricky and verbose in Powershell. In particular, add_OutputDataReceived is not available for some reason. (Not sure if this is a bug or a feature, at least this seems to be the case in PowerShell 5.1.) To work around it you can use Register-ObjectEvent.
$stdout = New-Object System.Text.StringBuilder
$stderr = New-Object System.Text.StringBuilder
$proc = [System.Diagnostics.Process]#{
StartInfo = #{
FileName = 'ping.exe'
Arguments = 'google.com'
RedirectStandardOutput = $true
RedirectStandardError = $true
UseShellExecute = $false
WorkingDirectory = $PSScriptRoot
}
}
$stdoutEvent = Register-ObjectEvent $proc -EventName OutputDataReceived -MessageData $stdout -Action {
$Event.MessageData.AppendLine($Event.SourceEventArgs.Data)
}
$stderrEvent = Register-ObjectEvent $proc -EventName ErrorDataReceived -MessageData $stderr -Action {
$Event.MessageData.AppendLine($Event.SourceEventArgs.Data)
}
$proc.Start() | Out-Null
$proc.BeginOutputReadLine()
$proc.BeginErrorReadLine()
Wait-Process -Id $proc.Id -TimeoutSec 5
if ($proc.HasExited) {
$exitCode = $proc.ExitCode
}
else {
Stop-Process -Force -Id $proc.Id
$exitCode = -1
}
# Be sure to unregister. You have been warned.
Unregister-Event $stdoutEvent.Id
Unregister-Event $stderrEvent.Id
Write-Output $stdout.ToString()
Write-Output $stderr.ToString()
Write-Output "Exit code: $exitCode"
The code shown is the happy path (stderr is empty)
To test the timeout path, set -TimeoutSec to .5
To test the sad path (stderr has content), set FileName to 'cmd' and Arguments to /C asdf
Here is my version of function that is returning standard System.Diagnostics.Process with 3 new properties
Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
Try {
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $commandPath
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.WindowStyle = 'Hidden'
$pinfo.CreateNoWindow = $True
$pinfo.Arguments = $commandArguments
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$p.WaitForExit()
$p | Add-Member "commandTitle" $commandTitle
$p | Add-Member "stdout" $stdout
$p | Add-Member "stderr" $stderr
}
Catch {
}
$p
}
You may want to also consider using the & operator combined with --% instead of start-process - that lets you easily pipe and process the command and/or error output.
put the escape parameter into a variable
put the arguments into a variable
$deploy= "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe"
$esc = '--%'
$arguments ="-source:package='c:\temp\pkg.zip' -verb:sync"
$output = & $deploy $esc $arguments
That passes the parameters to the executable without interference and let me get around the issues with start-process.
Combine Stderr and Stdout into one variable:
$output = & $deploy $esc $arguments 2>&1
Get separate variables for Stderr and Stdout
$err = $( $output = & $deploy $esc $arguments) 2>&1
Related
We have one PowerShell script file(.ps1) which ask user to provide input from PowerShell console and pass as an argument to another PowerShell script file(.ps1), this script file also ask for input from the user. for getting input we are using "Read-Host"
Below is the code snippet, while executing below code it is not asking input from the test2 script and process is stuck/pause.
can some help me how to achieve this use case ?
Code
Test1.ps1
[System.String] $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$new_ip = Read-Host -Prompt "Enter IP"
if([string]::IsNullOrWhiteSpace($new_ip))
{
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "PowerShell.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.WindowStyle = 'Hidden'
$pinfo.CreateNoWindow = $True
$pinfo.Arguments = "-file `"$ScriptPath\Test2.ps1`" -ServerIP `"$new_ip`""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$stderr = $p.StandardError.ReadToEnd()
$stdout = $p.StandardOutput.ReadToEnd()
$p.WaitForExit()
if (![string]::IsNullOrEmpty($stderr))
{
Write-Warning "stderr: $stderr"
}
if($p.ExitCode -ne 0)
{
$isValidIP = $false
Write-Warning " failed, Check above message"
}
else
{
Write-Host $stdout
$isValidIP = $true
Write-Host "Configured successfully"
}
}
**Test2.ps1**
Param([String]$ServerIP = $null
)
Write-Host "Server IP:"$ServerIP
$SqlServerIP = (Read-Host -prompt "Enter Sql Server IP address (Default : localhost) ")
Write-Host "Server IP:"$SqlServerIP
I am not sure if we can redirect the user input prompt of a running process in background to the Main Window. But we can send the input to the process waiting for user input by using
$p.StandardInput.WriteLine("yourIP");
Not sure if this is the appropricate answer
I have Webroot installed in my machine and I wrote a powershell script to automate the silentscan of the specific location.
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "C:\Program Files (x86)\Webroot\WRSA.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "-silentscan=C:\Program files"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start()
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$exitcode = $p.ExitCode
Write-Output $Exitcode
WRSA.exe file was executed properly, but as a result it is showing 2 as an Exit code. is it correct exit code or is there any issue in the script?
Arguments that you pass are not escaped, try:
$pinfo.Arguments = ' -silentscan="C:\Program files" '
I'm new to Powershell. I need to capture the output from a command-line call in Powershell continuously and preferably send it to another function. My code currently waits until the external program is completely finished. Here's what I've got:
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = "DataSet.exe"
$startInfo.CreateNoWindow = $true
$startInfo.UseShellExecute = $false
$startInfo.RedirectStandardError = $true
$startInfo.RedirectStandartOutput = $true
$startInfo.Arguments = "1 off"
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.Start() | Out-Null
$stdOut = $process.StandardOutput.ReadToEnd()
$stdErr = $process.StandardError.ReadToEnd()
$process.WaitForExit()
process($stdOut)
process($stdErr)
# Do stuff with $process.ExitCode
Here's what I need
...
while (readOutput)
{
process($stdOut)
process($stdErr)
}
...
I would recommend using a background job for this kind of thing, e.g. like this:
$job = Start-Job -ScriptBlock { & "some.exe" }
while ($job.State -eq 'Running') {
Receive-Job $job -OutVariable $outValue -ErrorVariable $errValue
if ($outValue) { Do-SomethingWith $outValue }
ir ($errValue) { Do-OtherWith $errValue }
Start-Sleep -Milliseconds 200
}
Before I was using a piece of code like this $prog="cmd.exe";
$params=#('/C','"D:\EmailConnector-Disc Optimus\run.bat"','connector.log')
$rc=start-process $prog $params -WorkingDirectory "D:\EmailConnector-Disc Optimus" -RedirectStandardOutput $emailconnecter_log -PassThru -wait The issue was with this code is not returing error code. Later I usedd a code like here below
$errorlog = "D:\EmailConnector-Disc Optimus\logs\error.log"
$emailconnecter_log = "D:\EmailConnector-Disc Optimus\logs\connector.log"
$prog="cmd.exe"
$params=#('/C','"D:\EmailConnector-Disc Optimus\run.bat"','connector.log')
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $prog
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $emailconnecter_log
$pinfo.UseShellExecute = $false
$pinfo.Arguments = $params
$pinfo.WorkingDirectory = "D:\EmailConnector-Disc Optimus"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
if ($p.ExitCode -gt 0)
{Some code is here }
Now the issue is $p.WaitForExit() not exiting, i was debuggin through PowershellISE. I dont know Why the code is not exiting. Also I want to write the console output to $emailconnecter_log. Previously everything working except the return code. Pleae help me.
I'm working on a build environment for our JavaScript project. We use require.js (r.js) to combine different js modules into one output js file. We use TeamCity and I wanted to configure Powershell build step that would call r.js, read it's standard output and exit code and pass that exit code back to TeamCity (by exiting from Powershell with this exit code) so that if the tasks fails (r.js comes back with exit code 1) it won't proceed with other build steps. Also I wanted the standard output of r.js to be saved in the TeamCity log to allow developers to quickly see the error causing r.js to stop.
This is the way how I can start r.js process with it's arguments, read it's exit code and use it to exit from Powershell:
$process = start-process r.js.cmd -ArgumentList "-o build-dev.js" -PassThru -Wait
exit $process.ExitCode
If I try to read standard output in this way before exiting:
Write-Host $process.StandardOutput.ReadToEnd();
I get this error, which probably suggests that I can't read StandardOutput in this way as it is a stream:
You cannot call a method on a null-valued expression.
At line:1 char:45
+ Write-Host $process.StandardOutput.ReadToEnd <<<< ();
+ CategoryInfo : InvalidOperation: (ReadToEnd:String) [], Runtime
Exception
+ FullyQualifiedErrorId : InvokeMethodOnNull
Process exited with code 1
Now I found a way of running the process so that I can read the standard output but I'm not able to read the exit code:
$psi = New-object System.Diagnostics.ProcessStartInfo
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
#$psi.FileName = "r.js.cmd"
$psi.FileName = "C:\Users\Administrator\AppData\Roaming\npm\r.js.cmd"
$psi.Arguments = #("-o build-dev.js")
#$psi.WorkingDirectory = (Get-Location).Path;
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
$process.Start() | Out-Host
$process.WaitForExit()
$output = $process.StandardOutput.ReadToEnd()
$stderr = $process.StandardError.ReadToEnd()
sleep(10)
$exit_code = $process.ExitCode
write-host "========== OUTPUT =========="
write-host $output
write-host "========== ERROR =========="
write-host $stderr
write-host "========== EXIT CODE =========="
write-host $exit_code
write-host "========== $LastExitCode =========="
#write-host $LastExitCode
#Exit $exit_code
But again this returns the console output but the exit code is always 0 even if r.js returns 1 because I have error in my js scripts.
Could anyone advise how can I read standard output with start-process or how can I read exit code with New-object System.Diadnostics.ProcessStartInfo
I can attach screenshots with more details of my TC build step configuration and output saved to the build log if that would help answering the question.
I usually tackle this situation by using a variation of the start-process command mentioned in the question.
$outputLog = "outputFile.log"
$errLog = "errorFile.log"
$process = Start-Process r.js.cmd -ArgumentList "-o build-dev.js" -PassThru -RedirectStandardOutput $outputLog -RedirectStandardError $errLog -Wait
$exitCode = $process.ExitCode
Now the log files $outputLog and $errorLog will have the standard output and error contents.
This is how you can do it. Cmd files are however not applications, they are associated with cmd so you might have to target cmd.exe and put the path to the cmd file as a parameter. Also UseShellExecute for process start info stops output and errors from being recorded so dont enable it if you need output. UseShellExecute is as if you put the $Path into the Win+R window. If its a png for example, windows will find an app that is supposed to open it and opens that file with it.
$Path = "C:\whatever.exe"
$WorkingDirectory = "C:\"
$CreateNoWindow = $true #$true to not create another window for console application
$Parameters = "-paremeters for -the app"
$WindowStyle = [Diagnostics.ProcessWindowStyle]::Normal
# prepare process start info
$processStartInfo = New-Object -TypeName 'System.Diagnostics.ProcessStartInfo' -ErrorAction 'Stop'
$processStartInfo.FileName = $Path
$processStartInfo.WorkingDirectory = $WorkingDirectory
$processStartInfo.ErrorDialog = $false
$processStartInfo.RedirectStandardOutput = $true
$processStartInfo.RedirectStandardError = $true
$processStartInfo.CreateNoWindow = $CreateNoWindow
If ($Parameters) { $processStartInfo.Arguments = $Parameters }
$processStartInfo.WindowStyle = $WindowStyle
#create a process and assign the process start info object to it
$process = New-Object -TypeName 'System.Diagnostics.Process' -ErrorAction 'Stop'
$process.StartInfo = $processStartInfo
#Add event handler to capture process's standard output redirection
[scriptblock]$processEventHandler = { If (-not [string]::IsNullOrEmpty($EventArgs.Data)) { $Event.MessageData.AppendLine($EventArgs.Data) } }
#create string builders to store the output and errors in
$stdOutBuilder = New-Object -TypeName 'System.Text.StringBuilder' -ArgumentList ''
$stdOutEvent = Register-ObjectEvent -InputObject $process -Action $processEventHandler -EventName 'OutputDataReceived' -MessageData $stdOutBuilder -ErrorAction 'Stop'
$stdErrBuilder = New-Object -TypeName 'System.Text.StringBuilder' -ArgumentList ''
$stdErrEvent = Register-ObjectEvent -InputObject $process -Action $processEventHandler -EventName 'ErrorDataReceived' -MessageData $stdErrBuilder -ErrorAction 'Stop'
#start the process
$null = $process.Start()
#begin reading the output and errors
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
#Instructs the Process component to wait indefinitely for the associated process to exit.
$process.WaitForExit()
#HasExited indicates that the associated process has terminated, either normally or abnormally. Wait until HasExited returns $true.
While (-not ($process.HasExited)) { $process.Refresh(); Start-Sleep -Seconds 1 }
## Get the exit code for the process
Try {
[int32]$returnCode = $process.ExitCode
}
Catch [System.Management.Automation.PSInvalidCastException] {
# Catch exit codes that are out of int32 range
[int32]$returnCode = 1
}
#unregister the events
If ($stdOutEvent) { Unregister-Event -SourceIdentifier $stdOutEvent.Name -ErrorAction 'Stop'; $stdOutEvent = $null }
If ($stdErrEvent) { Unregister-Event -SourceIdentifier $stdErrEvent.Name -ErrorAction 'Stop'; $stdErrEvent = $null }
$stdOut = $stdOutBuilder.ToString() -replace $null,''
$stdErr = $stdErrBuilder.ToString() -replace $null,''
## Free resources associated with the process, this does not cause process to exit
If ($process) { $process.Dispose() }
# check if we have output and return it
If ($stdOut) {
Write-Output "Process output:$stdOut"
}
If ($stdErr) {
Write-Output "Process errors:$stdErr"
}
# return exit code
Write-Output "Exit code:$returnCode"
Start-Sleep -Seconds 5
Exit $returnCode