Monthly Archives: March 2015

Fighting Clutter in SCOM

When I started to write this post I lingered for a while on what the title should be. I wanted it to suggest the continuous fight that all SCOM administrators face to keep their monitoring environment in order.
In particular I would like to cover a few aspects of it and some suggested solutions.

Management Packs changes and interdependencies

You have to agree with me that the quality of your SCOM monitoring environment is heavily driven by the quality of the Management Packs that are imported at any given time.
And regardless of your level of intimacy with Management Packs, you do know that they change several times (some of them rather often) in their lifetime. So here is the first challenge of staying in control: audit the Management Pack changes.
Each Management Pack has also dependencies: it depends on some MPs while other MPs depend on it. This becomes evident when you are trying for instance to delete an MP and you cannot as SCOM is complaining that other MPs depend on it.
In the course of its lifetime, a SCOM environment will most probably see an ever increasing number of MPs. And even if name conventions are in place and some guidelines on MP Authoring are documented to keep things tidy, chances are that some cleanup will be needed at times.
The cleanup task is a tedious one; it presents the challenge on where to start first, how to approach. If you ever encounter such a situation you probably thought how useful a “map” would be. Let’s say in the form of a diagram showing the MPs and the dependencies between them.
Hence the idea of authoring a management pack that will discover instances of a class that will represent a MP.

<ClassType ID="MPAudit.MP" Accessibility="Public" Abstract="false" Base="System!System.Entity" Hosted="false" Singleton="false" Extension="false">
          <Property ID="MPName" Type="string" AutoIncrement="false" Key="true" CaseSensitive="true" MaxLength="256" MinLength="0" Required="true" Scale="0" />
          <Property ID="MPDisplayName" Type="string" AutoIncrement="false" Key="false" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" />
          <Property ID="MPFriendlyName" Type="string" AutoIncrement="false" Key="false" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" />
          <Property ID="Version" Type="string" AutoIncrement="false" Key="false" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" />
          <Property ID="TimeCreated" Type="string" AutoIncrement="false" Key="false" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" />
</ClassType>

And while discovering the MPs, let’s also discover the relationships between MPs.

<RelationshipType ID="MPAudit.MPContainsMP" Accessibility="Public" Abstract="false" Base="System!System.Containment">
          <Source ID="Source" Type="MPAudit.MP" />
          <Target ID="Target" Type="MPAudit.MP" />
</RelationshipType>

You can use the following discovery:

<Discovery ID="MPAudit.MP.Discovery.Direct" Enabled="true" Target="SC!Microsoft.SystemCenter.RootManagementServer" ConfirmDelivery="true" Remotable="true" Priority="Normal">
        <Category>Discovery</Category>
        <DiscoveryTypes>
          <DiscoveryClass TypeID="MPAudit.MP" />
          <DiscoveryRelationship TypeID="MPAudit.MPContainsMP" />
        </DiscoveryTypes>
        <DataSource ID="PSDiscovery" TypeID="Windows!Microsoft.Windows.TimedPowerShell.DiscoveryProvider">
          <IntervalSeconds>14400</IntervalSeconds>
          <SyncTime />
          <ScriptName>MPsDiscovery.ps1</ScriptName>
          <ScriptBody><![CDATA[param($sourceId,$managedEntityId)

$erroractionpreference = "SilentlyContinue"

$api = new-object -comObject 'MOM.ScriptAPI'
$discoveryData = $api.CreateDiscoveryData(0, $SourceId, $ManagedEntityId)

$MS = gc env:computername
$ScriptName = "MPsDiscovery.ps1"
$LogEventID = 1510

$evt = New-Object System.Diagnostics.EventLog("Operations Manager")
$evt.Source = "SCAudit"
$infoevent  = [System.Diagnostics.EventLogEntryType]::Information
$warnevent  = [System.Diagnostics.EventLogEntryType]::Warning
$errorevent = [System.Diagnostics.EventLogEntryType]::Error

Write-Host "INF: Script started on $MS"
$evt.WriteEntry("Script $ScriptName started on $MS",$infoevent,$LogEventID)

$ModuleImportError = $false
Try
{
	#Import-Module OperationsManager

	$setupKey = Get-Item -Path "HKLM:\Software\Microsoft\Microsoft Operations Manager\3.0\Setup"
	$installDirectory = $setupKey.GetValue("InstallDirectory") | Split-Path
	$psmPath = $installdirectory + ‘\Powershell\OperationsManager\OperationsManager.psm1’

	Import-Module $psmPath

	$conn = (New-SCOMManagementGroupConnection –ComputerName $MS)
}
Catch [system.exception]
{
	$evt.WriteEntry("Errors detected while importing module OperationsManager on $MS; $error",$errorevent,$LogEventID)
	$ModuleImportError = $true
}

If (!$ModuleImportError)
{

	[System.XML.XMLDocument]$oXML = New-Object System.XML.XMLDocument

	[System.XML.XMLElement]$oXMLmps = $oXML.CreateElement("mps")
	$oXML.appendChild($oXMLmps)

	$mps = Get-SCOMManagementPack

	Foreach ($mp in $mps)
	{

		[System.XML.XMLElement]$oXMLmp = $oXMLmps.appendChild($oXML.CreateElement("mp"))
		$oXMLmp.SetAttribute("name",$mp.Name)
		$oXMLmp.SetAttribute("displayname",$mp.DisplayName)
		$oXMLmp.SetAttribute("friendlyname",$mp.FriendlyName)
		$oXMLmp.SetAttribute("version",$mp.Version)
		$oXMLmp.SetAttribute("timecreated",$mp.TimeCreated)
		$oXMLmp.SetAttribute("lastmodified",$mp.LastModified)

		foreach ($mpref in $mp.References)
		{

			[System.XML.XMLElement]$oXMLmpref=$oXMLmp.appendChild($oXML.CreateElement("mpref"))
			$oXMLmpref.SetAttribute("name",$mpref.Value.Name)

		}

	}

	Foreach ($MP In $oXML.mps.mp) 
	{ 
		$MPinstance = $discoveryData.CreateClassInstance("$MPElement[Name='MPAudit.MP']$")
		$MPinstance.AddProperty("$MPElement[Name='MPAudit.MP']/MPName$", $MP.name)
		#$MPinstance.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", $MP.name)
		$MPinstance.AddProperty("$MPElement[Name='MPAudit.MP']/MPDisplayName$", $MP.displayname)
		$MPinstance.AddProperty("$MPElement[Name='MPAudit.MP']/MPFriendlyName$", $MP.friendlyname)
		$MPinstance.AddProperty("$MPElement[Name='MPAudit.MP']/Version$", $MP.version)
		$MPinstance.AddProperty("$MPElement[Name='MPAudit.MP']/TimeCreated$", $MP.timecreated)
		$discoveryData.AddInstance($MPinstance)
	}
	
	Foreach ($MPRef In $oXML.mps.mp.mpref)
	{
		If ($MPRef -ne $null)
		{
			$MPRefInstance = $discoveryData.CreateClassInstance("$MPElement[Name='MPAudit.MP']$")
			$MPRefInstance.AddProperty("$MPElement[Name='MPAudit.MP']/MPName$", $MPRef.name)
			
			$MPParentInstance = $discoveryData.CreateClassInstance("$MPElement[Name='MPAudit.MP']$")
			$MPParentInstance.AddProperty("$MPElement[Name='MPAudit.MP']/MPName$", $MPRef.ParentNode.name)
			
			$rel = $discoveryData.CreateRelationshipInstance("$MPElement[Name='MPAudit.MPContainsMP']$")
			$rel.source = $MPParentInstance
			$rel.target = $MPRefInstance
			$discoveryData.AddInstance($rel)
		}
	}	
			
}

$evt.WriteEntry("Script $ScriptName finished on $MS",$infoevent,$LogEventID)

$discoveryData]]></ScriptBody>
          <Parameters>
            <Parameter>
              <Name>sourceID</Name>
              <Value>$MPElement$</Value>
            </Parameter>
            <Parameter>
              <Name>managedEntityID</Name>
              <Value>$Target/Id$</Value>
            </Parameter>
          </Parameters>
          <TimeoutSeconds>120</TimeoutSeconds>
        </DataSource>
</Discovery>

Once we have the MPs discovered, we just need a simulation monitor to be attached. It will serve as our simulation of the impact an MP change will have on other, dependent MPs; no alert is needed, of course.
An example on how to implement such a testing monitor is:

<UnitMonitorType ID="MPAudit.SimulateMPChange.UMT" Accessibility="Public">
        <MonitorTypeStates>
          <MonitorTypeState ID="Ok" NoDetection="false" />
          <MonitorTypeState ID="Bad" NoDetection="false" />
        </MonitorTypeStates>
        <Configuration>
          <xsd:element minOccurs="1" name="IntervalSeconds" type="xsd:integer" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
        </Configuration>
        <OverrideableParameters>
          <OverrideableParameter ID="IntervalSeconds" Selector="$Config/IntervalSeconds$" ParameterType="int" />
        </OverrideableParameters>
        <MonitorImplementation>
          <MemberModules>
            <DataSource ID="DS1" TypeID="System!System.SimpleScheduler">
              <IntervalSeconds>$Config/IntervalSeconds$</IntervalSeconds>
              <SyncTime></SyncTime>
            </DataSource>
            <ProbeAction ID="PassThrough" TypeID="System!System.PassThroughProbe" />
          </MemberModules>
          <RegularDetections>
            <RegularDetection MonitorTypeStateID="Ok">
              <Node ID="DS1" />
            </RegularDetection>
          </RegularDetections>
          <OnDemandDetections>
            <OnDemandDetection MonitorTypeStateID="Bad">
              <Node ID="PassThrough" />
            </OnDemandDetection>
          </OnDemandDetections>
        </MonitorImplementation>
</UnitMonitorType>
<UnitMonitor ID="MPAudit.MPSimulateMPMonitor.UM" Accessibility="Public" Enabled="true" Target="MPAudit.MP" ParentMonitorID="Health!System.Health.AvailabilityState" Remotable="true" Priority="Normal" TypeID="MPAudit.SimulateMPChange.UMT" ConfirmDelivery="true">
        <Category>Custom</Category>
        <OperationalStates>
          <OperationalState ID="Green" MonitorTypeStateID="Ok" HealthState="Success" />
          <OperationalState ID="Yellow" MonitorTypeStateID="Bad" HealthState="Warning" />
        </OperationalStates>
        <Configuration>
          <IntervalSeconds>600</IntervalSeconds>
        </Configuration>
</UnitMonitor>

An extra ingredient (a dependency monitor) is also required for our solution to work.

<DependencyMonitor ID="MPAudit.MPSimulationDependencyMonitor" Accessibility="Public" Enabled="true" Target="MPAudit.MP" ParentMonitorID="Health!System.Health.AvailabilityState" Remotable="true" Priority="Normal" RelationshipType="MPAudit.MPContainsMP" MemberMonitor="Health!System.Health.AvailabilityState">
        <Category>Custom</Category>
        <Algorithm>WorstOf</Algorithm>
        <MemberUnAvailable>Error</MemberUnAvailable>
</DependencyMonitor>

As you can see I kept things very basic: the monitor resets itself periodically to healthy; and for OnDemand (if Recalculate button is pushed) it will turn yellow. Just enough provided for our simulations to work, right? Here is how such a thing (if implemented as above) will work.

The example might not 100% reflect a real scenario but I think it will make the point. Let’s say I have the following EMC MPs imported and I am wondering WhatIf I am about to change/remove the Monitoring MP, what other MPs will be affected?

post2-1

In Discovered Inventory, with the EMC.SI.Monitoring MP object selected, open HealthExplorer and on the Simulate MP Change Unit Monitor push the Recalculate Health button.

post2-2

Then check which of the other MPs changed state as well.

post2-3

You can draw diagrams for the MP that you are interested.

post2-4

As you probably anticipated now it’s easy to follow-up with some monitoring for MP changes; every so often (15 min for example) a datasource will provide property bag with MP(s) that have changed in the last interval. I will leave the implementation of this monitoring to you as homework.

Where are groups stored?

One information that is missing from the OpsMgr console UI is an easy (or any for the matter) way to find where a group is stored. Then why don’t we get the groups discovered by our MP.

<Discovery ID="MPAudit.Group.Discovery.Direct" Enabled="true" Target="SC!Microsoft.SystemCenter.RootManagementServer" ConfirmDelivery="true" Remotable="true" Priority="Normal">
        <Category>Discovery</Category>
        <DiscoveryTypes>
          <DiscoveryClass TypeID="MPAudit.Group" />
        </DiscoveryTypes>
        <DataSource ID="PSDiscovery" TypeID="Windows!Microsoft.Windows.TimedPowerShell.DiscoveryProvider">
          <IntervalSeconds>14400</IntervalSeconds>
          <SyncTime />
          <ScriptName>GroupsDiscovery.ps1</ScriptName>
          <ScriptBody><![CDATA[param($sourceId,$managedEntityId)

$erroractionpreference = "SilentlyContinue"

$api = new-object -comObject 'MOM.ScriptAPI'
$discoveryData = $api.CreateDiscoveryData(0, $SourceId, $ManagedEntityId)

$MS = gc env:computername
$ScriptName = "GroupsDiscovery.ps1"
$LogEventID = 510

$evt = New-Object System.Diagnostics.EventLog("Operations Manager")
$evt.Source = "SCAudit"
$infoevent  = [System.Diagnostics.EventLogEntryType]::Information
$warnevent  = [System.Diagnostics.EventLogEntryType]::Warning
$errorevent = [System.Diagnostics.EventLogEntryType]::Error

Write-Host "INF: Script started on $MS"
$evt.WriteEntry("Script $ScriptName started on $MS",$infoevent,$LogEventID)

$ModuleImportError = $false
Try
{
	#Import-Module OperationsManager

	$setupKey = Get-Item -Path "HKLM:\Software\Microsoft\Microsoft Operations Manager\3.0\Setup"
	$installDirectory = $setupKey.GetValue("InstallDirectory") | Split-Path
	$psmPath = $installdirectory + ‘\Powershell\OperationsManager\OperationsManager.psm1’

	Import-Module $psmPath

	$conn = (New-SCOMManagementGroupConnection –ComputerName $MS)
}
Catch [system.exception]
{
	$evt.WriteEntry("Errors detected while importing module OperationsManager on $MS; $error",$errorevent,$LogEventID)
	$ModuleImportError = $true
}

If (!$ModuleImportError)
{

	$groups = Get-SCOMGroup

	Foreach ($group in $groups)
	{
		
		$groupinstance = $discoveryData.CreateClassInstance("$MPElement[Name='MPAudit.Group']$")
		$groupinstance.AddProperty("$MPElement[Name='MPAudit.Group']/FullName$", $group.FullName)
		$groupinstance.AddProperty("$MPElement[Name='MPAudit.Group']/DisplayName$", $group.DisplayName)
		$groupinstance.AddProperty("$MPElement[Name='MPAudit.Group']/MPWhereIsSaved$", $group.GetMostDerivedClasses().GetManagementPack().Name)
		$groupinstance.AddProperty("$MPElement[Name='MPAudit.Group']/TimeAdded$", $group.TimeAdded.ToString('g'))
		$discoveryData.AddInstance($groupinstance)
	}
			
}

$evt.WriteEntry("Script $ScriptName finished on $MS",$infoevent,$LogEventID)

$discoveryData]]></ScriptBody>
          <Parameters>
            <Parameter>
              <Name>sourceID</Name>
              <Value>$MPElement$</Value>
            </Parameter>
            <Parameter>
              <Name>managedEntityID</Name>
              <Value>$Target/Id$</Value>
            </Parameter>
          </Parameters>
          <TimeoutSeconds>120</TimeoutSeconds>
        </DataSource>
</Discovery>

What you will get is a nice inventory of your groups and a quick reference on where their definition is saved.

post2-5

To summary, I believe you have now some controls in place to keep your SCOM environment clean. For the better good…

Advertisements