I wrote an MP last year that I thought might be useful for the community. Of course this is just an example and needs some work (error handling, testing, etc…). The purpose of the MP is to alert when a resource is not on its preferred cluster node.
This MP supports both Windows Server 2003 and 2008.
How it works:
***Update***I made some changes to the MP.The changes are the following:1. I changed the script to cookdown so it only runs once per agent a. I create a property bag for each cluster group b. I modified the data source so it accepts 0 arguments c. I discover the server name within the script and use it to connect to WMI d. I added a condition detection for the data source so it can match each property bag to the right instance
A good method to test out the MP:
Enjoy!
<ManagementPack ContentReadable="true" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <Manifest> <Identity> <ID>Custom.Cluster.PreferredNodeDetection</ID> <Version>1.0.0.6</Version> </Identity> <Name>Custom.Cluster.PreferredNodeDetection</Name> <References> <Reference Alias="SC"> <ID>Microsoft.SystemCenter.Library</ID> <Version>6.0.6278.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> <Reference Alias="MicrosoftWindowsClusterLibrary"> <ID>Microsoft.Windows.Cluster.Library</ID> <Version>6.0.6278.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> <Reference Alias="Windows"> <ID>Microsoft.Windows.Library</ID> <Version>6.0.6278.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> <Reference Alias="Health"> <ID>System.Health.Library</ID> <Version>6.0.6278.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> <Reference Alias="System"> <ID>System.Library</ID> <Version>6.0.6278.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> </References> </Manifest> <TypeDefinitions> <ModuleTypes> <DataSourceModuleType ID="Custom.Cluster.PreferredNodeDetection.Script" Accessibility="Public" Batching="false"> <Configuration> <xsd:element minOccurs="1" name="IntervalSeconds" type="xsd:integer" /> <xsd:element minOccurs="1" name="ActiveGroupName" type="xsd:string" /> </Configuration> <ModuleImplementation Isolation="Any"> <Composite> <MemberModules> <DataSource ID="ScriptDS" TypeID="Windows!Microsoft.Windows.TimedScript.PropertyBagProvider"> <IntervalSeconds>$Config/IntervalSeconds$</IntervalSeconds> <SyncTime /> <ScriptName>NodeStacking.vbs</ScriptName> <Arguments></Arguments> <ScriptBody><![CDATA['Rslaten 11/12/2009
'Input = Pass the name of the virtual cluster into this script'Output = Property Bag: 'Stacking = True or False <required> 'If False, just return this value in the property bag 'If True, then populate the following (optional): 'PhysicalNode = <string> 'ActiveGroupName = <string> 'ActiveGroupDescription = <string> or NULL 'PreferredNode = <string>
'GlobalsDim sVirtualClusterName, oWMI, oAPI, oBag, bStacking, bFound, sAlertDescriptionDim oClusterNode, colClusterNodesDim oActiveGroup, colActiveGroupsDim oPreferredNode, colPreferredNodes
'Get arguments passed into scriptsVirtualClusterName = GetArgs()'WScript.Echo "Virtual Name: " & sVirtualClusterName
'Create OpsMgr Script API objectSet oAPI = GetMOMScriptAPI()'Call oAPI.LogScriptEvent("NodeStacking.vbs",9989,4,"Script Started")
'Connect to WMI on the clusterSet oWMI = GetMSClusterWMIObject(sVirtualClusterName)
'Query WMI for physical cluster nodesSet colClusterNodes = GetCollection("Select * from MSCluster_Node")
'Loop through the collection of physical cluster nodesFor Each oClusterNode in colClusterNodes Wscript.Echo " Physical Node: " & oClusterNode.Name 'Get the Active Group for each node Set colActiveGroups = GetCollection("ASSOCIATORS OF {MSCluster_Node='" & oClusterNode.Name & "'} WHERE AssocClass=MSCluster_NodeToActiveGroup") 'Loop through each Active Group For Each oActiveGroup in colActiveGroups 'Create a Property Bag for storing the data we find Set oBag = GetPropertyBag(oAPI) bStacking = false If oActiveGroup.Description <> "" or oActiveGroup.Description = NULL Then WScript.Echo " Active Group: " & oActiveGroup.Name WScript.Echo " Description: " & oActiveGroup.Description Else WScript.Echo " Active Group: " & oActiveGroup.Name End If
WScript.Echo " Group State: " & GetState(oActiveGroup.State)
'Now get the preferred node for this Active Group. If there isn't one we just skip this because we can't detect stacking. Set colPreferredNodes = GetCollection("ASSOCIATORS OF {MSCluster_ResourceGroup='" & oActiveGroup.name & "'} WHERE AssocClass=MSCluster_ResourceGroupToPreferredNode") bFound = false 'Loop through this first to determine if this Active Group is on a preferred node because there can be multiple preferred nodes in 2008 For Each oPreferredNode in colPreferredNodes sPreferredNode = oPreferredNode.name 'Needs to be an array, but this complicates the property bag If UCase(oPreferredNode.name) = UCase(oClusterNode.name) Then WScript.Echo " Preferred Node: " & oPreferredNode.name & " <- Good" bFound = True End If Next 'If we aren't on a preferred node create a property bag. Note this will submit only 1 of the preferred nodes if there are multiple. If Not bFound AND colPreferredNodes.count <> 0 Then WScript.Echo " Preferred Node: " & sPreferredNode & " <- Stacking Detected" bStacking = true oBag.AddValue "PhysicalNode", oClusterNode.Name oBag.AddValue "ActiveGroupName", oActiveGroup.Name oBag.AddValue "ActiveGroupDescription", oActiveGroup.Description oBag.AddValue "PreferredNode", sPreferredNode 'Populate a property to use for the alert description sAlertDescription = " Physical Node = " & oClusterNode.Name & vbCrLf & _ " Active Group = " & oActiveGroup.Name & vbCrLf & _ " Active Group Description = " & oActiveGroup.Description & vbCrLf & _ " Preferred Node = " & sPreferredNode oBag.AddValue "AlertDescription", sAlertDescription End If 'Create the required field in the property bag If bStacking = true Then oBag.AddValue "Stacking", true oAPI.AddItem oBag Else oBag.AddValue "Stacking", false oAPI.AddItem oBag End If NextNext
'Return property bags to OpsMgroAPI.ReturnItems
'ExitBailout
'FunctionsFunction GetArgs() 'On Error Resume Next Dim m_sVirtualClusterName 'm_sVirtualClusterName = UCASE(WScript.Arguments(0)) 'If Err <> 0 Then 'Need to add error handling here 'End If Set pc = CreateObject("Wscript.Network") m_sVirtualClusterName = pc.ComputerName 'If Err <> 0 Then 'Need to add error handling here 'End If 'Changed script to support cookdown GetArgs = m_sVirtualClusterNameEnd Function
'Converts the state of an Active Cluster Group from an integer to a string.'Pass an int from -1 through 4 and get the associated string returned Function GetState(i) Select Case i Case -1 GetState = "STATE_UNKNOWN" Case 0 GetState = "ONLINE" Case 1 GetState = "OFFLINE" Case 2 GetState = "FAILED" Case 3 GetState = "PARTIAL_ONLINE" Case 4 GetState = "PENDING" Case Else GetState = "Error Getting State" End SelectEnd Function
'Connects to WMI and returns the WMI object 'Pass the name of the virtual cluster Function GetMSClusterWMIObject(s) 'On Error Resume Next Dim m_oWMI Set m_oWMI = GetObject("winmgmts:{impersonationLevel=impersonate," _ & "authenticationLevel=pktPrivacy}!\\" & s & "\root\mscluster") If Err <> 0 Then 'Need to add error handling here End If Set GetMSClusterWMIObject = m_oWMI Set m_oWMI = NothingEnd Function
'Queries WMI and returns a collection'Pass in the WMI query you wantFunction GetCollection(s) 'On Error Resume Next Set GetCollection = oWMI.ExecQuery(s) If Err <> 0 Then 'Need to add error handling here End IfEnd Function
'Returns a MOM Script API objectFunction GetMOMScriptAPI() 'On Error Resume Next Dim m_oAPI Set m_oAPI = CreateObject("MOM.ScriptAPI") If Err <> 0 Then 'Need to add error handling here End If Set GetMOMScriptAPI = m_oAPI Set m_oAPI = NothingEnd Function
'Returns a property bag'Pass in the MOM Script API objectFunction GetPropertyBag(o) 'On Error Resume Next Dim m_oBag Set m_oBag = o.CreatePropertyBag() If Err <> 0 Then 'Need to add error handling here End If Set GetPropertyBag = m_oBag Set m_oBag = NothingEnd Function
'Closes out any open objectsSub Bailout Set colPreferredNodes = Nothing Set colActiveGroups = Nothing Set colClusterNodes = Nothing Set oWMI = Nothing Set oBag = Nothing Set oAPI = NothingEnd Sub]]></ScriptBody> <TimeoutSeconds>60</TimeoutSeconds> </DataSource> <ConditionDetection ID="CD" TypeID="System!System.ExpressionFilter"> <Expression> <SimpleExpression> <ValueExpression> <XPathQuery Type="String">Property[@Name='ActiveGroupName']</XPathQuery> </ValueExpression> <Operator>Equal</Operator> <ValueExpression> <Value Type="String">$Config/ActiveGroupName$</Value> </ValueExpression> </SimpleExpression> </Expression> </ConditionDetection> </MemberModules> <Composition> <Node ID="CD"> <Node ID="ScriptDS" /> </Node> </Composition> </Composite> </ModuleImplementation> <OutputType>System!System.PropertyBagData</OutputType> </DataSourceModuleType> <ConditionDetectionModuleType ID="Custom.Cluster.PreferredNodeDetection.ConditionDetection" Accessibility="Public" Batching="true" Stateful="true" PassThrough="false"> <Configuration> <xsd:element minOccurs="1" name="IterationsBeforeAlerting" type="xsd:integer" /> <xsd:element minOccurs="1" name="WithinTimePeriod" type="xsd:integer" /> </Configuration> <ModuleImplementation Isolation="Any"> <Composite> <MemberModules> <ConditionDetection ID="ExpressionFilter" TypeID="System!System.ExpressionFilter"> <Expression> <SimpleExpression> <ValueExpression> <XPathQuery Type="Boolean">Property[@Name='Stacking']</XPathQuery> </ValueExpression> <Operator>Equal</Operator> <ValueExpression> <Value Type="Boolean">true</Value> </ValueExpression> </SimpleExpression> </Expression> </ConditionDetection> <ConditionDetection ID="Consolidator" TypeID="System!System.ConsolidatorCondition"> <Consolidator> <ConsolidationProperties /> <TimeControl> <WithinTimeSchedule> <Interval>$Config/WithinTimePeriod$</Interval> </WithinTimeSchedule> </TimeControl> <CountingCondition> <Count>$Config/IterationsBeforeAlerting$</Count> <CountMode>OnNewItemTestOutputRestart_OnTimerSlideByOne</CountMode> </CountingCondition> </Consolidator> </ConditionDetection> </MemberModules> <Composition> <Node ID="Consolidator"> <Node ID="ExpressionFilter" /> </Node> </Composition> </Composite> </ModuleImplementation> <OutputType>System!System.ConsolidatorData</OutputType> <InputTypes> <InputType>System!System.PropertyBagData</InputType> </InputTypes> </ConditionDetectionModuleType> </ModuleTypes> </TypeDefinitions> <Monitoring> <Rules> <Rule ID="Custom.Cluster.PreferredNodeDetection.Rule" Enabled="true" Target="MicrosoftWindowsClusterLibrary!Microsoft.Windows.Cluster.VirtualServer" ConfirmDelivery="true" Remotable="true" Priority="Normal" DiscardLevel="100"> <Category>Alert</Category> <DataSources> <DataSource ID="DS" TypeID="Custom.Cluster.PreferredNodeDetection.Script"> <IntervalSeconds>300</IntervalSeconds> <ActiveGroupName>$Target/Property[Type="MicrosoftWindowsClusterLibrary!Microsoft.Windows.Cluster.VirtualServer"]/GroupName$</ActiveGroupName> </DataSource> </DataSources> <ConditionDetection ID="CD" TypeID="Custom.Cluster.PreferredNodeDetection.ConditionDetection"> <IterationsBeforeAlerting>2</IterationsBeforeAlerting> <WithinTimePeriod>630</WithinTimePeriod> </ConditionDetection> <WriteActions> <WriteAction ID="Alerter" TypeID="Health!System.Health.GenerateAlert"> <Priority>2</Priority> <Severity>2</Severity> <AlertMessageId>$MPElement[Name="AlertMessageID37465c8ef079455c871583295f0f45a7"]$</AlertMessageId> <AlertParameters> <AlertParameter1>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/DNSName$</AlertParameter1> <AlertParameter2>$Data/Context/DataItem/Property[@Name='PhysicalNode']$</AlertParameter2> <AlertParameter3>$Data/Context/DataItem/Property[@Name='ActiveGroupName']$</AlertParameter3> <AlertParameter4>$Data/Context/DataItem/Property[@Name='ActiveGroupDescription']$</AlertParameter4> <AlertParameter5>$Data/Context/DataItem/Property[@Name='PreferredNode']$</AlertParameter5> </AlertParameters> <Suppression> <SuppressionValue>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/DNSName$</SuppressionValue> <SuppressionValue>$Data/Context/DataItem/Property[@Name='ActiveGroupName']$</SuppressionValue> </Suppression> <Custom1>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/DNSName$</Custom1> <Custom2>$Data/Context/DataItem/Property[@Name='PhysicalNode']$</Custom2> <Custom3>$Data/Context/DataItem/Property[@Name='ActiveGroupName']$</Custom3> <Custom4>$Data/Context/DataItem/Property[@Name='ActiveGroupDescription']$</Custom4> <Custom5>$Data/Context/DataItem/Property[@Name='PreferredNode']$</Custom5> <Custom6 /> <Custom7 /> <Custom8 /> <Custom9 /> <Custom10 /> </WriteAction> </WriteActions> </Rule> </Rules> </Monitoring> <Presentation> <StringResources> <StringResource ID="AlertMessageID37465c8ef079455c871583295f0f45a7" /> </StringResources> </Presentation> <LanguagePacks> <LanguagePack ID="ENU" IsDefault="true"> <DisplayStrings> <DisplayString ElementID="AlertMessageID37465c8ef079455c871583295f0f45a7"> <Name>A Cluster Active Group is not on the Preferred Node</Name> <Description>A Cluster Active Group is not on one of its configured preferred nodes. If the cluster resource configuration is currently correct then please modify the preferred nodes so that they correspond to the current node that this Active Group resides on.
Virtual Node: {0} Physical Node: {1} Active Group: {2} Description: {3} Preferred Node: {4}</Description> </DisplayString> <DisplayString ElementID="Custom.Cluster.PreferredNodeDetection"> <Name>Custom Preferred Node Detection MP</Name> <Description>This MP detects whether a cluster group is not on its preferred node.</Description> </DisplayString> <DisplayString ElementID="Custom.Cluster.PreferredNodeDetection.ConditionDetection"> <Name>Preferred Node Condition Detection</Name> </DisplayString> <DisplayString ElementID="Custom.Cluster.PreferredNodeDetection.Rule"> <Name>Preferred Node Detection Rule</Name> </DisplayString> <DisplayString ElementID="Custom.Cluster.PreferredNodeDetection.Script"> <Name>Preferred Node Detection Data Source</Name> <Description /> </DisplayString> </DisplayStrings> </LanguagePack> </LanguagePacks></ManagementPack>