ResettoOOBE.ps1
|
<#PSScriptInfo .VERSION 1.0.1 .GUID 9d7d5c78-88d0-4d39-baa8-91a7e7f0f8aa .AUTHOR Dylan Descamps .COMPANYNAME .COPYRIGHT .TAGS .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES #> <# .DESCRIPTION Prepare Windows for Autopilot OOBE after SCCM Task Sequence #> Param() $ScriptBlock = @' $LogPath = "C:\Windows\Temp\PrepareOOBE.log" function Write-Log { param( [string]$Message, [string]$Level = "Info" ) $Stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Add-Content ` -Path $LogPath ` -Value "[$Stamp] [$Level] $Message" ` -ErrorAction SilentlyContinue } Write-Log "PrepareOOBE started" # Leave Azure AD registration Write-Log "Running dsregcmd /leave" Start-Process ` -FilePath "dsregcmd.exe" ` -ArgumentList "/leave" ` -Wait ` -NoNewWindow ` -ErrorAction SilentlyContinue # --------------------------------------------------------------- # STEP 1 - Kill all CCM processes so nothing holds file locks # --------------------------------------------------------------- Write-Log "Killing CCM processes" @( "CcmExec", "ccmsetup", "CMRcService", "CmRcService", "smsexec", "smsswd", "SensorWDService", "SensorManagedProvider", "ccmrestart", "SCNotification" ) | ForEach-Object { $procs = Get-Process -Name $_ -ErrorAction SilentlyContinue if ($procs) { $procs | Stop-Process -Force -ErrorAction SilentlyContinue Write-Log "Killed process: $_" } } # --------------------------------------------------------------- # STEP 2 - Stop and delete CCM services # --------------------------------------------------------------- Write-Log "Stopping and removing CCM services" @("CcmExec", "ccmsetup", "smstsmgr", "CmRcService") | ForEach-Object { $svc = Get-Service -Name $_ -ErrorAction SilentlyContinue if ($svc) { Stop-Service -Name $_ -Force -ErrorAction SilentlyContinue Start-Process "sc.exe" -ArgumentList "delete $_" -Wait -NoNewWindow -ErrorAction SilentlyContinue Write-Log "Removed service: $_" } } # --------------------------------------------------------------- # STEP 3 - Uninstall SCCM client and wait for it to fully finish # --------------------------------------------------------------- if (Test-Path "C:\Windows\ccmsetup\ccmsetup.exe") { Write-Log "Starting ccmsetup /Uninstall" Start-Process ` -FilePath "C:\Windows\ccmsetup\ccmsetup.exe" ` -ArgumentList "/Uninstall" ` -NoNewWindow ` -ErrorAction SilentlyContinue # ccmsetup /Uninstall launches a child process and exits immediately. # Poll until CcmExec service is gone and no ccmsetup child process remains. $TimeoutSec = 300 $Elapsed = 0 $Interval = 5 Write-Log "Waiting for CCM uninstall to complete (timeout: ${TimeoutSec}s)" do { Start-Sleep -Seconds $Interval $Elapsed += $Interval $ServiceGone = -not (Get-Service -Name "CcmExec" -ErrorAction SilentlyContinue) $ProcessGone = -not (Get-Process -Name "ccmsetup" -ErrorAction SilentlyContinue) $SetupGone = -not (Test-Path "C:\Windows\ccmsetup\ccmsetup.exe") Write-Log "Uninstall check at ${Elapsed}s - Service gone: $ServiceGone | Process gone: $ProcessGone | Setup exe gone: $SetupGone" } until (($ServiceGone -and $ProcessGone) -or $Elapsed -ge $TimeoutSec) if ($ServiceGone -and $ProcessGone) { Write-Log "CCM uninstall confirmed complete after ${Elapsed}s" } else { Write-Log "WARNING: CCM uninstall did not complete within ${TimeoutSec}s - continuing anyway" -Level Warning } } else { Write-Log "ccmsetup.exe not found - skipping uninstall" -Level Warning } # --------------------------------------------------------------- # STEP 4 - Remove CCM WMI namespaces (these hold DLL locks) # --------------------------------------------------------------- Write-Log "Removing CCM WMI namespaces" @("root\ccm", "root\ccm\Policy", "root\ccm\SoftwareMeteringAgent", "root\SmsDm", "root\cimv2\SMS") | ForEach-Object { try { $ns = $_ Get-WmiObject -Namespace $ns -Class "__Namespace" -ErrorAction Stop | Out-Null ([wmiclass]"\\.\$ns`:__Namespace").Delete() | Out-Null Write-Log "Removed WMI namespace: $ns" } catch { Write-Log "WMI namespace not found (OK): $_" } } # --------------------------------------------------------------- # STEP 5 - Remove CCM scheduled tasks # --------------------------------------------------------------- Write-Log "Removing CCM scheduled tasks" Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskPath -match 'Microsoft\\Configuration Manager' -or $_.TaskName -match 'CCM|SMS|SCCM' } | ForEach-Object { Unregister-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath -Confirm:$false -ErrorAction SilentlyContinue Write-Log "Removed scheduled task: $($_.TaskName)" } # --------------------------------------------------------------- # STEP 6 - Remove registry keys # --------------------------------------------------------------- Write-Log "Removing SCCM registry entries" @( "HKLM:\SOFTWARE\Microsoft\CCM", "HKLM:\SOFTWARE\Microsoft\CCMSetup", "HKLM:\SOFTWARE\Microsoft\SMS", "HKLM:\SOFTWARE\Microsoft\SystemCertificates\SMS", "HKLM:\SOFTWARE\Wow6432Node\Microsoft\CCM", "HKLM:\SOFTWARE\Wow6432Node\Microsoft\SMS", "HKLM:\SYSTEM\CurrentControlSet\Services\CcmExec", "HKLM:\SYSTEM\CurrentControlSet\Services\ccmsetup", "HKLM:\SYSTEM\CurrentControlSet\Services\smstsmgr", "HKLM:\SYSTEM\CurrentControlSet\Services\CmRcService" ) | ForEach-Object { if (Test-Path $_) { Remove-Item -Path $_ -Recurse -Force -ErrorAction SilentlyContinue if (-not (Test-Path $_)) { Write-Log "Verified removed: $_" } else { Write-Log "WARNING: Failed to remove: $_" -Level Warning } } else { Write-Log "Not found (OK): $_" } } # --------------------------------------------------------------- # STEP 7 - Remove CCM directories using robocopy empty-folder trick # (bypasses locks that Remove-Item cannot break) # --------------------------------------------------------------- Write-Log "Removing SCCM directories" function Remove-LockedDirectory { param([string]$Path) if (-not (Test-Path $Path)) { Write-Log "Not found (OK): $Path" return } # Robocopy mirrors an empty folder over the target, clearing contents without lock issues $Empty = "$env:TEMP\__empty_robocopy" New-Item -ItemType Directory -Path $Empty -Force -ErrorAction SilentlyContinue | Out-Null Start-Process "robocopy.exe" -ArgumentList "`"$Empty`" `"$Path`" /MIR /NFL /NDL /NJH /NJS /R:1 /W:1" -Wait -NoNewWindow -ErrorAction SilentlyContinue Remove-Item $Empty -Force -ErrorAction SilentlyContinue # Now take ownership and delete the emptied shell Start-Process "takeown.exe" -ArgumentList "/f `"$Path`" /r /d y" -Wait -NoNewWindow -ErrorAction SilentlyContinue Start-Process "icacls.exe" -ArgumentList "`"$Path`" /grant administrators:F /t" -Wait -NoNewWindow -ErrorAction SilentlyContinue Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue if (Test-Path $Path) { Write-Log "WARNING: $Path still present after forced removal" -Level Warning } else { Write-Log "Verified removed: $Path" } } @( "C:\Windows\CCM", "C:\Windows\CCMCache", "C:\Windows\CCMSetup", "C:\MININT" ) | ForEach-Object { Remove-LockedDirectory $_ } # Remove single files @("C:\Windows\SMSCFG.ini") | ForEach-Object { if (Test-Path $_) { Remove-Item -Path $_ -Force -ErrorAction SilentlyContinue Write-Log "Removed: $_" } } # Remove SMS .mif files $MifFiles = Get-ChildItem -Path "C:\Windows" -Filter "sms*.mif" -ErrorAction SilentlyContinue if ($MifFiles.Count -gt 0) { $MifFiles | Remove-Item -Force -ErrorAction SilentlyContinue Write-Log "Removed $($MifFiles.Count) SMS .mif files" } else { Write-Log "No SMS .mif files found" } # --------------------------------------------------------------- # STEP 8 - Remove MDM / co-management enrollment leftovers # These prevent Autopilot ZTID from being recognised # --------------------------------------------------------------- Write-Log "Removing MDM and co-management enrollment leftovers" # Remove all existing MDM enrollment GUIDs $EnrollmentRoot = "HKLM:\SOFTWARE\Microsoft\Enrollments" if (Test-Path $EnrollmentRoot) { Get-ChildItem $EnrollmentRoot -ErrorAction SilentlyContinue | ForEach-Object { Remove-Item -Path $_.PSPath -Recurse -Force -ErrorAction SilentlyContinue Write-Log "Removed enrollment: $($_.PSChildName)" } } # Remove enrollment status and tracking keys @( "HKLM:\SOFTWARE\Microsoft\Enrollments", "HKLM:\SOFTWARE\Microsoft\EnterpriseResourceManager", "HKLM:\SOFTWARE\Microsoft\PolicyManager\current", "HKLM:\SOFTWARE\Microsoft\PolicyManager\device", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MDMCommon", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MDM", "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\MDM", "HKLM:\SOFTWARE\Microsoft\CCM\CoManagementHandler", "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin" ) | ForEach-Object { if (Test-Path $_) { Remove-Item -Path $_ -Recurse -Force -ErrorAction SilentlyContinue Write-Log "Removed: $_" } else { Write-Log "Not found (OK): $_" } } # Remove MDM enrollment scheduled tasks Write-Log "Removing MDM enrollment scheduled tasks" @( "\Microsoft\Windows\EnterpriseMgmt", "\Microsoft\Windows\EnterpriseMgmtNoncritical" ) | ForEach-Object { Get-ScheduledTask -TaskPath "$_\*" -ErrorAction SilentlyContinue | ForEach-Object { Unregister-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath -Confirm:$false -ErrorAction SilentlyContinue Write-Log "Removed MDM task: $($_.TaskPath)$($_.TaskName)" } } # Remove MDM device certificates that belonged to the old enrollment Write-Log "Removing old MDM device certificates" Get-ChildItem "Cert:\LocalMachine\My" -ErrorAction SilentlyContinue | Where-Object { $_.Issuer -match "MS-Organization-Access|Microsoft Intune MDM Device CA|MDM" } | ForEach-Object { Remove-Item -Path "Cert:\LocalMachine\My\$($_.Thumbprint)" -Force -ErrorAction SilentlyContinue Write-Log "Removed MDM certificate: $($_.Subject) [$($_.Thumbprint)]" } # Re-run dsregcmd /leave to ensure AAD join state is fully cleared after above cleanup Write-Log "Re-running dsregcmd /leave to flush AAD state" Start-Process "dsregcmd.exe" -ArgumentList "/leave" -Wait -NoNewWindow -ErrorAction SilentlyContinue # --------------------------------------------------------------- # STEP 9 - Remove TS post action, unattend files, setup remnants # --------------------------------------------------------------- Write-Log "Removing SMSTSPostAction" Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\SMS\Task Sequence" -Name "SMSTSPostAction" -ErrorAction SilentlyContinue Write-Log "Removing cached unattend files" @("C:\Windows\Panther\Unattend.xml", "C:\Windows\Panther\unattend") | ForEach-Object { if (Test-Path $_) { Remove-Item -Path $_ -Recurse -Force -ErrorAction SilentlyContinue Write-Log "Removed $_" } } Write-Log "Removing setup state remnants" Remove-ItemProperty -Path "HKLM:\SYSTEM\Setup" -Name "CmdLine" -ErrorAction SilentlyContinue Remove-ItemProperty -Path "HKLM:\SYSTEM\Setup" -Name "SetupType" -ErrorAction SilentlyContinue $TagPath = "C:\Windows\Setup\Scripts\DisableCMDRequest.TAG" if (Test-Path $TagPath) { Remove-Item -Path $TagPath -Force -ErrorAction SilentlyContinue Write-Log "Removed DisableCMDRequest.TAG" } else { Write-Log "DisableCMDRequest.TAG not found" } # Launch OOBE Write-Log "Launching sysprep /oobe /reboot /quiet" $Sysprep = Start-Process ` -FilePath "C:\Windows\System32\Sysprep\Sysprep.exe" ` -ArgumentList "/oobe /reboot /quiet" ` -NoNewWindow ` -Wait ` -PassThru Write-Log "Sysprep exited with code: $($Sysprep.ExitCode)" if ($Sysprep.ExitCode -ne 0) { Write-Log "Sysprep failed - dumping Panther log" -Level Warning $PantherLog = "C:\Windows\System32\Sysprep\Panther\setupact.log" if (Test-Path $PantherLog) { Get-Content $PantherLog -Tail 40 -ErrorAction SilentlyContinue | ForEach-Object { Add-Content -Path $LogPath -Value " [Panther] $_" -ErrorAction SilentlyContinue } } } Write-Log "PrepareOOBE finished" '@ New-Item ` -ItemType Directory ` -Path "C:\Windows\Temp" ` -Force ` -ErrorAction SilentlyContinue | Out-Null New-Item ` -ItemType File ` -Path "C:\Windows\Temp\PrepareOOBE.ps1" ` -Value $ScriptBlock ` -Force | Out-Null Write-Output "PrepareOOBE.ps1 created at C:\Windows\Temp\PrepareOOBE.ps1" |