How to find Subscriptions with deleted Rules and Monitors

… Or an introduction to subscription criteria.

What I want to share today is a PowerShell snippet I've been sitting on that checks out the current subscriptions for rules or monitors that no longer exist. With some of the newer management packs replacing their old versions, this can really help save some time in tracking down one-off subscriptions that tie to those old rules and monitors. Also, it's always good to check this before performing any major migration or upgrade say from 2007 R2 to 2012. And there is always the case you might find yourself trying to edit a complex subscription and it’s resulting in a System.NullReferenceException error in the console.

Without further ado, the snippets…

2007 R2:

 foreach ($notificationsub in Get-NotificationSubscription) {
    $notificationsubname=$notificationsub.DisplayName
    $writesub = $true
    $criteria = [xml]$notificationsub.Configuration.Criteria.ToString()
    $xmlnsmgr = New-Object System.Xml.XmlNamespaceManager $criteria.CreateNavigator().NameTable
    foreach ($simpexpr in $criteria.SelectNodes("//SimpleExpression", $xmlnsmgr)) {
        $property = ""
        $guid=0
        $element=$null
        $property = $simpexpr.GetElementsByTagName("Property").Item(0)."#text".ToString()
        if ($property -eq "RuleId" -or $property -eq "ProblemId") {
            $value = $simpexpr.GetElementsByTagName("Value").Item(0)."#text".ToString()
            try {
                $guid=[guid]$value
            } catch {}            
        }
        if ($guid -ne 0) {
            if ($property -eq "RuleId") {
                $element=Get-Rule -Criteria "Id = '$guid'"
            } else {
                $element=Get-Monitor -Criteria "Id = '$guid'"
            }
            if ($element -eq $null) {
                if ($writesub) {
                    Write-Output ""
                    Write-Output "Subscription: $notificationsub"
                    Write-Output "Name: $notificationsubname"
                    Write-Output "------------------------------------------------------------------------------"
                    $writesub=$false
                }
                if ($property -eq "RuleId") {
                    Write-Output "Rule: $guid"
                } else {
                    Write-Output "Monitor: $guid"
                }
            }
        }
    }
    if (!$writesub) {
        Write-Output ""
    }
}

2012:

 foreach ($notificationsub in Get-SCOMNotificationSubscription) {
  $notificationsubname=$notificationsub.DisplayName
  $writesub = $true
  if ($notificationsub.Configuration.Criteria -ne $null) {
    $criteria = [xml]$notificationsub.Configuration.Criteria.ToString()
    $xmlnsmgr = New-Object System.Xml.XmlNamespaceManager $criteria.CreateNavigator().NameTable
    foreach ($simpexpr in $criteria.SelectNodes("//SimpleExpression", $xmlnsmgr)) {
        $property = ""
        $guid=0
        $element=$null
        $property = $simpexpr.GetElementsByTagName("Property").Item(0)."#text".ToString()
        if ($property -eq "RuleId" -or $property -eq "ProblemId") {
            $value = $simpexpr.GetElementsByTagName("Value").Item(0)."#text".ToString()
            try {
                $guid=[guid]$value
            } catch {}            
        }
        if ($guid -ne 0) {
            if ($property -eq "RuleId") {
                $element=Get-SCOMRule -Id $guid
            } else {
                $element=Get-SCOMMonitor -Id $guid
            }
            if ($element -eq $null) {
                if ($writesub) {
                    Write-Output ""
                    Write-Output "Subscription: $notificationsub"
                    Write-Output "Name: $notificationsubname"
                    Write-Output "------------------------------------------------------------------------------"
                    $writesub=$false
                }
                if ($property -eq "RuleId") {
                    Write-Output "Rule: $guid"
                } else {
                    Write-Output "Monitor: $guid"
                }
            }
        }
    }
    if (!$writesub) {
        Write-Output ""
    }
   }
}

Well that’s great and all, but how do we use it?

In either case of 2007 or 2012, either save the above script to a PS1 file or copy/paste it directly into the Operations Manager Shell.

Once run, one of two things may happen:

1) No output. Great! No problem.

2) Output…

Subscription: Subscription26c89132_b882_4d51_9924_74724e3bed9e
Name: Broken Sub
------------------------------------------------------------------------------
Rule: 36f41859-7246-60c9-1a24-834c9597fc42

Okay, so it found a subscription and returned a Rule’s GUID. It can’t return the name of the rule since it no longer exists. To fix this, either modify the Notification Internal Library MP XML or just remove the entire subscription and rebuild it from scratch.

Note, as tested in the 2012 SP1 console, when criteria contains a missing rule/monitor and it was the only one specified in the criteria, the UI will present it as though no criteria for the specific rule/monitor has been specified. If multiple rules/monitors are specified and one is missing, the console will not allow you to modify the criteria. Although different factors could result in different behavior, and therefore experiences may vary.

In order to modify the MP XML with this rule, first export the Notifications Internal Library MP aka Microsoft.SystemCenter.Notifications.Internal.xml.

Then search for the GUID returned by the PowerShell, in the example this was 36f41859-7246-60c9-1a24-834c9597fc42. It should be found in a <Criteria> block:

               <Criteria>
                <Expression>
                  <SimpleExpression xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
                    <ValueExpression>
                      <Property>RuleId</Property>
                    </ValueExpression>
                    <Operator>Equal</Operator>
                    <ValueExpression>
                      <Value>36f41859-7246-60c9-1a24-834c9597fc42</Value>
                    </ValueExpression>
                  </SimpleExpression>
                </Expression>
              </Criteria>

In this case that rule is the only criteria, so to go a step further, the whole rule can be deleted (everything between and including the <Rule ID="Subscription26c89132_b882_4d51_9924_74724e3bed9e"… and </Rule> tags). Then also remove the associated DisplayString block near the end of the MP.

In the case where it is not the only criteria, it can get a bit complex:

               <Criteria>
                <Expression>
                  <And xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
                    <Expression>
                      <Or>
                        <Expression>
                          <SimpleExpression>
                            <ValueExpression>
                              <Property>ProblemId</Property>
                            </ValueExpression>
                            <Operator>Equal</Operator>
                            <ValueExpression>
                              <Value>d064c5b4-1ca9-a551-04b3-8a6d6ca9919f</Value>
                            </ValueExpression>
                          </SimpleExpression>
                        </Expression>
                        <Expression>
                          <SimpleExpression>
                            <ValueExpression>
                              <Property>RuleId</Property>
                            </ValueExpression>
                            <Operator>Equal</Operator>
                            <ValueExpression>
                              <Value>36f41859-7246-60c9-1a24-834c9597fc42</Value>
                            </ValueExpression>
                          </SimpleExpression>
                        </Expression>
                      </Or>
                    </Expression>
                    <Expression>
                      <SimpleExpression>
                        <ValueExpression>
                          <Property>Severity</Property>
                        </ValueExpression>
                        <Operator>Equal</Operator>
                        <ValueExpression>
                          <Value>2</Value>
                        </ValueExpression>
                      </SimpleExpression>
                    </Expression>
                  </And>
                </Expression>
              </Criteria>

In short we need to remove the <Expression> block that contains the missing rule, but in the above case we have additional criteria that takes some careful consideration. And that is that the Rule/Monitor criteria is wrapped in an <Or> block, with additional criteria contained in an <And> block. A quick note about <Or> and <And> blocks, they require at least two <Expression> blocks. With that in mind, if <Expression> block containing the missing rule is removed, the MP XML will become invalid. In that case we need to remove the <Expression> and <Or> tags, along with their matching closing tags, but leaving the part of the desired criteria (the ProblemId expression). Since that barely made sense typing it, this is what the end result would look like:

               <Criteria>
                <Expression>
                  <And xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
                        <Expression>
                          <SimpleExpression>
                            <ValueExpression>
                              <Property>ProblemId</Property>
                            </ValueExpression>
                            <Operator>Equal</Operator>
                            <ValueExpression>
                              <Value>d064c5b4-1ca9-a551-04b3-8a6d6ca9919f</Value>
                            </ValueExpression>
                          </SimpleExpression>
                        </Expression>
                    <Expression>
                      <SimpleExpression>
                        <ValueExpression>
                          <Property>Severity</Property>
                        </ValueExpression>
                        <Operator>Equal</Operator>
                        <ValueExpression>
                          <Value>2</Value>
                        </ValueExpression>
                      </SimpleExpression>
                    </Expression>
                  </And>
                </Expression>
              </Criteria>

Be cautious when there is only one rule/monitor specified, but other criteria is present such as severity. If the rule/monitor criteria is removed, but the severity match remains, then it would change the subscription to match all alerts raised with that particular severity.

Once the XML has been modified to fit the new desired criteria, try to import the MP (preferably in a Lab/Dev/QA environment). If an error is returned hopefully it is descriptive enough to fix the issue. If not retrace the steps to make sure all XML tags have a valid start/end, and that there are no orphaned DisplayStrings if a Rule was removed. Once it imports, the issue should resolved.

What if:

- I’m not too good modifying XML: I recommend deleting and rebuilding the subscription.

- I have hundreds of these!

Theoretically this could be automated, but it would take a pretty fair understanding of XML and the criteria schema to check for the aforementioned scenarios, as well as any others I didn’t consider. The nice thing is if the criteria is broken, it won’t import the management pack. If there’s interest in an automated 2012 version, feel free to leave a comment and let me know.

Here are some resources that may be of some use:

ExpressionType Reference
https://msdn.microsoft.com/en-us/library/jj130463.aspx

Configuring Operations Manager 2007 R2 Product Connector Subscription Advanced Criteria – In case you came across this article to gain a deeper understanding notification subscription criteria or the ExpressionType syntax
https://support.microsoft.com/?kbid=2026093

“remove section of an XML file” – Where I might start if I were to automate this process. Keep in mind portions of criteria in And/Or statements that are intended to remain will need saved to a new node before the entire And/Or nodes are deleted.
https://social.technet.microsoft.com/Forums/windowsserver/en-US/702f3e4a-df64-4a97-af8b-1e247b6633e2/remove-section-of-an-xml-file?forum=winserverpowershell

Last, but not least: This is information is provided as-is and provides no warranty. Always maintain a backup of your management packs, and never work on notification subscriptions in a production environment.