Last week, I was launching a PowerShell script from a .Net application. This is something I already wrote about back in June 2018.
I still use something like this in my apps. But this time, I want to be able to run a script that is persisted on disk and, even more important, to be able to get the exit code of my script into my .Net application.
Of course PowerShell has the Exit statement but the code is not easily read by a .Net application.
The downloadable code
This month’s downloadable demo solution contains both VB and C# projects. The solution was created using Visual Studio 2019 but can be used in most older versions as well.
Figure 1: The demo application in action
The PowerShell Exit statement
I was aware of this statement to terminate the execution of a script and saw in the documentation that it was possible to pass and exitcode arguments (which is an integer) to return. I modified my PowerShell script to return a value using the Exit statement but was surprised to not be able to find it from my Process object in .Net and its ExitCode property.
I was extremely disappointed. But at the same time, it would have been too easy.
Searching for a workaround
I found an 10-years-old blog post with a very promising approach. Even if it is not dedicated to .Net, it was worth the test.
As explained in this blog post, we can call the SetShouldExit method with a numeric value and that should be available from the outside world. I did a quick test and it worked exactly as expected. My ExitCode property in .Net was filled with the value I put in my script.
Strange behavior when running in ISE
While doing some more test on my script directly in the Windows PowerShell ISE, I discovered that when I was running my script, the full ISE application was simply closing. Not particularly useful when you are running your script and it just shut down before you can do anything.
So here is my script
So, I really wanted to use SetShouldExit but really did not want my ISE to shut down every time I test my script.
I created a reusable function in my script (called ExitWithCode) that checks if the script is running from the console or not by checking the content of the $host.Name property. That function which accepts an integer parameter, can be called from anywhere in your script.
Here is my full test script:
function ExitWithCode{ param($pExitCode) Write-Host "ExitWithCode called with code $pExitCode " Write-Host "" Write-Host "script completed at " (Get-Date) if ($Host.Name -eq "ConsoleHost") { #running from the command-line - exit the script $host.SetShouldExit($pExitCode) } else { Write-Host "since we are running from PowerShell ISE, just stop the execution" Stop-Transcript exit }}[string]$transcriptFile = "demo_" + (Get-Date).ToString("yyyyMMdd_HHmmss") + ".log"Start-Transcript .\$transcriptFileclswrite-host "Starting script execution"write-host "demoscript is running"ExitWithCode 12345
Now that we have a script, let’s see what needs to be done on the .Net side.
Simple UI
There is no need of a complex UI to test this.
A textbox to be able to provide the path to the script, a button to launch the execution, and a textbox to display results.
The code
Do not expect rocket science here!
It is amazingly simple to use the Process object to launch a PowerShell script and I already told you that there is an ExitCode property available on that object to read the returned value.
Private Sub btnExecute_Click(sender As Object, e As EventArgs) Handles btnExecute.Click txtResults.Clear() Dim script As String = txtScript.Text txtResults.Text += "trying to run script " + script + Environment.NewLine Dim objStartInfo As New ProcessStartInfo With { .FileName = "powershell.exe", .UseShellExecute = False, .RedirectStandardError = True, .Arguments = $"-File ""{script}"" ", .WorkingDirectory = Application.StartupPath, .WindowStyle = ProcessWindowStyle.Normal, .CreateNoWindow = True } Dim objProcess As Process = Process.Start(objStartInfo) objProcess.WaitForExit() txtResults.Text += Environment.NewLine txtResults.Text += "-------" + Environment.NewLine txtResults.Text += "ExitCode= " + objProcess.ExitCode.ToString() + Environment.NewLine Dim errors As String = objProcess.StandardError.ReadToEnd() txtResults.Text += Environment.NewLine txtResults.Text += "-------" + Environment.NewLine txtResults.Text += "Errors:" + Environment.NewLine txtResults.Text += errors + Environment.NewLineEnd Sub
private void button1_Click(object sender, EventArgs e){ txtResults.Clear(); string script = txtScript.Text; txtResults.Text += "trying to run script " + script + Environment.NewLine; var objStartInfo = new System.Diagnostics.ProcessStartInfo { FileName = @"powershell.exe", UseShellExecute = false, RedirectStandardError = true, Arguments = $@"-File ""{script}"" ", WorkingDirectory = Application.StartupPath, WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal, CreateNoWindow = true }; System.Diagnostics.Process objProcess = System.Diagnostics.Process.Start(objStartInfo); objProcess.WaitForExit(); txtResults.Text += Environment.NewLine; txtResults.Text += "-------" + Environment.NewLine; txtResults.Text += "ExitCode= " + objProcess.ExitCode + Environment.NewLine; string errors = objProcess.StandardError.ReadToEnd(); txtResults.Text += Environment.NewLine; txtResults.Text += "-------" + Environment.NewLine; txtResults.Text += "Errors:" + Environment.NewLine; txtResults.Text += errors + Environment.NewLine;}
Conclusion
It is not because I misunderstood that the ExitCode in .Net does not contain that exit code of PowerShell that there is nothing that can be done.
A simple googling session provide me an easy workaround that I can now use in all my scripts.
And as a bonus, I also give you a trick to be able to test your scripts from the ISE with being frustrated about the app closing!