Tips for writing good PowerShell scripts for OpsMgr Workflows – Part 2

This is part 2 of my little series around writing good PowerShell scripts for OpsMgr workflows. You can find the other part(s) here:

This post focuses on just one topic:

How do I load the OpsMgr module within an OpsMgr PowerShell workflow the right way?

 

Usage scenarios

There are good reasons and several usage scenarios for creating and using a SCOM SDK connection (thus leveraging the Operations Manager PS module) within a SCOM workflow. Some examples are:

  • Creating MP backups on a regular basis
  • Cleaning up Alerts (Alert Housekeeping)
  • Resetting Monitor Health States (also Alert Housekeeping)
  • Exporting SCOM Key Performance data on a regular Basis (like Alert counts etc.)
  • Custom Maintenance solutions (well, not needed anymore with SCOM 2016)

Requirements

What do we need to connect to the SCOM SDK?

  • An account which is allowed SDK access (can be a dedicated RunAs account or a built in Action account)
  • The Operations Manager module itself. This module is only available on machines where the SCOM console is installed. So it is NOT available on a SCOM agent by default unless a SCOM console or the module itself is also deployed!
  • Network connectivity via TCP port 5724 to a SCOM Management Server

These requirements usually limit the usage of SDK connections within a workflow to SCOM Management Servers, because only these servers fulfil all requirements by default.
Therefore, all of my own workflows that uses a SDK connection are targeted against a SCOM Management Server.

 

Creating a SCOM SDK connection the usual way ...

“Ok, so what’s the deal?” you might think. The easiest way is to simply import the OpsMgr module in a PowerShell script by its name, create a new Management Group connection object and you are done:

Import-module OperationsManager
New-scommanagementgroupconnection -computer My.MS.local

This way will usually work. Most of the time.

... and the better, more stable way

But after some bad experiences I strongly recommend another, more stable method for creating a SCOM SDK connection by using the full qualified module path:

$OMModuleName = "OperationsManager"
$OMPowerShellKey = "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Powershell\V2" #Valid for OM12 and OM16
$OMModulePath = Join-Path (Get-ItemProperty $OMPowerShellKey).InstallDirectory $OMModuleName
Import-Module $OMModulePath
New-scommanagementgroupconnection -computer My.MS.local

 

Possible issue with the PowerShell host of the health service

Why do I recommend this way? Well, as I said, the first (simple) approach usually works. But under certain circumstances it might not! The health service (the component on an agent and a management server responsible for managing and running all SCOM workflows) uses its own optimized PowerShell host for executing PowerShell scripts within a workflow. When you import a module by using its name, the PowerShell host searches for the right module file (*.psm1) in all defined pathes in the environment variable PSModulePath:
2016-09-15-PSModulePaths

Under some circumstances, which I unfortunately cannot really grab or exactly define yet, at some point in time the PowerShell host of the health service “looses” the content of this variable and thus cannot find and load any new module.
That obviously causes all scripts to fail which are loading additional PowerShell modules! If this situation happens, only a restart of the health service (Microsoft Monitoring Agent) will fix the problem!

The tricky part is:
When this issue occurs, SCOM will not throw any error (neither in the Operations Manager event log nor in a verbose ETL trace). The only way you can notice this situation is by analyzing failed scripts (this would be the hard way) or by checking the  (hopefully) detailed error/debug logs of your scripts like this:

2016-09-15-Error

I have discussed this issue with several colleagues (special thanks here to Kevin Holman) and we all agreed, that loading a module via its full path is the better and more stable solution.
I can now prove this, because I have tested it in several situations at customer sites. When the usual “import-module modulename” method fails, the “import-module <FullPathToModule> ” still continues to work!

new-customscommgconnection

For my own convenience (and hopefully yours as well) I have written a small function new-customscommgconnection that I use to connect to the SDK. The function is published at the Technet Gallery here. It contains detailed error handling and logging functionality and returns an array with two values:

  • Array[0] contains a ManagementGroup connection object, when the connection was successful or $Null, if any error occurs
  • Array[1] contains a detailed error description if anything goes wrong.

So you can use the function in your scripts like this:
$result = new-customscommgconnection -managementserver my.ms.local
if ($result[0] -eq $null)
{#Stop script if something went wrong
$strMessage = "Something went wrong. Detailed error: {0}" -f $result[1]
Write-Error $strMessage
exit -1
}
#Continue script. $result[0] contains your Management Group connection object