Extending Microsoft System Center Configuration Manager 2012 Package Conversion Manager.

Any great tool should have an extensibility story and System Center Configuration Manager 2012 Package Conversion Manager is no different.

With the release of RC2 of Microsoft System Center Configuration Manager 2012 Package Conversion Manager (PCM) we get to experiment with a new feature; PCM Extensibility. PCM Extensibility is an extensibility point for PCM that provides IT shops with an opportunity to customize the package conversion process with the goal of speeding up the retirement of wrappers commonly used with Classic Software Dist. programs.

  • The Why: Many Configuration Manager customers have Classic Software Dist. packages with custom logic and wrappers that are unique to their environment. The Application Model in SCCM 2012 addresses many of the reasons customers originally invested in customizations. PCM allows our customers to unwind the extensibility of their program wrappers and to take full advantage of the App Model. Essentially PCM helps them divest in custom wrappers for scenarios that are a 1st class citizen within the new App Model.
  • The How: Unlock the the full breadth and scope of the App Model SDK during the conversion process by providing a custom executable with access to a copy the XML digest from a converted Application by building a PCM Plugin.
  • The What: An extensibility option called the PCM Extensibility.

 

The Basics

On the high level a PCM plugin is an executable that is launched by the conversion and the analysis process. Below is a diagram that shows the basic interactions. The dashed line represents the process boundary between PCM and the plugin processes. The Application object that is created by PCM is serialized to XML using the SccmSerializer class found within the App Model SDK. In the diagram below the plugin is reading and manipulating the XML. Once the plugin has made all necessary changes it overwrites the original file with the updated XML. Once the plugin process exits PCM deserializes the Application object from the updated XML and continues with the conversion or analysis process. PCM passes three arguments via the command line when launching the Plugin. It passes Package ID, full path to the XML digest of the serialized Application, and the name of the site server that the console is connected to.

It’s important to note that the PCM Plugin is ran on a per package basis. Or put differently, is executed within the context of a single package-to-app conversion. So  if 5 Automatic packages have been selected for Conversion the plugin will be launched 5 times.

image

 

Configuration

There are several options related to the PCM Plugin. These options are controlled via the Microsoft.ConfigurationManagement.exe.config file for the Admin Console. The Microsoft.ConfigurationManagement.exe.config file found within the “bin” folder under the Admin Console install location. The sample download at the bottom of this post contains a sample .config file.

Config Sections

The first step in setting up a PCM Plugin is to add a PCM binary to the configSections node. Add the following child node to configSections.

 <section name="Microsoft.ConfigurationManagement.UserCentric.Workflow.Properties.Settings" 
               type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
               requirePermission="false" />

Plugin Command Line

PCM launces a command shell process using a command line value specified by the .config file. At run time PCM appends to this command line three arguments. The PackageID of the source package, the full path to the XML digest of the derived Application object, and the site server that the console is connected to are appended in that order. To configure a plugin add the following setting to the Microsoft.ConfigurationManagement.UserCentric.Workflow.Properties.Settings node under applicationSettings similar to the example below.

 <setting name="PcmPlugIn" serializeAs="String">
   <value>powershell.exe "C:\sample\PlugInAppModel.ps1"</value>
</setting>

Based on the PcmPlugin value specified above the complete command line that will be used to launch the Plugin will look like:
cmd /c powershell.exe “c:\sample\PlugInAppModel.ps1” ABC00002 %temp%\filename.xml MySiteServer

It’s important to note that this is the only required configuration piece to get your plugin up and running within PCM.

Plugin Timeout

PCM has a configurable Plugin Timeout threshold. If the Plugin process runs for longer than the specified duration PCM will consider that an error and skip onto the next package. Below is an example of how to configure the timeout value in milliseconds.

 <setting name="PcmPlugInTimeoutMilliseconds" serializeAs="String">
       <value>100000</value>
</setting>

Plugin Exit Code

The Microsoft.ConfigurationManagement.exe.config also allows us to specify what success is in terms of process exit code. The default is 0 but if your plugin uses a different value to indicate success you can modify the setting as shown below.

 <setting name="PcmPluginExitCode" serializeAs="String">
   <value>8</value>
</setting>

PowerShell Sample

To help get the community off and running with building plugins for PCM we created a sample plugin built using PowerShell. In this blog post I will cover the core concepts demonstrated by the sample, however the sample plugin goes further by showing how to achieve more advanced tasks with the App Model SDK.

Arguments

First we need to get our arguments from the command line so we know where our XML digest is.

  1: $packageId      = $args[0]
  2: $fullFileName   = $args[1]
  3: $sccmServerName = $args[2]

Loading SDK Assemblies

A plug in can work directly with the XML and use XPath to navigate the digest. This requires an intimate understanding of the schema used by the App Model but it is still a valid approach. In the sample plugin we show how to use the App Model SDK via PowerShell so we don’t have to fiddle about with XPath.

  1: #Loads Application Model into Application Domain
  2: LoadSccmAssemblies

To load the SDK Assemblies for working with the Application class we use the following function from SharedFunctions.ps1

  1: #Load Sccm Assemblies
  2: function LoadSccmAssemblies()
  3: {
  4:     $sccmPath = "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\"
  5:     $filesToLoad = "Microsoft.ConfigurationManagement.ApplicationManagement.dll","AdminUI.WqlQueryEngine.dll", "AdminUI.DcmObjectWrapper.dll" 
  6:     
  7:     Set-Location $sccmPath
  8:     [System.IO.Directory]::SetCurrentDirectory($sccmPath)
  9:     
  10:     foreach($fileName in $filesToLoad)
  11:     {
  12:        $fullAssemblyName = [System.IO.Path]::Combine($sccmPath, $fileName)
  13:        if([System.IO.File]::Exists($fullAssemblyName ))
  14:        {   
  15:            $FileLoaded = [Reflection.Assembly]::LoadFrom($fullAssemblyName )
  16:        }
  17:        else
  18:        {
  19:             Write-Host ([System.String]::Format("File not found {0}",$fileName )) -backgroundcolor "red"
  20:        }
  21:     }
  22: }

Deserialize

Next we need to deserialize the XML digest into an instance of Application.

  1: #Deserialize XML file
  2: $applicaton = GetApplicationObject $fullFileName

This is also handled by a function from SharedFunctions.ps1.

  1: #Deserialize XML into Application
  2: function GetApplicationObject([string]$xmlFilePath)
  3: {
  4:    $fileContent = get-content $xmlFilePath -readcount 0   
  5:   [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($fileContent,$false)
  6: }

Modify

Now that we have an instance of Application to work with we can start to modify it. One easy example is changing the Title of the Application

  1: #Modifies Application Name
  2: $applicaton.Title = $applicaton.Title + "_ModifiedByPlugIn"
  3: $applicaton.DisplayInfo.Current.Title = $applicaton.DisplayInfo.Current.Title + "_ModifiedByPlugIn"

Unwinding Your Wrapper

Now that we have modified the title of our Application we need to move on to some more serious work. For example if your programs are currently wrapped with a custom executable or script a key piece to using the PCM Plugin to move away from a wrapper is updating the command line within the Deployment Type to point directly at an MSI. Below we show how one can get to the first DT in the Application and update the InstallCommandLine property to point at the MSI directly. If wrappers are being used to facilitate uninstall scenarios then using the plugin to populate the UninstallCommandLine property will also be valuable.

  1: $deploymentType = $applicaton.DeploymentTypes[0]
  2:         
  3: $deploymentType.Installer.InstallCommandLine = 'MSIExec.exe /i "SimpleMSI.msi" /q'
  4: $deploymentType.Installer.UninstallCommandLine = 'MSIExec.exe /uninstall "SimpleMSI.msi"'

 

Analysis and the Plugin

One important point that should be called out is that the Plugin is also invoked during Analysis and the Readiness rule set is applied to the derived Application after the plugin completes. The implication is that the PCM Plugin has an opportunity to modify the Application before readiness rules are applied. This gives the plugin the ability to affect the Readiness state of the package. The example below shows how a plugin can have an impact on Readiness.

Getting More Packages into the Ready State

The PCM Plugin concept is intended to help with moving away from wrappers that are made obsolete by the new Application Model. With that said you can create a plugin for a number of reasons. For example one might create a Plugin to get more packages into the Ready state. The number one blocker to getting your packages into the Ready state will be missing detection methods. PCM is limited in its ability to natively find detection methods. PCM can only find a detection method if an MSI can be located in the program command line. With the plugin we have an opportunity to add the capability to create an enhanced detection method or supply a product code. To determine if a Deployment Type is missing a detection method the plugin will need to examine the ProductCode and EnhancedDetectionMethod properties of the Installer within the DeploymentType instance. If both of these are empty then a detection method will be needed. Below is an example of how to set the ProductCode property to a random GUID. This would be ill-advised in a production  environment but gives an example of how to modify the ProductCode value.

  1: foreach($i in $application.DeploymentTypes)
  2:    {
  3:        if($i.Installer.ProductCode -eq " ")
  4:        {
  5:           $i.Installer.ProductCode = [Guid]::NewGuid().ToString()
  6:        }
  7:    }

Sample Code

Here is a link to the Sample code:

PowerShell Sample