← Back to articles Intune

Remove SCCM Client via Intune with PowerShell

Remove SCCM Client via Intune with PowerShell

What This Script Does

When you're migrating endpoints from Configuration Manager (SCCM/MECM) to a pure Intune-managed state, the built-in ccmsetup.exe /uninstall command rarely gets the job done cleanly. It leaves behind registry keys, WMI namespaces, stale services, and the smscfg.ini machine identity file — all of which can cause ghost enrollments, failed Intune policy evaluations, and co-management authority conflicts that are genuinely painful to troubleshoot at scale.

This script performs a complete, surgical removal of the SCCM client: it runs the native uninstaller, waits for it to finish, then systematically cleans up every artifact the uninstaller misses — configuration files, registry hives (both native and WOW64), WMI namespaces, and the CcmExec service registration. It's designed to run as an Intune Win32 app or a Platform Script and exits with standard 0/1 codes so Intune can report success or failure accurately.

Prerequisites:

  • PowerShell 5.1 or later (the CIM cmdlets used here are inbox on all supported Windows 10/11 versions)
  • The script must run in SYSTEM context — deploy via Intune with "Run as account: System"
  • No additional PowerShell modules required — everything uses built-in cmdlets
  • If deploying as a Win32 app, package with IntuneWinAppUtil.exe and set the detection rule to check for the absence of HKLM:\SOFTWARE\Microsoft\CCM

The Complete Script

#Requires -Version 5.1
<#
.SYNOPSIS
    Fully removes the SCCM/ConfigMgr client and all associated artifacts.

.DESCRIPTION
    Runs ccmsetup.exe /uninstall, then cleans registry keys, WMI namespaces,
    configuration files, and service registrations left behind by the native
    uninstaller. Designed for Intune deployment in SYSTEM context.

.NOTES
    Author      : msendpoint.com
    Version     : 1.2
    Tested On   : Windows 10 21H2, Windows 11 22H2/23H2
    Deploy As   : Intune Win32 App or Platform Script (System context)
    Exit Codes  : 0 = Success, 1 = Failure
#>
[CmdletBinding()]
param()

$success = $true
$ccmSetupPath = "$($Env:SystemRoot)\ccmsetup\ccmsetup.exe"

function Write-Log {
    param([string]$Message, [string]$Level = 'INFO')
    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    $colour = switch ($Level) {
        'INFO'    { 'Cyan' }
        'WARN'    { 'Yellow' }
        'SUCCESS' { 'Green' }
        'ERROR'   { 'Red' }
        default   { 'White' }
    }
    Write-Host "[$timestamp][$Level] $Message" -ForegroundColor $colour
}

try {

    # -------------------------------------------------------------------------
    # STEP 1: Run the native SCCM uninstaller if the client is present
    # -------------------------------------------------------------------------
    if (Test-Path -Path $ccmSetupPath) {
        Write-Log "SCCM client detected. Starting native uninstall..." 'INFO'

        $uninstallArgs = '/uninstall'
        $process = Start-Process -FilePath $ccmSetupPath `
                                 -ArgumentList $uninstallArgs `
                                 -Wait `
                                 -PassThru `
                                 -NoNewWindow

        Write-Log "ccmsetup.exe exited with code: $($process.ExitCode)" 'INFO'

        # Give the uninstaller time to release file handles and stop services
        Write-Log "Waiting 60 seconds for uninstaller to complete cleanup..." 'WARN'
        Start-Sleep -Seconds 60
    } else {
        Write-Log "ccmsetup.exe not found at expected path. Skipping native uninstall." 'WARN'
    }

    # -------------------------------------------------------------------------
    # STEP 2: Stop and remove the CcmExec service if still running
    # -------------------------------------------------------------------------
    $ccmService = Get-Service -Name 'CcmExec' -ErrorAction SilentlyContinue
    if ($ccmService) {
        Write-Log "CcmExec service still present. Stopping and removing..." 'WARN'
        Stop-Service -Name 'CcmExec' -Force -ErrorAction SilentlyContinue
        Start-Sleep -Seconds 5
        & sc.exe delete CcmExec | Out-Null
        Write-Log "CcmExec service removed." 'SUCCESS'
    }

    # -------------------------------------------------------------------------
    # STEP 3: Remove SCCM configuration file (machine identity)
    # -------------------------------------------------------------------------
    Write-Log "Removing SCCM configuration files..." 'WARN'
    $filesToRemove = @(
        "$($Env:WinDir)\smscfg.ini",
        "$($Env:WinDir)\System32\ccmsetup"
    )
    foreach ($file in $filesToRemove) {
        if (Test-Path -Path $file) {
            Remove-Item -Path $file -Force -Recurse -ErrorAction SilentlyContinue
            Write-Log "Removed: $file" 'INFO'
        }
    }

    # -------------------------------------------------------------------------
    # STEP 4: Remove SCCM registry keys
    # -------------------------------------------------------------------------
    Write-Log "Removing SCCM registry keys..." 'WARN'
    $registryPaths = @(
        'HKLM:\Software\Microsoft\SystemCertificates\SMS\Certificates',
        'HKLM:\SOFTWARE\Microsoft\CCM',
        'HKLM:\SOFTWARE\Wow6432Node\Microsoft\CCM',
        'HKLM:\SOFTWARE\Microsoft\SMS',
        'HKLM:\SOFTWARE\Wow6432Node\Microsoft\SMS',
        'HKLM:\Software\Microsoft\CCMSetup',
        'HKLM:\Software\Wow6432Node\Microsoft\CCMSetup',
        'HKLM:\SYSTEM\CurrentControlSet\Services\CcmExec',
        'HKLM:\SYSTEM\CurrentControlSet\Services\ccmsetup',
        'HKLM:\SYSTEM\CurrentControlSet\Services\smstsmgr'
    )
    foreach ($path in $registryPaths) {
        if (Test-Path -Path $path) {
            Remove-Item -Path $path -Force -Recurse -ErrorAction SilentlyContinue
            Write-Log "Registry key removed: $path" 'INFO'
        }
    }

    # -------------------------------------------------------------------------
    # STEP 5: Remove SCCM-related WMI namespaces
    # -------------------------------------------------------------------------
    Write-Log "Removing SCCM WMI namespaces..." 'WARN'

    # Brief pause to allow WMI repository to settle after uninstall
    Start-Sleep -Seconds 5

    $wmiNamespaceQueries = @(
        "Select * From __Namespace Where Name='CCM'",
        "Select * From __Namespace Where Name='CCMVDI'",
        "Select * From __Namespace Where Name='SmsDm'",
        "Select * From __Namespace Where Name='sms'"
    )
    foreach ($query in $wmiNamespaceQueries) {
        $ns = Get-CimInstance -Query $query -Namespace 'root' -ErrorAction SilentlyContinue
        if ($ns) {
            $ns | Remove-CimInstance -ErrorAction SilentlyContinue
            Write-Log "WMI namespace removed: $($ns.Name)" 'INFO'
        }
    }

    # -------------------------------------------------------------------------
    # STEP 6: Final sync delay for Intune reporting
    # -------------------------------------------------------------------------
    Write-Log "SCCM removal completed. Pausing for Intune sync..." 'SUCCESS'
    Start-Sleep -Seconds 30

} catch {
    Write-Log "An error occurred during SCCM removal: $($_.Exception.Message)" 'ERROR'
    Write-Log "Stack Trace: $($_.ScriptStackTrace)" 'ERROR'
    $success = $false
}

# Final exit code for Intune reporting
if ($success) {
    Write-Log "Script completed successfully. Exiting 0." 'SUCCESS'
    Exit 0
} else {
    Write-Log "Script completed with errors. Exiting 1." 'ERROR'
    Exit 1
}

How It Works

Why the Native Uninstaller Isn't Enough

The SCCM client uninstaller (ccmsetup.exe /uninstall) is asynchronous — it spawns child processes and returns before the work is done. If you simply call it and move on, your cleanup steps will race against a still-running uninstall. The 60-second Start-Sleep after Start-Process -Wait is deliberate and based on real-world observation: on slower or heavily loaded endpoints, the uninstaller process tree can keep running well after ccmsetup.exe itself exits. If you see intermittent failures in your environment, bump this to 90 seconds.

The Registry Cleanup — WOW64 Matters

The script targets both the native 64-bit registry hive and the Wow6432Node equivalent. This matters because the SCCM client writes to both locations on 64-bit Windows, and leaving the WOW64 keys behind is a common reason why re-enrollment or Intune health checks incorrectly report the client as still present. The smstsmgr and ccmsetup service keys under SYSTEM\CurrentControlSet\Services are frequently orphaned and worth explicitly targeting.

WMI Namespace Removal via CIM

The SCCM client registers several WMI namespaces under ROOT\ — most notably ROOT\CCM — that persist after uninstall and can interfere with Intune's own WMI-based inventory and compliance checks. Using Get-CimInstance against the __Namespace system class is the cleanest way to find and remove these without hardcoding full WMI paths that may vary. The 5-second sleep before this block gives the WMI repository time to release locks held by the uninstaller.

Exit Codes and Intune Reporting

Every step is wrapped in a single try/catch block that sets $success = $false on any terminating error. The script exits 0 on success and 1 on failure. When deployed as a Win32 app in Intune, this maps directly to the install success/failure state visible in the app installation report. Non-terminating errors (files or keys that simply don't exist) are suppressed with -ErrorAction SilentlyContinue because their absence is expected on endpoints where a partial uninstall already occurred.

Usage & Parameters

Running locally for testing (elevated PowerShell):

# Run directly — no parameters needed
.\Remove-SCCMClient.ps1

# Sample output:
# [2024-11-14 09:12:03][INFO] SCCM client detected. Starting native uninstall...
# [2024-11-14 09:12:03][INFO] ccmsetup.exe exited with code: 0
# [2024-11-14 09:12:03][WARN] Waiting 60 seconds for uninstaller to complete cleanup...
# [2024-11-14 09:13:05][WARN] Removing SCCM configuration files...
# [2024-11-14 09:13:05][INFO] Removed: C:\Windows\smscfg.ini
# [2024-11-14 09:13:05][WARN] Removing SCCM registry keys...
# [2024-11-14 09:13:05][INFO] Registry key removed: HKLM:\SOFTWARE\Microsoft\CCM
# [2024-11-14 09:13:05][INFO] Registry key removed: HKLM:\SOFTWARE\Wow6432Node\Microsoft\CCM
# [2024-11-14 09:13:05][WARN] Removing SCCM WMI namespaces...
# [2024-11-14 09:13:10][INFO] WMI namespace removed: CCM
# [2024-11-14 09:13:10][SUCCESS] SCCM removal completed. Pausing for Intune sync...
# [2024-11-14 09:13:40][SUCCESS] Script completed successfully. Exiting 0.

Deploying as an Intune Win32 app — install command:

powershell.exe -ExecutionPolicy Bypass -NoProfile -File Remove-SCCMClient.ps1

Detection rule (registry-based — key should NOT exist after successful removal):

# Detection Method: Registry
# Key Path : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CCM
# Detection type: Key exists = NOT detected (app is "installed" when key is absent)
# Or use a script-based detection:
if (-not (Test-Path 'HKLM:\SOFTWARE\Microsoft\CCM')) {
    Write-Host "Detected"
    Exit 0
} else {
    Exit 1
}

See the Microsoft Learn guide on adding Win32 apps to Intune for full packaging and assignment steps.

Customization Ideas

  • Log to a file for fleet-wide audit trails: Replace Write-Host in the Write-Log function with Add-Content -Path "C:\Windows\Temp\SCCMRemoval.log" — Intune's Win32 app log collection won't grab console output, but it will pick up files in %TEMP% if you configure log collection.
  • Add a pre-flight check for co-management workload status: Before running the uninstall, query HKLM:\SOFTWARE\Microsoft\CCM\CoManagementHandler to confirm the device's co-management authority is set to Intune for the relevant workloads. Uninstalling the client while workloads are still SCCM-authoritative will leave policies unmanaged.
  • Scope to a pilot group first: Assign the Win32 app to a dedicated AAD group of 20-30 test machines before broad deployment. Check the Intune device install status report after 24 hours and verify endpoints check in cleanly with dsregcmd /status showing MDMEnrolled: YES and no co-management conflicts.
  • Handle the ccmsetup.exe not found scenario gracefully: The current script logs a warning and continues. If you want a stricter posture — fail fast if the client isn't present because you're targeting the wrong group — change the else branch to Exit 0 (already clean) rather than continuing through cleanup steps that will silently no-op.
  • Chain into an Autopilot pre-provisioning workflow: If you're re-imaging and re-enrolling rather than in-place migrating, this script fits cleanly as a step in an Autopilot pre-provisioning or OSD task sequence to ensure no SCCM artifacts survive into the new enrollment identity.

🎓 Ready to go deeper?

Practice real MD-102 exam questions, get AI feedback on your weak areas, and fast-track your Intune certification.

Start Free Practice → Book a Session
Souhaiel Morhag
Souhaiel Morhag
Microsoft Endpoint & Modern Workplace Engineer

Souhaiel Morhag is a Microsoft Intune and endpoint management specialist with hands-on experience deploying and securing enterprise environments across Microsoft 365. He founded MSEndpoint.com to share practical, real-world guides for IT admins navigating Microsoft technologies — and built the MSEndpoint Academy at app.msendpoint.com/academy, a dedicated learning platform for professionals preparing for the MD-102 (Microsoft 365 Endpoint Administrator) certification. Through in-depth articles and AI-powered practice exams, Souhaiel helps IT teams move faster and certify with confidence.

Related Articles

Popular on MSEndpoint