Enable Implicit Uninstalls in SCCM Using PowerShell

In this post we discuss how to enable implicit uninstalls in SCCM using PowerShell and WMI.

There are certain prerequisites for implicit uninstalls to work:

  • You need to be running at least version 2107 of SCCM (MECM)
  • You need to be using device collections and not user collections
  • Your deployment must be a ‘required’ deployment
  • You must also specify an uninstall command.

But the setting….or checkbox, we are looking to amend can be seen below.  Navigate to your application, and then under the ‘Deployments’ tab you can right-click your assignment, choose Properties and then select the ‘Deployment Settings’ tab:

MECM Implicit Uninstall

Enable Implicit Uninstalls in SCCM Using PowerShell

The challenge at the moment is that the Set-CMApplicationDeployment PowerShell cmdlet does NOT support updating the implicit uninstall flag.  So the only alternative is to use WMI and the SMS_ApplicationAssignment class.  And there are two properties we need to set – OfferFlags (which is semi-documented) and AdditionalProperties (which is undocumented).

OfferFlags is only semi-documented because it is missing a flag – guess which one?  You guessed it – the implicit uninstall flag!

Value Offer flag
1 PREDEPLOY
2 ONDEMAND
4 ENABLEPROCESSTERMINATION
8 ALLOWUSERSTOREPAIRAPP
16 RELATIVESCHEDULE
32 HIGHIMPACTDEPLOYMENT
64 IMPLICITUNINSTALL

And AdditionalProperties is a string of XML which appears to be similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<Properties>
    <ActivationRandomizationMinutes>0</ActivationRandomizationMinutes>
    <RelativeDeadlineMinutes>0</RelativeDeadlineMinutes>
    <ImplicitUninstallEnabled>true</ImplicitUninstallEnabled>
</Properties>

The important node for this exercise is obviously ImplicitUninstallEnabled.

Since we need to update the assignment (deployment), in my scenario I will update the assignment by its ID since i know it will be unique.  I can find the ID for all of my assignments like so:

$siteCode = "your_sitecode"
$providerMachineName = "your_sccm_server"
$initParams = @{}

# Import the ConfigurationManager.psd1 module 
if((Get-Module ConfigurationManager) -eq $null) {
    Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" @initParams 
}

# Connect to the site's drive if it is not already present
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
    New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName @initParams
}

# Set the current location to be the site code.
Set-Location "$($SiteCode):\" @initParams

cls

#print out all applications and deployments
Get-CMApplication | foreach {          
    $appName = $_.LocalizedDisplayName
    write-host "(Application) $appName"

    Get-CMApplicationDeployment -Name $appName | foreach {     
                 
        $depAssignment = $_.AssignmentName
        $depAssignmentId = $_.AssignmentID
       
        write-host "    (Assignment) $depAssignment (ID: $depAssignmentId)"
    }
}

Note that the function below is still in test mode, so I’m not going to run it for every deployment above!  I will test it on a single application to begin with, in my dev environment since it’s probably not officially supported by Microsoft!

I have obtained the assignment ID of 16778268 and can now call the function like so:

$siteCode = "your_sitecode"
$providerMachineName = "your_sccm_server"
$initParams = @{}

# Import the ConfigurationManager.psd1 module 
if((Get-Module ConfigurationManager) -eq $null) {
    Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" @initParams 
}

# Connect to the site's drive if it is not already present
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
    New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName @initParams
}

# Set the current location to be the site code.
Set-Location "$($SiteCode):\" @initParams

cls

Function EnableImplicitUninstall($providerMachineName, $siteCode, $assignmentID) {

    $saveChanges = $false
     
    [flags()] 
    enum SMS_ApplicationAssignment_OfferFlags
    {
        None    = 0
        PREDEPLOY = 1
        ONDEMAND = 2
        ENABLEPROCESSTERMINATION = 4
        ALLOWUSERSTOREPAIRAPP = 8
        RELATIVESCHEDULE = 16
        HIGHIMPACTDEPLOYMENT = 32
        IMPLICITUNINSTALL = 64
    }
    
    $wmiObj = Get-WmiObject -ComputerName $providerMachineName -Namespace "root\sms\site_$siteCode" -class SMS_ApplicationAssignment -Filter "AssignmentID = '$assignmentID'" -ErrorAction SilentlyContinue
    $assignmentName = ""

    if ($wmiObj -ne $null) {

        $assignmentName = $wmiObj.AssignmentName

        if ($wmiObj.AdditionalProperties -eq $null -or $wmiObj.AdditionalProperties -eq "") {

            write-host "AdditionalProperties not set.  Creating new XML with ImplicitUninstallEnabled."

            #additional properties not set - create one

            #create XML for AdditionalProperties
            [xml]$doc = New-Object System.Xml.XmlDocument

            $dec = $doc.CreateXmlDeclaration("1.0","UTF-8",$null)
            $doc.AppendChild($dec) | Out-Null

            $root = $doc.CreateNode("element","Properties",$null)
            $doc.AppendChild($root) | Out-Null

            $ImplicitUninstallEnabled = $doc.CreateNode("element","ImplicitUninstallEnabled",$null)
            $ImplicitUninstallEnabled.InnerText = "true"
            $root.AppendChild($ImplicitUninstallEnabled) | Out-Null

            $additionalPropertiesXml = $doc.InnerXml

            $wmiObj.AdditionalProperties = $additionalPropertiesXml

            $saveChanges = $true

        } else {
            #AdditionalProperties is set to something     
      
            if ((([xml]$wmiObj.AdditionalProperties).Properties).ImplicitUninstallEnabled -ne $null) {
          
                #ImplicitUninstallEnabled is set - check if set to true!

                [xml]$doc = ([xml]$wmiObj.AdditionalProperties)

                if ($doc.Properties.ImplicitUninstallEnabled -ne "true") {

                    write-host "ImplicitUninstallEnabled is set but not to true.  Updating."
                    #node exists - set to true

                    #set to true
                    $doc.Properties.ImplicitUninstallEnabled = "true"

                    #get all XML
                    $additionalPropertiesXml = $doc.InnerXml

                    #update 
                    $wmiObj.AdditionalProperties = $additionalPropertiesXml

                    $saveChanges = $true
                }

            } else {

                #AdditionalProperties is set but ImplicitUninstallEnabled isn't - create XML node
                write-host "Additional Properties is set, but ImplicitUninstallEnabled isn't.  Setting."
            
                #get all XML
                [xml]$doc = ([xml]$wmiObj.AdditionalProperties)

                #create ImplicitUninstallEnabled node
                $ImplicitUninstallEnabled = $doc.CreateNode("element","ImplicitUninstallEnabled",$null)
                $ImplicitUninstallEnabled.InnerText = "true"
                $doc.Properties.AppendChild($ImplicitUninstallEnabled) | Out-Null

                #get all XML
                $additionalPropertiesXml = $doc.InnerXml

                #update 
                $wmiObj.AdditionalProperties = $additionalPropertiesXml

                $saveChanges = $true            
            }      
        }
    
        $offerFlags = $wmiObj.OfferFlags
        [SMS_ApplicationAssignment_OfferFlags]$assignmentFlags = $offerFlags

       
        if (!($assignmentFlags.HasFlag([SMS_ApplicationAssignment_OfferFlags]::IMPLICITUNINSTALL))) {
             #IMPLICITUNINSTALL not set so wevset it
            $wmiObj.OfferFlags = ($offerFlags + 64)
            $saveChanges = $true
        }
    
        if ($saveChanges) {
            #there are changes to save 
            $wmiObj.put() | Out-Null
            write-host "Updated assignment: $assignmentName"
        } else {
            write-host "Implicit uninstall already enabled for assignment: $assignmentName"
        }
    } else {
        write-host "Could not find assignment: $assignmentID"
    }
}

EnableImplicitUninstall $providerMachineName $siteCode "16778268"

In the script above there’s a cool (in a geeky way) example of how to use PowerShell to calculate bit flags too, which will come in very useful when I’m inspecting Windows Installer objects.