PrepareOOBE.ps1


<#PSScriptInfo
 
.VERSION 1.6
 
.GUID b28d9ccb-b2e5-4c78-8270-2a53c80ab0f6
 
.AUTHOR admin.descampd
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
.PRIVATEDATA
 
#>


<#
 
.DESCRIPTION
 1.6
 
#>
 
Param()

# Version : 1.4
# Changelog:
# 1.0 - Initial release
# 1.1 - Added force-removal of locked CCM directories via robocopy
# 1.2 - Added MDM/co-management enrollment cleanup for Autopilot ZTID
# 1.3 - Added CloudDomainJoin and OOBE state purge to prevent co-management re-enrollment
# 1.4 - Verbose logging on every step, dsregcmd status capture, cleanup summary

$ScriptBlock = @'
$LogPath = "C:\Windows\Temp\PrepareOOBE.log"
$Script:Removed = 0
$Script:NotFound = 0
$Script:Failed = 0

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
}

function Write-Step {
    param([string]$Title)
    $Sep = "-" * 60
    Write-Log ""
    Write-Log $Sep
    Write-Log " $Title"
    Write-Log $Sep
}

function Remove-RegKey {
    param([string]$Path)
    if (Test-Path $Path) {
        $children = (Get-ChildItem $Path -Recurse -ErrorAction SilentlyContinue).Count
        Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue
        if (-not (Test-Path $Path)) {
            Write-Log " [REMOVED] $Path ($children sub-items)"
            $Script:Removed++
        } else {
            Write-Log " [FAILED] $Path - still present after removal" -Level Warning
            $Script:Failed++
        }
    } else {
        Write-Log " [NOT FOUND] $Path"
        $Script:NotFound++
    }
}

function Remove-LockedDirectory {
    param([string]$Path)
    if (-not (Test-Path $Path)) {
        Write-Log " [NOT FOUND] $Path"
        $Script:NotFound++
        return
    }

    $FileCount = (Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue).Count
    Write-Log " [FOUND] $Path ($FileCount files/folders) - starting robocopy wipe"

    $Empty = "$env:TEMP\__empty_robocopy"
    New-Item -ItemType Directory -Path $Empty -Force -ErrorAction SilentlyContinue | Out-Null

    $RoboResult = Start-Process "robocopy.exe" `
        -ArgumentList "`"$Empty`" `"$Path`" /MIR /NFL /NDL /NJH /NJS /R:1 /W:1" `
        -Wait -NoNewWindow -PassThru -ErrorAction SilentlyContinue
    Write-Log " [ROBOCOPY] Exit code: $($RoboResult.ExitCode) (<=7 = success)"

    Remove-Item $Empty -Force -Recurse -ErrorAction SilentlyContinue

    Start-Process "takeown.exe" -ArgumentList "/f `"$Path`" /r /d y" -Wait -NoNewWindow -ErrorAction SilentlyContinue | Out-Null
    Start-Process "icacls.exe" -ArgumentList "`"$Path`" /grant administrators:F /t" -Wait -NoNewWindow -ErrorAction SilentlyContinue | Out-Null
    Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue

    if (Test-Path $Path) {
        Write-Log " [FAILED] $Path still present after forced removal" -Level Warning
        $Script:Failed++
    } else {
        Write-Log " [REMOVED] $Path"
        $Script:Removed++
    }
}

# ==============================================================
Write-Log "================================================================"
Write-Log " PrepareOOBE v1.4 started"
Write-Log " Computer : $env:COMPUTERNAME"
Write-Log " User : $env:USERNAME"
Write-Log " Time : $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Log "================================================================"

# ==============================================================
Write-Step "PRE-FLIGHT: dsregcmd /status"
# ==============================================================
$DsregBefore = & dsregcmd.exe /status 2>&1
$DsregBefore | Where-Object { $_ -match 'AzureAdJoined|WorkplaceJoined|DomainJoined|TenantName|DeviceId|MDMUrl|EnterpriseJoined' } |
    ForEach-Object { Write-Log " $($_.Trim())" }

# ==============================================================
Write-Step "STEP 1 - Initial dsregcmd /leave"
# ==============================================================
Write-Log " Running dsregcmd /leave"
$LeaveOut = & dsregcmd.exe /leave 2>&1
$LeaveOut | ForEach-Object { Write-Log " dsregcmd: $($_.ToString().Trim())" }
Write-Log " dsregcmd /leave exit code: $LASTEXITCODE"

# ==============================================================
Write-Step "STEP 2 - Kill CCM processes"
# ==============================================================
$Processes = @("CcmExec","ccmsetup","CMRcService","CmRcService","smsexec","smsswd","SensorWDService","SensorManagedProvider","ccmrestart","SCNotification")
foreach ($ProcName in $Processes) {
    $procs = Get-Process -Name $ProcName -ErrorAction SilentlyContinue
    if ($procs) {
        $ids = $procs.Id -join ", "
        $procs | Stop-Process -Force -ErrorAction SilentlyContinue
        Write-Log " [KILLED] $ProcName (PID: $ids)"
    } else {
        Write-Log " [NOT FOUND] Process: $ProcName"
    }
}

# ==============================================================
Write-Step "STEP 3 - Stop and remove CCM services"
# ==============================================================
foreach ($SvcName in @("CcmExec","ccmsetup","smstsmgr","CmRcService")) {
    $svc = Get-Service -Name $SvcName -ErrorAction SilentlyContinue
    if ($svc) {
        Write-Log " [FOUND] Service $SvcName (Status: $($svc.Status))"
        Stop-Service -Name $SvcName -Force -ErrorAction SilentlyContinue
        $ScResult = & sc.exe delete $SvcName 2>&1
        Write-Log " [SC DELETE] $SvcName - $ScResult"
    } else {
        Write-Log " [NOT FOUND] Service: $SvcName"
    }
}

# ==============================================================
Write-Step "STEP 4 - Uninstall SCCM client"
# ==============================================================
if (Test-Path "C:\Windows\ccmsetup\ccmsetup.exe") {
    Write-Log " [FOUND] ccmsetup.exe - starting /Uninstall"
    Start-Process -FilePath "C:\Windows\ccmsetup\ccmsetup.exe" -ArgumentList "/Uninstall" -NoNewWindow -ErrorAction SilentlyContinue

    $TimeoutSec = 300
    $Elapsed = 0
    $Interval = 5
    Write-Log " Polling for uninstall completion (timeout: ${TimeoutSec}s)..."

    do {
        Start-Sleep -Seconds $Interval
        $Elapsed += $Interval
        $SvcGone = -not (Get-Service -Name "CcmExec" -ErrorAction SilentlyContinue)
        $ProcGone = -not (Get-Process -Name "ccmsetup" -ErrorAction SilentlyContinue)
        $ExeGone = -not (Test-Path "C:\Windows\ccmsetup\ccmsetup.exe")
        Write-Log " [${Elapsed}s] ServiceGone=$SvcGone | ProcessGone=$ProcGone | ExeGone=$ExeGone"
    } until (($SvcGone -and $ProcGone) -or $Elapsed -ge $TimeoutSec)

    if ($SvcGone -and $ProcGone) {
        Write-Log " [DONE] CCM uninstall confirmed after ${Elapsed}s"
    } else {
        Write-Log " [WARNING] CCM uninstall timed out after ${TimeoutSec}s - continuing" -Level Warning
    }
} else {
    Write-Log " [NOT FOUND] ccmsetup.exe - skipping uninstall"
}

# ==============================================================
Write-Step "STEP 5 - Remove CCM WMI namespaces"
# ==============================================================
foreach ($ns in @("root\ccm","root\ccm\Policy","root\ccm\SoftwareMeteringAgent","root\SmsDm","root\cimv2\SMS")) {
    try {
        Get-WmiObject -Namespace $ns -Class "__Namespace" -ErrorAction Stop | Out-Null
        ([wmiclass]"\\.\$($ns):__Namespace").Delete() | Out-Null
        Write-Log " [REMOVED] WMI namespace: $ns"
        $Script:Removed++
    } catch {
        Write-Log " [NOT FOUND] WMI namespace: $ns"
        $Script:NotFound++
    }
}

# ==============================================================
Write-Step "STEP 6 - Remove CCM scheduled tasks"
# ==============================================================
$CcmTasks = Get-ScheduledTask -ErrorAction SilentlyContinue |
    Where-Object { $_.TaskPath -match 'Configuration Manager' -or $_.TaskName -match 'CCM|SMS|SCCM' }
if ($CcmTasks) {
    foreach ($Task in $CcmTasks) {
        Unregister-ScheduledTask -TaskName $Task.TaskName -TaskPath $Task.TaskPath -Confirm:$false -ErrorAction SilentlyContinue
        Write-Log " [REMOVED] Task: $($Task.TaskPath)$($Task.TaskName)"
        $Script:Removed++
    }
} else {
    Write-Log " [NOT FOUND] No CCM/SMS scheduled tasks"
    $Script:NotFound++
}

# ==============================================================
Write-Step "STEP 7 - Remove SCCM registry keys"
# ==============================================================
@(
    "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 { Remove-RegKey $_ }

# ==============================================================
Write-Step "STEP 8 - Remove SCCM directories (robocopy wipe)"
# ==============================================================
@("C:\Windows\CCM","C:\Windows\CCMCache","C:\Windows\CCMSetup","C:\MININT") |
    ForEach-Object { Remove-LockedDirectory $_ }

if (Test-Path "C:\Windows\SMSCFG.ini") {
    Remove-Item "C:\Windows\SMSCFG.ini" -Force -ErrorAction SilentlyContinue
    Write-Log " [REMOVED] C:\Windows\SMSCFG.ini"
    $Script:Removed++
} else {
    Write-Log " [NOT FOUND] C:\Windows\SMSCFG.ini"
    $Script:NotFound++
}

$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 file(s)"
    $Script:Removed += $MifFiles.Count
} else {
    Write-Log " [NOT FOUND] No SMS .mif files"
    $Script:NotFound++
}

# ==============================================================
Write-Step "STEP 9 - Remove MDM / co-management enrollment state"
# ==============================================================

# Log what enrollments exist before wiping
$EnrollmentRoot = "HKLM:\SOFTWARE\Microsoft\Enrollments"
if (Test-Path $EnrollmentRoot) {
    $Guids = Get-ChildItem $EnrollmentRoot -ErrorAction SilentlyContinue
    Write-Log " [FOUND] $($Guids.Count) MDM enrollment GUID(s):"
    foreach ($g in $Guids) {
        $etype = (Get-ItemProperty "$($g.PSPath)" -Name "EnrollmentType" -ErrorAction SilentlyContinue).EnrollmentType
        $upn = (Get-ItemProperty "$($g.PSPath)" -Name "UPN" -ErrorAction SilentlyContinue).UPN
        Write-Log " $($g.PSChildName) | Type=$etype | UPN=$upn"
        Remove-Item -Path $g.PSPath -Recurse -Force -ErrorAction SilentlyContinue
        Write-Log " [REMOVED] Enrollment GUID: $($g.PSChildName)"
        $Script:Removed++
    }
} else {
    Write-Log " [NOT FOUND] $EnrollmentRoot"
    $Script:NotFound++
}

@(
    "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",
    "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo",
    "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\TenantInfo",
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CloudExperienceHost",
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\OOBE",
    "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UnattendSettings"
) | ForEach-Object { Remove-RegKey $_ }

# MDM enrollment scheduled tasks
Write-Log " Checking MDM enrollment scheduled tasks..."
$MdmTaskCount = 0
foreach ($TaskPath in @("\Microsoft\Windows\EnterpriseMgmt\","\Microsoft\Windows\EnterpriseMgmtNoncritical\")) {
    $tasks = Get-ScheduledTask -TaskPath $TaskPath -ErrorAction SilentlyContinue
    foreach ($t in $tasks) {
        Unregister-ScheduledTask -TaskName $t.TaskName -TaskPath $t.TaskPath -Confirm:$false -ErrorAction SilentlyContinue
        Write-Log " [REMOVED] MDM task: $($t.TaskPath)$($t.TaskName)"
        $MdmTaskCount++
        $Script:Removed++
    }
}
if ($MdmTaskCount -eq 0) { Write-Log " [NOT FOUND] No MDM enrollment scheduled tasks" }

# MDM device certificates
Write-Log " Checking MDM device certificates in LocalMachine\My..."
$MdmCerts = Get-ChildItem "Cert:\LocalMachine\My" -ErrorAction SilentlyContinue |
    Where-Object { $_.Issuer -match "MS-Organization-Access|Microsoft Intune MDM Device CA|MDM" }
if ($MdmCerts) {
    foreach ($cert in $MdmCerts) {
        Remove-Item -Path "Cert:\LocalMachine\My\$($cert.Thumbprint)" -Force -ErrorAction SilentlyContinue
        Write-Log " [REMOVED] Cert: $($cert.Subject) | Issuer: $($cert.Issuer) | Thumb: $($cert.Thumbprint)"
        $Script:Removed++
    }
} else {
    Write-Log " [NOT FOUND] No MDM certificates in LocalMachine\My"
    $Script:NotFound++
}

# ==============================================================
Write-Step "STEP 10 - Final dsregcmd /leave after full cleanup"
# ==============================================================
Write-Log " Running dsregcmd /leave (post-cleanup flush)"
$LeaveOut2 = & dsregcmd.exe /leave 2>&1
$LeaveOut2 | ForEach-Object { Write-Log " dsregcmd: $($_.ToString().Trim())" }
Write-Log " Exit code: $LASTEXITCODE"

# ==============================================================
Write-Step "STEP 11 - Remove TS post action, unattend, setup remnants"
# ==============================================================
Write-Log " Removing SMSTSPostAction registry value"
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\SMS\Task Sequence" -Name "SMSTSPostAction" -ErrorAction SilentlyContinue

@("C:\Windows\Panther\Unattend.xml","C:\Windows\Panther\unattend") | ForEach-Object {
    if (Test-Path $_) {
        Remove-Item -Path $_ -Recurse -Force -ErrorAction SilentlyContinue
        Write-Log " [REMOVED] $_"
        $Script:Removed++
    } else {
        Write-Log " [NOT FOUND] $_"
        $Script:NotFound++
    }
}

Remove-ItemProperty -Path "HKLM:\SYSTEM\Setup" -Name "CmdLine" -ErrorAction SilentlyContinue
Remove-ItemProperty -Path "HKLM:\SYSTEM\Setup" -Name "SetupType" -ErrorAction SilentlyContinue
Write-Log " Cleared HKLM:\SYSTEM\Setup CmdLine and SetupType"

$TagPath = "C:\Windows\Setup\Scripts\DisableCMDRequest.TAG"
if (Test-Path $TagPath) {
    Remove-Item -Path $TagPath -Force -ErrorAction SilentlyContinue
    Write-Log " [REMOVED] DisableCMDRequest.TAG"
    $Script:Removed++
} else {
    Write-Log " [NOT FOUND] DisableCMDRequest.TAG"
    $Script:NotFound++
}

# ==============================================================
Write-Step "POST-FLIGHT: dsregcmd /status after cleanup"
# ==============================================================
$DsregAfter = & dsregcmd.exe /status 2>&1
$DsregAfter | Where-Object { $_ -match 'AzureAdJoined|WorkplaceJoined|DomainJoined|TenantName|DeviceId|MDMUrl|EnterpriseJoined' } |
    ForEach-Object { Write-Log " $($_.Trim())" }

# ==============================================================
Write-Step "CLEANUP SUMMARY"
# ==============================================================
Write-Log " Items removed : $($Script:Removed)"
Write-Log " Items not found : $($Script:NotFound)"
Write-Log " Items failed : $($Script:Failed)"

# ==============================================================
Write-Step "SYSPREP"
# ==============================================================
Write-Log " Launching: sysprep.exe /oobe /reboot /quiet"

$Sysprep = Start-Process `
    -FilePath "C:\Windows\System32\Sysprep\Sysprep.exe" `
    -ArgumentList "/oobe /reboot /quiet" `
    -NoNewWindow -Wait -PassThru

Write-Log " Sysprep exit 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 }
    } else {
        Write-Log " Panther log not found at $PantherLog" -Level Warning
    }
} else {
    Write-Log " Sysprep completed successfully - rebooting into OOBE"
}

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"