Reputation: 21
I am trying to have a powershell script called from a C# program give me the exit code of the powershell script. I have attempted it a few ways with no success. The PSObject from the invoke call always has a count of 0.
I have been using this C# code and the PSObject always has a count of 0
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript(File.ReadAllText(buildScript.ps1));
var psResults = ps.Invoke();
foreach (PSObject psObj in psResults)
{
var result = psObj.ToString());
}
ps.Dispose;
}
I trimmed down the .PS1 file for testing and it looks like this.
Start-Sleep -Seconds 30
Write-Host "Exit code is : 25"
exit 25
Upvotes: 2
Views: 107
Reputation: 437052
Use .AddCommand()
rather than .AddScript()
; .AddCommand()
allows direct invocation of *.ps1
files by file path, and reflects their exit code in the automatic $LASTEXITCODE
variable.[1]
After execution, you can invoke .Runspace.SessionStateProxy.GetVariable("LASTEXITCODE")
on your System.Management.Automation.PowerShell
instance to obtain the value of this variable.
Therefore:
// Create an initial default session state.
var iss = System.Management.Automation.Runspaces.InitialSessionState.CreateDefault2();
// Windows only:
// Set the session state's script-file execution policy
// (for the current session (process) only).
iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Bypass;
using (PowerShell ps = PowerShell.Create(iss))
{
ps.AddCommand(@"/path/to/your/buildScript.ps1");
// Invoke synchronously and process the success output.
// To retrieve output from other streams, use ps.Streams later.
foreach (PSObject psObj in ps.Invoke())
{
Console.WriteLine(psObj.ToString());
}
// Obtain the exit code.
int exitCode = (int)ps.Runspace.SessionStateProxy.GetVariable("LASTEXITCODE");
Console.WriteLine($"Exit code: {exitCode}");
}
[1] The reasons for preferring .AddCommand()
over .AddScript()
are:
(a) You can use script file paths that contain spaces and other metacharacters as-is (whereas .AddScript()
would require use of embedded quoting and &
, the call operator)
(b) You can pass
richly typed parameter values via .AddArgument()
/ .AddParameter()
/ .AddParameters()
(whereas .AddScript()
would require you to "bake" the parameter values as string literals into the single string argument passed to it).
In short: .AddScript()
is for executing arbitrary PowerShell source code, whereas .AddCommand()
is for executing a single command by name or path, such as a *.ps1
file. It is important to know this distinction, because AddScript()
will only behave like .AddCommand()
in the simplest of cases: with a space-less *.ps1
path that is also free of other metacharacters, to which no arguments need be passed.
See this answer for more information.
[2] Note, however, that if your machine's / user account's execution policy is controlled by GPOs, a process-level override will not work; see this answer for details.
Upvotes: 0
Reputation: 59772
Normally you shouldn't rely on exit codes from PowerShell script, but to answer your question, with your current implementation you can query $LASTEXITCODE
automatic variable before disposing your PowerShell instance, however, for this to work you will need to pass-in the script path as .AddScript(...)
argument, instead of reading the script content via File.ReadAllText(...)
. Preferably you should use an absolute path but relative might work
You should also handle the Write-Host
output in your code, that output you can find it in ps.Streams.Information
, it will not be output from .Invoke()
.
Alternatively, you could subscribe to DataAdding
event:
ps.AddScript(@".\buildScript.ps1");
ps.Streams.Information.DataAdding += (s, e) =>
{
InformationRecord info = (InformationRecord)e.ItemAdded;
Console.WriteLine(info.MessageData);
};
In summary you can do:
int exitCode = 0;
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript(@".\buildScript.ps1");
var psResults = ps.Invoke();
foreach (PSObject psObj in psResults)
{
var result = psObj.ToString();
}
// if not using the event driven approach
if (ps.Streams is { Information.Count: > 0 })
{
foreach (InformationRecord information in ps.Streams.Information)
{
// handle information output here...
}
}
if (ps.HadErrors)
{
ps.Commands.Clear();
exitCode = ps
.AddScript("$LASTEXITCODE")
.Invoke<int>()
.FirstOrDefault();
}
}
// Do something with exitCode
Upvotes: 2