Tips for writing good PowerShell scripts for OpsMgr Workflows – Part 1
I really love PowerShell and I do write a lot of scripts. This is part 1 of a series of blog posts providing some tips I use when writing PowerShell scripts that can be later used in OpsMgr workflows.
Tip: Use your own custom script template
I hate to reinvent the wheel over and over again. So I created a script template, which I can use for 90%+ of all my OpsMgr PowerShell script requirements (that is the reason why my empty template contains almost 500 LOC):
- Use descriptive PowerShell regions to modularize your script template. With regions you can easily navigate even in complex scripts. Editors like ISE, PowerGUI or VisualStudio support regions.
- To make my PowerShell scripts more readable (especially bigger ones), I place all main function code into a function “Main” infront of all other functions. That allows for faster access and hopefully more readable code.
- Prepopulate the template with a framework of text like:
Tip: Make use of logging
I always try to make my scripts as descriptive and traceable as possible (or needed). This is especially true for PowerShell scripts running as Operations Manager workflows. Troubleshooting and debugging those scripts can be quite cumbersome, especially in complex and big environments, where I do not have access to every SCOM agent my scripts are running on. Therefore, I learned to value good logging the hard way...
Where to log?
You can e.g. use plain text logs, databases or the Windows Event Log system. I prefer using event logs, as their records are easily collectable via OpsMgr into a central location (the OpsMgr DB). You can than write a simple event collection rule in OpsMgr to gather all script events from your specific agents.
If I know that my PowerShell script will be used in a OpsMgr task, writing logging information to StdOut (e.g. with write-host) can also be helpful. This output will be contained in the result window of the OpsMgr task and the task history object.
What to log?
That depends on the situation and your requirements. I prefer to log at least these kind of information:
- General information for every (event) log entry:
The name and the version of the script. This allows for easy identification which component has written the entry and the version number makes it easy to understand if you are using the most current version of a script.
- Script startup event, containing these additional information:
- User executing the script (MyDomain\MyUser). That helps a lot troubleshooting RunAs account issues!
- All current parameter used during the call of the script and their values. Makes general troubleshooting much easier!
- Script stop event, containing these additional information:
- Runtime in seconds, allows to detect slow runtimes or performance issues
- State of the execution: Sucessful? Error?
- List of all occurred PowerShell Errors, e.g. by dumping the content of the PowerShell $Error variable.
- Every other important step/output/status of the script
How to log
Depending on the type of logging, I use different methods/Cmdlets for that:
- Writing Event log information
For writing events into the OpsMgr Event log I use the LogEvent method of the MOMScriptAPI COM object (https://msdn.microsoft.com/de-de/library/bb437630.aspx)
MOMScriptAPI.LogScriptEvent(bstrScriptName, wEventID, wSeverity, bstrDescription)
If I need to write to a different event log like “Application”, I use the PowerShell built-in Cmdlets (write-eventlog).
- Writing text log information
For writing text logs I will use the built-in PowerShell Cmdlets like out-file etc.
Make your logging optional
If you really log a lot of information in a script that is executing frequently (e.g. every 5 min), that logging can be potentially filling up your resources. Therefore, I recommend making your logging optional, so that the user can decide and maybe turn it off if it is not necessary. For this reason I use a parameter called “DebugScript” in my script, which triggers the verboseness of my scripts.
NOTE: Using $MyInvocation in OpsMgr PowerShell scripts
I use the PowerShell built-in variable $MyInvocation a lot in my scripts. The methods of these variable are giving me easy access to the current Scriptname, the currently used parameter and its values etc pp.
Unfortunately $MyInformation works a bit different when executed in a OpsMgr PowerShell host:
The script name is a generic name and some properties like e.g. $MyInvocation.line (which gives us access to the script call) are empty and not available. If you run the same script in the normal PowerShell, e.g. the $MyInvocation.line is obviously available:
Therefore I place the script name into a static local variable and parse the $MyInvocation.BoundParameters property, if I need to know the script parameter and their exact values used when calling the script (see screenhost of my sample start event as an example).
That’s it for today. More tips will follow in the upcoming weeks.