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 # make sure the appropriate assemblies are loaded and retrieve the needed types. # Functions # Get-Resources # Start # Check to see if we have any management packs, if not, exit. # we need a connection to the server when we start creating # WRITE THE mpb |
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!
Good! It’s very useful for me!
Hi,
I have downloaded your script, updated the first parameter to use yuor xml file as the $mpfile and try to run it in powershell_ise, it produced the following errors:
PS C:\PG06\Business Workflows\Workflow IP\51530\Source\SWICIForm\SWICIForm\bin\Debug> C:\test\New-MPBFile.ps2.ps1
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:24 char:31
+ $EMPTY = $SMCORE.GetType <<<< ($SIGTYPE)::Empty
+ CategoryInfo : InvalidOperation: (GetType:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:25 char:31
+ $TYPEOFMP = $SMCORE.GetType <<<< ($MPTYPE)
+ CategoryInfo : InvalidOperation: (GetType:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:26 char:31
+ $TYPEOFMPR = $SMCORE.GetType <<<< ($MRESTYPE)
+ CategoryInfo : InvalidOperation: (GetType:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:27 char:36
+ $BFACTORY = $SMPACKAGING.GetType <<<< ($FACTYPE)
+ CategoryInfo : InvalidOperation: (GetType:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
New-Object : Cannot find type [Microsoft.EnterpriseManagement.EnterpriseManagementGroup]: make sure the assemb
ly containing this type is loaded.
At C:\test\New-MPBFile.ps2.ps1:107 char:18
+ $EMG = new-object <<<< $EMGTYPE $computername
+ CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:110 char:34
+ $BUNDLE = $BFACTORY::CreateBundle <<<< ()
+ CategoryInfo : InvalidOperation: (CreateBundle:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
New-Object : Cannot find type [Microsoft.EnterpriseManagement.Configuration.ManagementPack]: make sure the ass
embly containing this type is loaded.
At C:\test\New-MPBFile.ps2.ps1:117 char:24
+ $theMP = new-object <<<< $MPTYPE $mpfilepath,$EMG
+ CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand
VERBOSE: Adding MP:
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:119 char:30
+ $BUNDLE.AddManagementPack <<<< ($theMP)
+ CategoryInfo : InvalidOperation: (AddManagementPack:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:48 char:32
+ $Method = $mytype.GetMethod <<<< ($mymethod)
+ CategoryInfo : InvalidOperation: (GetMethod:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:49 char:47
+ $genericMethod = $Method.MakeGenericMethod <<<< ($TypeArguments)
+ CategoryInfo : InvalidOperation: (MakeGenericMethod:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:50 char:26
+ $genericMethod.Invoke <<<< ($object,$parameters)
+ CategoryInfo : InvalidOperation: (Invoke:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:135 char:46
+ $bundleWriter = $BFACTORY::CreateBundleWriter <<<< (${PWD})
+ CategoryInfo : InvalidOperation: (CreateBundleWriter:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\test\New-MPBFile.ps2.ps1:137 char:35
+ $mpbfullpath = $bundleWriter.Write <<<< ($BUNDLE,$mpbname)
+ CategoryInfo : InvalidOperation: (Write:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
VERBOSE: wrote mpb:
In the folder I started the powershell_ise has all the MP dll been copied to. Any help for how to debug your script would be much appreciated.
Thanks,
Roger
I haven’t look at this script in the light of the latest release of Service Manager. This is likely a result of a failure of line 22 where the Service Manager Core assembly has failed to load. To check, look at $SMCORE, is it non-null (and is it the assembly)? The rest of the errors are cascaded from the first error, I think.
You might also look at http://smlets.codeplex.com/ as there is a complete module for Service Manager there.
Thanks a lot for reply back to my posting. We eventually discovered the two assambly, the Microsoft.EnterpriseManagement.core and Microsoft.EnterpriseManagement.packaging are in the local folder were ignored by msbuild, had to be in the GAC or in the msbuild folder then it will work. That’s a know bug but not a high priority one. It will be fixed. The work around is to put those two assambly in GAC or the same folder as msbuild.exe.
Thanks,
Roger
Pingback: Add custom service request forms in #ServiceManager2012 « System Center Solutions