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:
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.
Hey, thanks for your script it worked for me 🙂
Maybe you could add the selection of only required deployments, the option is only available for this type of deployment. In case you want to make a bulk change on all deplyoments, i have used “Get-WmiObject -Namespace “root\SMS\Site_$SiteCode” -Class SMS_ApplicationAssignment | Where-Object {$_.OfferTypeID -eq 0 | select AssignmentID}” to get only the required ones.
Thanks for your feedback, Adam! Glad it worked!