Welcome to TechNet Blogs Sign in | Join | Help

As I ran out of time in the MMS session, I quickly showed a script that listed all performance monitors and their thresholds.  This came from the SharePoint Portal Server Management Pack Guide.  The only change I made was to comment out the command to export the results to a csv so the information would come scrolling across the screen.  I attached the script with that small change to this post.

There has been some question about how to get properties for monitoring objects with Command Shell.  I wanted to go into this in my MMS session last week, but I already ran out of time just trying to cram in all the topics I did cover.  Shouldn't be too tough to explain it here though.

If you use Get-MonitoringObject, you get a common set of properties for all objects.  Kind of like the following:

PS Monitoring:\OpsMgr01
>$mc = get-monitoringClass -name Microsoft.Windows.Computer
>$mc | get-monitoringObject | where {$_.name -match 'web01'} 

Id               : 0844f543-762c-5800-794c-a72a1823ea96
PathName         : web01.greengargantua.com
DisplayName      : web01.greengargantua.com
ManagementMode   :
ManagementGroup  : gaira
HealthState      : Success
OperationalState : 

PS Monitoring:\OpsMgr01
>$mc = get-monitoringClass -name Microsoft.SQLServer.2005.Database 
>$mc | get-monitoringObject | where {$_.name -match 'blob'}
Id : dfd4fd2b-6c7f-6c9c-a1cd-4d074613e3fd PathName : opsmgr01.greengargantua.com%003bMSSQLSERVER%003ablob DisplayName : blob ManagementMode : ManagementGroup : gaira HealthState : Success OperationalState :

That example shows two objects that are completely different - a database and a computer - but we show an identical set of properties.  We know that there a bunch of properties unique to each of those classes, but where are they?

In order to display all properties for the object, you can use one of the Format CmdLets with * indicating you want all properties.  That results in the following: 

PS Monitoring:\OpsMgr01
>$mc = get-monitoringClass -name Microsoft.SQLServer.2005.Database
>$mc | get-monitoringObject | where {$_.name -match 'blob'} | fl * PathName : opsmgr01.bwren.com%003bMSSQLSERVER%003ablob UniquePathName : Microsoft.SQLServer.Database%003aopsmgr01.bwren.com%003bMSSQLSERVER%003bblob [Microsoft.SQLServer.Database].DatabaseName : blob [Microsoft.SQLServer.Database].RecoveryModel : FULL [Microsoft.SQLServer.Database].DatabaseAutogrow : True [Microsoft.SQLServer.Database].DatabaseSize : 2 [Microsoft.SQLServer.Database].LogAutogrow : True [Microsoft.SQLServer.Database].Updateability : READ_WRITE [Microsoft.SQLServer.Database].UserAccess : MULTI_USER [Microsoft.SQLServer.Database].Collation : SQL_Latin1_General_CP1_CI_AS [Microsoft.SQLServer.Database].LogSize : 1 [Microsoft.SQLServer.Database].Owner : BWREN\Administrator [System.Entity].DisplayName : blob Name : blob Path : opsmgr01.bwren.com;MSSQLSERVER DisplayName : blob FullName : Microsoft.SQLServer.Database:opsmgr01.bwren.com;MSSQLSERVER;blob IsManaged : True LastModified : 11/26/2007 7:07:20 AM HealthState : Success StateLastModified : 5/1/2008 8:39:45 PM IsAvailable : True AvailabilityLastModified : 5/3/2008 5:20:29 PM InMaintenanceMode : False MaintenanceModeLastModified : 5/1/2008 8:39:06 PM MonitoringClassIds : {10c1c7f7-ba0f-5f9b-c74a-79a891170934, cae6be07-6483-361b-710f-c7612e29fa7b} LeastDerivedNonAbstractMonitoringClassId : 10c1c7f7-ba0f-5f9b-c74a-79a891170934 Id : dfd4fd2b-6c7f-6c9c-a1cd-4d074613e3fd ManagementGroup : bwren ManagementGroupId : 80342cfb-a3e1-aa59-7eac-cb5b5f167c38

The properties specific to the individual class are preceded with the name of the class they came from surrounded by brackets.  I'll explain the details of that later.  For now, just know that you have to specify the entire thing to retrieve the property value.  Since PowerShell sees those brackets as special characters, you need to surround the entire property name with quotes. 

>$mc = get-monitoringClass -name Microsoft.SQLServer.2005.Database
>$mo = $mc | get-monitoringObject | where {$_.name -match 'blob'}
>$mo."[Microsoft.SQLServer.Database].LogAutogrow"

That syntax can actually get tricky, and I'll be honest that I haven't figured out how to get it working in all cases.  The other approach you can use is the Get-MonitoringObjectProperty CmdLet.  The name property from that CmdLet will be the simple name of the property. 

>$mc = get-monitoringClass -name Microsoft.SQLServer.2005.Database 
>$mo = $mc | get-monitoringObject | where {$_.name -match 'blob'}
>$mo | get-monitoringObjectProperty | sort parentElement,name | ft parentElement,name,value ParentElement Name Value ------------- ---- ----- Microsoft.SQLServer.Database Collation SQL_Latin1_General_CP1_CI_AS Microsoft.SQLServer.Database DatabaseAutogrow True Microsoft.SQLServer.Database DatabaseName blob Microsoft.SQLServer.Database DatabaseSize 2 Microsoft.SQLServer.Database LogAutogrow True Microsoft.SQLServer.Database LogSize 1 Microsoft.SQLServer.Database Owner BWREN\Administrator Microsoft.SQLServer.Database RecoveryModel FULL Microsoft.SQLServer.Database Updateability READ_WRITE Microsoft.SQLServer.Database UserAccess MULTI_USER System.Entity DisplayName blob

If you already know about the concept of base classes in OpsMgr, then you already know what that that ParentElement class is.  If not, let me provide a very brief description just so you at least have a little background information.If you want more details on this whole concept of class structures in OpsMgr, the Authoring Guide has complete documentation.

All classes in OpsMgr have a base class.  That base class may have a base class, and its base class may have a base class, etc.  All classes will eventually find their way up to System.Entity which is the only class with no base.  The object tree for the SQL 2005 Database class is shown below.

image

 

If a class has properties associated with it, then any classes that inherit from it (ie. they use that class as their base class) inherit its properties and any properties along the class tree.  There are no properties explicitly assigned to SQL 2005 Database.  Instead, those properties you see (Collation, DatabaseAutogroup, etc) are defined on the SQL Database class.  As you might imagine, SQL 2000 Database inherits from that same class so it inherits the same properties as SQL Database.

My apologies that I forgot to talk about this in my MMS session earlier today.  I did mention it to a couple of people after the session.  Derek Harkin came up with an interesting method for initiating maintenance mode from an agent.  No OpsMgr client of any kind required - just a simple script for the user to execute.  No need to reiterate the details here, have a look at Derek's blog entry.

As promised, here are all the samples that I am showing in my MMS session on Command Shell.  There are also a several samples I'm planning on mentioning but won't have time to show.  Hope it's helpful.

Given the length of the this, chances are pretty good that I have an error somewhere.  Please let me know if there's anything that doesn't work, if there's anything you think I forgot, or if you have any improvements to any of these scenarios. 

 

Management Groups

Add a management group connection:

> new-managementGroupConnection OpsMgr02

 

Change your default management group connection:

> new-defaultManagementGroupConnection OpsMgr02 $true

 

View all current management group connections:

> get-ManagementGroupConnection

or

> cd \
> dir

 

Management Packs

Export a specific management pack (sealed or unsealed):

> get-managementPack -name Microsoft.SQLServer.2005.Monitoring | export-managementPack -path c:\mp 

 

Export all management packs in a management group:

> get-managementPack | export-managementPack -path c:\mp

 

Install a management pack to multiple management groups:

> new-managementGroupConnection OpsMgr02
> cd \

> install-managementPack c:\mp\Contoso.MyManagementPack.mp

 

Export management pack from test environment, seal it, and import to two production management groups:

> get-managementPack -name bwren.MMS | export-managementPack -path c:\mp
> MPSeal.exe  Contoso.MyManagementPack.xml /I c:\mp /Keyfile c:\keys\contosoKeyPair.snk  /Company "Contoso"
> new-managementGroupConnection ggOpsMgr01.greenGargantua.com
> cd \ggOpsMgr01.greenGargantua.com
> install-managementPack c:\mp\bwren.mms.xml
> cd \OpsMgr01.bwren.com
> get-managementGroupConnection | where {$_.managementServerName -eq 'ggOpsMgr01.greenGargantua.com'} | remove-managementGroupConnection

 

Agents

Install an agent on a single computer:

> $ms = get-managementServer | where {$_.name -eq 'OpsMgr02.GreenGargantua.com'}
> install-agentByName -name 'srv01' -managementServer $ms

 

Install agents to a list of computers from a text file:

> $ms = get-managementServer | where {$_.name -eq 'OpsMgr02.GreenGargantua.com'}
> gc c:\scripts\agents.txt | foreach {install-agentByName -name $_ -managementServer $ms}

 

Schedule install of agents to a list of computers from a text file. 

Script: InstallAgents.ps1

param($file,$managementServer,$rmsServerName)

Add-PSSnapin "Microsoft.EnterpriseManagement.OperationsManager.Client";
Set-Location "OperationsManagerMonitoring::";
$mgConn = New-ManagementGroupConnection -connectionString:$rmsServerName
Set-Location $rmsServerName

$ms = get-managementServer | where {$_.name -eq $managementServer}
gc $file | foreach {install-agentByName -managementServer $ms -name $_}

Run from Task Scheduler:

C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe "c:\scripts\InstallAgents.ps1" -file:'c:\scripts\computers.txt' -managementServer:OpsMgr02 -rmsServerName:'OpsMgr01'

 

Discover all domain controllers and install an agent on each: 

> $ms = get-managementServer | where {$_.name -eq 'OpsMgr02.GreenGargantua.com'}
> $discoveryConfig = new-ldapQueryDiscoveryCriteria -domain contoso.com -ldapQuery "(primaryGroupID=516)"
> $discoveryResult = start-discovery -managementServer $ms -windowsDiscoveryConfiguration $discoveryConfig
> $installResult = install-agent -managementServer $ms -agentManagedComputer $discoveryResult.CustomMonitoringObjects
> $installResult.MonitoringTaskResults

 

Display the proxy setting for all agents with a particular string in the computer name:

> get-agent | where {$_.computerName -match 'dc'} | ft name,proxyingEnabled

 

Enable the proxy setting for all agents with a particular string in the computer name:

> $agents = get-agent | where {$_.computerName -match 'dc'}
> $agents | foreach {$_.ProxyingEnabled = $true}
> $agents | foreach {$_.ApplyChanges()}

 

Move all agents on a subnet to a different management server:

> $ms = get-managementServer | where {$_.name -eq 'OpsMgr02.greenGargantua.com'}
> $agents = get-agent | where {$_.IPAddress -match '10.2.*.*'}
> $agents | set-managementServer -primaryManagementServer $ms

 

Security

Get list of roles a user belongs to:

> get-userRole | where {$_.users -match 'iggy'} | ft name

 

Add list of users from a text file to a user role:

Script: AddUsersFromFile.ps1

param($file)

 

$entries = import-csv -path $file
foreach ($entry in $entries) {
    $role = get-userRole | where {$_.name -eq $entry.role}
    add-userToUserRole -userRole $role -user $entry.user
}

Sample Text File: users.txt

role,user
Marketing,contoso\iggy
Marketing,contoso\johnny
Finance,contoso\sid
IT,contoso\trent

Running Script:

> c:\scripts\AddUsersFromFile -file c:\scripts\users.txt

   

Monitoring Classes and Objects

List all monitoring classes:

> get-monitoringClass | ft name

 

List all SQL 2005 databases:

> (get-monitoringClass -name Microsoft.SQLServer.2005.Database) | get-monitoringObject | ft name

OR

> (get-monitoringClass | where {$_.displayName -eq 'SQL 2005 DB'} | get-monitoringObject | ft name

 

List all Windows computers and their IP addresses:

> get-monitoringClass -name Microsoft.Windows.Computer | get-monitoringObject | foreach {$_."[Microsoft.Windows.Computer].IPAddress"}

 

List all monitoring classes in the SQL Server 2005 Discovery management pack

> $mp = get-managementPack -name Microsoft.SQLServer.2005.Discovery
> $mp | get-monitoringClass | ft name

 

 

Display the proxy settings for all agents holding a particular class:

> $mc = get-monitoringClass -name 'Microsoft.Windows.Server.AD.DomainControllerRole'
> $
mos = $mc | get-monitoringObject
> $mo | foreach {get-agent | where {$_.computerName -eq $mo.name}} | ft name,proxyingEnabled

 

Enable the proxy settings for all agents holding a particular class:

> $mc = get-monitoringClass -name 'Microsoft.Windows.Server.AD.DomainControllerRole'
> $mos = $mc | get-monitoringObject
> foreach ($mo in $mos) {
   $agent = get-agent | where {$_.computerName -eq $mo.displayName}
   $agent.proxyingEnabled = $true
   $agent.applyChanges()
}

 

Maintenance Mode

Set maintenance mode for a single object: 

> $StartTime = (get-Date '4/1/2008 22:00').ToUniversalTime()
> $EndTime = (get-Date '4/2/2008 2:00').ToUniversalTime()
> $mc = get-monitoringClass -name Microsoft.SQLServer.2005.Database
> $mo = get-monitoringObject -monitoringClass $mc | where {$_.name -eq 'MyDatabase'}
> new-maintenanceWindow -monitoringObject $mo -startTime $StartTime -endTime $EndTime -reason PlannedOther -comment "Scheduled maintenance."

 

Set maintenance mode for an object and all of its contained objects:

> $StartTime = (get-Date '4/1/2008 22:00').ToUniversalTime()
> $EndTime = (get-Date '4/2/2008 2:00').ToUniversalTime()
> $mc = get-monitoringClass -name Microsoft.Windows.Computer
> $mo = get-monitoringObject -monitoringClass $mc | where {$_.name -eq 'srv01'}
> $mo.ScheduleMaintenanceMode($StartTime,$EndTime,PlannedOther,"Nightly reboot.","Recursive")

 

Schedule maintenance mode for all computers in a group (uses GroupMM.ps1):

> C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe "c:\scripts\GroupMM.ps1" -groupName:'Nightly Reboots' -hours:2 -rmsServerName:'OpsMgr01' -startMM:$true

 

View maintenance mode history for an object:

> $mc = get-monitoringClass -name Microsoft.Windows.Computer
> $mo = get-monitoringObject -monitoringClass $mc | where {$_.name -eq 'srv01'}
> $mo | get-maintenanceWindow -history

 

Tasks

Run task to enable audit collection on all agents:

> $task = get-task | where {$_.name -eq 'Microsoft.SystemCenter.EnableAuditCollectionService'}
> $mc = get-monitoringClass | where {$_.name -eq 'Microsoft.SystemCenter.HealthService'}
> $mc | get-monitoringObject | foreach {start-task -task $task -targetMonitoringObject $_}

 

Data

View all unresolved alerts:

> get-alert -criteria "ResolutionState <> 255"

 

View all alerts grouped by severity and name

> get-alert -criteria "ResolutionState <> 255" | sort severity,name | group severity,name

 

View alerts from a particular rule group by managed object:

> get-alert -criteria "ResolutionState <> 255" | where {$_.name -match 'Script or Executable Failed to run'} | group monitoringObjectDisplayName

 

Change resolution state of alerts from a particular rule group by managed object:

> get-alert -criteria "ResolutionState <> 255" | where {$_.name -match 'Script or Executable Failed to run'} | group monitoringObjectDisplayName | foreach {
>> $_.ResolutionState = 50
>> $_.update("Comment")
>>}
>>

 

Resolve all alerts generated by rules as opposed to monitors:

> get-alert -criteria "ResolutionState <> 255 and IsMonitorAlert = 'False'" | resolve-Alert

 

Reset health for a monitor called "Manual monitor" on all objects of the class "Contoso.MyCustomClass" currently in an Error state.

> $mon = get-monitor | where {$_.displayName -eq 'Manual monitor'}
> $mc = get-monitoringClass -name Contoso.MyCustomClass
> $mc | get-monitoringObject | where {$_.HealthState -eq 'Error'} | foreach {$_.ResetMonitoringState($mon)}

 

Performance Data

Extract processor utilization data for all computers for the month of March 2008 to a comma delimited file.

> $startTime = get-date '3/1/2008'
> $endTime = get-date '3/31/2008'
> $pc = get-performanceCounter -criteria: "ObjectName='Processor' and CounterName='% Processor Time' and MonitoringObjectPath='web02.bwren.com'"
> get-performanceCounterValue -startTime $startTime -endTime $endTime -performanceCounter $pc | export-csv c:\scripts\mom\perf.csv

 

 

Management Pack Elements

List all rules by management pack:

> get-rule | select @{name="MP";expression={foreach-Object {$_.GetManagementPack().DisplayName}}},DisplayName | sort mp,displayName

 

List all monitors in a specific management pack:

> (get-managementPack -name Microsoft.SQLServer.2005.Monitoring) | get-monitor | sort displayName | ft displayName

 

List all disabled discoveries:

> get-discovery | where {$_.enabled -eq 'false'} | ft displayName

 

View the display name, category, and enabled status of all performance collection rules in the SQL Server 2005 Monitoring management pack:

> get-rule -managementPack $mp | where {$_.category -eq 'PerformanceCollection'} | ft displayName,category,enabled

Andrzej Lipka, a fellow consultant in Poland, took my maintenance mode sample MP and added some interesting features.  Have a look at the specific post here.  I also added a link to Andrzej's blog to my general list since he has some great posts on OpsMgr and SCCM.

The Windows Service template in the Operations Console lets you discover and monitor a Windows service by doing little more than typing in the service name.  I was just talking with someone who had a situation where the name of the service includes the local computer name meaning that it's different on every computer.  It's the same service and should be monitored as such - just a slightly different name on each agent.  Unfortunately, the template won't allow us to provide a wildcard in the service name. 

For those who are skilled with management pack authoring, you're probably not relying on the template anyway, and this wouldn't be too difficult of a task.  As such, I'm going to write this post from the point of view of someone who primarily lives within the Operations Console.  We do have to get exposed to a bit of XML in order to pull this off, but it really should be minimal.

First, let's understand what the Windows Service template does.  It performs two functions: 1) creates a new class (aka target) for your service and 2) creates a discovery to find instances of that new class.  The new class that the template created should be fine as is.  We can provide the template with any name we want, and it doesn't have to have anything to do with the name of the service itself.  The discovery is what we need to think about.

The discovery that the template uses is a special one specifically designed for finding a service, and it doesn't allow wildcards.  We can replace that discovery with a WMI discovery module since we can pretty easily write a WMI query to find all services that match a certain criteria, and that criteria can include a wildcard.  There is a module called WmiProviderWithClassSnapshotDataMapper in the Windows Library management pack that will work perfectly.

Let's go through this step by step.  I have a virtual machine running under the beta of Hyper-V in Windows Server 2008.  That includes a few services that start with the three letters "vmi".  I know this is a bit of a difference scenario than I described above, but it will still illustrate the point of discovering services using a wildcard. 

1.  Walk through the Windows Template wizard as normal providing your wildcard string.  Note that we are going to pass this off to WMI which uses % as a wildcard character, not *.

image image

2.  Export the management pack you just used for the template.

image

 

3.  Open the XML file you just exported.  Use an XML editor if you have one.  Otherwise, you could use Notepad.

image

 

4.  You have to locate the discovery you just created which can be a little tricky since the names generated by the Operations Console are pretty long and ugly.  Probably the easiest method would be to search on the wildcard string you typed in. 

image

5.  The Discovery will look something like mine which I pasted below.  The long ID strings will be different in yours, but the rest should be pretty similar.  There's a lot going on here, but we're not going to have to worry about most of it.  I highlighted in blue the parts that we're going to need to change, and that shouldn't be too intimidating.

 

<Discovery ID="ServiceStateProbePage_eaa08473602945a592b2a117b62c634f.DiscoveryRule" Enabled="true" Target="Windows!Microsoft.Windows.Computer" ConfirmDelivery="false" Remotable="true" Priority="Normal">
  <Category>Discovery</Category>
  <DiscoveryTypes>
    <DiscoveryClass TypeID="ServiceStateProbePage_eaa08473602945a592b2a117b62c634f" />
  </DiscoveryTypes>
  <DataSource ID="DS" TypeID="Windows!Microsoft.Windows.Win32ServiceInformationProviderWithClassSnapshotDataMapper">
    <ComputerName>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/NetworkName$</ComputerName>
    <ServiceName>vmi%</ServiceName>
   
<Frequency>60</Frequency>
    <ClassId>$MPElement[Name="ServiceStateProbePage_eaa08473602945a592b2a117b62c634f"]$</ClassId>
    <InstanceSettings>
      <Settings>
        <Setting>
          <Name>$MPElement[Name="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Name>
          <Value>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="MicrosoftSystemCenterNTServiceLibrary!Microsoft.SystemCenter.NTService"]/ServiceName$</Name>
          <Value>$Data/Property[@Name='Name']$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="MicrosoftSystemCenterNTServiceLibrary!Microsoft.SystemCenter.NTService"]/ServiceProcessName$</Name>
          <Value>$Data/Property[@Name='BinaryPathName']$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="MicrosoftSystemCenterNTServiceLibrary!Microsoft.SystemCenter.NTService"]/DisplayName$</Name>
          <Value>$Data/Property[@Name='DisplayName']$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="MicrosoftSystemCenterNTServiceLibrary!Microsoft.SystemCenter.NTService"]/Description$</Name>
          <Value>$Data/Property[@Name='Description']$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="System!System.Entity"]/DisplayName$</Name>
          <Value>Hyper-V Services</Value>
        </Setting>
      </Settings>
    </InstanceSettings>
  </DataSource>
</Discovery>

 

6.  Modify the discovery to use the WMI discovery module.  I highlighted in red the modifications you want to make.  You could just copy and paste right from this pots into your management pack.  Obviously, in that query, you're going to use your wildcard string as opposed to the vmi% that I used.  All we're doing is changing out the module we're using as the data source for the discovery.  Then we're replacing the parameters that we were passing to the old module to the parameters expected by the new one. 

<Discovery ID="ServiceStateProbePage_eaa08473602945a592b2a117b62c634f.DiscoveryRule" Enabled="true" Target="Windows!Microsoft.Windows.Computer" ConfirmDelivery="false" Remotable="true" Priority="Normal">
  <Category>Discovery</Category>
  <DiscoveryTypes>
    <DiscoveryClass TypeID="ServiceStateProbePage_eaa08473602945a592b2a117b62c634f" />
  </DiscoveryTypes>
  <DataSource ID="DS" TypeID="Windows!Microsoft.Windows.WmiProviderWithClassSnapshotDataMapper">
    <NameSpace>root\cimv2</NameSpace>
    <Query>select * from win32_service where name like 'vmi%'</Query>
   
<Frequency>60</Frequency>
    <ClassId>$MPElement[Name="ServiceStateProbePage_eaa08473602945a592b2a117b62c634f"]$</ClassId>
    <InstanceSettings>
      <Settings>
        <Setting>
          <Name>$MPElement[Name="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Name>
          <Value>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="MicrosoftSystemCenterNTServiceLibrary!Microsoft.SystemCenter.NTService"]/ServiceName$</Name>
          <Value>$Data/Property[@Name='Name']$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="MicrosoftSystemCenterNTServiceLibrary!Microsoft.SystemCenter.NTService"]/ServiceProcessName$</Name>
          <Value>$Data/Property[@Name='PathName']$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="MicrosoftSystemCenterNTServiceLibrary!Microsoft.SystemCenter.NTService"]/DisplayName$</Name>
          <Value>$Data/Property[@Name='DisplayName']$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="MicrosoftSystemCenterNTServiceLibrary!Microsoft.SystemCenter.NTService"]/Description$</Name>
          <Value>$Data/Property[@Name='Description']$</Value>
        </Setting>
        <Setting>
          <Name>$MPElement[Name="System!System.Entity"]/DisplayName$</Name>
          <Value>Hyper-V Services</Value>
        </Setting>
      </Settings>
    </InstanceSettings>
  </DataSource>
</Discovery>

7.  Import the modified management pack.  If you get an error on import, then you made a mistake in the modification.  Go back and export the management pack and try again.

8.  Go to Discovered Inventory and change the target type to your class.  Within a minute or two, you should start to see instances showing up.

image

I just got done with my previous post on setting maintenance mode for a group of objects, and I happened to check Boris Yanushpolsky's blog.   Bori's blog is an outstanding reference for various Command Shell scripts, and I have to give him credit for helping me come up to speed on several concepts.  Turns out just tonight put up a script for....yep, you guessed it....setting maintenance mode on a group of objects.

As it turns out, I spent more time getting the script to run in a management pack, using overrides, etc.  The script that Boris did is more complex than the one I did by putting computers into maintenance mode along with their health service.  If that one fits your requirements better, than it should take all that much effort to move it into my sample management pack.  Just a matter of pasting the script code into my write action and swapping around some script arguments.

I was given a challenge recently to come up with a solution for an organization that wanted to suppress any alerts for certain computers and services during a particular time window every night.  The particular set of objects and the times in question had to be manageable by the administrators - preferably from the Operations Console.

This is a great example for a few concepts, so this will be a pretty long post where I'll describe each step along the way.  If you just want the answer, scroll to the link at the end for the sample MP.  Otherwise, you should find some good information on the following concepts:

  • Launching a PowerShell script from a rule
  • Composing a rule made up of a data source and write action
  • Replacing explicit values in a rule with overrides
  • Creating a simple custom class

First of all, the obvious answer to suppressing alerts is to get those objects into maintenance mode.  That way we not only aren't bothered by the alerts, but we can also remove this time window from any availability reporting.  The questions that we're faced with though are:

  1. How can we schedule maintenance mode on a regular basis?
  2. How can we set maintenance mode for a set of objects?
  3. How can we give control to the administrators through the Operations Console?

One answer for our challenge might be to write a Command Shell script to put an object in maintenance mode and then launch it from the Windows Scheduler.  That would handle question 1, but it would leave us hanging on questions 2 and 3.

A very straightforward solution would be to create a group in OpsMgr.  Instead of setting maintenance mode for a single object, the script could put each member of the group in maintenance mode.  Administrators could easily populate the group with any objects that should be included in the maintenance window.  Assuming we had a script that could do that group enumeration, question 2 is covered.  In order to really address question 3, we would need to launch the script from an OpsMgr management pack instead of the Windows Scheduler and have the ability to overwrite critical parameters. 

So that leaves us with two challenges - setting maintenance mode for a group of objects and creating a rule to launch the script from a management pack.  Let's take them one at a time.  First the script.....

Setting Maintenance Mode on a Group of Objects

We're going to need Command Shell in order to enumerate the group and set maintenance mode.  I'm assuming that we're going to end up creating a rule calling powershell.exe as described in my previous blog post, so we're going to need to need the script to include the code to load the Command Shell snap-in and setup the connection to the management server.  When we launch Command Shell from the menu, this is done automatically done for us.

The script should accept the group name as an argument and then enumerate all members of that group.  In specific OpsMgr terms, that means that we need to enumerate the objects that are target of a Containment relationship with the group's class. 

If we use the New-MaintenanceWindow CmdLet, then we would need to write a function to recurs through the group members and follow each group members down through its set of hosted and contained objects.  Rather than use that CmdLet, I'm going to use the ScheduleMaintenanceMode method on the monitoring object class.  That method includes an argument called TraversalDepth.  A value of "OneLevel" means that only the object itself will be put into maintenance while a value of "Recursive" means that we will follow hosting and containment relationships all the way down the object tree setting maintenance mode on each object along the way. A middle ground would be nice where we could just address the second level of objects, which in our case would be the specific group members and not their contained objects, but that's a scenario we're going to have to address on our own.

I thus have two scenarios I want the script to support - setting maintenance mode for just the objects contained in the group and setting maintenance mode for those objects in addition to all of their contained objects.  The second scenario is actually easier since we just execute ScheduleMaintenanceMode against the group with TraversalDepth set the "Recursive".  For the first method, we're going to have the enumerate the group contents ourselves and then set maintenance mode for each one.

With all of that said, my script is below.  Notice that I'm accepting as arguments the name of the group (not to be confused with the Display Name), the number of hours for maintenance, whether to include hosted objects, and the description.  We'll make most of these overrideable on the rule so the administrator has control over them.

$groupName = $args[0]
$HoursInMaintenance = $args[1]
$IncludeHostedObjects = $args[2]
$Description = $args[3]
$CommandShellPath = $args[4]

Add-PSSnapin "Microsoft.EnterpriseManagement.OperationsManager.Client";
cd "$CommandShellPath"
.\Microsoft.EnterpriseManagement.OperationsManager.ClientShell.Startup.ps1

$api = new-object -comObject "MOM.ScriptAPI"
$api.LogScriptEvent("SetMaintenanceMode",100,4,"Maintenance mode set for all members of group: $groupName")

$startTime = (Get-Date).ToUniversalTime()
$endTime = $startTime.AddHours($HoursInMaintenance)

$group = get-monitoringClass -name $groupName | get-monitoringObject

if ($IncludeHostedObjects -eq $true) 
{
    $group.ScheduleMaintenanceMode($startTime,$endTime,"PlannedOther",$Description,"Recursive")
}
else
{
    $classContain = get-relationshipClass -name 'System.Containment'
    $relations = $group | get-relationshipObject | where {$_.SourceMonitoringObject -eq $group} 
    $groupMembers = $relations | foreach {$_.TargetMonitoringObject}

    foreach ($object in $groupMembers)
    {
        $object.ScheduleMaintenanceMode($startTime,$endTime,"PlannedOther",$Description,"OneLevel")
    }
}
 

Creating the Rule

The next question is how we're going to launch that script and give administrators control over the schedule.  We could do that from the Windows Scheduler, but it would be more straightforward for the administrator if we put it into an OpsMgr rule.  If we configure it correctly, we can allow our administrators to use overrides to control the schedule, length of maintenance mode, and whether to include contained objects. 

Data Source

The rule will need a data source and a write action.  Since we want to execute the script on a nightly schedule, System.Scheduler will be a perfect data source.  There is one little trick with that data source though.  It allows you to define multiple schedules that have a start and a stop time.  In the context we are going to use it though, the stop time is ignored.  It will have to be larger than the start time to prevent errors, but other than that it won't be used. 

You could just use System.Scheduler unchanged in which case you'll get the benefit of the dialog box for setting multiple schedules.  In that case, you would need to modify the rule itself to change the schedule though.  Since I want to control it through overrides I'm going to limit us to a single schedule, and I'm going to have to set the parameters myself.  The Start and End times are pretty obvious.  The DaysOfWeekMask is a little confusing.  This is the integer value of a bit mask with each day representing a single bit.  If you're familiar with bit masks you won't have a problem. If not, just determine the appropriate value by using the following values for each day of the week: Su-1, M-2, T-4, W-8, Th-16, F-32, Sa-64.  Just add up the values for the days you want the script to run.  If you want all days of the week, they'll add up to 127.  If you just want Sunday, the value is 1.  Just Tuesday and Thursday would be 20.

With all of that said, here's a valid data source for running the script every day of the week at 10:00 pm.

    <DataSources>
        <DataSource ID="Scheduler" TypeID="System!System.Scheduler">
            <Scheduler>
                <WeeklySchedule>
                    <Windows>
                        <Daily>
                            <Start>22:00$</Start>
                            <End>23:59</End>
                            <DaysOfWeekMask>127</DaysOfWeekMask>
                        </Daily>
                    </Windows>
                </WeeklySchedule>
                <ExcludeDates/>
            </Scheduler>
        </DataSource>
    </DataSources>

 

To define overrides though, we have to create a new module type.  Rather than using this data source directly in the rule, I'll create a data source allowing the StartTime and DaysOfWeekMask to be overriden.  The rule will then use the new data source module.  Notice in the data source below that I've changed the changed the values for <Start> and <DaysOfTheWeeMask> to overrideable values that will be provided by the rule itself.

<DataSourceModuleType ID="bwren.Maintenance.DataSource.Scheduler" Accessibility="Public">
    <Configuration>
        <xsd:element name="StartTime" type="xsd:string" xmlns:xsd="
http://www.w3.org/2001/XMLSchema"/>
        <xsd:element name="DaysOfWeekMask" type="xsd:integer" xmlns:xsd="
http://www.w3.org/2001/XMLSchema"/>
    </Configuration>
    <OverrideableParameters>
        <OverrideableParameter ID="StartTime" ParameterType="string" Selector="$Config/StartTime$"/>
        <OverrideableParameter ID="DaysOfWeekMask" ParameterType="int" Selector="$Config/DaysOfWeekMask$"/>
    </OverrideableParameters>
    <ModuleImplementation>
        <Composite>
            <MemberModules>
                <DataSource ID="DS" TypeID="System!System.Scheduler">
                    <Scheduler>
                        <WeeklySchedule>
                            <Windows>
                                <Daily>
                                    <Start>$Config/StartTime$</Start>
                                    <End>23:59</End>
                                    <DaysOfWeekMask>$Config/DaysOfWeekMask$</DaysOfWeekMask>
                                </Daily>
                            </Windows>
                        </WeeklySchedule>
                        <ExcludeDates/>
                    </Scheduler>
                </DataSource>
            </MemberModules>
            <Composition>
                <Node ID="DS"/>
            </Composition>
        </Composite>
    </ModuleImplementation>
    <OutputType>System!System.TriggerData</OutputType>
</DataSourceModuleType>

Write Action

The write action is just a matter of launching our PowerShell script.  We'll again need a new module type to handle overrides.  That strategy has other benefits anyway, since we can put the complexity in the module type and keep the rule simple.  That will make it easy to create other rules for other groups.

The module type using our script is shown below.  I won't bother explaining all the details since you can get that from the PowerShell post.  The formatting gets pretty screwed up here, so you might just want to have a look at it in the sample MP.

            <WriteActionModuleType ID="bwren.Maintenance.WriteAction.SetMaintenance" Accessibility="Public">
                <Configuration>
                    <xsd:element name="GroupName" type="xsd:string" xmlns:xsd="
http://www.w3.org/2001/XMLSchema"/>
                    <xsd:element name="HoursInMaintenance" type="xsd:integer" xmlns:xsd="
http://www.w3.org/2001/XMLSchema"/>
                    <xsd:element name="IncludeHostedObjects" type="xsd:string" xmlns:xsd="
http://www.w3.org/2001/XMLSchema"/>
                    <xsd:element name="Description" type="xsd:string" xmlns:xsd="
http://www.w3.org/2001/XMLSchema"/>
                </Configuration>
                <OverrideableParameters>
                    <OverrideableParameter ID="HoursInMaintenance" Selector="$Config/HoursInMaintenance$" ParameterType="int"/>
                    <OverrideableParameter ID="IncludeHostedObjects" Selector="$Config/IncludeHostedObjects$" ParameterType="string"/>
                </OverrideableParameters>
                <ModuleImplementation Isolation="Any">
                    <Composite>
                        <MemberModules>
                            <WriteAction ID="WA1" TypeID="System!System.CommandExecuter">
                                <ApplicationName>%windir%\system32\windowspowershell\v1.0\powershell.exe</ApplicationName>
                                <WorkingDirectory/>
                                <CommandLine>-Command "&amp; {.\SetMaintenance.ps1 $Config/GroupName$ $Config/HoursInMaintenance$ $Config/IncludeHostedObjects$ '$Config/Description$'}"</CommandLine>
                                <SecureInput/>
                                <TimeoutSeconds>30</TimeoutSeconds>
                                <RequireOutput>true</RequireOutput>
                                <Files>
                                    <File>
                                        <Name>SetMaintenance.ps1</Name>
                                        <Contents><![CDATA[
$groupName = $args[0]
$HoursInMaintenance = $args[1]
$IncludeHostedObjects = $args[2]
$Description = $args[3]

Add-PSSnapin "Microsoft.EnterpriseManagement.OperationsManager.Client";
cd C:\"Program Files"\"System Center Operations Manager 2007"
.\Microsoft.EnterpriseManagement.OperationsManager.ClientShell.Startup.ps1

$startTime = (Get-Date).ToUniversalTime()
$endTime = $startTime.AddHours($HoursInMaintenance)

$group = get-monitoringClass -name $groupName | get-monitoringObject

if ($IncludeHostedObjects -eq $true)
{
    $group.ScheduleMaintenanceMode($startTime,$endTime,"PlannedOther",$Description,"Recursive")
}
else
{
    $classContain = get-relationshipClass -name 'System.Containment'
    $relations = $group | get-relationshipObject | where {$_.SourceMonitoringObject -eq $group}
    $groupMembers = $relations | foreach {$_.TargetMonitoringObject}

    foreach ($object in $groupMembers)
    {
        $object.ScheduleMaintenanceMode($startTime,$endTime,"PlannedOther",$Description,"OneLevel")
    }
}
                                        ]]></Contents>
                                        <Unicode>true</Unicode>
                                    </File>
                                </Files>
                            </WriteAction>
                        </MemberModules>
                        <Composition>
                            <Node ID="WA1"/>
                        </Composition>
                    </Composite>
                </ModuleImplementation>
                <OutputType>System!System.CommandOutput</OutputType>
                <InputType>System!System.BaseData</InputType>
            </WriteActionModuleType>

 

Setting the Rule Target

With those modules created, we need a rule that includes each data source and specifies the required arguments.  The biggest question is what to use as its target.  Technically, we can target anything that will end up deploying to an agent with PowerShell and Command Shell installed.  We only want a single agent to get it though.  We also want to make sure that our target is going to be accessible to the server admins.  The root management server typically has Command Shell installed, and this is a perfect candidate for this task.  Chances are pretty good though that we don't want to give permission to our administrators to author rules against our RMS.

I had thought of targeting the group itself since, as described in another previous post the root management server "hosts" groups, so that's where the rule would get deployed.  I had some issues with this working reliably though, and I also thought it might get a bit confusing.

The solution I went with was to create a new class based on ComputerRole called CommandShellProxy. This can serve as the target for any rules I want to create that launch a Command Shell script.  I can give permission to different people to override rules targeted at it without giving them access to the rules and monitors directly targeting the RMS.  As an added benefit, I stored the path to the PowerShell executable and the Command Shell snap-in so I don't have to hardcode those into the rule.

<ClassType ID="bwren.MaintenanceMode.CommandShellProxy" Accessibility="Public" Abstract="false" Base="Windows!Microsoft.Windows.ComputerRole" Hosted="true" Singleton="false">
    <Property ID="PowerShellPath" Type="string"/>
    <Property ID="CommandShellPath" Type="string"/>
</ClassType>

I did a simple registry discovery for my new class and targeted it at the Root Management Server class.  The only criteria I'm using is determining whether Command Shell is installed.  If you want to use something other than the RMS as your Command Shell proxy, all you would have to do is change that discovery.

I didn't bother pasting in the discovery XML since I've already cluttered up this post with enough code.  You can have a look at it in the sample MP though.

 

End Result

Go to the bottom of this post for a link to the sample MP.  You can go through the following steps to get it up and running:

1.  Load the management pack

image

 

2.  Add an object or two to the group Maintenance Mode Group 1.

image

 

3.  Make sure that you are discovering the Command Shell Proxy by selecting Discovered Inventory.  Then click on Change Target and select Command Shell Proxy from the list.

 image

 

4.  Go to the Authoring tab and look at rules for the target Command Shell Proxy.

image

 

4.  Create an override on the rule Set maintenance mode - group 1, and set appropriate values for StartTime, HoursInMaintenance, IncludeHostedObjects, and Description.  The allowable values for IncludeHostedObjects are $true and $false (those are the PowerShell variables for True and False.  You could do a little script work to allow a boolean in the override and then convert appropriately if you really want).

image

 

I know this is a long and detailed post, but it seemed like a good opportunity to illustrate a few points.  Hope it's helpful.

 

I will be doing a presentation at the upcoming MMS 2008 titled Administrative Scenarios using the Operations Manager 2007 Command Shell.  This is a bit of a departure for me since I've presented several times at MMS but usually on management packs and custom monitoring scenarios.  This will be a chance to present different solutions I've created with Command Shell working with various customers.  Not management packs but still automation, so we're well within my comfort zone.

I'm working out the different scenarios I'm going to cover right now and already have a list longer than time is going to allow.  That list currently includes the following:

  • Importing and exporting management packs from a command line
  • Analyzing management pack contents
  • Deploying agents from a command line
  • Modifying settings across multiple agents with a single command
  • Scheduling maintenance mode for single object and related objects
  • Resolving alerts matching specified criteria
  • Launching tasks from a command line
  • Extracting performance and alert data

Depending on the scenario, the solution may be a CmdLet or two or may require a full blown script.  I personally hate trying to feverishly copy code and specific command lines while sitting in a presentation, so I'll include the code for each scenario here on the blog just prior to the presentation itself. 

If you're going to be attending MMS this year and have any scenarios you'd like to see covered in my presentation, let me know.  Obviously I can't guarantee anything, but I would like to see if there are any interesting scenarios I haven't thought of.  I could also use a little feedback on the scenarios that are most interesting since a couple are probably going to have to fall off of that list due to time constraints.

Just a few days ago, I mentioned that the new Operations Manager 2007 Management Pack Authoring Guide was going to be released "very soon".  Sure enough, it's been posted at http://download.microsoft.com/download/7/4/d/74deff5e-449f-4a6b-91dd-ffbc117869a2/OM2007_AuthGuide.doc.

This is the guide that all of us in the authoring community have been waiting on for some time.  At 238 pages, it provides a ton of information on management pack concepts, structuring work flows, creating custom modules, etc.  It also includes a complete reference on the XML schema of a management pack.

This is a first version of the authoring guide, and it will be getting updated with additional information.  One topic with minimal information in this version is the details of writing a script in a management pack.  I'm going to get out a blog entry out later this week on this topic to get some of the basics out there until the guide itself provides complete details on it.

Considering all my work with OpsMgr management packs and my infatuation with PowerShell, it's surprising that I've never put the two together in a public forum.  I keep meaning to, just don't ever seem to get there.  I finally committed myself to this blog post while sitting at my local It's a Grind (that's a coffee house to all you Seattle Starbucks fans.  It's a Grind was born in Long Beach, and I am fanatically loyal to my home town).

So here's the basic question - can I use a PowerShell script in an OpsMgr management pack?  In other words, can I execute a PowerShell script from a rule, monitor, diagnostic, recovery, or task?  The answer is absolutely you can, but there are some considerations to keep in mind.  First let's cover those considerations, then we'll get to concepts and code.  If you just want the modules to copy and paste into your MP, go ahead and jump to the end.

The biggest issue is that most of the servers in your environment don't have PowerShell installed.  Rules and monitors sent to an agent are executed locally on the agent computer, and any executables must be installed prior to the agent attempting to use them.  In the case of VBScript and JScript, we rely on cscript.exe being present, but that's a safe bet since it is automatically installed on all Windows machines.  Powershell is obviously not installed by default, and even in Windows Server 2008 it's an optional feature.  If you want to use a PowerShell script for a console task, then you're going to need to make sure that it is installed on the client workstation the task is being executed from.  This is less of a concern though since, while PowerShell and Command Shell are not required for the OpsMgr User Interfaces, they are pretty strongly suggested.

There can also be some significant overhead from launching PowerShell.  Go ahead and launch a PowerShell window and then have a look at Task Manager.  Powershell.exe will probably be consuming something like 30 MB of memory which is about 5x the typical cscript.exe instance in my personal testing.  This is not surprising considering all the rich functionality that PowerShell provides.  My only point is to try to stay away from scenarios where you need to launch a PowerShell script every couple of minutes.  I've heard the OpsMgr product team is working on some strategies to reduce this overhead, but until then you want to use PowerShell scripts where they don't have to be launched too frequently.

Concepts

The basic idea of running a PowerShell script is to use of the Command Executer modules to launch powershell.exe with your script.  This is actually the method that quite a few of the scripts in existing management packs use - calling cscript.exe with these modules.  The Command Executer modules will launch the executable of your choice, allow you to specify command line arguments, and allow you to specify the name and contents of one or more text files (which will obviously be your script).  These text files are created on the agent prior to command execution, so you can be guaranteed they will be in place when your specified command is launched.

Have a look at Microsoft.Windows.ScriptProbeAction in Microsoft.Windows.Library for example.  That module uses System.CommandExecuterProbe from System.Library.  It specifies cscript.exe as the command to execute, provides the appropriate command line arguments such as /nologo and the name of the script, and then passes in the body of the script as a file.  In order to execute Powershell, we really just need to figure out the command line required to launch PowerShell, execute a script, and then exit.

PowerShell Command Line

If you run powershell.exe /?, you get the command line arguments for PowerShell.  To launch a command and exit, you use the -command argument, the  invoke operator (&), and a command to execute.  The example syntax given by that help is as follows:

powershell -command "& {get-eventlog -logname security}"

We're going to need that basic syntax but put it in a format that the management pack can understand.  It won't handle that ampersand, and we're go to have to have to specify a path for the script.  PowerShell demands a complete path to a script even if it's in the current directory.  The Command Executer will drop the script to a temporary directory and then use that directory as its default when it executes the script, so we can assume the script will be located in the current directory.  Assuming that we are going to use a parameter called ScriptName for the name of the script, then the command line in the management pack would look like the following:

-command "&amp; {.\$Config/ScriptName$}"

If you're completely baffled by that syntax, here's a quick explanation.  We replace & with &amp; because OpsMgr interprets the & as a special character.  We get away with that character in the script itself if we enclose the script in CDATA tags, but we can't use CDATA tags on our command line.  $Config/ScriptName$ is a context variable.  OpsMgr will replace the variable inside the dollar signs with its actual value at run time.  Config refers to the parameters of the module, and ScriptName is the name of the parameter.  Finally, the .\ just refers to the current directory.  So, if we used MyScript.ps1 for the script name, we would end up running the following command:

powershell -command "& {.\MyScript.ps1}"

Implementing the Modules

Rather than write a specific rule that uses the Command Executer modules and includes the specific PowerShell complexity, it is way more valuable to create a couple of base modules that can be leveraged by rules, monitors, and tasks.  A Data Source module that runs a PowerShell script on a timed basis and returns a property bag could be used for a rule, monitor, or diagnostic.  Another Data Source module returning discovery   A Write Action module that just runs a PowerShell script on demand to perform some defined action would support a task or recovery.

I'll provide the data source below.  Given this it should be pretty straightforward to create a write action (hint - use System!System.CommandExecuter).  You could also create a discovery module based on System!System.CommandExecuterDiscoveryDataSource.

<DataSourceModuleType ID="PowerShell.Library.PSScriptPropertyBagSource" Accessibility="Public">
    <Configuration>
        <xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="IntervalSeconds" type="xsd:integer"/>
        <xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="TimeoutSeconds" type="xsd:integer"/>
        <xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ScriptName" type="xsd:string"/>
        <xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Arguments" type="xsd:string"/>
        <xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ScriptBody" type="xsd:string"/>
    </Configuration>
    <ModuleImplementation>
        <Composite>
            <MemberModules>
                <DataSource ID="DS" TypeID="System!System.CommandExecuterPropertyBagSource">
                    <IntervalSeconds>60</IntervalSeconds>
                    <ApplicationName>%windir%\system32\windowspowershell\v1.0\powershell.exe</ApplicationName>
                    <WorkingDirectory/>
                    <CommandLine>-Command "&amp; '$Config/ScriptName$"</CommandLine>
                    <SecureInput/>
                    <TimeoutSeconds>30</TimeoutSeconds>
                    <RequireOutput>false</RequireOutput>
                    <Files>
                        <File>
                            <Name>$Config/ScriptName$</Name>
                            <Contents>$Config/ScriptBody$</Contents>
                            <Unicode>true</Unicode>
                        </File>
                    </Files>
                </DataSource>
            </MemberModules>
            <Composition>
                <Node ID="DS"/>
            </Composition>
        </Composite>
    </ModuleImplementation>
    <OutputType>System!System.PropertyBagData</OutputType>
</DataSourceModuleType>

Writing the Script

For the write action, there's really nothing special about the script.  Any working PowerShell script will be fine.  If you need to return a property bag or discovery data from a data source though, you're going to need to use MOM.ScriptAPI just like in VBScript.  PowerShell works fine with COM objects, so this is not a problem at all.  The following line will create an object variable, and using it is pretty similar to how you did it in VBScript.

$api = new-object -comObject "MOM.ScriptAPI"

For example, below is a PowerShell script to output the name and size of a specified file as a property bag.  This could be called from a rule or monitor that uses a condition detection to map the property bag information to performance data. While this is coded in PowerShell, the basic process is identical to a script

$file = Get-Item $args[0]
$api = New-Object -comObject "MOM.ScriptAPI"
$bag = $api.CreatePropertyBag()
$bag.AddValue($file.Name,$file.Length)
$api.Return($bag)

More Later

This should be enough information to get people off and running.  I'd like to provide some more samples and thought of actually writing a library MP with a complete set of PowerShell modules.  No hard commitment on that, but I'll do my best in coming weeks.

I've been asked a few times where I got all the information behind the different management pack related posts I've done.  I actually don't have any more access to documentation than anyone else.  I come across this information through www.authormps.com, blogs, existing management packs, etc.  Well...then I do have contact with the Operations Manager product group, so that's obviously helped a bit.

For those looking for documentation on management pack authoring though, be patient.  I don't have the exact details (and wouldn't be at liberty to share them if I did anyway), but I have it from very good sources that there is complete documentation on the way very soon.  I'll make sure to do a blog post as soon as it's available.

This is one I've been asked about several times and am finally getting around to blogging the answer.  The Distributed Application Designer in Operations Manager 2007 makes creating and modifying distributed applications quite easy.  You can create a component group with a few clicks and then drag in managed objects that are related to the application.  What cannot be done with the designer though is create a component group that is automatically populated through a rule like you can do with Operations Manager groups. 

To illustrate this point, note the distributed application below that represents a simple application with a farm of web servers.  I created this using the Line of Business Web Application template.  The component group Monster Island Web Application Web Sites includes all web sites serving the application.  These were manually added to the distributed application by dragging the appropriate instances into the group.  This is the way that distributed applications are typically built.

image

In this example, any web site called Gargantua should be added to the Web Sites component group.  If a new web server is installed, then we would need to open the Distributed Application Designer and drag this new web site instance to the group.  If we had a means to create a discovery for the component group though, then it would automatically be added once the new web site instance were discovered by Operations Manager.

Creating such a discovery is entirely possible and actually not all that difficult.  You won't be able to use the Operations Console though, and you will need to be familiar with either the Authoring Console or working with management packs in XML.  The basic strategy is to modify the GroupPopulator module for the component group to use a membership rule instead of using specific managed objects. 

I'm going to use the Authoring Console to have a look at this discovery.  Since I created it in the Operations Console, the name of it is going to be pretty ugly.  If you have a large management pack, it might take a little searching to find the right item.  The name will start with SC_, and it will have Microsoft.SystemCenter.GroupPopulator as a data source.  Here's a look at the details of mine.

 

image

 

Note the IncludeList section which has two MonitoringObjectID entries.  Those are the GUIDs of the two web sites that I added to the component group.  If I was to drag a third site into the group, we would see that entry.

The goal is to replace that Membership Rule with one that uses include logic instead of specific instances.  That way whenever the discovery executes, it will dynamically find appropriate objects to add to the component group.  It turns out that the same GroupPopulator that this discovery is based on is used for regular groups in Operations manager.  Groups have a nice wizard in the Operations Console for creating a population rule.  An easy strategy is to create a temporary group using the logic we want, and then copying and paste that logic into the component group discovery.

For this example, I'll create a rule that includes all web sites with the name Gargantua.  I created a dummy group called Temp Group in the Operations Console and used the wizard to create the logic shown in the figure below.

 

 image

 

Viewing the discovery for that group in the Authoring Console, we see an Expression instead of an Include List.  That's exactly what we want to go into our component group discovery.

image

 

In order to pull  this off, you're going to need to edit some XML.  First, export the management pack into an XML file where you can open it with an XML editor or the Authoring Console.  The Authoring Console will launch your editor of choice if you click the E