Creating Complex SCCM 2012 Applications with Powershell

SCCM 2012 R2 is a great product but I have some strong disagreements with some elements of how it deals with software management.  By default, the product suggests that a Windows Installer Product Code can be used to uniquely identify when a package is installed to a machine.     This is a misunderstanding that “Product Codes” are not unique to Windows Installer packages – “Package Codes” are.  It also overlooks that the same utility or product may be installed in an organisation as different variants such as the “HR package” or the “Spanish package” or the “metric package”, all of which have exactly the same product code even though they are completely different packages.

Using Product Codes to detect custom tailored in-house packages isn’t good enough for an organisation of any size.  Essentially packages of the same piece of software need to be differentiated so there is no substitute for using the file system or registry to record package names when they are installed.

Application Creation with Enhanced Detection

Using “enhanced detection” for applications requires a lengthy piece of scripting.

The PowerShell code below creates an SCCM 2012 R2 Application that utilises a script based installer and a file based detection method.

cls
#####################################################
# Add Required Type Libraries
 
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"
Add-Type -Path "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ManagementProvider.dll"
Add-Type -Path "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ApplicationManagement.Extender.dll"
#used for creating rules
Add-Type -Path "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\DcmObjectModel.dll"
#WQL Connection to Server
Add-Type -Path "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\AdminUI.WqlQueryEngine.dll"
#Application Wrapper and Factory
Add-Type -Path "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\AdminUI.AppManFoundation.dll"
 
 
 
#####################################################
# Core Application Details
 
[string]$sPackageDirectory         ="\\sccmserver\Library\7Zip_09.20_86a"
[string]$sApplicationName          = "RhubarbApp1"
[string]$sApplicationDestription   = "Example Application"
[string]$sApplicationVersion       = "02.00"
[string]$sApplicationPublisher     = "Vendor A"
[string]$ApplicationOwner          = ""
 
[string]$sSCCMServerName           = "SCCMServerName"
[string]$sSCCMUsername             = "SCCMUserName"
[string]$sSCCMPassword             = "SCCMPassword"
 
 
 
 
#####################################################
# Functions
 
# WQLConnect
# Purpose: To create a WQL query connection to an SCCM Server
#          This will utilise credentials if script isn't being run on the server itself
#
function WQLConnect($Server, $User, $Password) {
 
  $namedValues              = New-Object Microsoft.ConfigurationManagement.ManagementProvider.SmsNamedValuesDictionary
  if ($namedValues -ne $null) { write-host " namedValues object Created"} else {write-host " namedValues object Creation failed"; exit}
 
  $connection               = New-Object Microsoft.ConfigurationManagement.ManagementProvider.WqlQueryEngine.WqlConnectionManager
  if ($connection  -ne $null) { write-host " connection  object Created"} else {write-host " connection  object Creation failed"; exit}
 
 
  # Connect with credentials if not running on the server itself
  if ($env:computername.ToUpper() -eq $Server.ToUpper()){
     write-host  "Local WQL Connection Made"
     [void]$connection.Connect($Server)
  }
  else
  {
     write-host  "Remote WQL Connection Made: " + $sSCCMServerName
     [void]$connection.Connect($Server, $User, $Password)
 
  }
  return $connection
 
}
 
# Store
# Purpose: To commit the completed application to SCCM
#
function store ($Application){
 
                # Set the application into the provider object.
                $oAppManWrapper.InnerAppManObject = $Application
 
                # "Initializing the SMS_Application object with the model."
                $oApplicationFactory.PrepareResultObject($oAppManWrapper);
 
                # Save to the database.
                $oAppManWrapper.InnerResultObject.Put();
}
 
 
#####################################################
# Main
 
#create connection to SCCMServer (not used until the end of the script)
$oServerConnection                       = WQLConnect $sSCCMServerName $sSCCMUsername $sSCCMPassword
$oApplicationFactory                     = New-Object Microsoft.ConfigurationManagement.AdminConsole.AppManFoundation.ApplicationFactory
$oAppManWrapper                           = [Microsoft.ConfigurationManagement.AdminConsole.AppManFoundation.AppManWrapper]::Create( $oServerConnection , $oApplicationFactory)
 
 
write-host  "Creating Application Object"
$oApplication                        = New-Object Microsoft.ConfigurationManagement.ApplicationManagement.Application
   if ($oApplication -ne $null) { write-host " oApplication object Created"} else {write-host " oApplication object Creation failed"; exit}
 
 
$oApplication.Title                       = $sApplicationName
$oApplication.SoftwareVersion             = $sApplicationVersion
$oApplication.Publisher                   = $sApplicationPublisher
$oApplication.DisplayInfo.DefaultLanguage = "en-US"
 
write-host  "Creating Application Display Info"
 
$oApplicationDisplayInfo = New-Object Microsoft.ConfigurationManagement.ApplicationManagement.AppDisplayInfo
  if ($oApplicationDisplayInfo -ne $null) { write-host " oApplicationDisplayInfo object Created"} else {write-host " oApplicationDisplayInfoo object Creation failed"; exit}
 
 
$oApplicationDisplayInfo.Title            = $sApplicationName
$oApplicationDisplayInfo.Description      = $sApplicationDestription
$oApplicationDisplayInfo.Language         = "en-US"
$oApplication.DisplayInfo.DefaultLanguage = "en-US"
$oApplication.DisplayInfo.Add($oApplicationDisplayInfo)
 
 
$oAppinstaller = New-Object Microsoft.ConfigurationManagement.ApplicationManagement.ScriptInstaller
  if ($oAppinstaller -ne $null) { write-host " oAppinstaller object Created"} else {write-host " oAppinstaller object Creation failed"; exit}
 
#Note that this example hardcodes install and remove syntax by using
#a launcher for all applications
 
$oAppinstaller.InstallCommandLine          = '"Launcher.exe"'
$oAppinstaller.UninstallCommandLine        = '"Launcher.exe" /Remove'
 
#####################################################
# Upload File Content to Server
 
write-host  "Upload Content"
 
$oContent                                   = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentImporter]::CreateContentFromFolder($sPackageDirectory)
$oContent.OnSlowNetwork                     = "Download"
$oAppinstaller.Contents.Add($oContent)
 
 
#####################################################
# Main Creating Enhanced File Detection Method (file based)
# This uses a standard Package signature file under c:\windows\logs
# to determine if the package is installed
 
write-host  "Enabling Enhanced File Detection"
$oAppinstaller.DetectionMethod = [Microsoft.ConfigurationManagement.ApplicationManagement.DetectionMethod]::Enhanced
$oEnhancedDetection = New-Object Microsoft.ConfigurationManagement.ApplicationManagement.EnhancedDetectionMethod
  if ($oEnhancedDetection -ne $null) { write-host " oEnhancedDetection object Created"} else {write-host " oEnhancedDetection object Creation failed"; exit}
 
$oDetectionType              = [Microsoft.ConfigurationManagement.DesiredConfigurationManagement.ConfigurationItemPartType]::File
$oFileSetting                 = New-Object Microsoft.ConfigurationManagement.DesiredConfigurationManagement.FileOrFolder( $oDetectionType , $null)
  if ($oFileSetting -ne $null) { write-host " oFileSetting object Created"} else {write-host " oFileSetting object Creation failed"; exit}
 
$oFileSetting.FileOrFolderName = $sApplicationName + ".sig"
$oFileSetting.Path             = "%Windir%\Logs"
$oFileSetting.Is64Bit          = 1
$oFileSetting.SettingDataType  = [Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.DataType]::Int64
 
$oEnhancedDetection.Settings.Add($oFileSetting)
 
 
write-host  "Settings Reference"
$oSettingRef                  = New-Object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.SettingReference(
$oApplication.Scope,
$oApplication.Name,
$oApplication.Version,
$oFileSetting.LogicalName,
$oFileSetting.SettingDataType,
$oFileSetting.SourceType,
[bool]0 )
# setting bool 0 as false
if ($oSettingRef -ne $null) { write-host " oSettingRef object Created"} else {write-host " oSettingRef object Creation failed"; exit}
 
 
$oSettingRef.MethodType    = [Microsoft.ConfigurationManagement.DesiredConfigurationManagement.ConfigurationItemSettingMethodType]::Value
$oConstValue               = New-Object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.ConstantValue( 0, 
[Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.DataType]::Int64)
   if ($oConstValue -ne $null) { write-host " oConstValue object Created"} else {write-host " oConstValue object Creation failed"; exit}
 
$oFileCheckOperands = new-object Microsoft.ConfigurationManagement.DesiredConfigurationManagement.CustomCollection``1[[Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.ExpressionBase]]
$oFileCheckOperands.Add($oSettingRef)
$oFileCheckOperands.Add($oConstValue)
 
$FileCheckExpression = new-object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.Expression(
[Microsoft.ConfigurationManagement.DesiredConfigurationManagement.ExpressionOperators.ExpressionOperator]::NotEquals, $oFileCheckOperands)
 
$oRule              = new-object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.Rule("IsInstalledRule", 
[Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.NoncomplianceSeverity]::None, $null, $FileCheckExpression)
   if ($oRule  -ne $null) { write-host " rule object Created"} else {write-host " rule object Creation failed"; exit}
 
 
$oEnhancedDetection.Rule = $oRule 
$oAppinstaller.EnhancedDetectionMethod = $oEnhancedDetection
 
#####################################################
# Add Deployment Type to Application
 
 
write-host  "Adding Deployment Type"
$oApplicationDeploymentType = new-object Microsoft.ConfigurationManagement.ApplicationManagement.DeploymentType($oAppinstaller, 
[Microsoft.ConfigurationManagement.ApplicationManagement.ScriptInstaller]::TechnologyId, 
[Microsoft.ConfigurationManagement.ApplicationManagement.NativeHostingTechnology]::TechnologyId)
 
if ($oApplicationDeploymentType -ne $null) { write-host " NewApplicationDeploymentType object Created"} else {write-host " NewApplicationDeploymentType object Creation failed"; exit}
 
 
$oApplicationDeploymentType.Title = $oApplication.Title
 
 
 
#####################################################
 
write-host "Limiting Collections to 64bit workstations"
 
$oOperands = new-object "Microsoft.ConfigurationManagement.DesiredConfigurationManagement.CustomCollection``1[[Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.RuleExpression]]"
   ####if ($oOperands -ne $null) { write-host " oOperands object Created"} else {write-host " oOperands object Creation failed"; exit}
 
# Its possible to limit the deployment of application to specific operating systems
# A current list of possible values may be found t:
# <a href="http://www.laurierhodes.info/?q=node/60">http://www.laurierhodes.info/?q=node/60
 
</a>$oOperands.Add("Windows/All_x64_Windows_XP_Professional")
$oOperands.Add("Windows/All_x64_Windows_7_Client")
$oOperands.Add("Windows/All_x64_Windows_8_Client")
$oOperands.Add("Windows/All_x64_Windows_8.1_Client")
 
$oOSExpression           = new-object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.OperatingSystemExpression -ArgumentList ([Microsoft.ConfigurationManagement.DesiredConfigurationManagement.ExpressionOperators.ExpressionOperator]::OneOf), $oOperands    
 
$oAnnotation             = new-object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.Annotation      
$oAnnotation.DisplayName = new-object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.LocalizableString -ArgumentList "DisplayName", "Operating system One of {All x86 Windows XP (64bit)}", $null
 
$oDTRule = new-object "Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.Rule" -ArgumentList `
            ("Rule_" + [Guid]::NewGuid().ToString()), 
            ([Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.NoncomplianceSeverity]::None), 
             $oAnnotation, 
             $oOSExpression
 
write-host "Adding Deployment Type rule to Application"     
$oApplicationDeploymentType.Requirements.Add($oDTRule)
 
############################################
# Finish
 
 
# Add Deployment Type to Application
$oApplication.DeploymentTypes.Add($oApplicationDeploymentType)
 
# Verify the Application structure
write-host "Verifying Application Object"  
$oApplication.Validate
 
#Add Application to SCCM
write-host "Loading SCCM Application"  
Store $oApplication

Related posts that may be of interest are:

Constructing SCCM Rules with Powershell,  Scripting SCCM Application DependenciesCreating SCCM Global Conditions, SCCM Enhanced Detection - Registry