Menu Close

How to discover instances of a SCOM class based on Computer Name

When creating rules and monitors in SCOM, we need a good, specific class for targeting.

Often times, a customer will come to you and say “I just wanted to monitor for three services on those 5 computers”

In SCOM – doing something that sounds so easy, actually isn’t.  What customers would often LIKE to do, is simply create a group of Windows Computers, and just target the service monitors to the group.  We all know – that does not work.  You cannot target workflows at groups, as the workflow will not execute on the group members. 

You “could” create the service monitors disabled, target something generic like “Windows Server Operating System” then override the monitors to enabled for the group.  That works, but it is really ugly.

SCOM uses a discovery model for targeting, and sometimes there just isn’t an easy way to discover what you want.

I have a fragment that might help with that. 

This fragment is called Class.And.Discovery.Script.ByServerName.mpx.  This fragment will let you discover instances of a custom class, and will only discover an instance if the computer name matches a static list.  Here is how to use it:

I will import this fragment using Silect MP Author Pro, but it works the same in Visual Studio (VSAE). 

image

I only need to provide the standard stuff, then a comma separated list of server names that I want to be members of the class.  That’s IT!

Then, I import the MP, and need to override the discovery.  The discovery in this MP is disabled by default (because I don’t want to run this script on ALL machines…. so I will override the discovery just for the servers that I want it to run on.

image

When I go look in discovered inventory – I can see my instances have discovered:

image

I can now write simple rules and monitors, and have a good class for targeting.  If you want to remove a server, simply edit the discovery and take the name out.  Add new ones, and just make sure to enable the discovery so it runs for them.

My MP example is below, or you can get the fragment from my fragment library:  https://github.com/thekevinholman/FragmentLibrary

<?xml version="1.0" encoding="utf-8"?> <ManagementPack ContentReadable="true" SchemaVersion="2.0" OriginalSchemaVersion="1.1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <Manifest> <Identity> <ID>Demo.DiscoverClassByServerName</ID> <Version>1.0.0.0</Version> </Identity> <Name>Demo.DiscoverClassByServerName</Name> <References> <Reference Alias="SC"> <ID>Microsoft.SystemCenter.Library</ID> <Version>7.0.8433.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> <Reference Alias="Windows"> <ID>Microsoft.Windows.Library</ID> <Version>7.5.8501.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> <Reference Alias="Health"> <ID>System.Health.Library</ID> <Version>7.0.8433.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> <Reference Alias="System"> <ID>System.Library</ID> <Version>7.5.8501.0</Version> <PublicKeyToken>31bf3856ad364e35</PublicKeyToken> </Reference> </References> </Manifest> <TypeDefinitions> <EntityTypes> <ClassTypes> <ClassType ID="Demo.MyCustomerApp.DiscoverByName.Class" Accessibility="Public" Abstract="false" Base="Windows!Microsoft.Windows.LocalApplication" Hosted="true" Singleton="false" Extension="false" /> </ClassTypes> </EntityTypes> <ModuleTypes> <DataSourceModuleType ID="Demo.MyCustomerApp.DiscoverByName.Class.Discovery.DS" Accessibility="Internal" Batching="false"> <Configuration> <xsd:element name="IntervalSeconds" type="xsd:integer" xmlns:xsd="http://www.w3.org/2001/XMLSchema" /> <xsd:element name="SyncTime" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" /> <xsd:element name="TimeoutSeconds" type="xsd:integer" xmlns:xsd="http://www.w3.org/2001/XMLSchema" /> <xsd:element name="DebugLogging" type="xsd:boolean" xmlns:xsd="http://www.w3.org/2001/XMLSchema" /> <xsd:element name="ComputerNameList" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" /> </Configuration> <OverrideableParameters> <OverrideableParameter ID="IntervalSeconds" Selector="$Config/IntervalSeconds$" ParameterType="int" /> <OverrideableParameter ID="SyncTime" Selector="$Config/SyncTime$" ParameterType="string" /> <OverrideableParameter ID="TimeoutSeconds" Selector="$Config/TimeoutSeconds$" ParameterType="int" /> <OverrideableParameter ID="DebugLogging" Selector="$Config/DebugLogging$" ParameterType="bool" /> <OverrideableParameter ID="ComputerNameList" Selector="$Config/ComputerNameList$" ParameterType="string" /> </OverrideableParameters> <ModuleImplementation Isolation="Any"> <Composite> <MemberModules> <DataSource ID="DS" TypeID="Windows!Microsoft.Windows.TimedPowerShell.DiscoveryProvider"> <IntervalSeconds>$Config/IntervalSeconds$</IntervalSeconds> <SyncTime>$Config/SyncTime$</SyncTime> <ScriptName>Demo.MyCustomerApp.DiscoverByName.Class.Discovery.DS.ps1</ScriptName> <ScriptBody> #================================================================================= # SCOM Script to discover instances of a class when provided a list of ComputerNames # # Author: Kevin Holman # v1.0 #================================================================================= param($SourceId,$ManagedEntityId,[string]$ComputerName,[string]$MGName,$DebugLogging,[string]$ComputerNameList) # Manual Testing section - put stuff here for manually testing script - typically parameters: #================================================================================= # $SourceId = '{00000000-0000-0000-0000-000000000000}' # $ManagedEntityId = '{00000000-0000-0000-0000-000000000000}' # [string]$ComputerName = "SQL1.OPSMGR.NET" # [string]$MGName = "MGNAME" # $DebugLogging = "false" # [string]$ComputerNameList = "SQL1,SQL2A" #================================================================================= # Constants section - modify stuff here: #================================================================================= # Assign script name variable for use in event logging. # ScriptName should be the same as the ID of the module that the script is contained in $ScriptName = "Demo.MyCustomerApp.DiscoverByName.Class.Discovery.DS.ps1" $EventID = "1240" #================================================================================= # Starting Script section - All scripts get this #================================================================================= # Gather the start time of the script $StartTime = Get-Date #Set variable to be used in logging events $whoami = whoami # Load MOMScript API $momapi = New-Object -comObject MOM.ScriptAPI #================================================================================= # Discovery Script section - Discovery scripts get this #================================================================================= # Load SCOM Discovery module $DiscoveryData = $momapi.CreateDiscoveryData(0, $SourceId, $ManagedEntityId) #================================================================================= # Begin MAIN script section #================================================================================= #Build NetBIOSName from ComputerName $NetBIOSName = $ComputerName.Split(".")[0] $NetBIOSName = $NetBIOSName.Trim() #Log script event that we are starting task $momapi.LogScriptEvent($ScriptName,$EventID,0,"`nScript is starting. `nRunning as ($whoami). `nManagement Group: ($MGName). `nComputerName: ($ComputerName). `nNetBIOSName: ($NetBIOSName). `nDebugLogging: ($DebugLogging). `nComputerNameList: ($ComputerNameList)") #Show concept of additional debug logging IF ($DebugLogging.ToUpper() -eq "TRUE") { $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n This event is being logged because debug Logging was set to: ($DebugLogging)") } #Build an array of the ComputerNameList $ComputerNameList = $ComputerNameList.Replace(" ","") [array]$ComputerNameListArray = $ComputerNameList.Split(",") #Check to see if this NetBIOS Name for the computer is in the ComputerNameList provided by the discovery IF ($ComputerNameListArray -contains $NetBIOSName) { #This is a match. We should discover an instance of the class $instance = $DiscoveryData.CreateClassInstance("$MPElement[Name='Demo.MyCustomerApp.DiscoverByName.Class']$") $instance.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $ComputerName) $instance.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", $ComputerName) $DiscoveryData.AddInstance($instance) # Log an event that objects were discovered $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Discovery script is returning discoverydata objects for ($ComputerName).") } ELSE { #Do nothing. This computer is not in the ComputerNameList } # Return Discovery Items Normally $DiscoveryData # Return Discovery Bag to the command line for testing (does not work from ISE) # $momapi.Return($DiscoveryData) #================================================================================= # End MAIN script section # End of script section #================================================================================= #Log an event for script ending and total execution time. $EndTime = Get-Date $ScriptTime = ($EndTime - $StartTime).TotalSeconds $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Script Completed. `n Script Runtime: ($ScriptTime) seconds.") #================================================================================= # End of script </ScriptBody> <Parameters> <Parameter> <Name>SourceId</Name> <Value>$MPElement$</Value> </Parameter> <Parameter> <Name>ManagedEntityId</Name> <Value>$Target/Id$</Value> </Parameter> <Parameter> <Name>ComputerName</Name> <Value>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value> </Parameter> <Parameter> <Name>MGName</Name> <Value>$Target/ManagementGroup/Name$</Value> </Parameter> <Parameter> <Name>DebugLogging</Name> <Value>$Config/DebugLogging$</Value> </Parameter> <Parameter> <Name>ComputerNameList</Name> <Value>$Config/ComputerNameList$</Value> </Parameter> </Parameters> <TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds> </DataSource> </MemberModules> <Composition> <Node ID="DS" /> </Composition> </Composite> </ModuleImplementation> <OutputType>System!System.Discovery.Data</OutputType> </DataSourceModuleType> </ModuleTypes> </TypeDefinitions> <Monitoring> <Discoveries> <Discovery ID="Demo.MyCustomerApp.DiscoverByName.Class.Discovery" Enabled="false" Target="Windows!Microsoft.Windows.Server.OperatingSystem" ConfirmDelivery="false" Remotable="true" Priority="Normal"> <Category>Discovery</Category> <DiscoveryTypes> <DiscoveryClass TypeID="Demo.MyCustomerApp.DiscoverByName.Class" /> </DiscoveryTypes> <DataSource ID="DS" TypeID="Demo.MyCustomerApp.DiscoverByName.Class.Discovery.DS"> <IntervalSeconds>86333</IntervalSeconds> <SyncTime /> <TimeoutSeconds>120</TimeoutSeconds> <DebugLogging>false</DebugLogging> <ComputerNameList>SQL2014A,SQL2014B</ComputerNameList> </DataSource> </Discovery> </Discoveries> </Monitoring> <LanguagePacks> <LanguagePack ID="ENU" IsDefault="true"> <DisplayStrings> <DisplayString ElementID="Demo.DiscoverClassByServerName"> <Name>Demo DiscoverClassByServerName</Name> </DisplayString> <DisplayString ElementID="Demo.MyCustomerApp.DiscoverByName.Class"> <Name>Demo MyCustomerApp DiscoverByName Class</Name> </DisplayString> <DisplayString ElementID="Demo.MyCustomerApp.DiscoverByName.Class.Discovery"> <Name>Demo MyCustomerApp DiscoverByName Class Discovery</Name> </DisplayString> </DisplayStrings> </LanguagePack> </LanguagePacks> </ManagementPack>

20 Comments

  1. CyrAz

    We could even avoid running Powerhsell with a simple filtered class snapshot datamapper, wouldn’t we?
    Something like

    $Config/ComputerNameList$

    ContainsSubstring $Target/Host/Property[Type=”Windows!Microsoft.Windows.Computer”]/PrincipalName$

      • Kevin Holman

        I thought about that, even reg or WMI providers…. the problem is you’d have to do some kind of complex regex. You cannot do a contains substring because you could have server1 and server11, etc. I could not figure out a perfect regex that would not discover servers that you didn’t want, and handle the commas, whether the user put spaces or not, FQDN or not, etc…. so I stuck with PS. I would bet if someone is REALLY good with Regex, they could figure it out and make this less “heavy handed” with a script.

  2. tiboro

    I’m missing some point here: Trying to avoid to create a disabled monitor with an override for a group (which is ugly), you end up creating a disabled discovery with an override for a group…

    • Kevin Holman

      I can see how that might be confusing. I’ll try to explain:

      1. You don’t have to enable it for specific machines, but you can if you want. Enabling it for all is no big deal.
      2. Enabling a disabled discovery is MUCH cleaner than mass targeting monitors to generic classes.
      3. The whole point is to build a good CLASS for targeting, for notifications, for grouping, for views. Without a class, life is much harder for application level monitoring.

  3. Matt

    This is excellent and exactly what I was looking for thanks!
    I do have a question though, How would I add a subclass to this? I am looking to discover an application by Servername and discover it’s individual components by server name too but have not had much luck. Essentially I’d like to be able to have a class and dependant subclasses all discoverable by servername so I can then use them as targets for windows service monitors to generate availability reports etc.

  4. Todd Henard

    Kevin!
    Many thanks for this! This is exactly what I was looking for. I have a slew of app service monitors targeted to Windows Server…..like you said – it works – but its ugly.

    I do have a qq and sure hoping you will chime in.

    I have built a new 2019 env, and am hoping to get this working before I start cutting over from 2012.
    I have completed all the steps. I have only 2 test systems in this new envioronment. I have included both systems in my comma seperated .txt file.
    I checked the UNC path in the .xml, and file open as desired. I can see both systems receive the MP with the discovery. However they do not populate in the Discovered Inventory.

    Any pointers?

  5. Todd Henard

    I see the Health Service Script completes in the agents event log:

    fab.MyApp.DiscoverByName.Class.Discovery.DS.ps1
    Script Completed. Script Runtime: (0.0624787) seconds.

    I replaced actual app name/org id with your example of “fab.MyApp”.

  6. Todd Henard

    I see the the required references:

    This fragment depends on references:
    RequiredReference: Alias=”System”, ID=”System.Library”
    RequiredReference: Alias=”Windows”, ID=”Microsoft.Windows.Library”

    My reference versions:
    ID: Microsoft.Windows.Library 7.5.8501.0
    ID: System.Library 7.5.8501.0

  7. Todd Henard

    I got it working!

    And now i see this:# [string]$ComputerNameList = “SQL1,SQL2A”

    I had a file and unc path, thats why it was not working.

    Thanks Kevin!

    • Kevin Holman

      You can, but you’d have to change a lot of things. My fragments work well for simple, repeatable application monitoring. When you get into a more complex class structure, you really need to plan those out and then you will find yourself forklifting XML from simple solutions into more complex ones.

  8. amit

    Hi KEVIN Hi I tried to use MP in another class called
    Target=windwos.server.comuter
    Because my server is not in the OperatingSystem group

    But as soon as I change I get the 1101 error you know why this is happening

    Thanks Amit

    • Kevin Holman

      Why would any Windows Server not have an instance of the Operating System? I’d fix that first.

      • amit

        Hi KEVIN HOLMAN
        i solved my Problem i have server in domain workgroup and virtual Server
        i solved in the discovery i add this line and now all work

        #=================================================================================
        # Template_with_ClasterM Script to discover instances of a class when provided a list of ComputerNames
        #
        # Author: Kevin Holman
        # v1.0
        #=================================================================================
        param($SourceId,$ManagedEntityId,[string]$ComputerName,[string]$MGName,$DebugLogging,[string]$ComputerNameList)

        # Manual Testing section – put stuff here for manually testing script – typically parameters:
        #=================================================================================
        # $SourceId = ‘{00000000-0000-0000-0000-000000000000}’
        # $ManagedEntityId = ‘{00000000-0000-0000-0000-000000000000}’
        # [string]$ComputerName = “SQL1.OPSMGR.NET”
        # [string]$MGName = “MGNAME”
        # $DebugLogging = “false”
        # [string]$ComputerNameList = “SQL1,SQL2A”
        #=================================================================================

        # Constants section – modify stuff here:
        #=================================================================================
        # Assign script name variable for use in event logging.
        # ScriptName should be the same as the ID of the module that the script is contained in
        $ScriptName = “CAL.Template_with_Claster.Main.Class.Discovery.DS.ps1”
        $EventID = “1240”
        #=================================================================================

        # Starting Script section – All scripts get this
        #=================================================================================
        # Gather the start time of the script
        $StartTime = Get-Date
        #Set variable to be used in logging events
        $whoami = whoami
        # Load MOMScript API
        $momapi = New-Object -comObject MOM.ScriptAPI
        #=================================================================================

        # Discovery Script section – Discovery scripts get this
        #=================================================================================
        # Load Template_with_ClasterM Discovery module
        $DiscoveryData = $momapi.CreateDiscoveryData(0, $SourceId, $ManagedEntityId)
        #=================================================================================

        # Begin MAIN script section
        #=================================================================================
        #Build NetBIOSName from ComputerName
        $NetBIOSName = $ComputerName.Split(“.”)[0]
        $NetBIOSName = $NetBIOSName.Trim()

        #Log script event that we are starting task
        $momapi.LogScriptEvent($ScriptName,$EventID,0,”`nScript is starting. `nRunning as ($whoami). `nManagement Group: ($MGName). `nComputerName: ($ComputerName). `nNetBIOSName: ($NetBIOSName). `nDebugLogging: ($DebugLogging). `nComputerNameList: ($ComputerNameList)”)

        #Show concept of additional debug logging
        #IF ($DebugLogging.ToUpper() -eq “TRUE”)
        #{
        # $momapi.LogScriptEvent($ScriptName,$EventID,0,”`n This event is being logged because debug Logging was set to: ($DebugLogging)”)
        #}

        #Build an array of the ComputerNameList
        $ComputerNameList = $ComputerNameList.Replace(” “,””)
        [array]$ComputerNameListArray = $ComputerNameList.Split(“,”)

        #Check to see if this NetBIOS Name for the computer is in the ComputerNameList provided by the discovery
        IF ($ComputerNameListArray -contains $NetBIOSName)
        {

        $instance = $DiscoveryData.CreateClassInstance(“$MPElement[Name=’CAL.Template_with_Claster.Main.Class’]$”)
        $instance.AddProperty(“$MPElement[Name=’System!System.Entity’]/DisplayName$”,$ComputerName)
        $instance.AddProperty(“$MPElement[Name=’Windows!Microsoft.Windows.Computer’]/PrincipalName$”,$ComputerName)
        $DiscoveryData.AddInstance($instance)

        $momapi.LogScriptEvent($ScriptName,$EventID,0,”`n Discovery script is returning discoverydata objects for ($ComputerName).”)
        }
        ELSE
        {
        foreach ($Server in $ComputerNameListArray)
        {

        $instance = $DiscoveryData.CreateClassInstance(“$MPElement[Name=’CAL.Template_with_Claster.Main.Class’]$”)
        $instance.AddProperty(“$MPElement[Name=’System!System.Entity’]/DisplayName$”,$Server)
        $instance.AddProperty(“$MPElement[Name=’Windows!Microsoft.Windows.Computer’]/PrincipalName$”,$Server)
        $DiscoveryData.AddInstance($instance)
        write-host $Server -ForegroundColor Red
        }
        }

        # Return Discovery Items Normally
        $DiscoveryData
        # Return Discovery Bag to the command line for testing (does not work from ISE)
        # $momapi.Return($DiscoveryData)
        #=================================================================================
        # End MAIN script section

        # End of script section
        #=================================================================================
        #Log an event for script ending and total execution time.
        $EndTime = Get-Date
        $ScriptTime = ($EndTime – $StartTime).TotalSeconds
        $momapi.LogScriptEvent($ScriptName,$EventID,0,”`n Script Completed. `n Script Runtime: ($ScriptTime) seconds.”)
        #=================================================================================
        # End of script

  9. Saiyad Rahim

    Hi Kevin,

    For the uninitiated in the world of MP Authoring, can you clear my confusion regarding this statement:

    “….. What customers would often LIKE to do, is simply create a group of Windows Computers, and just target the service monitors to the group. We all know – that does not work. You cannot target workflows at groups, as the workflow will not execute on the group members. ”

    But when we create a Service Monitor in SCOM, it asks us to Target a Group.

    I have been creating Individual Groups for each Server that had a Service that needed to be monitored.
    Has worked so far.

    What am i misunderstanding here.

  10. Saiyad Rahim

    Thanks Kevin,

    That makes sense.

    I have a request to setup Service monitoring for an Application which has 8 servers and 55 Services.

    I was dreading the fact that I will have to create a Group for individual Servers and then create a Service Monitor for each service and point to this individual Server Groups.

    or I could just put “All” Servers into 1 group and start creating Service Monitors and point them to this one Master Group.

    Like you said, it is how the Service Template works but not very efficient.

    I have come across 2 of your articles:

    https://kevinholman.com/2016/06/04/part-3-use-vsae-fragments-to-monitor-a-service/
    https://kevinholman.com/2019/07/25/how-to-discover-instances-of-a-scom-class-based-on-computer-name/

    Not entirely sure if these are what I should be looking into to achieve my 55 Service monitor.

    Any recommendations please would be appreciated.

Leave a Reply

Your email address will not be published. Required fields are marked *