mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-08-12 21:53:05 -06:00
Handle pending reboots before FFU sysprep
Detect pending reboot markers before invoking Sysprep and log the exact reason, including CBS, Windows Update, WinSxS pending.xml, and every PendingFileRenameOperations entry. When a reboot is required, register a one-time Audit mode RunOnce retry that reloads the Apps media, waits for Explorer, closes Audit-mode Sysprep UI races, and relaunches Run-Sysprep.ps1.
This commit is contained in:
@@ -1,10 +1,448 @@
|
||||
#The below lines will remove the unattend.xml that gets the machine into audit mode. If not removed, the OS will get stuck booting to audit mode each time.
|
||||
#Also kills the sysprep process in order to automate sysprep generalize
|
||||
function Stop-SysprepProcess {
|
||||
$sysprepProcesses = @(Get-Process -Name 'sysprep' -ErrorAction SilentlyContinue)
|
||||
if (0 -eq $sysprepProcesses.Count) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host 'Stopping existing Sysprep process before continuing...'
|
||||
$sysprepProcesses | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$deadline = (Get-Date).AddSeconds(30)
|
||||
do {
|
||||
Start-Sleep -Seconds 1
|
||||
$sysprepProcesses = @(Get-Process -Name 'sysprep' -ErrorAction SilentlyContinue)
|
||||
} while ($sysprepProcesses.Count -gt 0 -and (Get-Date) -lt $deadline)
|
||||
|
||||
if ($sysprepProcesses.Count -gt 0) {
|
||||
throw 'Unable to stop the existing Sysprep process before continuing.'
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-CompetingSysprepProcess {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$SysprepProcessId
|
||||
)
|
||||
|
||||
$competingSysprepProcesses = @(Get-Process -Name 'sysprep' -ErrorAction SilentlyContinue | Where-Object { $_.Id -ne $SysprepProcessId })
|
||||
foreach ($competingSysprepProcess in $competingSysprepProcesses) {
|
||||
Write-Host "Stopping competing Sysprep process $($competingSysprepProcess.Id)."
|
||||
Stop-Process -Id $competingSysprepProcess.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Join-ProcessArgumentList {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string[]]$ArgumentList
|
||||
)
|
||||
|
||||
$escapedArguments = foreach ($argument in $ArgumentList) {
|
||||
if ($argument -match '[\s"]') {
|
||||
'"{0}"' -f ($argument -replace '"', '\"')
|
||||
}
|
||||
else {
|
||||
$argument
|
||||
}
|
||||
}
|
||||
|
||||
return ($escapedArguments -join ' ')
|
||||
}
|
||||
|
||||
function Invoke-SysprepProcess {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string[]]$ArgumentList
|
||||
)
|
||||
|
||||
$sysprepPath = 'C:\windows\system32\sysprep\sysprep.exe'
|
||||
$sysprepArgumentString = Join-ProcessArgumentList -ArgumentList $ArgumentList
|
||||
Write-Host "Starting Sysprep with arguments: $sysprepArgumentString"
|
||||
$sysprepProcess = Start-Process -FilePath $sysprepPath -ArgumentList $sysprepArgumentString -PassThru -ErrorAction Stop
|
||||
|
||||
do {
|
||||
Stop-CompetingSysprepProcess -SysprepProcessId $sysprepProcess.Id
|
||||
Start-Sleep -Seconds 1
|
||||
$sysprepProcess.Refresh()
|
||||
} while (-not $sysprepProcess.HasExited)
|
||||
|
||||
return $sysprepProcess.ExitCode
|
||||
}
|
||||
|
||||
Write-Host "Removing existing unattend.xml files and stopping sysprep process if running..."
|
||||
Remove-Item -Path "C:\windows\panther\unattend\unattend.xml" -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path "C:\windows\panther\unattend.xml" -Force -ErrorAction SilentlyContinue
|
||||
Stop-Process -Name "sysprep" -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 10
|
||||
Stop-SysprepProcess
|
||||
|
||||
$sysprepRetryTaskName = 'FFU-Sysprep-Retry'
|
||||
$sysprepRetryRunOncePath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'
|
||||
$sysprepRetryRunOnceName = '!FFU-Sysprep-Retry'
|
||||
$sysprepRetryStatePath = 'C:\Windows\Setup\Scripts\Logs\SysprepRetry.state'
|
||||
$sysprepRetryScriptPath = 'C:\Windows\Setup\Scripts\Run-FFUSysprepRetry.ps1'
|
||||
$maxSysprepRebootRetries = 1
|
||||
|
||||
function Disable-AuditModeSysprepStartup {
|
||||
$startupRegistryPaths = @(
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce',
|
||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
|
||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'
|
||||
)
|
||||
|
||||
foreach ($startupRegistryPath in $startupRegistryPaths) {
|
||||
$startupRegistryKey = Get-Item -Path $startupRegistryPath -ErrorAction SilentlyContinue
|
||||
if ($null -eq $startupRegistryKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
foreach ($valueName in $startupRegistryKey.GetValueNames()) {
|
||||
if ($valueName -eq $sysprepRetryRunOnceName) {
|
||||
continue
|
||||
}
|
||||
|
||||
$valueData = [string]$startupRegistryKey.GetValue($valueName)
|
||||
if ($valueData -notmatch '(?i)sysprep\.exe') {
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "Removing Audit mode Sysprep startup value '$valueName' from '$startupRegistryPath'."
|
||||
Remove-ItemProperty -Path $startupRegistryPath -Name $valueName -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Disable-AuditModeSysprepStartup
|
||||
|
||||
function Resolve-FFUAppsMediaRoot {
|
||||
$appsRoot = ([string]$env:FFUAppsRoot).Trim().TrimEnd('\')
|
||||
if (-not [string]::IsNullOrWhiteSpace($appsRoot) -and (Test-Path -Path $appsRoot)) {
|
||||
return $appsRoot
|
||||
}
|
||||
|
||||
foreach ($driveInfo in Get-PSDrive -PSProvider FileSystem) {
|
||||
if ([string]::IsNullOrWhiteSpace([string]$driveInfo.Root)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$candidateOrchestrator = Join-Path -Path $driveInfo.Root -ChildPath 'Orchestration\Orchestrator.ps1'
|
||||
$candidateSysprep = Join-Path -Path $driveInfo.Root -ChildPath 'Orchestration\Run-Sysprep.ps1'
|
||||
if ((Test-Path -Path $candidateOrchestrator -PathType Leaf) -and (Test-Path -Path $candidateSysprep -PathType Leaf)) {
|
||||
return $driveInfo.Root.TrimEnd('\')
|
||||
}
|
||||
}
|
||||
|
||||
return 'D:'
|
||||
}
|
||||
|
||||
function Get-SysprepRetryCount {
|
||||
if (-not (Test-Path -Path $sysprepRetryStatePath)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
try {
|
||||
$retryCount = [int](Get-Content -Path $sysprepRetryStatePath -Raw -ErrorAction Stop).Trim()
|
||||
return $retryCount
|
||||
}
|
||||
catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function Set-SysprepRetryCount {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$RetryCount
|
||||
)
|
||||
|
||||
$retryStateFolder = Split-Path -Path $sysprepRetryStatePath -Parent
|
||||
New-Item -Path $retryStateFolder -ItemType Directory -Force | Out-Null
|
||||
Set-Content -Path $sysprepRetryStatePath -Value ([string]$RetryCount) -Encoding ASCII -Force
|
||||
}
|
||||
|
||||
function Clear-SysprepRetryResume {
|
||||
Unregister-ScheduledTask -TaskName $sysprepRetryTaskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||
Remove-ItemProperty -Path $sysprepRetryRunOncePath -Name $sysprepRetryRunOnceName -ErrorAction SilentlyContinue
|
||||
Remove-ItemProperty -Path $sysprepRetryRunOncePath -Name $sysprepRetryTaskName -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path $sysprepRetryScriptPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function Clear-SysprepRetryState {
|
||||
Clear-SysprepRetryResume
|
||||
Remove-Item -Path $sysprepRetryStatePath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function Test-PendingReboot {
|
||||
return ((Get-PendingRebootReasons).Count -gt 0)
|
||||
}
|
||||
|
||||
function Get-RegistryChildSummary {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$childNames = @(Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Select-Object -First 8 -ExpandProperty PSChildName)
|
||||
if (0 -eq $childNames.Count) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return ' Child keys: {0}' -f ($childNames -join ', ')
|
||||
}
|
||||
|
||||
function Get-PendingRebootReasons {
|
||||
$pendingRebootReasons = @()
|
||||
|
||||
$cbsRebootPendingPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
|
||||
if (Test-Path -Path $cbsRebootPendingPath) {
|
||||
$pendingRebootReasons += "CBS RebootPending marker found at $cbsRebootPendingPath.$(Get-RegistryChildSummary -Path $cbsRebootPendingPath)"
|
||||
}
|
||||
|
||||
$cbsPackagesPendingPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending'
|
||||
if (Test-Path -Path $cbsPackagesPendingPath) {
|
||||
$pendingRebootReasons += "CBS PackagesPending marker found at $cbsPackagesPendingPath.$(Get-RegistryChildSummary -Path $cbsPackagesPendingPath)"
|
||||
}
|
||||
|
||||
$windowsUpdateRebootRequiredPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
|
||||
if (Test-Path -Path $windowsUpdateRebootRequiredPath) {
|
||||
$pendingRebootReasons += "Windows Update RebootRequired marker found at $windowsUpdateRebootRequiredPath.$(Get-RegistryChildSummary -Path $windowsUpdateRebootRequiredPath)"
|
||||
}
|
||||
|
||||
$pendingXmlPath = 'C:\Windows\WinSxS\pending.xml'
|
||||
if (Test-Path -Path $pendingXmlPath) {
|
||||
$pendingXml = Get-Item -Path $pendingXmlPath -ErrorAction SilentlyContinue
|
||||
if ($null -ne $pendingXml) {
|
||||
$pendingRebootReasons += "WinSxS pending.xml exists at $pendingXmlPath. LastWriteTime: $($pendingXml.LastWriteTime); Size: $($pendingXml.Length) bytes."
|
||||
}
|
||||
else {
|
||||
$pendingRebootReasons += "WinSxS pending.xml exists at $pendingXmlPath."
|
||||
}
|
||||
}
|
||||
|
||||
$sessionManagerPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
foreach ($pendingRenameValueName in @('PendingFileRenameOperations', 'PendingFileRenameOperations2')) {
|
||||
$pendingFileRename = (Get-ItemProperty -Path $sessionManagerPath -Name $pendingRenameValueName -ErrorAction SilentlyContinue).$pendingRenameValueName
|
||||
if ($null -eq $pendingFileRename) {
|
||||
continue
|
||||
}
|
||||
|
||||
$pendingFileRenameEntries = @($pendingFileRename | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) })
|
||||
$pendingRebootReasons += "Session Manager $pendingRenameValueName contains $($pendingFileRenameEntries.Count) pending file rename/delete entries:"
|
||||
$pendingFileRenameIndex = 1
|
||||
foreach ($pendingFileRenameEntry in $pendingFileRenameEntries) {
|
||||
$pendingRebootReasons += 'Session Manager {0} entry {1}: {2}' -f $pendingRenameValueName, $pendingFileRenameIndex, $pendingFileRenameEntry
|
||||
$pendingFileRenameIndex++
|
||||
}
|
||||
}
|
||||
|
||||
$updatesPath = 'HKLM:\SOFTWARE\Microsoft\Updates'
|
||||
$updateExeVolatile = (Get-ItemProperty -Path $updatesPath -Name 'UpdateExeVolatile' -ErrorAction SilentlyContinue).UpdateExeVolatile
|
||||
if ($null -ne $updateExeVolatile -and 0 -ne $updateExeVolatile) {
|
||||
$pendingRebootReasons += "Microsoft Updates UpdateExeVolatile is $updateExeVolatile at $updatesPath."
|
||||
}
|
||||
|
||||
return @($pendingRebootReasons)
|
||||
}
|
||||
|
||||
function Write-PendingRebootReasons {
|
||||
param(
|
||||
[string[]]$PendingRebootReasons
|
||||
)
|
||||
|
||||
if ($null -eq $PendingRebootReasons -or 0 -eq $PendingRebootReasons.Count) {
|
||||
Write-Host 'Pending reboot detected, but no specific pending reboot marker details were collected.'
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host 'Pending reboot reason(s) detected:'
|
||||
foreach ($pendingRebootReason in $PendingRebootReasons) {
|
||||
Write-Host " - $pendingRebootReason"
|
||||
}
|
||||
}
|
||||
|
||||
function Test-SysprepPendingRebootFailure {
|
||||
$sysprepLogPaths = @(
|
||||
'C:\Windows\System32\Sysprep\Panther\setuperr.log',
|
||||
'C:\Windows\System32\Sysprep\Panther\setupact.log'
|
||||
)
|
||||
|
||||
foreach ($sysprepLogPath in $sysprepLogPaths) {
|
||||
if (-not (Test-Path -Path $sysprepLogPath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$sysprepLogText = Get-Content -Path $sysprepLogPath -Tail 200 -ErrorAction SilentlyContinue | Out-String
|
||||
if ($sysprepLogText -match '0x8007139f|pending\s+reboot|reboot\s+required|required\s+reboot|Windows Updates?.*reboot|pre-validate sysprep cleanup internal providers') {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return (Test-PendingReboot)
|
||||
}
|
||||
|
||||
function New-SysprepRetryLauncher {
|
||||
$retryScriptFolder = Split-Path -Path $sysprepRetryScriptPath -Parent
|
||||
New-Item -Path $retryScriptFolder -ItemType Directory -Force | Out-Null
|
||||
|
||||
$retryScript = @'
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$logPath = 'C:\Windows\Temp\FFUSysprepRetry.log'
|
||||
Start-Transcript -Path $logPath -Append -Force | Out-Null
|
||||
|
||||
function Disable-AuditModeSysprepStartup {
|
||||
$startupRegistryPaths = @(
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce',
|
||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
|
||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'
|
||||
)
|
||||
|
||||
foreach ($startupRegistryPath in $startupRegistryPaths) {
|
||||
$startupRegistryKey = Get-Item -Path $startupRegistryPath -ErrorAction SilentlyContinue
|
||||
if ($null -eq $startupRegistryKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
foreach ($valueName in $startupRegistryKey.GetValueNames()) {
|
||||
$valueData = [string]$startupRegistryKey.GetValue($valueName)
|
||||
if ($valueData -notmatch '(?i)sysprep\.exe') {
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "Removing Audit mode Sysprep startup value '$valueName' from '$startupRegistryPath'."
|
||||
Remove-ItemProperty -Path $startupRegistryPath -Name $valueName -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Host 'Starting FFU sysprep retry.'
|
||||
Disable-AuditModeSysprepStartup
|
||||
|
||||
Write-Host 'Waiting for the Audit mode desktop shell to load before retrying.'
|
||||
$explorerProcesses = @()
|
||||
$shellDeadline = (Get-Date).AddMinutes(2)
|
||||
do {
|
||||
$explorerProcesses = @(Get-Process -Name 'explorer' -ErrorAction SilentlyContinue)
|
||||
if ($explorerProcesses.Count -gt 0) {
|
||||
Write-Host 'Detected Explorer shell. Continuing retry.'
|
||||
break
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
} while ((Get-Date) -lt $shellDeadline)
|
||||
|
||||
if (0 -eq $explorerProcesses.Count) {
|
||||
Write-Host 'Explorer shell was not detected before timeout. Continuing retry.'
|
||||
}
|
||||
|
||||
Write-Host 'Waiting briefly for the Audit mode Sysprep UI to launch, then closing it before retrying.'
|
||||
$sysprepUiSeen = $false
|
||||
$minimumSysprepUiWaitDeadline = (Get-Date).AddSeconds(15)
|
||||
$sysprepUiQuietDeadline = (Get-Date).AddSeconds(15)
|
||||
$sysprepUiDeadline = (Get-Date).AddMinutes(2)
|
||||
do {
|
||||
$sysprepProcesses = @(Get-Process -Name 'sysprep' -ErrorAction SilentlyContinue)
|
||||
if ($sysprepProcesses.Count -gt 0) {
|
||||
$sysprepUiSeen = $true
|
||||
foreach ($sysprepProcess in $sysprepProcesses) {
|
||||
Write-Host "Stopping Audit mode Sysprep process $($sysprepProcess.Id) before retrying."
|
||||
Stop-Process -Id $sysprepProcess.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$sysprepUiQuietDeadline = (Get-Date).AddSeconds(10)
|
||||
}
|
||||
elseif ((Get-Date) -ge $minimumSysprepUiWaitDeadline -and (Get-Date) -ge $sysprepUiQuietDeadline) {
|
||||
break
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 1
|
||||
} while ((Get-Date) -lt $sysprepUiDeadline)
|
||||
|
||||
if ($sysprepUiSeen) {
|
||||
Write-Host 'Audit mode Sysprep UI is closed.'
|
||||
}
|
||||
else {
|
||||
Write-Host 'Audit mode Sysprep UI was not detected before retrying.'
|
||||
}
|
||||
|
||||
$deadline = (Get-Date).AddMinutes(10)
|
||||
$sysprepScript = $null
|
||||
|
||||
do {
|
||||
$fileSystemDrives = @(Get-PSDrive -PSProvider FileSystem | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.Root) })
|
||||
foreach ($driveInfo in $fileSystemDrives) {
|
||||
$candidatePath = Join-Path -Path $driveInfo.Root -ChildPath 'Orchestration\Run-Sysprep.ps1'
|
||||
Write-Host "Checking for sysprep script at $candidatePath"
|
||||
if (Test-Path -Path $candidatePath -PathType Leaf) {
|
||||
$sysprepScript = $candidatePath
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($sysprepScript)) {
|
||||
break
|
||||
}
|
||||
|
||||
Write-Host 'Apps ISO sysprep script was not found yet. Waiting before retry.'
|
||||
Start-Sleep -Seconds 5
|
||||
} while ((Get-Date) -lt $deadline)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($sysprepScript)) {
|
||||
throw 'Unable to locate Apps ISO Run-Sysprep.ps1 after waiting for Apps media.'
|
||||
}
|
||||
|
||||
$env:FFUAppsRoot = Split-Path -Parent (Split-Path -Parent $sysprepScript)
|
||||
Write-Host "Using Apps media root: $env:FFUAppsRoot"
|
||||
Write-Host "Launching sysprep script: $sysprepScript"
|
||||
& $sysprepScript
|
||||
}
|
||||
catch {
|
||||
Write-Error "FFU sysprep retry failed: $($_.Exception.Message)"
|
||||
throw
|
||||
}
|
||||
finally {
|
||||
Stop-Transcript -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
'@
|
||||
|
||||
Set-Content -Path $sysprepRetryScriptPath -Value $retryScript -Encoding ASCII -Force
|
||||
}
|
||||
|
||||
function Register-SysprepRetryResume {
|
||||
Disable-AuditModeSysprepStartup
|
||||
New-SysprepRetryLauncher
|
||||
|
||||
$cmdPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\cmd.exe'
|
||||
$powerShellPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
||||
$runOnceCommand = "`"$cmdPath`" /c start `"FFU Sysprep Retry`" `"$powerShellPath`" -NoProfile -ExecutionPolicy Bypass -File `"$sysprepRetryScriptPath`""
|
||||
New-Item -Path $sysprepRetryRunOncePath -Force | Out-Null
|
||||
New-ItemProperty -Path $sysprepRetryRunOncePath -Name $sysprepRetryRunOnceName -Value $runOnceCommand -PropertyType String -Force | Out-Null
|
||||
}
|
||||
|
||||
function Restart-SysprepRetry {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Reason,
|
||||
|
||||
[string[]]$PendingRebootReasons
|
||||
)
|
||||
|
||||
$retryCount = Get-SysprepRetryCount
|
||||
if ($retryCount -ge $maxSysprepRebootRetries) {
|
||||
return $false
|
||||
}
|
||||
|
||||
Set-SysprepRetryCount -RetryCount ($retryCount + 1)
|
||||
Register-SysprepRetryResume
|
||||
Write-Host $Reason
|
||||
Write-PendingRebootReasons -PendingRebootReasons $PendingRebootReasons
|
||||
Write-Host 'Restarting now. Sysprep will resume during the next Audit mode logon.'
|
||||
Restart-Computer -Force -ErrorAction Stop
|
||||
return $true
|
||||
}
|
||||
|
||||
# Detect and remediate per-user, non-provisioned Appx packages that would block Sysprep.
|
||||
Write-Host "Checking for per-user Appx packages not provisioned for all users (potential Sysprep blockers)..."
|
||||
@@ -80,12 +518,42 @@ else {
|
||||
|
||||
# If an Unattend.xml has been provided on the mounted Apps ISO,
|
||||
# pass it to sysprep; otherwise, run without /unattend.
|
||||
$appsMediaRoot = if ([string]::IsNullOrWhiteSpace([string]$env:FFUAppsRoot)) { "D:" } else { ([string]$env:FFUAppsRoot).Trim().TrimEnd('\') }
|
||||
$appsMediaRoot = Resolve-FFUAppsMediaRoot
|
||||
$env:FFUAppsRoot = $appsMediaRoot
|
||||
$unattendOnAppsIso = Join-Path -Path $appsMediaRoot -ChildPath "Unattend\Unattend.xml"
|
||||
|
||||
$sysprepArguments = @('/quiet', '/generalize', '/oobe')
|
||||
if (Test-Path -Path $unattendOnAppsIso) {
|
||||
Write-Host "Using $unattendOnAppsIso from Apps ISO..."
|
||||
& "C:\windows\system32\sysprep\sysprep.exe" /quiet /generalize /oobe /unattend:$unattendOnAppsIso
|
||||
$sysprepArguments += "/unattend:$unattendOnAppsIso"
|
||||
}
|
||||
else {
|
||||
& "C:\windows\system32\sysprep\sysprep.exe" /quiet /generalize /oobe
|
||||
|
||||
$pendingRebootReasons = @(Get-PendingRebootReasons)
|
||||
if ($pendingRebootReasons.Count -gt 0) {
|
||||
if (Restart-SysprepRetry -Reason 'Pending reboot detected immediately before sysprep. Restarting before continuing sysprep...' -PendingRebootReasons $pendingRebootReasons) {
|
||||
return
|
||||
}
|
||||
|
||||
Clear-SysprepRetryState
|
||||
throw "Pending reboot is still detected after $maxSysprepRebootRetries sysprep retry reboot(s). Sysprep cannot continue."
|
||||
}
|
||||
|
||||
Clear-SysprepRetryResume
|
||||
|
||||
Disable-AuditModeSysprepStartup
|
||||
Stop-SysprepProcess
|
||||
$sysprepExitCode = Invoke-SysprepProcess -ArgumentList $sysprepArguments
|
||||
|
||||
if (0 -eq $sysprepExitCode) {
|
||||
Clear-SysprepRetryState
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-SysprepPendingRebootFailure) {
|
||||
if (Restart-SysprepRetry -Reason 'Sysprep reported a pending-reboot condition. Restarting and retrying sysprep once...' -PendingRebootReasons (Get-PendingRebootReasons)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Clear-SysprepRetryState
|
||||
throw "Sysprep failed with exit code $sysprepExitCode. Review C:\Windows\System32\Sysprep\Panther\setuperr.log and setupact.log."
|
||||
|
||||
Reference in New Issue
Block a user