Repository navigation
Uninstall Methods
This document provides a comprehensive overview of the uninstall script types and methods supported by the Cimian package management system.
Cimian supports multiple uninstall methods and script types to accommodate different software packaging formats and uninstallation requirements. The system follows Munki's proven approach to package uninstallability with both explicit control and automatic determination.
The uninstall execution path is InstallerService.UninstallAsync in cli/managedsoftwareupdate/Services/InstallerService.cs. The auto-uninstallability rules are defined by CatalogItem.IsUninstallable() in cli/managedsoftwareupdate/Models/UpdateModels.cs.
Cimian provides distinct support for flags and switches to accommodate different argument conventions used across Windows applications:
- Use forward slash prefix (
/) - Common in Windows installers and native applications
- Examples:
/silent,/norestart,/log=install.log
- Use dash prefix (
--or-) - Auto-default to
--prefix when not explicitly specified - Common in cross-platform and modern applications
- Examples:
--quiet,--force,--config=value
uninstaller is a list in the pkginfo model, but only the first entry (item.Uninstaller[0]) is executed by UninstallAsync. Supported type values are msi, exe, powershell/ps1, and msix/appx.
uninstaller:
- type: msi
product_code: "{12345678-1234-1234-1234-123456789012}"
switches:
- q # Becomes /q
- norestart # Becomes /norestart
- log=c:\temp\uninstall.log # Becomes /log=c:\temp\uninstall.log
flags:
- passive # Becomes --passive
- force # Becomes --force
- "-v" # Preserved as -v (explicit single dash)
- "--debug" # Preserved as --debug (explicit double dash)
# For type: exe / powershell, the executable or script is given in `command`:
uninstaller:
- type: exe
command: C:\Program Files\MyApp\uninstall.exe
switches:
- S # Becomes /S (silent mode)
- REMOVEDATA # Becomes /REMOVEDATA
flags:
- quiet # Becomes --quiet
- force # Becomes --force
- config=production # Becomes --config=productionSmart prefix detection: When switches/flags are given without an explicit prefix, UninstallerInfo.GetAllArgs() normalizes switches to / and flags to -- (single-character flags are normalized to -). Explicit prefixes (-v, --debug, /SILENT) are preserved as-is.
-
Type:
msi - Method: Uses Windows Installer's built-in uninstall functionality
-
Command:
msiexec /x <ProductCode> /qn /norestart(theProductCodeis read from theuninstallerblock, frominstalls[]oftype: msi, or from a legacyinstaller.product_codefield) -
Auto-uninstallable: Yes — when a
ProductCodeis available - Example:
installer:
type: msi
location: /packages/app-1.0.0.msi
# Automatically uninstallable via msiexec /x — ProductCode is captured by
# cimiimport/makepkginfo into installs[] (type: msi) and used at uninstall time.-
Type:
powershellorps1 -
Method: Executes the script defined by
command:via the script service -
Auto-uninstallable: Only when an explicit
uninstallerblock is defined; an installer oftype: ps1alone does NOT make the package self-uninstallable - Example:
installer:
type: ps1
location: /scripts/install-app.ps1
uninstaller:
- type: powershell
command: /scripts/uninstall-app.ps1
flags: [Force, Quiet]-
Type:
exe -
Method: Executes the uninstaller given in
command:with switches/flags/args -
Auto-uninstallable: Only when an explicit
uninstallerblock is defined - Example:
installer:
type: exe
location: /packages/app-installer.exe
uninstaller:
- type: exe
command: C:\Program Files\MyApp\uninstall.exe
switches: [S, NORESTART]-
Type:
nupkg - Install: Routed through sbin-installer with a Chocolatey fallback
-
Uninstall: Not currently handled as a first-class case —
UninstallAsynchas nonupkgbranch and falls back to the MSI handler, which requires aProductCode. To remove a nupkg-installed package, supply an explicituninstallerblock (e.g.,type: powershellinvokingchoco uninstall) or rely oninstalls[]-based tracking with a separate uninstall mechanism. -
Status: Planned — first-class
choco uninstallsupport is not yet implemented.
-
Type:
msix(also coversappx,msixbundle,appxbundle) -
Install command:
Add-AppxProvisionedPackage -Online -PackagePath <file> -SkipLicense, preceded by a preflight check that removes older per-user installs for the same identity if necessary (see "Install behavior" below). -
Uninstall command: Two steps in sequence, both required to fully remove the app:
-
Remove-AppxProvisionedPackage -Online -PackageName <PackageFullName>— removes the system-wide provisioning entry so new user profiles don't re-provision it. -
Get-AppxPackage -AllUsers -Name <IdentityName> | Remove-AppxPackage -AllUsers— removes any per-user registrations left behind (from the provisioned package itself, from vendor auto-updates, or from previous Store installs). Removing only the provisioned entry leaves the app fully functional for currently- registered users, which surprised us during testing and is inconsistent with what an admin expects from "uninstall".
-
-
Auto-uninstallable: Yes — cimiimport emits an
installsentry withtype: msixandidentity_name, and a matchinguninstallerblock. When the pkginfo has no explicit uninstaller, Cimian synthesizes one from the installs-array entry. -
How PackageFullName is resolved: at install time, the
PackageNamereturned byAdd-AppxProvisionedPackageis persisted toHKLM\SOFTWARE\ManagedInstalls\<Name>alongsideIdentityNameandInstallerType. Uninstall reads that value. If the registry entry is missing, uninstall falls back to a runtimeGet-AppxProvisionedPackage -Onlinequery filtered byidentity_name.
Cimian's MSIX install path runs a preflight against both Appx stores before calling
Add-AppxProvisionedPackage:
-
Higher version already installed → silent skip, no action. This is the
never-downgrade policy. If
Get-AppxPackage -AllUsersorGet-AppxProvisionedPackagereports the package at a version >= the catalog version, Cimian logs"Newer version installed"and returns success without calling the install cmdlet. Detection normally catches this case first, but the preflight is a safety net for races where the auto-updater runs between detection and install. -
Older per-user install blocks provisioning → auto-remediation. When a vendor
like Slack auto-updates itself, the app is registered per-user rather than
provisioned. An older per-user registration conflicts with
Add-AppxProvisionedPackageand surfaces as0x80070490 Element not found. Cimian auto-remediates by runningRemove-AppxPackage -AllUsersfor the identity first, then provisioning the catalog version. User data is preserved by Windows' Appx subsystem — Slack chat history, settings, and anything else under%LOCALAPPDATA%\Packages\<PackageFamilyName>\survives the remediation, so the user experience is "Slack restarted" not "Slack reset". This is worth communicating to end users in deployment announcements so nobody panics the first time they see a reinstall in their session. -
Nothing installed → direct provisioning. The common path: just
Add-AppxProvisionedPackage, capture the returnedPackageFullName, persist to the ManagedInstalls registry, done.
-
Signing certificate must be trusted on the client before install. MSIX
packages from vendors whose signing certs aren't in the Windows-managed trusted
roots (many legitimate open-source and third-party vendors) will fail install
with
0x800b0109 CERT_E_UNTRUSTEDROOT. Cimian does not auto-install publisher certs intoLocalMachine\TrustedPublisher— that's an explicit policy decision, because once a cert is inTrustedPublisher, any future package signed by that publisher can sideload without further review. Use one of these pre-deployment channels instead:-
Group Policy:
Computer Configuration → Windows Settings → Security Settings → Public Key Policies → Trusted Publishers, importing the vendor's code-signing cert. - Intune / MDM cert profile: deploy a trusted-publisher certificate profile from your MDM tenant before the MSIX package is assigned.
-
One-off manual import (dev machines / pilot testing):
Import-Certificate -FilePath vendor.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisherVendor certs from Microsoft, Slack, Adobe, etc. are typically already trusted via Windows' cross-signing roots and don't need this step. The failure mode is obvious in the Cimian install log, so you'll know immediately which packages need this treatment.
-
Group Policy:
- License files (
.xml) required by Store-signed apps — usepreinstall_scripttoAdd-AppxProvisionedPackage -LicensePath .... - Per-architecture nested manifest extraction from
.msixbundlepayloads. Bundles install transparently throughAdd-AppxProvisionedPackage, but cimiimport only parses the top-level bundle manifest into pkginfo metadata. - Auto-extraction of the MSIX signing cert during import (considered and rejected — see "Requirements" above for why).
installer:
type: msix
location: /apps/comms/Slack-x64-4.45.69.0.msix
installs:
- type: msix
identity_name: com.tinyspeck.slackdesktop
version: 4.45.69.0
uninstaller:
- type: msix
identity_name: com.tinyspeck.slackdesktop-
Status: Planned — not yet implemented. The current
CatalogItemmodel does NOT define anuninstalls:property, andInstallerService.UninstallAsyncdoes not consume one. The dispatch path only branches onitem.Uninstaller[0](the first entry of theuninstaller:list) oritem.UninstallScript, with self-uninstallable fallbacks for MSI and MSIX. A typed multi-operationuninstalls:array (file/directory/registry/application removal) is a Munki-inspired feature that has not been wired into the C# code yet. -
Workaround today: For multi-step cleanup, use a
postuninstall_script(PowerShell) that runs afterUninstallAsyncsucceeds.
- Type: Pre/post install scripts support batch format
-
Method: Detects batch scripts by content (
@echo off,rem,::) - Support: Limited to pre/post install scripts
- Detection: Automatic based on script content
CatalogItem.IsUninstallable() returns true when uninstallable is not explicitly false AND any of the following applies:
-
Explicit uninstaller defined —
uninstaller:has at least one entryuninstaller: - type: exe command: C:\Program Files\MyApp\uninstall.exe switches: [S]
-
Registry tracking defined —
check.registry.nameis setcheck: registry: name: MyApplication version: 1.0.0
Note:
IsUninstallable()returns true in this case, butUninstallAsyncwill only succeed if there is also a usable uninstaller (explicituninstaller:block, aninstalls[]MSI/MSIX entry as below, or anuninstall_script). Otherwise it returns "No uninstaller defined". -
Self-uninstallable MSI via
installs[]— aninstallsentry oftype: msiwith aproduct_code.UninstallAsyncsynthesizes an MSI uninstaller from this entry.installs: - type: msi product_code: "{12345678-1234-1234-1234-123456789012}" version: 1.0.0
-
Self-uninstallable MSI via legacy
installer.product_code— for pkginfos written beforeproduct_codemoved toinstalls[]. -
Self-uninstallable MSIX/APPX via
installs[]— aninstallsentry oftype: msixortype: appxwithidentity_name.UninstallAsyncsynthesizes an MSIX uninstaller using the two-stepRemove-AppxProvisionedPackage+Remove-AppxPackageflow.installs: - type: msix identity_name: com.tinyspeck.slackdesktop version: 4.45.69.0
You can explicitly control uninstallability:
# Prevent uninstalling
uninstallable: false
# Allow uninstalling
uninstallable: true
# Auto-determine (default)
uninstallable: null # or omit the keyInstallerService.UninstallAsync executes the following steps for an item flagged for uninstall:
-
Run
preuninstall_scriptif defined; abort on failure. -
Dispatch by uninstaller type (first entry of
uninstaller:):-
msi→msiexec /x <ProductCode> /qn /norestartplus any switches/flags -
exe→ executecommand:with combined switches/flags/args -
powershell/ps1→ run the script incommand:via the script service -
msix/appx→ two-stepRemove-AppxProvisionedPackage+Remove-AppxPackage -AllUsers - Anything else falls through to the MSI handler (which will fail without a
ProductCode)
-
-
If no
uninstaller:block, fall back to:-
uninstall_script(raw script body) if present, or - A synthesized MSI uninstall if
installs[]carries aproduct_code, or - A synthesized MSIX uninstall if
installs[]carriestype: msix/appxwithidentity_name.
-
-
Run
postuninstall_scriptif the uninstall succeeded (warns but does not fail on script error). -
Unregister from
HKLM\SOFTWARE\ManagedInstalls\<Name>to clear installation tracking.
- Automatically removes dependent packages first
- Handles update relationships
- Prevents orphaned dependencies
- Checks for running applications that would block uninstall
- Provides warnings about blocking processes
- Allows for graceful handling of locked files
- Continues with other packages if some fail
- Provides detailed logging of failures
- Returns success if any packages were successfully removed
name: critical-system-component
version: 1.0.0
installer:
type: msi
location: /packages/critical-1.0.0.msi
uninstallable: false # Explicitly prevent removalname: visual-studio-code
version: 1.85.0
installer:
type: exe
location: /packages/VSCode-1.85.0.exe
uninstaller:
- type: exe
command: C:\Program Files\Microsoft VS Code\unins000.exe
switches: [SILENT, NORESTART]
# Auto-determined as uninstallable because uninstaller is definedname: configuration-script
version: 1.0.0
installer:
type: ps1
location: /scripts/configure.ps1
# Auto-determined as non-uninstallable (no tracking)name: maintenance-task
version: 1.0.0
installer:
type: ps1
location: /scripts/maintenance.ps1
OnDemand: true
# Auto-determined as non-uninstallable- Uninstall scripts are executed with the privileges of the
managedsoftwareupdateprocess (typically SYSTEM when run via the scheduled task / service) - PowerShell scripts run via the script service with
-NoProfileand-ExecutionPolicy Bypass - MSI uninstalls use quiet mode (
/qn /norestart) by default - MSIX/APPX uninstall is a two-step PowerShell flow (
Remove-AppxProvisionedPackagethenRemove-AppxPackage -AllUsers) — see the MSIX section above
-
Provide an explicit
uninstaller:block for EXE-based installations -
Use
installs[]withproduct_codeoridentity_nameto enable self-uninstall for MSI/MSIX without writing a separate uninstaller -
Test uninstall paths thoroughly before deployment (use
--checkonlyto dry-run detection) -
Mark system-critical packages as
uninstallable: falseto block accidental removal
- Batch scripts (
.bat) are supported only for pre/post install operations, not as primary uninstallers - OnDemand items cannot be uninstalled as they're not considered "installed"
- EXE installers without an explicit
uninstaller:block are not automatically uninstallable - PowerShell-only installers without an explicit
uninstaller:block are not automatically uninstallable - The
uninstaller:field is a list but only the first entry is dispatched — multi-step uninstall must currently be handled by a single PowerShell script (orpostuninstall_script) - A typed
uninstalls:array (Munki-style file/directory/registry/application cleanup) is not implemented — see the "Uninstalls Array" section above
This document reflects the current implementation of Cimian's uninstall system as of the latest version. For the most up-to-date information, refer to the source code and official documentation.
Cimian — managed software deployment for Windows, modelled on Munki. Apache License 2.0. Source and issues at windowsadmins/cimian.
Start here
Repository
Authoring packages
- Installation Checks
- Uninstall Methods
- Uninstallable Key
- Conditional Items
- Importing EXE Bundle Installers
- PowerShell Execution Policy
- Chocolatey Shim Prevention
- Managed Profiles and Apps
Client runtime
Diagnostics
- Troubleshooting
- Logging
- Error Reporting
- Item Source Traceability
- CimiTrigger Troubleshooting
- Privilege Elevation
Integrations
Contributing