Listing SCCM 2012 R2 Applications with Orchestrator

As Orchestrator is guaranteed to grow in importance for most Microsoft based organisations, the need to query the Configuration Manager database becomes more important as well. 

At the moment there are a number of limitations with Orchestrator and with the available Management Pack for SCCM 2012 R2.  But thanks to the flexibility of Powershell (and the generosity of people like Karl Prosser for sharing their Orchestrator workarounds), interfacing the two products can be achieved simply.

This example uses DLL files from the SCCM Console application to query the Application collection from an SCCM server.  It can easily be modified as a template for retrieving other details from the system.

Powershell 3 Workaround

The first thing to notice with any Powershell scripting with Orchestrator is that all of the “Run .Net Script” types are hardcoded to use outdated versions of their languages.  As most of us will develop and test our scripts in an IDE that uses more current versions of Dot Net languages, troubleshooting why working scripts fail inside Orchestrator can be a time consuming problem.

Thankfully, there is a widely publicised trick to shell-out from the depreciated Powershell interface provided with Orchestrator to the updated version of Powershell installed on the server.  This is simply done by invoking powershell with braces to enclose the script you want run.

Eg.

Eg. Powershell { $Var = “Hello World” }

It’s a great workaround but (as always), there is a problem.  The strength of Orchestrator is that data can be shared between running components.  Usernames and Passwords can be embedded within a script as variables from a common data bus and data produced can be published for other components to access as well.  It no longer becomes possible to use variables and published data within the confines of the shelled out script.

This problem does have a workaround too.

Karl Prosser pointed out to the community that calling powershell within the script was opening a stream.  When that powershell session completes, the stream will return into the original Powershell 2 environment where variables and published data continues to work.  There is nothing to stop the passing of an object into the stream when the Powershell 3 environment is opened and out the other end of the stream when the Powershell 3 session finishes.

$VarObject = new-object pscustomobject -property @{ 
  SCCMSERVER = “SCCMSERVER” 
  SITE = “SCCMSITE” 
} 
 
$psresult = $VarObject | Powershell { 
  $inobject = $input | select -first 1 
  $SCCMServer = $inobject.SCCMSERVER 
  $SiteCode = $inobject.SITE 
 
  # Powershell 3 Code happens here then we create a new object to return 
 
  new-object pscustomobject -property @{ 
   APPLIST = $ApplicationsList 
  } 
} 
$StringList = $psresult.APPLIST 

By passing an object in and another object out at the end of the process we can inject all the variables we need into the object.

Getting Orchestrator to loop on the results

The Powershell workaround passed a delimited string back into the original environment.  By using the “-split” action on the string, Powershell can automatically turn the list of applications into an array of strings.  To be able to leverage the automation of Orchestrator, this array of strings needs to be converted into an array of objects which can be published at the end of the script activity.  When a downstream activity selects an array of objects from the published data bus it invokes a “for each” loop over each of the objects.  Converting the string array elements into a usable object array is done like:  

[string] $StringList = $psresult.APPLIST 
$ApplicationStringArray = $StringList -split ',' 
$AppObjArray = @() 
 
ForEach($Application in $ApplicationStringArray) { 
$AppObjArray += $Application 
} 
 
$AppObjArray 

The Complete Code

#create variable object (values set by Orchestrator variables) 
 
[PSCustomObject] $VarObject = new-object pscustomobject -property @{ 
  SCCMSERVER = "\`d.T.~Vb/{500079E6-7DB2-49CE-B9B8-16C1B61ABA24}\`d.T.~Vb/" 
  SITE = "\`d.T.~Vb/{33E9D2F7-5995-4800-88F1-C6B9E7130A14}\`d.T.~Vb/" 
  } 
 
  $psresult = $VarObject | Powershell { 
  [PSCustomObject] $inobject = $input | select -first 1 
 
  [string] $SCCMServer = $inobject.SCCMSERVER 
  [string] $SiteCode = $inobject.SITE 
 
  Add-Type -Path "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ApplicationManagement.dll" 
  Add-Type -Path "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ApplicationManagement.MsiInstaller.dll" 
 
  [string] $sPath = [string]::Format("\\{0}\ROOT\sms\site_{1}", $SCCMServer, $siteCode) 
 
  $oScope = new-object System.Management.ManagementScope -ArgumentList $sPath $oQuery = New-Object System.Management.ObjectQuery 
  $oQuery.QueryString = "Select * from sms_application where islatest = 1" 
  $oManagementObjectSearcher = New-Object System.Management.ManagementObjectSearcher($oQuery) 
  $oManagementObjectSearcher.Scope.Path = "\\{0}\ROOT\sms\site_{1}" -f $SCCMServer, $siteCode 
 
  $ResultsCollection = $oManagementObjectSearcher.Get() 
  $ResultsCollectionEnumerator = $ResultsCollection.GetEnumerator() 
  $ApplicationsList ="" 
 
  $iret = $ResultsCollectionEnumerator.MoveNext() 
 
  do { 
    $CurrentApp = $ResultsCollectionEnumerator.Current 
    $getResult = $CurrentApp.Get() 
 
    [string] $sdmPackageXml = $CurrentApp.Properties["SDMPackageXML"].Value.ToString() 
    $oFoundDependency = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($sdmPackageXml,0) 
    [xml]$books = $CurrentApp.Properties["SDMPackageXML"].Value.ToString() 
    [string] $AppTitle = $books | % {$_.AppMgmtDigest.Application.Title.'#text'} 
    # other useful fields… 
    # [string] $Version = $books | % {$_.AppMgmtDigest.Application.Version.'#text'} 
    # [string] $Owner = $books | % {$_.AppMgmtDigest.Application.Owners.User} | select -ExpandProperty Id 
    # [string] $LogicalName = $books | % {$_.AppMgmtDigest.DeploymentType} | select -ExpandProperty LogicalName 
 
    $ApplicationsList = $AppTitle + "," + $ApplicationsList 
    } 
    until($ResultsCollectionEnumerator.MoveNext() -eq $False) 
 
    new-object pscustomobject -property @{ APPLIST = $ApplicationsList } 
  } 
 
  [string] $StringList = $psresult.APPLIST 
 
  $ApplicationStringArray = $StringList -split ',' 
  $AppObjArray = @() 
 
  ForEach($Application in $ApplicationStringArray) { $AppObjArray += $Application } 
 
  $AppObjArray

 

Demonstrating the results

My intention is to make the array of SCCM Application names available to other elements of my Runbook.  I do this by publishing the $AppObjArray that was created by my Powershell script.

To test that it’s working correctly, I can use another activity to append text lines to file (as shown in the Image below).