James w's profileTime is an illusion. Lun...PhotosBlogListsMore Tools Help

James w truher

Occupation
Location
Interests

Time is an illusion. Lunchtime doubly so.

10/22/2009

Batch Operations in Service Manager 2010 with PowerShell – Removing Instances

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!

image

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!

10/2/2009

Service Manager and the PowerShell 1 liner

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!

9/3/2009

Introducing Management Pack Bundles

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”.

image

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).

ResourceResults

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!

8/17/2009

Getting data from Service Manager – a scripted approach

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
8/14/2009

Creating Data in Service Manager

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.

 
Photo 1 of 8