Tuesday, September 17, 2013

Return error codes or result codes from PowerShell scripts

You write a script and create a scheduled task to run it.  It runs fine every night for weeks.  Then you notice that the work has been piling up.  You check Task Scheduler, and it reports the task completed successfully.  But it also reports that the script finished in seconds when it should have taken several minutes.  What happened?

Well first of all, your script is broken.  But I don't care.  You can figure that out by yourself.  It's your imaginary script, after all.

More interesting is what happened with Task Scheduler.

Task Scheduler was told to launch PowerShell with a parameter to run the script.  It did that successfully.  It then waits for PowerShell to finish and go away, and log the completion time.  It did that successfully as well.  Task Scheduler doesn't know or care what did or did not happen during the execution of the script.  As far as it is concerned, this was yet another task perfectly executed.

But we want to know the script failed, and we want a hint as to why.

You will see this same behavior and face this same challenge when you launch PowerShell scripts from System Center Orchestrator, or from a command line, or from within another PowerShell script, or from anything else that lets you do such things.

What we want to do, of course, is pass a result code back to the calling application, in our case, Task Scheduler, so that it can respond when appropriate, or at least log the result code.

There are two steps to accomplish this: passing a result code out of the script, and passing the result code on to the calling application.

The trick to the second part, is instead of telling PowerShell to run your script, you tell it to run a two-line code snippet, which in turn runs your script, and then returns the result code.

Currently your task launches PowerShell with parameter:

-File D:\Scripts\MyFavoriteScript.ps1

(You can and probably do leave off the "-File" parameter name.)

Instead, use this:

-Command { . D:\Scripts\MyFavoriteScript.ps1; Exit $LastResultCode }

Dot referencing the script executes it.

$LastResultCode is a built-in variable where PowerShell stores the result code from the last time something within our script or code snippet ran a child instance or PowerShell and returned a result code.

The Exit function exits the code snippet and passes out the contents of $LastResultCode as the result code for the snippet, which in turn will be used as the result code for the task.

Similarly, we use Exit to exit the script and pass a result code out to the snippet.

You can test some condition at the end of or anywhere within your script, and if you don't find what you like, exit the script and pass the result code of your choice:

If ( -not Test-Path D:\NewFile.pdf ) { Exit 42 }

Or you can use built in variables to test the success of a command and if exit it fails.

New-Item D:\BadPath\LogFiles -ItemType directory

If ( -not $? ) { Exit 84 }

To signal successful completion of the script, Exit with code 0, or use Exit without an explicit code, or just let your script finish normally without an Exit function.

So to sum up, use Exit in your script to pass out your desired result code:

Exit 42

and use a code snippet to launch your script and pass on the result code:

PowerShell.exe -Command { . D:\Script1.ps1; Exit $LastResultCode }

Thanks to Ben Schneider at General Mills for his great talk about automation with System Center Orchestrator at the Minneapolis System Center Users Group, which inspired this article.

1 comment:

  1. Nice article again, and practical! Wish I had this when I was learning about exit codes. Now, I use a different exit code for every possible failure condition. Easier and faster than anything to find where in the code it broke... LastResultCode was 13, search for "exit 13" in my code and there's where it broke. Then I can trace through with the debug/verbose messages.