Saturday, November 29, 2014

Powershell -- cascading exit codes through nested shells

Finally resolved why I couldn't repro the issue in this cut down case; so, for future reference, just the real problem, and none of the dead ends:

I have a problem. I want to run a set of PowerShell scripts from an orchestrating PowerShell script, each in their own process so that I can relinquish assemblies that they've Add-Typed quickly, and thus allow them to be updated when I re-deploy the whole system. And those scripts can potentially fail for some reasons, and the failure can be soft (retry with different parameters) or hard (abort entirely).

Plus, I don't want to capture the (write-)output of the inner scripts as I want to watch their progress; which leaves me with the exit code as mechanism, which is enough for my need.

We can test this mechanism with a simple script that we can make fail on demand:

And drive it like

This results in

PS> $LASTEXITCODE
0
PS> .\OuterScript.ps1
output
host

01 December 2014 17:34:50
Inner script done
Got file code 0
output
host

01 December 2014 17:34:52
Inner script done
Got file code 23
PS> $LASTEXITCODE
23

However, if I add in one line (the one with the comment):

We get

PS> $LASTEXITCODE
23
PS> $LASTEXITCODE = 0
PS> $LASTEXITCODE
0
PS> .\OuterScript.ps1
output
host

01 December 2014 17:36:14
Inner script done
Got file code 0
output
host

01 December 2014 17:36:15
Inner script done
Got file code 0
output
host

01 December 2014 17:36:17
Inner script done
Got file code 0
PS> $LASTEXITCODE
0
PS> 

we get bitten by PowerShell's odd behaviour regarding automatic variables, which makes the local use of the name somehow refer to a different (and locally overriding) thing to what gets set by process exit -- another variation on the gotcha I hit a couple of years ago.

What I'd been hitting was just that explicit zeroing of the exit code (in a dense block of initialisations, where I'd not spotted it) had been happening, before a process launch and completion had created the "real" $LASTEXITCODE. Remove that line, leave the value unset on start, and it all just works.