Batch Operations in Service Manager 2010 with PowerShell – Removing Instances   2 comments

Sometimes, when I am developing a demo for Service Manager, I wind up creating a lot of Service Requests or Incidents when I’m trying to get the demo just right. However, after I’ve gotten everything working just like I want and then I give the demo, I don’t really want to have all those earlier things visible because they get in the way of the what I’m trying to show. The Service Manager 2010 provides a way to removing instances from the console, and I could use that, but I like to script everything so I want to create a script instead of using the UI. With this script, I can use this to remove any instance in the CMDB, including Incidents and Service Requests. 

Our programming interfaces provide a way to remove instances and I’ve written my script to work a couple of ways:

  • If you provide the script with  ClassName parameter, the script will remove every instance of that class!
  • If you pipe an EnterpriseManagementObject at the script, the script will remove that instance

These are pretty big hammers, so I’ve made sure that you can use –WhatIf and I’ve also set ConfirmImpact as High which will ask for confirmation even if you don’t specify –confirm. My last warning is that you should not put this script anywhere near your production machines. It will remove the data forever, so be sure you are careful!!

I think the most interesting bit of the script is on line 21. This is where an IncrementalDiscoveryData object is created. The IncrementalDiscoveryData object allows you to deal with instances in bulk.  I can use this object to remove instances then remove them all by the single call to Commit in line 73.  The code between lines 29 and 35 represent the code that’s needed to call our generic methods, the script uses reflection to build the generic method and then call it.

The PROCESS block starting on line 50 handles the case when you pipe objects to the script. It first checks to be sure that it’s an EnterpriseManagementObject, and if so, adds the object to the IncrementalDiscoveryData collection which will be used in the END block. Rather than wrapping the call to Commit in another ShouldProcess block, I just check to be sure I have objects to remove. If there are, I make the Commit call. I don’t like it when my scripts ask me “Do you really want to do this” after I’ve already answered it once.

This script is a PowerShell version 2.0 script (as seen in line 1). This way I can take advantage of the ConfirmImpact and the other PowerShell 2.0 goodies.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
#requires -version 2.0
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
param ( 
    [Parameter(Position=0)]
    $classname,
    [Parameter(ValueFromPipeline=$true)]$EMO
    )
BEGIN
{
    # oh for a way to specify namespaces
    $NS = "Microsoft.EnterpriseManagement"
    if ( ! ("${NS}.Common.EnterpriseManagementObject" -as "type"))
    {
        [reflection.assembly]::LoadWithPartialName("${NS}.Core")
    }
    
    $LFX          = "ConnectorFramework"
    $DEFAULT      = ("${NS}.Common.ObjectQueryOptions" -as "type")::Default
    $EMOT         = "${NS}.Common.EnterpriseManagementObject" -as "type"
    $EMG          = new-object "${NS}.EnterpriseManagementGroup" localhost
    $IDD          = new-object "${NS}.${LFX}.incrementaldiscoverydata"
    $guid         = $EMG.ConnectorFramework.GetDefaultConnectorId().guid
    $SDKConnector = $EMG.ConnectorFramework.GetConnector($guid)
    $REMOVECOUNT  = 0
    # only go through this process if you got a classname and are
    # going to remove all instances of that class
    if ( $classname )
    {
        $IMgmt    = $EMG.EntityObjects.GetType()
        $class = $EMG.EntityTypes.GetClasses()|?{$_.name -eq $classname}
        [array]$arguments = ($class -as "${NS}.Configuration.ManagementPackClass"),$DEFAULT
        [type[]]$TYPES = ("${NS}.Configuration.ManagementPackClass" -as "type"),
                     ("${NS}.Common.ObjectQueryOptions" -as "type")
        $ObjectReader = $IMgmt.getmethod("GetObjectReader",$TYPES)
        $GenericMethod = $ObjectReader.MakeGenericMethod($EMOT)
        if ( ! $class ) { throw "no class $classname" }
        # GET THE OBJECTS
        $SMObjects = $GenericMethod.invoke($EMG.EntityObjects,$arguments) 
        if ( ! $SMObjects ) { "No objects to remove"; exit }
        $SMObjects|%{ 
            if ( $PSCmdlet.ShouldProcess( $_.displayname ) )
            {
                $REMOVECOUNT++
                $IDD.Remove($_)
            }
        } 
    }
}

PROCESS
{
    if ( $EMO -is "${NS}.Common.EnterpriseManagementObject")
    {
        if ( $PSCmdlet.ShouldProcess( $EMO.displayname ) )
        {
            $REMOVECOUNT++
            $IDD.Remove($EMO)
        }
    }
    elseif ( ! $EMO ) { ; }
    else
    {
        Write-Error "$_ is not an EnterpriseManagementObject, skipping"
    }
}

END
{
    # only actually call this if there are any to delete
    if ( $REMOVECOUNT )
    {
        Write-Verbose "Committing Changes"
        $IDD.Commit(${SDKConnector})
    }
}

<#
.SYNOPSIS
    Remove an instance from the Service Manager 2010 CMDB
.DESCRIPTION
    The cmdlet removes instances from the Service Manager CMDB.
    If the classname parameter is provided, every instance will
    be removed from the CMDB.
    Optionally, instances may be piped to this cmdlet in which case
    only those instances will be removed.
.PARAMETER ClassName
    A Service Manager 2010 class name
.PARAMETER EMO
    An instance to be removed from the Service Mangaer 2010 CMDB
.EXAMPLE
remove-smobject -classname Microsoft.Windows.Computer
Removes all instances of Microsoft.Windows.Computer from the
Service Manager 2010 CMDB
.EXAMPLE
get-smobject Microsoft.Windows.Computer | ?{$_.displayname -match "Computer00"}|remove-smobject
Removes all instances of Microsoft.Windows.Computer from the
Service Manager 2010 CMDB where the displayname matches "Computer00"
.INPUTS
    Output from get-smobject
    Any EnterpriseManagementObject
.OUTPUTS
    None
.LINK
    get-smobject-ManagementPack
    get-smclass
#>

Here’s an example of removing every Microsoft.Windows.Computer from Service Manager (I’m not actually going to do this, so I’m using –Whatif). If you need a reminder, Get-SmObject.ps1 was a blog posting here.

PS> ./get-smobject microsoft.windows.computer|./remove-smobject -whatif
What if: Performing operation "remove-smobject.ps1" on Target "Computer028".
What if: Performing operation "remove-smobject.ps1" on Target "Computer008".
What if: Performing operation "remove-smobject.ps1" on Target "computer1.contoso.com".
What if: Performing operation "remove-smobject.ps1" on Target "Computer027".
What if: Performing operation "remove-smobject.ps1" on Target "Computer001".
What if: Performing operation "remove-smobject.ps1" on Target "WIN-752HJBSX24M.woodgrove.com".
What if: Performing operation "remove-smobject.ps1" on Target "Computer002".
What if: Performing operation "remove-smobject.ps1" on Target "Computer024".
What if: Performing operation "remove-smobject.ps1" on Target "Computer030".
What if: Performing operation "remove-smobject.ps1" on Target "Computer007".
What if: Performing operation "remove-smobject.ps1" on Target "Computer023".
What if: Performing operation "remove-smobject.ps1" on Target "Computer025".
What if: Performing operation "remove-smobject.ps1" on Target "Computer026".
What if: Performing operation "remove-smobject.ps1" on Target "Computer003".
What if: Performing operation "remove-smobject.ps1" on Target "Computer029".

If I want to remove one computer, I can just filter for what I want.

PS> ./get-smobject microsoft.windows.computer|?{$_.displayname -match "Computer028"}|
>> ./remove-smobject -whatif
What if: Performing operation "remove-smobject.ps1" on Target "Computer028".

This is what it will look like when you really remove it!

Computer028 is gone! Notice that this is really where PowerShell provides lots of value, the interaction to confirm the removal is done with the Cmdlet attribute in line 2 – ConfirmImpact=”High”, that plus the $PSCmdlet.ShouldProcess in lines 41 and 54 make it really easy to write scripts that won’t shoot me in the foot!

Posted October 22, 2009 by jtruher3 in ServiceManager

Service Manager and the PowerShell 1 liner   Leave a comment

I’ve written a number of fairly complicated scripts for Service Manager over the last few months, and while talking to a team-mate about something he wanted to do, it looked like it was just 1-line PowerShell script. That got me thinking about what other things in Service Manager could be handled by a really simple (say less than 5 lines) of PowerShell. The list is good sized, so I thought it would be good if I shared them.

The problem we had at hand was how I could help one our development partners figure out in which management pack a particular class resides. It turns out it was 3 lines of script to find out.

PS> [reflection.assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.Core")

GAC    Version        Location
---    -------        --------
True   v2.0.50727     C:\Windows\assembly\GAC_MSIL\Microsoft.EnterpriseManagement.Core\7.0.5000.0__31bf3856ad364e35\...

PS> $EMG = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup localhost
PS> $EMG.EntityTypes.GetClasses()|ft name,{$_.GetManagementPack().Name}

Name                                                        $_.GetManagementPack().Name
----                                                        ---------------------------
System.Entity                                               System.Library
System.Collections                                          System.Library
System.ConfigItem                                           System.Library
System.LogicalEntity                                        System.Library
. . .

the business end of the script is just the last line. The first two lines are just what I need to get access to the Service Manager Data Access Service. I do this so much that put those two lines in my $profile.

After that, it’s just a matter of adding a filter to find out the actual class of interest.

PS> $EMG.EntityTypes.GetClasses()|?{$_.name -match "System.Knowledge.Article"}|ft name,{$_.GetManagementPack().Name}

Name                                                        $_.GetManagementPack().Name
----                                                        ---------------------------
System.Knowledge.Article                                    System.Knowledge.Library

Now, the label for the second column may not pretty, but I’m not fussed about that, I’ve got the data that I need. The next thing I needed to figure out was the properties of this class. That’s another one liner:

PS> $emg.entitytypes.GetClasses()|?{$_.name -match "System.Knowledge.Article"}|
>> select -expand propertycollection|ft name,key,type -au
>>

Name                Key     Type
----                ---     ----
ArticleType       False     enum
ArticleTemplate   False   string
ArticleOwner      False   string
Category          False     enum
Comments          False   string
CreatedDate       False datetime
CreatedBy         False   string
PrimaryLocaleID   False      int
Status            False     enum
Tag               False     enum
VendorArticleID   False   string
Title             False   string
Abstract          False   string
Keywords          False   string
ArticleId          True   string
EndUserContent    False   binary
AnalystContent    False   binary
ExternalURLSource False   string
ExternalURL       False   string

(ok, so it’s a long line, but it’s still a single pipeline)

This doesn’t quite tell the whole story, because if I wanted to create one of these classes, I may have more properties available to me (based on the base classes for the class I want). That’s just *2* lines:

PS> $class = $emg.entitytypes.GetClasses()|?{$_.name -match "System.Knowledge.Article"}
PS> (new-object microsoft.enterprisemanagement.common.CreatableEnterpriseManagementObject $emg,$class).GetProperties()|
>> ft name,key,type -au
>>

Name                Key     Type
----                ---     ----
ArticleType       False     enum
ArticleTemplate   False   string
ArticleOwner      False   string
Category          False     enum
Comments          False   string
CreatedDate       False datetime
CreatedBy         False   string
PrimaryLocaleID   False      int
Status            False     enum
Tag               False     enum
VendorArticleID   False   string
Title             False   string
Abstract          False   string
Keywords          False   string
ArticleId          True   string
EndUserContent    False   binary
AnalystContent    False   binary
ExternalURLSource False   string
ExternalURL       False   string
ObjectStatus      False     enum
AssetStatus       False     enum
Notes             False richtext
DisplayName       False   string

I save the class and then use it to create the object I want with new-object.

Sometimes, I need to know which management pack an enumeration is in. Another 1 liner:

PS> $emg.EntityTypes.GetEnumerations()|?{$_.name -match "high"}|ft name,{$_.getmanagementpack().name} -au

Name                                                    $_.getmanagementpack().name
----                                                    ---------------------------
System.WorkItem.TroubleTicket.ImpactEnum.High           System.WorkItem.Library
System.WorkItem.TroubleTicket.UrgencyEnum.High          System.WorkItem.Library
System.ServiceManagement.ServicePriority.High           ServiceManager.ServiceMaps.Configuration
IncidentResolutionCategoryEnum.FixedByHigherTierSupport ServiceManager.IncidentManagement.Configuration
ChangePriorityEnum.High                                 ServiceManager.ChangeManagement.Configuration
ChangeRiskEnum.High                                     ServiceManager.ChangeManagement.Configuration
ActivityPriorityEnum.High                               ServiceManager.ActivityManagement.Configuration

One of my early examples for retrieving management packs. That’s a 1 liner:

PS> $emg.ManagementPacks.GetManagementPacks()|ft Sealed,Version,Name
Sealed Version    Name
------ -------    ----
 False 7.0.5228.0 Microsoft.SystemCenter.ServiceManager.Connector.Configuration
  True 7.0.5228.0 Microsoft.SystemCenter.Internal
  True 7.0.5228.0 ServiceManager.Reporting.Help
  True 7.0.5228.0 Microsoft.SystemCenter.Report.Library
  True 7.0.5228.0 ServiceManager.LinkingFramework.Library
  True 7.0.5228.0 System.ApplicationLog.Library
  True 7.0.5228.0 ServiceManager.IncidentManagement.Library.Datawarehouse
  True 7.0.5228.0 Microsoft.EnterpriseManagement.ServiceManager.UI.Console
  True 7.0.5228.0 ServiceManager.ActivityManagement.Library.Datawarehouse
  True 7.0.5228.0 ServiceManager.ChangeManagement.Library
  True 7.0.5228.0 ServiceManager.IncidentManagement.Report.Library
  True 7.0.5228.0 ServiceManager.ChangeManagement.Report.Library
. . .

What if I wanted to remove a management pack? Before I do, I had better find out whether it’s possible, as if other Management Packs depend on the one I want to remove. So I need to find the dependent management packs – 2 lines!

PS> $crLib = $emg.ManagementPacks.GetManagementPacks()|?{$_.name -eq "System.WorkItem.ChangeRequest.Library"}
PS> $emg.ManagementPacks.GetDependentManagementPacks($crLib)|ft sealed,version,name -au

Sealed Version    Name
------ -------    ----
  True 7.0.5228.0 ServiceManager.ActivityManagement.Library.Datawarehouse
  True 7.0.5228.0 ServiceManager.ChangeManagement.Library
  True 7.0.5228.0 ServiceManager.ChangeManagement.Report.Library
  True 7.0.5228.0 ServiceManager.ServiceMaps.Library
  True 7.0.5228.0 ServiceManager.ChangeManagement.Library.Datawarehouse
 False 7.0.5228.0 ServiceManager.ConfigurationManagement.Configuration
  True 7.0.5228.0 ServiceManager.ChangeManagement.Help
  True 7.0.5228.0 Microsoft.SystemCenter.ServiceManager.Portal
 False 7.0.5228.0 ServiceManager.ChangeManagement.Configuration

In this case, I won’t be able to remove the ChangeRequest Library, because of all the dependencies, but if I have a management pack that is not needed by other management packs, removing the management pack is another one-liner:

PS> $emg.ManagementPacks.GetManagementPacks()|?{$_.name -eq "MPToRemove"}|
>> %{ $emg.ManagementPacks.UninstallManagementPack($_) }
>>

Perhaps I want to find out how much localization I need to do. To understand how much work I will need to do, I should find out how many lines of text I need to localize.  How do I find out how many different English display strings are stored in my management packs? 1 line!

PS> ($emg.LanguagePacks.getlanguagepacks()|?{$_.name -eq "ENU"}|select-object -Expand DisplayStringCollection).count
6456

and if I wanted to know the count of my display strings for each language? 1 line!

PS> $emg.LanguagePacks.getlanguagepacks()|select-object -ExpandProperty DisplayStringCollection |
>> Group-Object LanguageCode|format-table Count,Name -au
>>

Count Name
----- ----
 6456 ENU
 6455 DEU
 6282 JPN

and what if I wanted to see the actual English display strings from the System.Library management pack? Just another line!

PS> $emg.LanguagePacks.GetLanguagePacks()|?{$_.Name -eq "ENU" -and $_.GetManagementPack().Name -eq "System.Library"}|
>> Select-Object -Expand DisplayStringCollection|ft name,description
>>

Name                                                        Description
----                                                        -----------
Display Name                                                Display name
Timeout Seconds
Database                                                    Defines the basic properties of databases
Local Application                                           Defines the basic properties of applications that are di...
Reference                                                   Defines the basic properties of directed relationships
. . .

yow!

Posted October 2, 2009 by jtruher3 in ServiceManager

Introducing Management Pack Bundles   5 comments

Historically, management packs have been comprised of a single XML file (either in XML format in a .xml file or in a binary representation in a signed .mp file). With the new version of the common System Center management pack infrastructure that ships in Service Manager, the definition of a management pack is being extended to include associated “resources” such as images, form assemblies, workflow assemblies, reports, T-SQL scripts, and more. The aggregation of the XML (or even multiple XMLs) plus its associated resources is called a “management pack bundle”.

A “management pack bundle” is really a MSI file with a file extension of .mpb (I should tell you that these .msi’s aren’t installable, we’re just using MSI as a file format). These bundles can be imported into Service Manager as a whole through a new MP import interface on the Data Access Service. You can import .mpb files via either the Management Packs view in the Administration workspace in the Service Manager console or using the Import-SCSMManagementPack PowerShell cmdlet (available in Beta 2). After import, the resources are automatically distributed to the appropriate places.

In this post I’ll explain how to to aggregate your assemblies and images and multiple mps into a single file, using the BundleFactory in the Microsoft.EnterpriseManagement.Packaging assembly. This factory will let you create .MPB files, which can include resources needed by the management pack. I’ve written a PowerShell script to make this easier for you. You can either just use the script attached to this blog post to create management pack bundles or continue on to learn more about how to use the BundleFactory APIs to create management pack bundles.

The script inspects the management pack defined in the ‘Resources” section of the management pack XML and retrieves the resources. Here’s what this section looks like in the MP I’m using as an example:

<Resources>
  <Image ID="SmileyImage" Accessibility="Public" FileName="Smiley.png" HasNullStream="false" />
</Resources>

once the script has the retrieved the resources it looks in the current directory for the files, if it finds the file, it adds the resource to the bundle. If the file can’t be found, it reports a warning, but continues to create the .mpb file. Note that this .mpb file won’t be able to be imported, but I decided to do this because I wanted to keep going to find all the issues in creating the .mpb file.

The script is one of the more complicated scripts that I’ve done in this blog at about 140 lines so we’ll go through it in sections.

Lines 10 through 19 declare some “constants” which I’ll use in the rest of the script.

Lines 22 and 23 load the needed assemblies. Since we install the assemblies into the GAC on the management server or a computer that has the Service Manager console installed on it, I can use the static LoadWithPartialName method on Reflection.Assembly to load the assemblies we need if this script is run where the assemblies are installed. Since the LoadWithPartialName method returns the assembly, this is saved away so I can use it in lines 24 through 27 to retrieve the types I need later. I’ve done this to avoid the requirement of loading the needed assemblies before running the script. This means that the script has fewer preconditions.

Lines 29 through 78 have function declarations. I declare two functions; the first function (Invoke-GenericMethod) allows me to invoke a generic method, which is how the resources from the management pack are retrieved. It’s a pretty tricky function which uses reflection to invoke the methods in Service Manager which use Generics. The second function, “Get-Resources” retrieves the resources and emits a stream of hash tables which contain the stream and the name of the resource. I need this information when I actually associate the resource with the management pack in the .mpb file.

Lines 80 through 103 collect the management packs into an array. This script allows you to create a .mpb file with more than a single management pack. Line 99 has a check to be sure that I actually got some files in my array, if not, the script exits.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# New-MPBFile.ps1
# this takes files (.mp or .xml) and creates a .mpb file
param (
    $mpFile = $( throw "Must have mpfile" ),
    [string]$mpbname = "testmpb",
    $computername = "localhost"
    )

# VARIABLES NEEDED BY SCRIPT
$VerbosePreference = "continue"
$SMDLL    = "Microsoft.EnterpriseManagement.Core"
$SMPKG    = "Microsoft.EnterpriseManagement.Packaging"
$MPTYPE   = "Microsoft.EnterpriseManagement.Configuration.ManagementPack"
$MRESTYPE = "Microsoft.EnterpriseManagement.Configuration.ManagementPackResource"
$SIGTYPE  = "Microsoft.EnterpriseManagement.Packaging.ManagementPackBundleStreamSignature"
$FACTYPE  = "Microsoft.EnterpriseManagement.Packaging.ManagementPackBundleFactory"
$EMGTYPE  = "Microsoft.EnterpriseManagement.EnterpriseManagementGroup"
$OPEN     = [System.IO.FileMode]"Open"
$READ     = [System.IO.FileAccess]"Read"

# make sure the appropriate assemblies are loaded and retrieve the needed types.
$SMCORE      = [reflection.assembly]::LoadWithPartialName($SMDLL)
$SMPACKAGING = [reflection.assembly]::LoadWithPartialName($SMPKG)
$EMPTY       = $SMCORE.GetType($SIGTYPE)::Empty
$TYPEOFMP    = $SMCORE.GetType($MPTYPE)
$TYPEOFMPR   = $SMCORE.GetType($MRESTYPE)
$BFACTORY    = $SMPACKAGING.GetType($FACTYPE)

# Functions
# Invoke-GenericMethod
# allows scripts to call generic methods.
# arguments
# mytype – the type inspect for the needed method
# mymethod – the method name
# typearguments – an array of types used by MakeGenericMethod
# object – the object against which invoke is called
# parameters – any parameters needed by invoke
# it returns whatever is returned by invoke
function Invoke-GenericMethod
{
    param (
        [type]$mytype, 
        [string]$mymethod, 
        $TypeArguments, 
        $object, 
        [object[]]$parameters = $null 
        )
    $Method = $mytype.GetMethod($mymethod)
    $genericMethod = $Method.MakeGenericMethod($TypeArguments)
    $genericMethod.Invoke($object,$parameters)
}

# Get-Resources
# this function retrieves resources from the MP. Because our GetResources API
# uses generics, it’s a bit tricky to call
# it returns a hash table of the stream, and the name for each resource
# it takes a Management Pack object
function Get-Resources
{
    param ( $mpObject )
    invoke-GenericMethod $TYPEOFMP "GetResources" $TYPEOFMPR $mpObject | %{ 
        # check to see if we could find the file
        $fullname = (resolve-path $_.FileName -ea SilentlyContinue).path
        if ( ! $fullname ) 
        { 
            write-host -for red "
    WARNING:
    (‘Cannot find resource: ‘ + $_.FileName)
    Skipping this resource, your MPB will probably not import
    Make sure that the resources are in the same directory as the MP"

        }
        else
        {
            $stream = new-object io.filestream $fullname,$OPEN,$READ
            @{ Stream = $stream; Name = $_.Name }
        }
    }
}

# Start
# Collect all the mps to add to the mpb!
$mpfileArray = @()
foreach ( $file in $mpFile )
{
    foreach ( $item in resolve-path $file )
    {
        if ( $item.path ) 
        { 
            $mpfileArray += $item.path
        }
        else
        {
            Write-Host -for red "ERROR: Cannot find file $item, skipping" 
        }
    }
}

# Check to see if we have any management packs, if not, exit.
if ( $mpFileArray.Count -eq 0 )
{
    Write-Host -for red "Error: No files to add"
    exit
}

# we need a connection to the server when we start creating
# the management pack objects
$EMG = new-object $EMGTYPE $computername
# In order to create .mpb, we need to create one
# we’ll use the BundleFactory for this
$BUNDLE = $BFACTORY::CreateBundle()
# we’ll keep a collection of all the resources that we open
$AllResources = @()
foreach($mpfilepath in $mpfileArray)
{
    # This should handle creating mpb from a local file store.
    # For now, just create the mp object using the EnterpriseManagementGroup
    $theMP = new-object $MPTYPE $mpfilepath,$EMG
    Write-Verbose ("Adding MP: " + $theMP.Name)
    $BUNDLE.AddManagementPack($theMP) 
    # Add the resources if any are associated with the MP
    $Resources = Get-Resources $theMP
    # Add the resources for this MP to the collection
    $AllResources += $Resources
    if ( $Resources )
    {
        $Resources  | %{ 
            Write-Verbose ("Adding stream: " + $_.Name)
            $BUNDLE.AddResourceStream($theMP,$_.Name,$_.Stream,$EMPTY) 
        }
    }
}

# WRITE THE mpb
# First we need a BundleWriter
$bundleWriter = $BFACTORY::CreateBundleWriter(${PWD})
# then we can write out the .mpb
$mpbfullpath = $bundleWriter.Write($BUNDLE,$mpbname)
write-verbose "wrote mpb: $mpbfullpath"
# Cleanup the resources
if ( $AllResources )
{
    $AllResources | %{ if ( $_.Stream ) { $_.Stream.Close(); $_.Stream.Dispose() } }
}

Line 107 is where we connect to the Service Manager Data Access Service. This used when the management pack objects are created in line 117.

Line 110 is where we finally create our bundle object which we use to aggregate all the file.

Since we’re going to be creating a number of resources, Line 112 declares an array which we’ll use to keep all the resources so we can clean up in the end.

The foreach loop in lines 113 to 131 is where the work really takes place:

  • Line 117 is where a management pack object is created, this is needed by the AddManagementPack method call in line 119
  • Line 121 is the where we collect the resources that this management pack uses.
  • For each one of the resources, it’s added with the AddResourceStream method in line 128. This method needs some very specific things. 
    • The management pack with which the resource is associated
    • The name of the resource as defined by the management pack (which is why Get-Resources returns a hash table, so we can keep track of the resource name)
    • The stream representing the resource (which is the other element in the hash table returned by Get-Resources)
    • The last parameter ($EMPTY) is an optional signature (which would allow you to sign the resource) and we don’t need a signature for this example.

We’re not done yet. We’ve created our bundle, but we need to write it, so line 135 creates a BundleWriter object with the BundleFactory and then line 137 writes the .mpb file.

Finally, we have a bit of clean up, so if there were any resources, we will close the stream and then dispose. Strictly speaking, this is probably not needed because when the script exits, the variables go out of scope and are then cleaned up eventually by the garbage collector, but it doesn’t hurt to be tidy.

The following is an example of using the script. It creates a new .mpb file based on an MP (ResourceExample.xml) which has a single resource (an image file) and some instructions to create a new folder with the image. The MP (as an XML file) and the image file are in my sky drive if you want to use them to try it out.

PS> new-mpbfile .\ResourceExample.xml resourceexample
VERBOSE: Adding MP: ResourceExample
VERBOSE: Adding stream: SmileyImage
VERBOSE: wrote mpb: C:\Program Files\System Center Management Packs\resourceexample.mpb

Here’s what it looks like in the Service Manager Console after I import the .mpb (using the Import-SCSMManagementPack cmdlet that is available in Beta2).

awesome!

Now that we can create a .mpb file, it sure would be nice if we could inspect one. The following script does that very thing. It takes as a .mpb file and returns the management packs and resources found in it.

This requires PowerShell V2 because of the way I’m using new-object which takes advantage of new features.

Instead of using a BundleWriter, I create a BundleReader to retrieve the management packs (line 18 through 20) and for each management pack (line 22), get the associated streams (line 26)

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
#requires -version 2.0
# required because of the way we use new-object
# Get-MPBInfo.ps1
# Retrieve management pack and resource information from a .MPB file
param ( $file, $computername = "localhost" )
$path = (resolve-path $file).path
if ( ! $path ) { throw "Could not find ‘$file’" }
$PACKDLL = "Microsoft.EnterpriseManagement.Packaging"
$FSTYPE  = "Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackFileStore"
$BUNDLET = "Microsoft.EnterpriseManagement.Packaging.ManagementPackBundleFactory"
$pkgasm = [reflection.assembly]::LoadWithPartialName($PACKDLL)
if ( ! $pkgasm ) { throw "Can’t load packaging dll" }
# get a bundlefactory type
$BFACTORYT = $pkgasm.GetType($BUNDLET)
# create a bundle reader
# create a filestore, which is used by the bundlereader
# and then read the bundle
$br = $BFACTORYT::CreateBundleReader()
$fs = new-object $FSTYPE
$mpb = $br.Read($path,$fs)
# for each managementpack, get the resources and create a custom object
$mpb.ManagementPacks|%{
    $ManagementPack = $_.name
    # keep track of whether the MP is sealed or no
    if ( $_.Sealed ) { $Sealed = "Sealed" } else { $Sealed = "Not Sealed" }
    $mpb.GetStreams($_) |%{ 
        $streams = $_
        # retrieve the keys and create a custom object which we’ll use
        # in formatting
        $streams.keys | %{
            $ResourceName = $_
            $Length = $streams.Item($ResourceName).Length
            # this emits a custom object which can then be used with
            # PowerShell formatting
            # Get-MPBInfo <file>|ft -group ManagementPack Length,ResourceName
            new-object -type psobject -prop @{
                ManagementPack = "$ManagementPack ($sealed)"
                Length         = $Length
                ResourceName   = $ResourceName
                }
            }
        }
    }

Here’s what it looks like when we use it. First on the MPB we just created:

PS> get-mpbinfo resourceexample.mpb|ft -group managementpack length,resourcename -au


   ManagementPack: ResourceExample (Not Sealed)

Length ResourceName
------ ------------
   861 SmileyImage

Since we ship some .mpb files, I can use the script to inspect our product files:

PS> get-mpbinfo ConfigManagementPack.mpb|ft -gro managementpack length,resourcename -au


   ManagementPack: ServiceManager.ConfigurationManagement.Library (Sealed)

Length ResourceName
------ ------------
100224 ConfigurationManagementFormsAssembly
 55152 JA.ConfigurationManagementFormResourcesAssembly
 55152 EN.ConfigurationManagementFormResourcesAssembly
 55152 DE.ConfigurationManagementFormResourcesAssembly
 96112 ServiceManager.ConfigurationManagement.Library.Assembly.Form
 46960 EN.ServiceManager.ConfigurationManagement.Library.Assembly.FormResource
 42864 JA.ServiceManager.ConfigurationManagement.Library.Assembly.FormResource
 42864 DE.ServiceManager.ConfigurationManagement.Library.Assembly.FormResource
 38784 ServiceManager.ConfigurationManagement.Library.Assembly.Task
 11136 EN.ServiceManager.ConfigurationManagement.Library.Assembly.TaskResource
 10608 JA.ServiceManager.ConfigurationManagement.Library.Assembly.TaskResource
 10608 DE.ServiceManager.ConfigurationManagement.Library.Assembly.TaskResource
  1399 ConfigItemImage32x32
   712 ConfigItemImage16x16
   492 ServiceManager.ConfigItem.Image.Edit
   922 ServiceManager.ConfigurationManagement.Library.Image.User
  3320 ServiceManager.ConfigurationManagement.Library.Image.DeletedItem


   ManagementPack: ServiceManager.ConfigurationManagement.Configuration (Not Sealed)

Length ResourceName
------ ------------
   712 ComputerImage16x16
   815 SoftwareImage16x16
   800 PrinterImage16x16
  1073 SoftwareUpdateImage16x16

 

Thanks to Lee Holmes and his “Set-ClipboardScript” script which provided the formatting of the code samples!

Posted September 3, 2009 by jtruher3 in ServiceManager

Getting data from Service Manager – a scripted approach   Leave a comment

In one of my earlier posts, I said that you needed some C# to get data from Service Manager because of the way some of our methods use generics. It was pointed out to me that I was wrong, wrong, wrong. So I thought I better post a completely scripted approach for retrieving data from Service Manager.

The following script will return all instances of the class that’s passed as a parameter.

param ( $classname )
$emg      = new-object microsoft.enterprisemanagement.enterprisemanagementgroup localhost
$class    = $emg.EntityTypes.GetClasses()|?{$_.name -eq $classname}
if ( ! $class )
{
    Write-Error "`nERROR: Class '$classname' not found, exiting."
    exit
}
$DEFAULT  = [Microsoft.EnterpriseManagement.Common.ObjectQueryOptions]::Default
$EMOT     = [Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject]
# Retrieve the interface for EntityObjects, which we'll use when we create our generic method
$IMGMT    = $emg.EntityObjects.GetType()
# the types of the parameters, this is so we can find the right method
[type[]]$TYPES = [Microsoft.EnterpriseManagement.Configuration.ManagementPackClass],
                 [Microsoft.EnterpriseManagement.Common.ObjectQueryOptions]
# Retrieve the method
$ObjectReader = $IMGMT.GetMethod("GetObjectReader",$TYPES)
# Create a generic method
$GenericMethod = $ObjectReader.MakeGenericMethod($EMOT)
# Invoke the method with our arguments
[array]$arguments = [Microsoft.EnterpriseManagement.Configuration.ManagementPackClass]$class,$DEFAULT
$GenericMethod.invoke($emg.EntityObjects,$arguments) | %{
    # Create a custom object based on the original object
    $o = new-object psobject $_
    # elevate the properties in the Values collection to the top level
    $o.values|%{ $o | add-member -force NoteProperty $_.Type $_.Value }
    # assign a synthetic typename to the object, so we can use our formatting
    # more easily
    $name = $_.GetLeastDerivedNonAbstractClass().name
    $o.psobject.typenames.Insert(0, "EnterpriseManagementObject#$name")
    # now, emit the object!
    $o
    }

It uses reflection to retrieve the method that I want and then uses that to create a generic method, which can then be invoked with the parameters that I want.  In this case, it’s fairly straightforward, since I want to retrieve all instances of a particular class, I use the overload of GetObjectReader which takes a ManagementPackClass and then provide a default ObjectQueryOptions.

The last thing of interest is how I make the object more useful.  First by using each one of the Values property on EnterpriseManagementObject and creating a note property, it lets me see the “real” properties of the object (the ones on the Service Manager class).  By adding the name of the class to the TypeNames collection of the psobject, I can then use that with a formatting .ps1xml file so I can customize the output by the Service Manager class.

PS> get-smobject.ps1 microsoft.windows.computer|ft DisplayName,LastModified -au

DisplayName                   LastModified
-----------                   ------------
Computer2.woodgrove.com       8/14/2009 10:48:24 PM
Computer5.woodgrove.com       8/14/2009 10:48:24 PM
WIN-752HJBSX24M.woodgrove.com 8/13/2009 8:09:02 PM
Computer1.woodgrove.com       8/14/2009 10:48:24 PM
Computer4.woodgrove.com       8/14/2009 10:48:24 PM
Computer3.woodgrove.com       8/14/2009 10:48:24 PM

Posted August 17, 2009 by jtruher3 in ServiceManager

Creating Data in Service Manager   Leave a comment

In my last post, we saw how we were able to retrieve data from Service Manager, where we also said farewell to scripting.  In this post, I’ll quickly go through how to create instance data in Service Manager.  It was a short farewell to scripting, because unlike the last post were we needed some C# to do what we wanted, we can create most objects in the Service Manager CMDB directly from script.  In order to create instances in Service Manager, we need to use CreatableEnterpriseManagementObject.  The constructor for this object takes a reference to the EnterpriseManagementGroup and a ManagementPackClass.  After this, it’s simply a matter of assigning values to various properties of the object.  Here’s a script that creates 5 instances of Microsoft.Windows.Computer and sets a number of the property values.

 

$NS   = "Microsoft.EnterpriseManagement"
$EMGT = "${NS}.EnterpriseManagementGroup"
$EMG = new-object $EMGT localhost
$CEMOT = "${NS}.Common.CreatableEnterpriseManagementObject"
$ComputerClass = $EMG.EntityTypes.GetClasses()|?{$_.name -eq "Microsoft.Windows.Computer"}
1..5 | %{
    $NewComputer = new-object $CEMOT $EMG,$ComputerClass
    $PrincipalNameProperty = $NewComputer.GetProperties()|?{$_.name -eq "PrincipalName"}
    $Name = "Computer${_}.woodgrove.com"
    $NewComputer.Item($PrincipalNameProperty).Value = $Name
    $NewComputer.Commit()
}

this should add 5 computers to our system, (computer1 to computer5).

We can check this with our previous cmdlets:

PS> get-smclass microsoft.windows.computer$|get-scsmobject | ft DisplayName

DisplayName
-----------
Computer2.woodgrove.com
Computer5.woodgrove.com
WIN-752HJBSX24M.woodgrove.com
Computer1.woodgrove.com
Computer4.woodgrove.com
Computer3.woodgrove.com

Woo hoo!  There are my new instances! However, it’s not quite as simple as this.  Some properties are required and we have to be sure that we provide values for those properties.  So, how can we find out what they are?  These properties are designated as key properties.  We can determine what these key properties are, by inspecting the properties of the newly created object. 

PS> $NS   = "Microsoft.EnterpriseManagement"
PS> $EMGT = "${NS}.EnterpriseManagementGroup"
PS> $EMG = new-object $EMGT localhost
PS> $CEMOT = "${NS}.Common.CreatableEnterpriseManagementObject"
PS> $ComputerClass = $EMG.EntityTypes.GetClasses()|?{$_.name -eq "Microsoft.Windows.Computer"}
PS> $NewComputer = new-object $CEMOT $EMG,$ComputerClass

PS> $newcomputer.getproperties()|ft key,name,type -au

  Key Name                                Type
  --- ----                                ----
 True PrincipalName                     string
False DNSName                           string
False NetbiosComputerName               string
False NetbiosDomainName                 string
False IPAddress                         string
False NetworkName                       string
False ActiveDirectoryObjectSid          string
False IsVirtualMachine                    bool
False DomainDnsName                     string
False OrganizationalUnit                string
False ForestDnsName                     string
False ActiveDirectorySite               string
False LogicalProcessors                    int
False OffsetInMinuteFromGreenwichTime      int
False LastInventoryDate               datetime
False Owner                             string
False Customer                          string
False Engineer                          string
False Description                       string
False URL                               string
False ServerType                        string
False ObjectStatus                        enum
False AssetStatus                         enum
False Notes                           richtext
False DisplayName                       string

So, I can see that the only the PrincipalName is required to create an instance, and the type of value that I must supply is a string.

In my next post, I’ll look more closely at how to provide values for things other than strings.

Posted August 14, 2009 by jtruher3 in ServiceManager

Getting system uptime   Leave a comment

Sometimes I miss my Unix system. Actually, that’s not true, sometimes I miss the tools that are available.  For example, I needed to figure out when the system booted.  Thankfully, there are performance counters that can tell me!

PS# cat get-uptime.ps1
$PCounter = "System.Diagnostics.PerformanceCounter"
$counter = new-object $PCounter System,"System Up Time"
$value = $counter.NextValue()
$uptime = [System.TimeSpan]::FromSeconds($counter.NextValue())
"Uptime: $uptime"
"System Boot: " + ((get-date) - $uptime)

Simple!

Posted July 28, 2009 by jtruher3 in PowerShell

PowerShell and the Service Manager Instance Space   Leave a comment

In my last blog, I wrote about the Service Manager Type Environment, this time we’re going to work with what’s called the instance space.  This “space” represents the actual data which is kept for your environment.  When you get data from one of the connectors, create a change request or incident, an instance (or a set of instances) is created.  In this post, we’ll investigate how we can take advantage of this from PowerShell.

FAREWELL TO SCRIPTING

This is also the time where we will bid adieu to a completely script based interaction model.  This is due to a couple of reasons; First and foremost, when you are working with the instance space in Service Manager, you need to use the EntityObjects interface on the EnterpriseManagementGroup object.  The methods that allow you to retrieve data from SM requires the use of generics, and PowerShell doesn’t have support in its syntax for direct method invocation which includes generics.

Let’s take a look:

PS> $emg.EntityObjects|gm getobjects|ft name,definition -au

Name                         Definition
----                         ----------
GetObjects                   System.Collections.Generic.IList[T] GetObjects[T](Microsoft.Enterpr...

We can see that PowerShell tells us that the methods require a type – you can see the bit I highlighted in red above, PowerShell indicates the type by putting it in square brackets, but sadly, you can’t call the method in this way.  There’s just no good way to call this method from within a PowerShell script, you have to write some C#.  PowerShell V2 will let me embed C# in a script and create a type (see Get-PowerShell » Embedded C# for a good example) on the fly which I could use to paint over the generics bit, but I think for my purposes, creating a compiled C# cmdlet will be better overall.

I need to do the following:

  1. Create a connection to the Data Access Server (create my EnterpriseManagementGroup object)
  2. Call the appropriate method on the EntityObjects Interface

In looking at the available methods, I see a number that might be useful

PS> $emg = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup localhost
PS> $emg.EntityObjects|gm


   TypeName: Microsoft.EnterpriseManagement.InstancesManagement

Name                                MemberType Definition
----                                ---------- ----------
Equals                              Method     bool Equals(System.Object obj)
GetHashCode                         Method     int GetHashCode()
GetObject                           Method     T GetObject[T](System.Guid id, Microsoft.EnterpriseManagement.Common....
GetObjectHistoryTransactions        Method     System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetObjectProjectionReader           Method     Microsoft.EnterpriseManagement.Common.IObjectProjectionReader[T] GetO...
GetObjectProjectionWithAccessRights Method     Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectProje...
GetObjectReader                     Method     Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReade...
GetObjects                          Method     System.Collections.Generic.IList[T] GetObjects[T](Microsoft.Enterpris...
GetObjectWithAccessRights           Method     Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject GetO...
GetParentObjects                    Method     System.Collections.Generic.IList[T] GetParentObjects[T](System.Guid i...
GetRelatedObjects                   Method     System.Collections.Generic.Dictionary[System.Guid,System.Collections....
GetRelationshipObject               Method     Microsoft.EnterpriseManagement.Common.EnterpriseManagementRelationshi...
GetRelationshipObjects              Method     System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetRelationshipObjectsBySourceClass Method     System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetRelationshipObjectsByTargetClass Method     System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetRelationshipObjectsWhereSource   Method     System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetRelationshipObjectsWhereTarget   Method     System.Collections.Generic.IList[Microsoft.EnterpriseManagement.Commo...
GetType                             Method     type GetType()
RefreshGroupMembers                 Method     System.Void RefreshGroupMembers(Microsoft.EnterpriseManagement.Config...
ToString                            Method     string ToString()

I have a little filter that I use when I want to see the a method, it’s a little different than the definition property of get-member output, and for me a little clearer.

filter Get-Definition
{
    param ( $MemberName )
    $_.GetType().GetMembers()|
    ?{ $_.name -eq $MemberName -and $_.membertype -eq "method"}|
    %{
    ($_.returntype.tostring() -replace "``1") + " " + $_.name + "[T] ("
    $_.GetParameters() | %{ 
            $s = @()
        }{ 
            $s += ,("   " + $_.parametertype + " " + $_.name) 
        }{
            $s -join ",`n"
        }
    "   )"
    }
}

Here’s what the output looks like for GetObjectReader.

PS> $emg.EntityObjects | get-definition GetObjectReader
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
   Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectGenericCriteria criteria,
   Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
   )
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
   Microsoft.EnterpriseManagement.Configuration.ManagementPackClass managementPackClass,
   Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
   )
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
   Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectCriteria criteria,
   Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
   )
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
   System.Collections.Generic.ICollection[Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectCri
teria] criteriaCollection,
   Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
   )
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
   Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectGenericCriteria criteria,
   Microsoft.EnterpriseManagement.Configuration.ManagementPackClass mpClass,
   Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
   )
Microsoft.EnterpriseManagement.Common.IObjectReader[T] GetObjectReader[T] (
   System.Collections.Generic.ICollection[System.Guid] ids,
   Microsoft.EnterpriseManagement.Common.ObjectQueryOptions queryOptions
   )

This is the method that will do what I need, it will return a reader (which I can use to turn the results into a collection) and there is one overload that takes a management pack class and some options.  This will return all the instances of a particular class, based on a set of passed options, which in my case will be our default options (return everything).

In my case, all I need now is a Management Pack Class (which means I can use my earlier Get-SMClass script).  In my case I just need to wrap the call to GetObjectReader in a fairly simple cmdlet and viola!

01 using System;
02 using System.Management.Automation;
03 using Microsoft.EnterpriseManagement;
04 using Microsoft.EnterpriseManagement.Common;
05 using Microsoft.EnterpriseManagement.Configuration;
06 namespace ServiceManager.Powershell.Demo
07 {
08     [Cmdlet("Get","SCSMObject")]
09     public class GetSMObjectCommand : PSCmdlet
10     {
11         private ManagementPackClass _class = null;
12         [Parameter(ParameterSetName="Class",Position=0,Mandatory=true,ValueFromPipeline=true)]
13         public ManagementPackClass Class
14         {
15             get { return _class; }
16             set { _class = value; }
17         }
18         private EnterpriseManagementGroup _mg;
19         protected override void BeginProcessing()
20         {
21             _mg = new EnterpriseManagementGroup("localhost");
22         }
23         protected override void ProcessRecord()
24         {
25             foreach(EnterpriseManagementObject o in 
26                     _mg.EntityObjects.GetObjectReader<EnterpriseManagementObject>(Class,ObjectQueryOptions.Default) 
27                 ) 
28             {
29                 WriteObject(o);
30             }
31         }
32     }
33 }

A couple of things to note here:

  1. I’m assuming that I’m running on the server, in line 21 where I create the EnterpriseManagementGroup object, I use “localhost” as the hostname.
  2. I set the ValueFromPipeline attributed on the ManagementPackClass parameter to true, which will allow me to pipe the result of Get-SMClass to Get-SCSMObject (line 12).
  3. I use GetObjectReader and emit each of the objects explicitly (line 29)

Woo!  Let’s give it a go!

PS> get-smclass windows.computer$|get-scsmobject


PropertyAccessRights : Unknown
Parent               : {WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M, WOODGROVE...}
Type                 : PrincipalName
Value                : WIN-752HJBSX24M.woodgrove.com
Id                   : 00000000-0000-0000-0000-000000000000
ManagementGroup      : ServiceManagerMgmtGroup1
ManagementGroupId    : 8884fff9-00d4-abcc-6620-e6d25f39e0d6

...

PropertyAccessRights : Unknown
Parent               : {WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M, WOODGROVE...}
Type                 : DisplayName
Value                : WIN-752HJBSX24M.woodgrove.com
Id                   : 00000000-0000-0000-0000-000000000000
ManagementGroup      : ServiceManagerMgmtGroup1
ManagementGroupId    : 8884fff9-00d4-abcc-6620-e6d25f39e0d6

This doesn’t look quite like what I wanted – what I want is a table of the properties of the windows.computer class. Let’s investigate:

PS> $o = get-smclass windows.computer$|get-scsmobject
PS> $o|gm


   TypeName: Microsoft.EnterpriseManagement.Common.EnterpriseManagementSimpleObject

Name                 MemberType Definition
----                 ---------- ----------
Equals               Method     bool Equals(System.Object obj)
GetHashCode          Method     int GetHashCode()
...
ManagementGroup      Property   Microsoft.EnterpriseManagement.EnterpriseManagementGroup ManagementGroup {get;}
ManagementGroupId    Property   System.Guid ManagementGroupId {get;}
Parent               Property   Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectBaseWithProperties P...
PropertyAccessRights Property   Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectAccessRights Propert...
Type                 Property   Microsoft.EnterpriseManagement.Configuration.ManagementPackProperty Type {get;}
Value                Property   System.Object Value {get;set;}

This isn’t certainly isn’t what I wanted, I should be getting an EnterpriseManagementObject not an EnterpriseManagementSimpleObject, perhaps piping the input is a problem.

PS> gm -input $o


   TypeName: Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject

Name                                         MemberType            Definition
----                                         ----------            ----------
ApplyTemplate                                Method                System.Void ApplyTemplate(Microsoft.EnterpriseMan...
Commit                                       Method                System.Void Commit(Microsoft.EnterpriseManagement...
ContainsProperty                             Method                bool ContainsProperty(Microsoft.EnterpriseManagem...
CreateNavigator                              Method                System.Xml.XPath.XPathNavigator CreateNavigator()
Equals                                       Method                bool Equals(System.Object obj)
GetClasses                                   Method                System.Collections.Generic.IList[Microsoft.Enterp...
GetEnumerator                                Method                System.Collections.Generic.IEnumerator[Microsoft....
...
FullName                                     Property              System.String FullName {get;}
Id                                           Property              System.Guid Id {get;}
LastModified                                 Property              System.DateTime LastModified {get;}
LastModifiedBy                               Property              System.Nullable`1[[System.Guid, mscorlib, Version...
LeastDerivedNonAbstractManagementPackClassId Property              System.Guid LeastDerivedNonAbstractManagementPack...
ManagementGroup                              Property              Microsoft.EnterpriseManagement.EnterpriseManageme...
ManagementGroupId                            Property              System.Guid ManagementGroupId {get;}
...

ah – there’s the culprit – EnterpriseManagementObject has a GetEnumerator method, which will get called when the formatter gets invoked (or when it hits the pipeline).  We can keep change our formatting a bit to see the information differently:

PS> fl -input $o -expand coreonly


Name                                         : WIN-752HJBSX24M.woodgrove.com
Path                                         :
DisplayName                                  : WIN-752HJBSX24M.woodgrove.com
FullName                                     : Microsoft.Windows.Computer:WIN-752HJBSX24M.woodgrove.com
ManagementPackClassIds                       : {7b070bc5-0f54-6663-f840-17affa1d6304, a754daf6-0e0e-9fd3-beef-9da66b39e
                                               41f, 139a4e2d-47fe-ac19-02c4-a31210c457cc, 9f178faf-54f4-b13b-6b32-a684d
                                               c6d3ec1...}
LeastDerivedNonAbstractManagementPackClassId : 7b070bc5-0f54-6663-f840-17affa1d6304
TimeAdded                                    : 7/1/2009 11:23:41 PM
LastModifiedBy                               : 7431e155-3d9e-4724-895e-c03ba951a352
LastModified                                 : 7/2/2009 9:50:34 PM
Id                                           : e4011629-ae31-a8a6-9bee-99980c68ba69
ManagementGroup                              : ServiceManagerMgmtGroup1
ManagementGroupId                            : 8884fff9-00d4-abcc-6620-e6d25f39e0d6

Well, this is better (at least I’m getting a single list rather than multiple objects), but it’s not still not quite right.  What’s I really want is the values of the various properties of  object from the Service Manager perspective.  This information is kept in the collection of properties (available via the GetProperties method).  This is what I was seeing at first, just in a format I wasn’t expecting.  Let’s try a different approach, I noticed that when I saw the data flashing by the first time, it looked like a couple of properties were interesting.   If I select the Type and Value properties of the EnterpriseSimpleObject, that may be what I want.

PS> get-smclass windows.computer$|get-scsmobject|format-table Type,Value -au

Type                            Value
----                            -----
PrincipalName                   WIN-752HJBSX24M.woodgrove.com
DNSName                         WIN-752HJBSX24M.woodgrove.com
NetbiosComputerName             WIN-752HJBSX24M
NetbiosDomainName               WOODGROVE
IPAddress
NetworkName                     WIN-752HJBSX24M.woodgrove.com
ActiveDirectoryObjectSid
IsVirtualMachine                True
DomainDnsName
OrganizationalUnit
ForestDnsName
ActiveDirectorySite
LogicalProcessors
OffsetInMinuteFromGreenwichTime
LastInventoryDate
InstallDirectory
IsVirtualNode
ObjectStatus
AssetStatus
Notes
DisplayName                     WIN-752HJBSX24M.woodgrove.com

OK!  this is more along the lines that I was thinking.  It’s the representation of the information about the computer that I was expecting.  However, having to do this formatting is a pain, and it’s not possible for me to refer to a windows.computer objects properties the way I want, so what I will do is build a simple-minded data adapter, using PowerShell’s capability for building objects on the fly.

  1 using System;
  2 using System.Management.Automation;
  3 using Microsoft.EnterpriseManagement;
  4 using Microsoft.EnterpriseManagement.Common;
  5 using Microsoft.EnterpriseManagement.Configuration;
  6 namespace ServiceManager.Powershell.Demo
  7 {
  8     [Cmdlet("Get","SCSMObject")]
  9     public class GetSMObjectCommand : PSCmdlet
 10     {
 11         private ManagementPackClass _class = null;
 12         [Parameter(ParameterSetName="Class", Position=0,Mandatory=true,ValueFromPipeline=true)]
 13         public ManagementPackClass Class
 14         {
 15             get { return _class; }
 16             set { _class = value; }
 17         }
 18         private EnterpriseManagementGroup _mg;
 19         protected override void BeginProcessing()
 20         {
 21             _mg = new EnterpriseManagementGroup("localhost");
 22         }
 23         protected override void ProcessRecord()
 24         {
 25             foreach(EnterpriseManagementObject o in
 26                     _mg.EntityObjects.GetObjectReader(Class,ObjectQueryOptions.Default)
 27                 )
 28             {
 29                 PSObject pso = new PSObject();
 30                 pso.Properties.Add(new PSNoteProperty("__base",o));
 31                 pso.TypeNames.Insert(0, "EnterpriseManagementObject#"+Class.Name);
 32                 foreach ( ManagementPackProperty p in o.GetProperties())
 33                 {
 34                     pso.Properties.Add(new PSNoteProperty(p.Name, o[p].Value));
 35                 }
 36                 WriteObject(pso);
 37             }
 38         }
 39     }
 40 }

So, it’s a fairly simple change, an additional few lines of code (lines 29-36) and it should be what we want.  Just a couple of things to note:

  1. I save the entire original object in a NoteProperty called “__base”
  2. I add a new string to the TypeNames collection, this way I can create formatting based on the Service Manager class.  This is similar to what the PowerShell team did for WMI.
  3. For each one of the Service Manager properties in the class, I created a NoteProperty (lines 32-35)

Let’s see what we have now!

PS> get-smclass windows.computer$|get-scsmobject


__base                          : {WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M.woodgrove.com, WIN-752HJBSX24M, WOODG
                                  ROVE...}
PrincipalName                   : WIN-752HJBSX24M.woodgrove.com
DNSName                         : WIN-752HJBSX24M.woodgrove.com
NetbiosComputerName             : WIN-752HJBSX24M
NetbiosDomainName               : WOODGROVE
IPAddress                       :
NetworkName                     : WIN-752HJBSX24M.woodgrove.com
ActiveDirectoryObjectSid        :
IsVirtualMachine                : True
DomainDnsName                   :
OrganizationalUnit              :
ForestDnsName                   :
ActiveDirectorySite             :
LogicalProcessors               :
OffsetInMinuteFromGreenwichTime :
LastInventoryDate               :
InstallDirectory                :
IsVirtualNode                   :
ObjectStatus                    :
AssetStatus                     :
Notes                           :
DisplayName                     : WIN-752HJBSX24M.woodgrove.com

Ah!  This is much better!  Now I’m getting the interesting properties of the instance.  Let’s try get-member

PS> get-smclass windows.computer$|get-scsmobject|gm


   TypeName: EnterpriseManagementObject#Microsoft.Windows.Computer

Name                            MemberType   Definition
----                            ----------   ----------
Equals                          Method       bool Equals(System.Object obj)
GetHashCode                     Method       int GetHashCode()
GetType                         Method       type GetType()
ToString                        Method       string ToString()
ActiveDirectoryObjectSid        NoteProperty  ActiveDirectoryObjectSid=null
ActiveDirectorySite             NoteProperty  ActiveDirectorySite=null
AssetStatus                     NoteProperty  AssetStatus=null
DisplayName                     NoteProperty System.String DisplayName=WIN-752HJBSX24M.woodgrove.com
DNSName                         NoteProperty System.String DNSName=WIN-752HJBSX24M.woodgrove.com
DomainDnsName                   NoteProperty  DomainDnsName=null
ForestDnsName                   NoteProperty  ForestDnsName=null
InstallDirectory                NoteProperty  InstallDirectory=null
IPAddress                       NoteProperty  IPAddress=null
IsVirtualMachine                NoteProperty System.Boolean IsVirtualMachine=True
IsVirtualNode                   NoteProperty  IsVirtualNode=null
LastInventoryDate               NoteProperty  LastInventoryDate=null
LogicalProcessors               NoteProperty  LogicalProcessors=null
NetbiosComputerName             NoteProperty System.String NetbiosComputerName=WIN-752HJBSX24M
NetbiosDomainName               NoteProperty System.String NetbiosDomainName=WOODGROVE
NetworkName                     NoteProperty System.String NetworkName=WIN-752HJBSX24M.woodgrove.com
Notes                           NoteProperty  Notes=null
ObjectStatus                    NoteProperty  ObjectStatus=null
OffsetInMinuteFromGreenwichTime NoteProperty  OffsetInMinuteFromGreenwichTime=null
OrganizationalUnit              NoteProperty  OrganizationalUnit=null
PrincipalName                   NoteProperty System.String PrincipalName=WIN-752HJBSX24M.woodgrove.com
__base                          NoteProperty Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject __base...

And finally, let’s take a look at this as a table.

PS> get-smclass windows.computer$|get-smobject|ft Type,DisplayName
       
Type                               DisplayName                                      
----                               -----------                                      
Microsoft.Windows.Computer         Client4.woodgrove.com                            
Microsoft.Windows.Computer         Client5.woodgrove.com                            
Microsoft.Windows.Computer         Client3.woodgrove.com                            
Microsoft.Windows.Computer         Server1.woodgrove.com                            
Microsoft.Windows.Computer         Server3.woodgrove.com                            
Microsoft.Windows.Computer         Server2.woodgrove.com                            
Microsoft.Windows.Computer         WIN-752HJBSX24M.woodgrove.com                    
Microsoft.Windows.Computer         Client1.woodgrove.com                            
Microsoft.Windows.Computer         Client2.woodgrove.com 

This is a great start, and we’ll use this as a starting point for the future!  I’ve put all the module files in my Sky Drive, from here on I’ll be adding features to the module.

Posted July 27, 2009 by jtruher3 in ServiceManager

PowerShell and the Service Manager Type Environment   Leave a comment

Service Manager, like Operations Manager, has a dynamic type system.  ManagementPacks define types, called classes, which define the actual data that you want to keep track of.  You declare a class which may or may not be based on another class and you can add properties to your class, and manipulate it in a number of ways.  Once you’ve imported your management pack, you’ll have access to that new class, and can create instances of that class.  There are two discrete categorizations of types.  The first is a simple class, and the more complex is called a type projection.  In this blog, we’ll discuss how to find out what Service Manager stores as simple classes and how to discover them.

It’s important to note that these are not .NET types, these are types as defined in the Service Manager environment.  When you actually create instances of these types, they are returned as instances of the .NET type of Microsoft.EnterpriseManagement.Common.EnterpriseManagementObject.  That object has a property called PropertyCollection which is where the actual data is held.  I’ll talk more about that in a future blog, but for now, what you should know is that Service Manager has it’s own types which are created in Management Packs.

To get access to the list of classes that have been imported to the Service Manager environment, we’ll use the EnterpriseManagementGroup object again (see my previous posts).  The EnterpriseManagementGroup object has an interface named "EntityTypes", which has a number of methods that we’ll investigate.  First, we’ll create an EnterpriseManagementGroup, and take a look at the EntityTypes interface.

PS> $EMG = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup localhost
PS> $EMG.EntityTypes|get-member


   TypeName: Microsoft.EnterpriseManagement.EntityTypeManagement

Name                       MemberType Definition
----                       ---------- ----------
Equals                     Method     bool Equals(System.Object obj)
GetCategories              Method     System.Collections.Generic.IList[Micro...
GetCategory                Method     Microsoft.EnterpriseManagement.Configu...
GetCategoryList            Method     System.Collections.Generic.List[Micros...
GetChildEnumerations       Method     System.Collections.Generic.IList[Micro...
GetClass                   Method     Microsoft.EnterpriseManagement.Configu...
GetClasses                 Method     System.Collections.Generic.IList[Micro...
GetClassesList             Method     System.Collections.Generic.List[Micros...
GetEnumeration             Method     Microsoft.EnterpriseManagement.Configu...
GetEnumerationList         Method     System.Collections.Generic.List[Micros...
GetEnumerations            Method     System.Collections.Generic.IList[Micro...
GetHashCode                Method     int GetHashCode()
GetRelationshipClass       Method     Microsoft.EnterpriseManagement.Configu...
GetRelationshipClasses     Method     System.Collections.Generic.IList[Micro...
GetRelationshipClassesList Method     System.Collections.Generic.List[Micros...
GetTopLevelEnumerations    Method     System.Collections.Generic.IList[Micro...
GetType                    Method     type GetType()
GetTypeProjection          Method     Microsoft.EnterpriseManagement.Configu...
GetTypeProjectionList      Method     System.Collections.Generic.List[Micros...
GetTypeProjections         Method     System.Collections.Generic.IList[Micro...
ToString                   Method     string ToString()

The methods break up in two big groups: The first group returns all various different objects that you can define in a management pack (those objects which have to do with types, anyway). That list is:

GetCategories
GetClasses
GetEnumerations
GetRelationshipClasses
GetTopLevelEnumerations
GetTypeProjections

The other methods on this interface take arguments and return a reduced set of results based on the arguments. It is important to note that the Service Manager environment caches most of this configuration information, so when we get all of them, it doesn’t result in lots of trips to the database. That’s good news from a performance perspective.  From a PowerShell perspective, it means that we can grab all the data and use the PowerShell filtering capabilities rather than bothering with the other available methods.

If we want to see all the different classes that Service Manager knows about, we can do that easily:

PS> $EMG.EntityTypes.GetClasses() 


PropertyCollection     : {DisplayName}
Base                   :
Hosted                 : False
Singleton              : False
Extension              : False
OptimizationCollection : {}
FullTextSearchable     :
XmlTag                 : ClassType
Abstract               : True
Accessibility          : Public
ManagementGroup        : WIN-752HJBSX24M
ManagementGroupId      : 386f5f57-9f7a-3c6b-1f53-ccc02d6206d4
Name                   : System.Entity
Id                     : ac5cddfc-a96a-ee99-745d-ec74845f53f6
DisplayName            : Object
Description            : All objects
LanguageCode           : ENU
Comment                :
Status                 : Unchanged
LastModified           : 5/14/2009 11:56:48 PM
TimeAdded              : 5/14/2009 11:56:48 PM

PropertyCollection     : {}
Base                   : ManagementPackElementUniqueIdentifier=ac5cddfc-a96a-ee
                         99-745d-ec74845f53f6
Hosted                 : False
Singleton              : False
Extension              : False
. . .

Wow! There’s a lot here – let’s find out how many:

PS> $Classes = $EMG.EntityTypes.GetClasses()
PS> $Classes.Count 231

We should determine the .NET type as well, as we can use this when we create our formatting instructions

PS> $classes[0].gettype().fullname
Microsoft.EnterpriseManagement.Configuration.ManagementPackClass

With a result as large as this, it makes more sense to see as a table, rather than a list and I’ll select the first 5, just to cut down on space, so now we have:

PS> $Classes|select-object -first 5| format-table Abstract,Name,DisplayName -au
Abstract Name                       DisplayName
-------- ----                       -----------
    True System.Entity              Object
    True System.AdminItem           Admin Item
   False System.Announcement.Config Config
   False System.Announcement.Item   Announcement
    True System.Collections         Collections

We can see that this supports the PowerShell filters as well, let’s find all the types which pertain to printers.  The first approach would be to select only those objects whose name matches print!

PS> $EMG.EntityTypes.GetClasses()|?{$_.name –match "print"}|
>> format-table -auto Abstract,Name,DisplayName
>>

Abstract Name DisplayName
-------- ---- -----------
True System.Printer Printers
False Microsoft.AD.Printer Active Directory Printers

The objects returned work well with the PowerShell environment.  We can invoke the other methods on the EntityTypes interface:

PS> $emg.EntityTypes.GetCategories().Count
285
PS> $emg.EntityTypes.GetCategories()|select-object -first 5|format-table Name,DisplayName -auto

Name                                                                  DisplayName
----                                                                  -----------
ServiceManager.ActivityManagement.EditActivity.Task.FlagCategory                 
ServiceManager.ActivityManagement.Library.ApprovalEnumVisibleCategory            
ServiceManager.ActivityManagement.Library.DecisionEnumVisibleCategory            
ServiceManager.ActivityManagement.Library.DecisionEnumCategory                   
ServiceManager.ActivityManagement.Library.ApprovalEnumCategory                   


PS> $emg.EntityTypes.GetClasses().Count
231
PS> $emg.EntityTypes.GetClasses()|select-object -first 5|format-table Name,DisplayName -auto

Name                       DisplayName 
----                       ----------- 
System.Entity              Object      
System.AdminItem           Admin Item  
System.Announcement.Config Config      
System.Announcement.Item   Announcement
System.Collections         Collections 


PS> $emg.EntityTypes.GetEnumerations().Count
414
PS> $emg.EntityTypes.GetEnumerations()|select-object -first 5|format-table Name,DisplayName -auto

Name                                                        DisplayName          
----                                                        -----------          
ActivityAreaEnum.Messaging.Client                           Client               
ServiceManager.ConfigurationManagement.WindowsPrintersTasks Windows Printer Tasks
IncidentSourceEnum.DCM                                      SCCM (DCM)           
System.Knowledge.CategoryEnum.Software                                           
ActivityStageEnum.Develop                                   Develop              


PS> $emg.EntityTypes.GetRelationshipClasses().Count
118
PS> $emg.EntityTypes.GetRelationshipClasses()|select-object -first 5|format-table Name,DisplayName -auto

Name                                DisplayName                     
----                                -----------                     
System.ComputerPrimaryUser          Computer Primary User           
System.ConfigItemContainsConfigItem Config Item Contains Config Item
System.ConfigItemHasFileAttachment  Config Item Has File Attachment 
System.ConfigItemImpactsCustomers   Config Item Impacts Customers   
System.ConfigItemOwnedByUser        Config Item Owned By User       


PS> $emg.EntityTypes.GetTopLevelEnumerations().Count
72
PS> $emg.EntityTypes.GetTopLevelEnumerations()|select-object -first 5|format-table Name,DisplayName -auto

Name                                                                          DisplayName         
----                                                                          -----------         
System.Internal.ManagementPack                                                Management Pack     
System.WorkItem.ActionLogEnum                                                 Action Log Enum     
ChangeManagement.CreateTask                                                   Create Task         
Microsoft.EnterpriseManagement.ServiceManager.UI.Authoring.AllObjectTemplates All Object Templates
System.Knowledge.CategoryEnum                                                                     


PS> $emg.EntityTypes.GetTypeProjections().Count
43
PS> $emg.EntityTypes.GetTypeProjections()|select-object -first 5|format-table Name,DisplayName -auto

Name DisplayName
---- -----------
                

It looks like we have reasonable output for everything except TypeProjections.  By looking at the count, I can tell that we have some sort of results, so let’s take a closer look at the object:

PS> $emg.EntityTypes.GetTypeProjections()[0]

Key                                     Value
---                                     -----
SyncStatus                              {}

That’s not terribly useful, perhaps get-member will help me.

PS> $emg.EntityTypes.GetTypeProjections()|get-member


   TypeName: Microsoft.EnterpriseManagement.Configuration.ManagementPackTypeProjection

Name                MemberType            Definition
----                ----------            ----------
CreateNavigator     Method                System.Xml.XPath.XPathNavigator Cr...
Equals              Method                bool Equals(System.Object obj)
GetCategories       Method                System.Collections.Generic.IList[M...
GetDisplayString    Method                Microsoft.EnterpriseManagement.Con...
GetEnumerator       Method                System.Collections.Generic.IEnumer...
GetFolders          Method                Microsoft.EnterpriseManagement.Con...
GetHashCode         Method                int GetHashCode()
GetImageReferences  Method                System.Collections.Generic.IEnumer...
GetKnowledgeArticle Method                Microsoft.EnterpriseManagement.Con...
GetManagementPack   Method                Microsoft.EnterpriseManagement.Con...
GetType             Method                type GetType()
Reconnect           Method                System.Void Reconnect(Microsoft.En...
ToString            Method                string ToString()
WriteXml            Method                System.Void WriteXml(System.Xml.Xm...
Item                ParameterizedProperty Microsoft.EnterpriseManagement.ITy...
Accessibility       Property              Microsoft.EnterpriseManagement.Con...
Alias               Property              System.String Alias {get;}
Comment             Property              System.String Comment {get;set;}
ComponentCollection Property              System.Collections.Generic.IList`1...
Description         Property              System.String Description {get;set;}
DisplayName         Property              System.String DisplayName {get;set;}
Id                  Property              System.Guid Id {get;}
LanguageCode        Property              System.String LanguageCode {get;set;}
LastModified        Property              System.DateTime LastModified {get;...
ManagementGroup     Property              Microsoft.EnterpriseManagement.Ent...
ManagementGroupId   Property              System.Guid ManagementGroupId {get;}
Name                Property              System.String Name {get;}
Parent              Property              Microsoft.EnterpriseManagement.ITy...
Status              Property              Microsoft.EnterpriseManagement.Con...
TargetConstraint    Property              Microsoft.EnterpriseManagement.Con...
TargetEndpoint      Property              Microsoft.EnterpriseManagement.Con...
TargetType          Property              Microsoft.EnterpriseManagement.Con...
TimeAdded           Property              System.DateTime TimeAdded {get;set;}
Type                Property              Microsoft.EnterpriseManagement.Con...
TypeProjection      Property              Microsoft.EnterpriseManagement.Con...
XmlTag              Property              System.String XmlTag {get;}

Oho!  it looks like this object has an enumerator.  This means that when PowerShell attempts to format the object, it will call the enumerator and format the enumerated contents (rather than the object).  We can suppress this in PowerShell by specifying the –EXPAND parameter with format-table:

PS> $emg.EntityTypes.GetTypeProjections()|select-object -first 5|
>> format-table -Expand coreonly Name,DisplayName -au
>>

Name                                                          DisplayName
----                                                          -----------
Microsoft.SystemCenter.LinkingFramework.SyncStatus.Projection
System.LinkingFramework.DataConnector.Projection
OpsMgrConnector.Config.Projection
System.NotificationChannel.SMTP.ProjectionType                SMTP Projection Type
System.User.Projection                                        User Projection

That’s better! Although I have a feeling that this really isn’t a good long term solution, perhaps we’ll deal with this in a future posting.

Now that we have a set of these methods we can invoke, it’s a great opportunity to build a PowerShell V2 Module.  We can easily convert these bits of script into functions and aggregate those functions into a module.  All I need to do is put my module (as a .psm1 file) in the right place (see PowerShell V2 documentation on Modules for more help) and call import-module!

Here’s the script file:

# MODULE VARIABLES
$SMDIR = "C:\Program Files\Microsoft System Center\Service Manager 2010"
$SMDLL = "${SMDIR}\SDK Binaries\Microsoft.EnterpriseManagement.Core.dll"
$EMGTYPE = "Microsoft.EnterpriseManagement.EnterpriseManagementGroup"

# Before anything load the Service Manager core dll
[reflection.assembly]::LoadFile( $SMDLL ) | out-null

# MODULE FUNCTIONS
# Create an EnterpriseManagementGroup object
function New-EMG
{
   param ( $ComputerName = "localhost" ) 
   new-object $EMGTYPE $ComputerName
}

# Return the Categories
function Get-SMCategory
{
    param ( $CategoryName )
    if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
    $EMG.EntityTypes.GetCategories()|?{$_.Name -match $CategoryName }
}
# Return the Classes
function Get-SMClass
{
    param ( $ClassName )
    if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
    $EMG.EntityTypes.GetClasses()|?{$_.Name -match $ClassName }
}
# Return the RelationshipClasses
function Get-SMRelationshipClass
{
    param ( $RelationshipClassName )
    if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
    $EMG.EntityTypes.GetRelationshipClasses()|?{$_.Name -match $RelationshipClassName }
}
# Return the TopLevelEnumerations
function Get-SMTopLevelEnumeration
{
    param ( $TopLevemEnumerationName )
    if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
    $EMG.EntityTypes.GetTopLevelEnumerations()|?{$_.Name -match $TopLevemEnumerationName }
}
# Return the TypeProjections
function Get-SMTypeProjection
{
    param ( $TypeProjetionName )
    if ( ! $EMG.IsConnected ) { $EMG.Reconnect() }
    $EMG.EntityTypes.GetTypeProjections()|?{$_.Name -match $TypeProjetionName }
}
# We want to have an EMG in our environment!
$GLOBAL:EMG = new-EMG

Notice also that I added a parameter to the functions so I can pass in a string and reduce the result without having to always add my own where-object pipeline.  Notice further that I created a global instance of the EnterpriseManagementGroup, that way I can use it outside of the module.  I’ll use this as the contents for my EntityTypes.PSM1 file and import the module. 

    You should note that this assumes an installation on the Server machine only.  If you want to run these on a machine where only the console is installed, you’ll need to load the SMDLL a little differently.   You would need to do the following instead:

    [Reflection.Assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.Core")

    And you will also need to provide the name of the Service Manager server system when you create your EnterpriseManagementGroup object by calling new-EMG:

    $GLOBAL:EMG = new-EMG servername

On to the module!

PS> get-childitem C:\users\Administrator\Documents\WindowsPowerShell\Modules\EntityTypes


    Directory: C:\users\Administrator\Documents\WindowsPowerShell\Modules\EntityTypes


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         6/19/2009   2:27 PM       1726 EntityTypes.psm1


PS> get-module -list

ModuleType Name                      ExportedCommands
---------- ----                      ----------------
Script     EntityTypes               {}
Manifest   BitsTransfer              {}
Manifest   PSDiagnostics             {}


PS> import-module entitytypes

and we can see that my commands are all present!

PS> get-module entitytypes|fl


Name              : entitytypes
Path              : C:\Users\Administrator\Documents\WindowsPowerShell\Modules\entitytypes\entitytypes.psm1
Description       :
ModuleType        : Script
Version           : 0.0
NestedModules     : {}
ExportedFunctions : {Get-Category, Get-Class, Get-RelationshipClass, Get-TopLevelEnumeration...}
ExportedCmdlets   : {}
ExportedVariables : {}
ExportedAliases   : {}

And I can use them just like a cmdlet:

PS> get-class system.user$|ft abstract,name,displayname -au

Abstract Name        DisplayName
-------- ----        -----------
    True System.User Users

In my next post, I’ll discuss the Service Manager instance space and create cmdlets to use against the actual data that we store in Service Manager.

Posted June 22, 2009 by jtruher3 in ServiceManager

Even more with Management Packs   Leave a comment

Last post, I went through the process of exporting management packs, so we can see what they do and what they define.  In this post, I’ll discuss importing management packs.  In order to get any benefit from a Management Pack (MP), it needs to be added to the Service Manager platform.  The process of adding an MP to the system is called "Importing".   For this example, we’ll use just a very simple management pack (the contents aren’t really interesting, but to go through the exercise we need one of these).

<ManagementPack ContentReadable="true" SchemaVersion="1.1" 
           xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
           xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <Manifest>
    <Identity>
      <ID>Simple.ManagementPack</ID>
      <Version>7.0.5622.0</Version>
    </Identity>
    <Name>A Simple ManagementPack</Name>
    <References>
      <Reference Alias="System">
        <ID>System.Library</ID>
        <Version>7.0.5622.0</Version>
        <PublicKeyToken>9396306c2be7fcc4</PublicKeyToken>
      </Reference>
    </References>
  </Manifest>
  <TypeDefinitions>
    <EntityTypes>
      <ClassTypes>
        <ClassType ID="Simple.Class" Accessibility="Public" Abstract="false"  
                       Base="System!System.Entity" Hosted="false"  
                       Singleton="false" Extension="false">
          <Property ID="Id"     Type="guid"   Key="true"  Required="true"  />
          <Property ID="Name"   Type="string" Key="false" Required="false" 
                       CaseSensitive="false" MaxLength="256" MinLength="0" />
          <Property ID="Value1" Type="string" Key="false" Required="false"  
                       CaseSensitive="false" MaxLength="256" MinLength="0" />
          <Property ID="Value2" Type="string" Key="false" Required="false"  
                       CaseSensitive="false" MaxLength="256" MinLength="0" />
        </ClassType>
      </ClassTypes>
    </EntityTypes>
  </TypeDefinitions>
  <LanguagePacks>
    <LanguagePack ID="ENU" IsDefault="true">
      <DisplayStrings>
        <DisplayString ElementID="Simple.Class">
          <Name>Simple Class</Name>
          <Description>A simple class declaration</Description>
        </DisplayString>
      </DisplayStrings>
    </LanguagePack>
  </LanguagePacks>
</ManagementPack>

The Service Manager console has a way to import this, of course, here’s what it looks like:

But you can also do this from PowerShell with a few of simple lines of script

# the two .NET types, we'll need to create
$EMGTYPE = "Microsoft.EnterpriseManagement.EnterpriseManagementGroup"
$MPTYPE  = "Microsoft.EnterpriseManagement.Configuration.ManagementPack"
# Create a connection to the Data Access Service
$EMG = new-object ${EMGTYPE} localhost
# Create a management pack object based on the management pack file and 
# connection to the Data Access Service
$MP = new-object ${MPTYPE} c:\temp\Simple.ManagementPack.xml,$EMG
# Call the import method on the ManagementPacks interface - all done!
$EMG.ManagementPacks.ImportManagementPack($MP)

As you can see, it’s pretty straightforward.  However, this isn’t the whole story.  If you notice in the XML above, there’s a section for "References", this section of the MP allows you to declare that your MP relies on other MPs for class definitions, etc.  This means that if those references aren’t installed, you’re MP isn’t going to work.  We go along way to keep bad data out of the system, so if you try to import an MP which references an MP which isn’t present on the system, then that import will fail.  When you’re considering a single MP, working out these references may not be to bad, but if you have a number of MPs that you want to import and some of them depend on other new imported MPs, you’ve got to get the order correct, or you won’t be able to import.  Most of the time, you’ll wind up inspecting those sections of the MPs, create your order, and finally import the MPs.  (I’ve seen a number of batch files that do this very thing, so I know that this happens).  That’s no good as it’s an awful waste of time and error prone as well.  I’ve created a script to go through any number of MPs and figure out what order they should be installed, and then install them!  The script is built so you can pipe the output of get-childitem to it. 

From a pseudo-code perspective, here’s what we need to do

Find out what MPs are already installed
and then
FOREACH of MPs to install
   retrieve the references
   FOREACH reference
         check to see if the referenced MPs is not already installed (or will be installed)
         IF the needed MP is not installed, skip for now
         IF all the references are
             installed and 
             the version is compatible and 
             the referenced MP is sealed and 
             the keytoken matches
         THEN
             add this MP to the list of MPs to which we'll be able to install
             remove this MP from the list of MPs that we want to install
             add it to the list of MPs that are already installed (since it will be)
             since the list of MPs to install has now changed, restart the main loop

If there are any MPs left in the list to install, it means that we weren’t able to resolve the references, so we won’t be able to install that MP.  Note that the failure can be for any reason, it could rely on an unsealed MP (I don’t want to discuss sealing here, suffice it to say that sealing an MP makes it immutable, this way we know the things that we rely on won’t change), the reference could be missing, it could require the wrong version, or it could have an incorrect KeyToken.   There really isn’t much automatic remediation that we can do, but we can report what went wrong.

So, now that we have a mechanism to put MPs in order, let’s see how it looks.


For our simple test, we’ll create 6 MPs, each one of them dependent on the next MP (from an alphabetical point of view). 

  1. SimpleMP.A depends on SimpleMP.B
  2. SimpleMP.B depends on SimpleMP.C
  3. SimpleMP.C depends on SimpleMP.D
  4. SimpleMP.D depends on SimpleMP.L
  5. SimpleMP.L depends on SimpleMP.M
  6. SimpleMP.M depends on System.Library

this means that the need to be installed in the opposite order, M installed before L, etc. 

First we’ll start by importing the XML file, since XML files can’t be sealed, we should get an error for every MP except the only one that can be installed (SimpleMP.M, since it relies on System.Library).

Now we can see where we have problems, and which MPs we can install.  Because I’m importing unsealed MPs, the references that I had can’t be satisfied.  Only SimpleMP.M can be installed, since it has only one reference to a sealed MP (System.Library).  Note also, that SimpleMP.L can’t be installed because it references SimpleMP.M which is unsealed.  The other MPs can’t be installed because their references aren’t there.  Now, let’s try this again with sealed MPs.

PS> Get-ChildItem *.mp


    Directory: Microsoft.PowerShell.Core\FileSystem::C:\test


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---          4/2/2009  12:49 PM       4608 SimpleMP.A.mp
-a---          4/2/2009  12:49 PM       4608 SimpleMP.B.mp
-a---          4/2/2009  12:49 PM       4608 SimpleMP.C.mp
-a---          4/2/2009  12:49 PM       4608 SimpleMP.D.mp
-a---          4/2/2009  12:49 PM       4608 SimpleMP.L.mp
-a---          4/2/2009  12:49 PM       4608 SimpleMP.M.mp

If we tried to import these MPs in alphabetical order, only the last one would succeed, so we could import them by creating a script which does the import explicitly, one at a time, but we can use our script to do it in one fell swoop.  This means that we don’t really have to work out the dependencies manually – automation – that’s the key!

PS> get-childitem *.mp|./Import-ManagementPack -whatif 
WhatIf: Importing Management Pack SimpleMP.M
WhatIf: Importing Management Pack SimpleMP.L
WhatIf: Importing Management Pack SimpleMP.D
WhatIf: Importing Management Pack SimpleMP.C
WhatIf: Importing Management Pack SimpleMP.B
WhatIf: Importing Management Pack SimpleMP.A

Wahoo!  Even though the MPs have dependencies in reverse to the order returned by get-childitem, they’ll be installed in the correct order.  Granted, this example is pretty simple and the dependencies are straightforward, however, this script should handle pretty complicated sets of dependencies.

When actually coding this script, I decided that I wanted to make sure that I could be a bit more flexible than what was strictly possible from within Service Manager.  So I added options to avoid checking for the correct version, whether the MP in the reference is sealed and if the KeyToken is correct.    You still won’t be able to actually import if some of these things are wrong, but you’ll see the order if everything was correct and proper.  I also added -Verbose and -Debug parameters as well as a -WhatIf parameter which allows you to not actually Import the MPs, but will tell you what order they would be installed.  The script it a little on the larger size (about 200 lines), so I’m not going to walk through the script in this blog, but the code is fairly well commented. 

Here’s a link to the script:

http://cid-7143da6e51a2628d.skydrive.live.com/embedrowdetail.aspx/PowerShellFiles/Import-ManagementPack.ps1

Posted June 1, 2009 by jtruher3 in ServiceManager

More with management packs   Leave a comment

Last post, I wrote about retrieving management packs from Service Manager and I don’t really have a lot more to say about retrieving the management pack information, except for provide a way where we don’t have to specify formatting.   Generally, I want to see whether the management pack is sealed, the version number and then name, which translates into using format-table like this:

format-table Sealed,Version,Name -autosize

I’ll create a ServiceManager.Format.ps1xml file which will format the default view of management packs.

<configuration>
 <viewdefinitions>
  <view>
   <name>ManagementPackView</name>
   <viewselectedby>
    <typename>Microsoft.EnterpriseManagement.Configuration.ManagementPack</typename>
   </viewselectedby>
   <tablecontrol>
    <autosize />
    <tableheaders>
     <tablecolumnheader>
      <label>Sealed</label>
     </tablecolumnheader>
     <tablecolumnheader>
      <label>Version</label>
     </tablecolumnheader>
     <tablecolumnheader>
      <label>Name</label>
     </tablecolumnheader>
    </tableheaders>
    <tablerowentries>
     <tablerowentry>
      <tablecolumnitems>
       <tablecolumnitem>
        <propertyname>Sealed</propertyname>
       </tablecolumnitem>
       <tablecolumnitem>
        <propertyname>Version</propertyname>
       </tablecolumnitem>
       <tablecolumnitem>
        <propertyname>Name</propertyname>
       </tablecolumnitem>
      </tablecolumnitems>
     </tablerowentry>
    </tablerowentries>
   </tablecontrol>
  </view>
 </viewdefinitions>
</configuration>

In order to add this to my environment, all I need to do is use the Update-FormatData cmdlet using the filename as an argument.  After that, retrieving management packs will be be in the format that I want.

PS> $MGroup.ManagementPacks.GetManagementPacks()

Sealed Version    Name
------ -------    ----
True   7.0.3683.0 ServiceManager.ServiceMaps.Library
True   7.0.3683.0 Microsoft.SystemCenter.InstanceGroup.Library
...
False  7.0.3683.0 ServiceManager.OpsMgrConnector.Configuration

So less typing for me, yay! 

For even less typing, I’ll create a script called Get-ManagementPack which creates a connection to Service Manager and retrieves the management packs.

here’s the script:

PS> get-content Get-ManagementPack.ps1
param ( $computerName = "localhost" )
$SMDIR = "C:\Program Files\Microsoft System Center\Service Manager 2010"
$COREDLL = "${SMDIR}/SDK Binaries/Microsoft.EnterpriseManagement.Core.dll"
[reflection.assembly]::LoadFile($COREDLL) | out-null
$MGroup = new-object Microsoft.EnterpriseManagement.EnterpriseManagementGroup $computerName
$MGroup.ManagementPacks.GetManagementPacks()

and to run:

PS> Get-ManagementPack

Sealed Version    Name
------ -------    ----
True   7.0.3683.0 ServiceManager.ServiceMaps.Library
True   7.0.3683.0 Microsoft.SystemCenter.InstanceGroup.Library
...
False  7.0.3683.0 ServiceManager.OpsMgrConnector.Configuration

Now we have a simple script and default formatting.  But we’re not done with management packs – if I want to see the contents of a management pack, I can do that via a process called "exporting".  Exporting a management pack lets me create an XML file of the management pack which I can then inspect the various elements of the management pack so I can see what it does (and how it does it).  An object exists for just the purpose of exporting management packs – Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter and using the WriteManagementPack method, I can easily create the XML files.

This is perfect for a foreach pipeline, so for each management pack that I retrieve, I’ll create an XML file of the management pack contents.

PS> Get-ManagementPack | %{
>> $xmlWriter = new-object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter C:\temp 
>> } { 
>> $xmlWriter.WriteManagementPack($_) 
>> }

The first script block in the foreach command creates me an xmlWriter which will used for all the management pack objects that are passed from Get-ManagementPack.   The ManagementPackXmlWriter object has two constructors.  The constructor that I’m using takes a string which points to a directory which will contain the exported xml files.  When invoked, the method returns a string which is the fullname of the exported XML file, so when I execute it, I see the following (ellipses used to save space):

C:\temp\ServiceManager.ServiceMaps.Library.xml
C:\temp\Microsoft.SystemCenter.InstanceGroup.Library.xml
...
C:\temp\ServiceManager.OpsMgrConnector.Configuration.xml

I can easily incorporate this into a script as well:

PS> Get-Content Export-ManagementPack.ps1
param ( $targetDirectory = $( throw "Need a target directory"), [switch]$verbose )
begin {
  if ( $verbose )
  {
    $verbosePreference = "Continue"
  }
  $xmlWriter = new-object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter $targetDirectory
}
process {
  if ( $_ -is "Microsoft.EnterpriseManagement.Configuration.ManagementPack" )
  {
    $path = $xmlWriter.WriteManagementPack($_)
    if ( $verbose )
    {
      Write-Verbose "Exporting: $path"
    }
  }
  else
  {
    Write-Error "$_ is not a management pack"
  }
}

I’ve also added support for -verbose so I can see what’s being exported if I want, along with just a little checking to be sure that I’ve actually got a management pack.

Now I can run the following:

PS> Get-ManagementPack | Export-ManagementPack C:\temp

or

PS> Get-ManagementPack | Export-ManagementPack C:\temp -verbose

 

and export all my management packs in one simple step.   Next time I’ll discuss importing management packs.

Posted March 10, 2009 by jtruher3 in ServiceManager