PrepareOOBE.ps1


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



<#
 
.DESCRIPTION
1.7
 
#>
 
Param()

# Version : 1.5
# 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
# 1.5 - Added NGC, Cloud AP token cache, Autopilot provisioning cache, AAD cert wipe for ZTID

$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
 
    $Color = switch ($Level) {
        "Warning" { "Yellow" }
        "Error" { "Red" }
        "Success" { "Green" }
        default {
            if ($Message -match '^\s*\[REMOVED\]') { "Green" }
            elseif ($Message -match '^\s*\[FAILED\]') { "Red" }
            elseif ($Message -match '^\s*\[KILLED\]') { "Magenta" }
            elseif ($Message -match '^\s*\[NOT FOUND\]'){ "DarkGray" }
            elseif ($Message -match '^\s*\[FOUND\]') { "Cyan" }
            elseif ($Message -match '^\s*\[WARNING\]') { "Yellow" }
            else { "White" }
        }
    }
    Write-Host "[$Stamp] [$Level] $Message" -ForegroundColor $Color
}
 
function Write-Step {
    param([string]$Title)
    $Sep = "-" * 60
    Write-Log ""
    Write-Host ""
    Write-Host $Sep -ForegroundColor DarkCyan
    Write-Host " $Title" -ForegroundColor Cyan
    Write-Host $Sep -ForegroundColor DarkCyan
    Add-Content -Path $LogPath -Value $Sep -ErrorAction SilentlyContinue
    Add-Content -Path $LogPath -Value " $Title" -ErrorAction SilentlyContinue
    Add-Content -Path $LogPath -Value $Sep -ErrorAction SilentlyContinue
}
 
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 AAD / ZTID identity artifacts"
# ==============================================================
# These are what cause OOBE to skip the Autopilot ZTID check
# and enroll as a plain device instead of an Autopilot device.
 
# NGC (Next Generation Credentials / Windows Hello) folders.
# These hold TPM-bound device keys from the previous AAD join.
# If present during OOBE, Windows reuses the old device identity
# instead of performing a fresh Autopilot hardware hash lookup.
Write-Log " Removing NGC (Windows Hello) credential folders"
@(
    "C:\Windows\ServiceProfiles\LocalService\AppData\Local\Microsoft\NGC",
    "C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\NGC",
    "C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\NGC"
) | ForEach-Object { Remove-LockedDirectory $_ }
 
# Cloud AP plugin token cache.
# Cached AAD access tokens that allow Windows to silently re-join
# the same AAD device without going through the Autopilot ZTID flow.
Write-Log " Removing Cloud AP plugin token cache"
@(
    "C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Windows\CloudAPPlugin",
    "C:\Windows\ServiceProfiles\LocalService\AppData\Local\Microsoft\Windows\CloudAPPlugin",
    "C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Windows\CloudAPPlugin"
) | ForEach-Object { Remove-LockedDirectory $_ }
 
# Autopilot provisioning cache.
# If a cached profile exists here from a prior enrollment, OOBE uses
# it instead of fetching the current profile from the Autopilot service.
# This can cause OOBE to apply an old/wrong profile or skip ZTID entirely.
Write-Log " Removing Autopilot provisioning cache"
@(
    "C:\Windows\Provisioning\Autopilot",
    "C:\ProgramData\Microsoft\Windows\DeviceManagementProvisioning"
) | ForEach-Object { Remove-LockedDirectory $_ }
 
# Identity store cache - device credential store used for silent re-auth
Write-Log " Removing identity store cache"
@(
    "HKLM:\SOFTWARE\Microsoft\IdentityStore\Cache",
    "HKLM:\SOFTWARE\Microsoft\IdentityStore\LogonCache",
    "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WorkplaceJoin",
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CDJ"
) | ForEach-Object { Remove-RegKey $_ }
 
# AAD identity cache folder
Remove-LockedDirectory "C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\IdentityCache"
 
# All AAD-related certificates (broader than just MDM certs).
# The MS-Organization-Access cert is the device identity cert issued
# during AAD join. If it survives sysprep, the device re-joins as the
# same AAD object and Intune matches it to the old co-managed record.
Write-Log " Removing AAD device identity certificates"
$AadCertStores = @("Cert:\LocalMachine\My", "Cert:\LocalMachine\Root", "Cert:\LocalMachine\CA")
foreach ($Store in $AadCertStores) {
    $Certs = Get-ChildItem $Store -ErrorAction SilentlyContinue | Where-Object {
        $_.Issuer -match "MS-Organization-Access|MS-Organization-P2P-Access|Microsoft Intune MDM Device CA|AAD|AzureAD|EnterpriseRegistration"
    }
    foreach ($Cert in $Certs) {
        Remove-Item -Path "$Store\$($Cert.Thumbprint)" -Force -ErrorAction SilentlyContinue
        Write-Log " [REMOVED] Cert [$Store]: $($Cert.Subject) | $($Cert.Thumbprint)"
        $Script:Removed++
    }
    if (-not $Certs) {
        Write-Log " [NOT FOUND] No AAD certs in $Store"
        $Script:NotFound++
    }
}
 
# ==============================================================
Write-Step "STEP 12 - 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 full 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"