Building an OEM-Style Factory Reset for Windows 11 — Complete Guide
Building an OEM-Style Factory Reset for Windows 11 — Complete Guide
A Step-by-Step Blog Post
# How to Create an OEM-Style Factory Reset Partition in Windows 11
### Restore Your PC with All Apps, Drivers & Settings — Like Dell/HP/Lenovo
---
## Table of Contents
1. [Introduction](#introduction)
2. [Architecture Overview](#architecture-overview)
3. [Prerequisites](#prerequisites)
4. [Phase 1: Create the Recovery Partition](#phase-1-create-the-recovery-partition)
5. [Phase 2: The PowerShell Script — Explained](#phase-2-the-powershell-script)
6. [Phase 3: How the Restore Scripts Work](#phase-3-restore-scripts)
7. [Phase 4: Testing & Validation](#phase-4-testing)
8. [Lessons Learned & Pitfalls](#lessons-learned)
9. [Complete Script Download](#complete-script)
10. [FAQ](#faq)
---
## Introduction
Ever wondered how Dell, HP, and Lenovo ship PCs with a "Factory Reset" option
that restores Windows with **all pre-installed applications and drivers**?
Windows 11's built-in "Reset this PC" feature **strips away all Win32
desktop applications** during the reset process. It's designed to return
Windows to a clean state — not to restore a fully configured system.
This guide shows you how to build a **true OEM-style factory reset**
that preserves everything:
- ✅ All installed applications (Win32, .NET, etc.)
- ✅ All hardware drivers
- ✅ System settings and configurations
- ✅ Silent OOBE (no setup screens after restore)
- ✅ Hidden, protected recovery partition
- ✅ Works even when Windows won't boot
### Why Not Just Use "Reset this PC"?
| Feature | Reset this PC | Our Solution |
|---------|:------------:|:------------:|
| Restores base Windows | ✅ | ✅ |
| Keeps Win32 apps | ❌ | ✅ |
| Keeps drivers | ❌ | ✅ |
| Silent OOBE | ❌ | ✅ |
| Works from hidden partition | ❌ | ✅ |
| OEM-style experience | ❌ | ✅ |
---
## Architecture Overview
### How It Works
```text
CREATING THE RECOVERY IMAGE:
┌─────────────────┐ VSS Snapshot ┌──────────────────┐
│ C:\ (live OS) │ ──────────────────→ │ Shadow Copy │
│ Files locked │ │ No locks ✅ │
└─────────────────┘ └────────┬─────────┘
│
DISM /Capture
│
┌────────▼─────────┐
│ Recovery Partition│
│ install.wim │
│ unattend.xml │
│ restore.cmd │
└──────────────────┘
RESTORING THE SYSTEM:
┌─────────────────┐
│ Boot to WinRE │
│ (SHIFT+Restart) │
└────────┬────────┘
│
▼
┌───────────────────────────────┐
│ CMD Prompt |
| Diskpart │
| list vol |
|check for the vol name recovery|
|Select that volume |
|Assign Letter R |
|CD R:\RecoveryImage |
│ Run restore.cmd │
└────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ restore.cmd: │
│ 1. Scan all drives for install.wim │
│ 2. Format C: │
│ 3. DISM /Apply-Image to C:\ │
│ 4. Rebuild bootloader │
│ 5. Apply unattend.xml │
│ 6. Re-hide recovery partition │
│ 7. Reboot → Windows ready! │
└──────────────────────────────────────────┘
File Structure
Recovery Partition (hidden):
└── RecoveryImage/
├── install.wim ← Full system image (15-40 GB)
├── unattend.xml ← Silent OOBE configuration
├── restore.cmd ← Interactive factory reset
├── diagnose.cmd ← Safe pre-restore testing
├── quick_restore.cmd ← Automated (no prompts)
├── recovery_metadata.txt ← Partition info reference
└── INSTRUCTIONS.txt ← Human-readable guide
Desktop (on C:):
├── Factory Reset.bat ← Double-click launcher
├── FactoryReset_Launcher.ps1 ← PowerShell confirmation
└── Factory Reset - Instructions.txt ← Restore guide
Prerequisites
System Requirements
- Windows 11 (any edition)
- Administrator privileges
- All desired applications and drivers installed
- At least 30-50 GB free space for recovery partition
- UEFI boot mode (not Legacy BIOS)
Before You Begin
- Install all applications and drivers
- Configure all settings
- Run Windows Update
- Clean up temp files (Disk Cleanup)
- Restart the PC for a clean state
Phase 1: Create the Recovery Partition
Before running the script, create a partition manually:
Step 1: Open Disk Management
Win + X → Disk Management
Step 2: Shrink C: Drive
1. Right-click C: → "Shrink Volume"
2. Enter amount: 30000-50000 MB (30-50 GB)
(Should be ~70% of your used space on C:)
3. Click "Shrink"
Step 3: Create New Volume
1. Right-click the new "Unallocated" space
2. "New Simple Volume"
3. Wizard:
- Use all available space
- Assign letter: R
- Format: NTFS
- Volume Label: Recovery
4. Click "Finish"
You should now see a Recovery (R:) drive in File Explorer.
Phase 2: The PowerShell Script
How the Script Works
The PowerShell script automates 4 steps:
| Step | What It Does | How |
|---|---|---|
| 1 | Capture system image | VSS Shadow Copy + DISM /Capture |
| 1.5 | Generate silent OOBE config | Creates unattend.xml |
| 2 | Create restore scripts | Generates WinRE-compatible .cmd files |
| 3 | Create desktop shortcuts | Factory Reset.bat + instructions |
| 4 | Hide partition (optional) | DiskPart removes letter + sets GPT attributes |
Key Technical Decisions
Why VSS Shadow Copy?
You cannot capture a live Windows drive — files are locked by the OS:
Direct DISM on C:\ → ERROR 0x80070020 (Sharing Violation)
VSS creates a frozen point-in-time snapshot that DISM can capture without any file locks.
# Create shadow copy
$result = (Get-WmiObject -List Win32_ShadowCopy).Create("C:\", "ClientAccessible")
# Mount it
cmd /c "mklink /d C:\ShadowCopyMount \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\"
# Capture from snapshot (no locks!)
DISM.exe /Capture-Image /ImageFile:R:\RecoveryImage\install.wim `
/CaptureDir:C:\ShadowCopyMount /Name:"Recovery" /Compress:maximum
Why Not “Reset this PC” with reagentc?
reagentc /setosimage → "Reset this PC" → Windows RECONSTRUCTS the OS
→ Win32 apps are STRIPPED OUT ❌
DISM /Apply-Image → Writes image BYTE-FOR-BYTE back to disk
→ EVERYTHING preserved ✅
Why Hardcoded Paths in restore.cmd?
WinRE has a stripped-down cmd.exe that doesn’t support EnableDelayedExpansion:
:: BROKEN in WinRE:
setlocal EnableDelayedExpansion
set WIM=R:\RecoveryImage\install.wim
DISM.exe /ImageFile:"!WIM!" ← Expands to "" → Error 87
:: WORKS in WinRE:
DISM.exe /ImageFile:R:\RecoveryImage\install.wim ← Hardcoded, always works
Why Scan All Drive Letters?
WinRE reassigns drive letters differently than normal Windows:
Normal Windows: WinRE:
C: = Windows C: = Windows (usually)
R: = Recovery D: = Recovery (auto-assigned!)
R: = nothing
The restore script scans every letter before resorting to diskpart:
if exist D:\RecoveryImage\install.wim set RECOVERY_DRIVE=D:& goto FOUND
if exist E:\RecoveryImage\install.wim set RECOVERY_DRIVE=E:& goto FOUND
...
:: If none found, mount via diskpart with known disk/partition numbers
Why ping Instead of timeout?
timeout.exe doesn’t exist in WinRE:
timeout /t 3 /nobreak > nul ← ERROR in WinRE
ping -n 4 127.0.0.1 > nul ← Works everywhere (3 second delay)
Why Separate DiskPart Calls for Re-hide?
Combining all operations in one script causes cascading failures:
:: BROKEN: If "remove letter" fails, set id and gpt also fail
select partition 5
remove letter=R ← fails if letter isn't R
set id=de94bba4... ← skipped!
gpt attributes=0x80... ← skipped!
:: FIXED: Each operation is independent
:: Call 1:
select partition 5
remove ← removes whatever letter is assigned
exit
:: Call 2:
select partition 5
set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac override
exit
:: Call 3:
select partition 5
gpt attributes=0x8000000000000001
exit
The Complete Script
Save as Create-RecoveryPartition.ps1 and run as Administrator:
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Windows 11 Recovery Partition Creator — Final Version
.DESCRIPTION
All fixes included:
- VSS Shadow Copy for WIM capture
- Hardcoded DISM paths (no variables, no delayed expansion)
- ping instead of timeout (WinRE compatible)
- Separate diskpart calls for re-hide
- Full logging to C:\temp\restore_log.txt
- Unattend.xml for silent OOBE
- Hidden partition support
- Desktop shortcuts
.NOTES
Run as Administrator
Requires existing recovery partition (create in Disk Management first)
#>
# ============================================================
# CONFIGURATION
# ============================================================
$RecoveryDriveLetter = ""
$RecoveryFolderName = "RecoveryImage"
$RecoveryVolumeLabel = "Recovery"
$ImageName = "Windows11CustomRecovery"
$ImageDescription = "FullSystemWithAppsAndDrivers"
$CompressionType = "maximum"
$LogFile = "$env:USERPROFILE\Desktop\RecoverySetup_Log.txt"
$ShadowMountPoint = "C:\ShadowCopyMount"
# ============================================================
# LOGGING
# ============================================================
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
switch ($Level) {
"INFO" { Write-Host $logEntry -ForegroundColor Cyan }
"SUCCESS" { Write-Host $logEntry -ForegroundColor Green }
"WARNING" { Write-Host $logEntry -ForegroundColor Yellow }
"ERROR" { Write-Host $logEntry -ForegroundColor Red }
"STEP" {
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Magenta
Write-Host " $Message" -ForegroundColor Magenta
Write-Host ("=" * 70) -ForegroundColor Magenta
}
}
Add-Content -Path $LogFile -Value $logEntry
}
function Show-Banner {
Clear-Host
Write-Host ""
Write-Host " ╔════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host " ║ Windows 11 - Recovery Partition Creator (Final) ║" -ForegroundColor Cyan
Write-Host " ║ Full System Restore — Apps + Drivers + Settings ║" -ForegroundColor Cyan
Write-Host " ║ WinRE Compatible — Logging — Hidden Partition Support ║" -ForegroundColor Cyan
Write-Host " ╚════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
}
# ============================================================
# PARTITION SELECTION & PREREQUISITES
# ============================================================
function Show-AvailablePartitions {
$volumes = Get-Volume | Where-Object {
$_.DriveLetter -and $_.DriveType -eq 'Fixed' -and $_.DriveLetter -ne 'C'
} | Sort-Object DriveLetter
if ($volumes.Count -eq 0) {
Write-Log "No suitable partitions found!" "ERROR"
Write-Host ""
Write-Host " Create a recovery partition first:" -ForegroundColor Yellow
Write-Host " 1. Win+X → Disk Management" -ForegroundColor White
Write-Host " 2. Shrink C: by 30-50 GB" -ForegroundColor White
Write-Host " 3. Create New Simple Volume (NTFS, label: Recovery)" -ForegroundColor White
Write-Host ""
return $null
}
Write-Host " Available Drives:" -ForegroundColor Yellow
Write-Host " ────────────────────────────────────────────────────────" -ForegroundColor Gray
foreach ($vol in $volumes) {
$sizeGB = [math]::Round($vol.Size / 1GB, 2)
$freeGB = [math]::Round($vol.SizeRemaining / 1GB, 2)
$label = if ($vol.FileSystemLabel) { $vol.FileSystemLabel } else { "No Label" }
Write-Host (" [{0}:] {1,-20} Total: {2,8} GB | Free: {3,8} GB" -f
$vol.DriveLetter, $label, $sizeGB, $freeGB) -ForegroundColor White
}
Write-Host " ────────────────────────────────────────────────────────" -ForegroundColor Gray
Write-Host ""
return $volumes
}
function Get-RecoveryDrive {
$volumes = Show-AvailablePartitions
if ($null -eq $volumes) { return $null }
do {
$inputDrive = Read-Host " Enter Recovery partition drive letter (e.g., R, D, E)"
$inputDrive = $inputDrive.Trim().ToUpper().TrimEnd(':')
$selectedVol = $volumes | Where-Object { $_.DriveLetter -eq $inputDrive }
if (-not $selectedVol) { Write-Log "Invalid drive letter. Try again." "WARNING" }
} while (-not $selectedVol)
return "${inputDrive}:"
}
function Test-Prerequisites {
param([string]$RecoveryDrive)
Write-Log "Running prerequisite checks..." "INFO"
Write-Host ""
$allPassed = $true
# Admin check
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
if ($principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host " [✓] Administrator privileges" -ForegroundColor Green
} else {
Write-Host " [✗] Administrator privileges" -ForegroundColor Red
$allPassed = $false
}
# DISM check
if (Test-Path "$env:SystemRoot\System32\Dism.exe") {
Write-Host " [✓] DISM.exe found" -ForegroundColor Green
} else {
Write-Host " [✗] DISM.exe not found" -ForegroundColor Red
$allPassed = $false
}
# Recovery drive check
if (Test-Path "$RecoveryDrive\") {
Write-Host " [✓] Recovery drive ($RecoveryDrive) accessible" -ForegroundColor Green
} else {
Write-Host " [✗] Recovery drive ($RecoveryDrive) not accessible" -ForegroundColor Red
$allPassed = $false
}
# VSS check
$vssSvc = Get-Service -Name VSS -ErrorAction SilentlyContinue
if ($vssSvc) {
Write-Host " [✓] VSS Service available ($($vssSvc.Status))" -ForegroundColor Green
} else {
Write-Host " [✗] VSS Service not found" -ForegroundColor Red
$allPassed = $false
}
# Free space check
$recVol = Get-Volume -DriveLetter ($RecoveryDrive.TrimEnd(':')) -ErrorAction SilentlyContinue
$srcVol = Get-Volume -DriveLetter 'C' -ErrorAction SilentlyContinue
if ($recVol -and $srcVol) {
$usedGB = [math]::Round(($srcVol.Size - $srcVol.SizeRemaining) / 1GB, 2)
$requiredGB = [math]::Round($usedGB * 0.7, 2)
$freeGB = [math]::Round($recVol.SizeRemaining / 1GB, 2)
if ($freeGB -ge $requiredGB) {
Write-Host " [✓] Free space OK (Need ~${requiredGB}GB, Have ${freeGB}GB)" -ForegroundColor Green
} else {
Write-Host " [✗] Insufficient space (Need ~${requiredGB}GB, Have ${freeGB}GB)" -ForegroundColor Red
$allPassed = $false
}
Write-Host " [i] C: used space: ${usedGB} GB" -ForegroundColor Gray
}
Write-Host ""
return $allPassed
}
# ============================================================
# VSS SHADOW COPY
# ============================================================
function New-ShadowCopy {
Write-Log "Creating VSS Shadow Copy of C: drive..." "INFO"
Start-Service VSS -ErrorAction SilentlyContinue
Start-Service swprv -ErrorAction SilentlyContinue
Start-Sleep -Seconds 3
try {
$result = (Get-WmiObject -List Win32_ShadowCopy).Create("C:\", "ClientAccessible")
if ($result.ReturnValue -ne 0) {
Write-Log "Shadow copy failed with code: $($result.ReturnValue)" "ERROR"
return $null
}
$shadowID = $result.ShadowID
$shadowCopy = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $shadowID }
$shadowPath = $shadowCopy.DeviceObject
Write-Log "Shadow copy created: $shadowPath" "SUCCESS"
return @{
ID = $shadowID
DevicePath = $shadowPath
}
}
catch {
Write-Log "Shadow copy exception: $_" "ERROR"
return $null
}
}
function Mount-ShadowCopy {
param([string]$ShadowDevicePath, [string]$MountPoint)
if (Test-Path $MountPoint) {
cmd /c "rmdir `"$MountPoint`"" 2>$null
}
$deviceWithSlash = $ShadowDevicePath
if (-not $deviceWithSlash.EndsWith('\')) {
$deviceWithSlash += '\'
}
cmd /c "mklink /d `"$MountPoint`" `"$deviceWithSlash`"" 2>&1 | Out-Null
if ((Test-Path $MountPoint) -and (Get-ChildItem $MountPoint -ErrorAction SilentlyContinue)) {
Write-Log "Shadow copy mounted at $MountPoint" "SUCCESS"
return $true
}
Write-Log "Shadow copy mount failed!" "ERROR"
return $false
}
function Remove-ShadowCopyAndMount {
param([string]$ShadowID, [string]$MountPoint)
if (Test-Path $MountPoint) {
cmd /c "rmdir `"$MountPoint`"" 2>$null
}
if ($ShadowID) {
try {
$shadow = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $ShadowID }
if ($shadow) { $shadow.Delete() }
Write-Log "Shadow copy cleaned up." "SUCCESS"
}
catch {
Write-Log "Shadow cleanup warning: $_" "WARNING"
}
}
}
# ============================================================
# STEP 1: CAPTURE WIM IMAGE
# ============================================================
function Invoke-Step1_CaptureWIM {
param(
[string]$RecoveryDrive,
[string]$FolderName,
[string]$Name,
[string]$Description,
[string]$Compression
)
Write-Log "STEP 1: CAPTURE WIM IMAGE (via VSS Shadow Copy)" "STEP"
$recoveryPath = Join-Path $RecoveryDrive $FolderName
$wimFile = Join-Path $recoveryPath "install.wim"
$shadowInfo = $null
try {
# Create folder
if (-not (Test-Path $recoveryPath)) {
New-Item -Path $recoveryPath -ItemType Directory -Force | Out-Null
Write-Log "Created folder: $recoveryPath" "INFO"
}
# Check existing WIM
if (Test-Path $wimFile) {
$fileSize = [math]::Round((Get-Item $wimFile).Length / 1GB, 2)
Write-Host ""
Write-Host " ⚠ Existing WIM found: $wimFile ($fileSize GB)" -ForegroundColor Yellow
$overwrite = Read-Host " Overwrite? (Y/N)"
if ($overwrite -ne 'Y' -and $overwrite -ne 'y') {
Write-Log "Using existing WIM file." "WARNING"
return @{ Success = $true; WimFile = $wimFile; Skipped = $true }
}
Remove-Item $wimFile -Force
}
# Create shadow copy
$shadowInfo = New-ShadowCopy
if ($null -eq $shadowInfo) {
return @{ Success = $false; WimFile = $null; Skipped = $false }
}
# Mount shadow copy
$mounted = Mount-ShadowCopy -ShadowDevicePath $shadowInfo.DevicePath -MountPoint $ShadowMountPoint
if (-not $mounted) {
return @{ Success = $false; WimFile = $null; Skipped = $false }
}
# Capture
Write-Host ""
Write-Host " ⏳ Capturing image... This takes 20-60 minutes." -ForegroundColor Yellow
Write-Host " ⚠ Do NOT restart or shut down your PC." -ForegroundColor Yellow
Write-Host ""
$startTime = Get-Date
$batchFile = "$env:TEMP\dism_capture.cmd"
@"
@echo off
DISM.exe /Capture-Image /ImageFile:"$wimFile" /CaptureDir:"$ShadowMountPoint" /Name:"$Name" /Description:"$Description" /Compress:$Compression
exit /b %errorlevel%
"@ | Out-File -FilePath $batchFile -Encoding ASCII -Force
$proc = Start-Process -FilePath "cmd.exe" `
-ArgumentList "/c `"$batchFile`"" `
-NoNewWindow -Wait -PassThru
$duration = (Get-Date) - $startTime
Remove-Item $batchFile -Force -ErrorAction SilentlyContinue
if ($proc.ExitCode -eq 0 -and (Test-Path $wimFile)) {
$wimSize = [math]::Round((Get-Item $wimFile).Length / 1GB, 2)
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────┐" -ForegroundColor Green
Write-Host " │ ✅ CAPTURE SUCCESSFUL! │" -ForegroundColor Green
Write-Host " │ File : $wimFile" -ForegroundColor Green
Write-Host " │ Size : $wimSize GB" -ForegroundColor Green
Write-Host " │ Duration : $($duration.ToString('hh\:mm\:ss'))" -ForegroundColor Green
Write-Host " └──────────────────────────────────────────────────┘" -ForegroundColor Green
Write-Host ""
# Verify
Write-Log "Verifying captured image..." "INFO"
& DISM.exe /Get-ImageInfo /ImageFile:"$wimFile"
return @{ Success = $true; WimFile = $wimFile; Skipped = $false }
}
else {
Write-Log "DISM capture FAILED! Exit code: $($proc.ExitCode)" "ERROR"
Write-Host " Check DISM log: notepad C:\Windows\Logs\DISM\dism.log" -ForegroundColor Yellow
return @{ Success = $false; WimFile = $null; Skipped = $false }
}
}
catch {
Write-Log "Exception during capture: $_" "ERROR"
return @{ Success = $false; WimFile = $null; Skipped = $false }
}
finally {
# Always clean up shadow copy
Write-Log "Cleaning up shadow copy..." "INFO"
if ($shadowInfo) {
Remove-ShadowCopyAndMount -ShadowID $shadowInfo.ID -MountPoint $ShadowMountPoint
}
elseif (Test-Path $ShadowMountPoint) {
cmd /c "rmdir `"$ShadowMountPoint`"" 2>$null
}
}
}
# ============================================================
# STEP 1.5: UNATTEND.XML GENERATION
# ============================================================
function Get-CurrentSystemLocale {
$culture = Get-Culture
$uiLang = Get-WinSystemLocale
$timezone = (Get-TimeZone).Id
$keyboard = (Get-WinUserLanguageList)[0].InputMethodTips[0]
$kbLayout = if ($keyboard) { $keyboard } else { "0409:00000409" }
return @{
SystemLocale = $uiLang.Name
UILanguage = $culture.Name
UserLocale = $culture.Name
InputLocale = $kbLayout
TimeZone = $timezone
LanguageName = $culture.DisplayName
}
}
function Get-UnattendSettings {
Write-Log "STEP 1.5: CONFIGURE SILENT OOBE (unattend.xml)" "STEP"
$currentLocale = Get-CurrentSystemLocale
Write-Host ""
Write-Host " ── Detected System Settings ──" -ForegroundColor Yellow
Write-Host " Language : $($currentLocale.LanguageName)" -ForegroundColor Gray
Write-Host " Locale : $($currentLocale.SystemLocale)" -ForegroundColor Gray
Write-Host " Keyboard : $($currentLocale.InputLocale)" -ForegroundColor Gray
Write-Host " Time Zone : $($currentLocale.TimeZone)" -ForegroundColor Gray
Write-Host ""
$useCurrentLocale = Read-Host " Use current region/keyboard/language? (Y/N) [Y]"
if ([string]::IsNullOrEmpty($useCurrentLocale)) { $useCurrentLocale = 'Y' }
if ($useCurrentLocale -eq 'Y' -or $useCurrentLocale -eq 'y') {
$locale = $currentLocale.SystemLocale
$uiLanguage = $currentLocale.UILanguage
$userLocale = $currentLocale.UserLocale
$inputLocale = $currentLocale.InputLocale
$timeZone = $currentLocale.TimeZone
}
else {
Write-Host " Common codes: en-US, fr-FR, de-DE, es-ES, nl-NL, pt-BR, ja-JP" -ForegroundColor Gray
$locale = Read-Host " System Locale (e.g., fr-FR)"
$uiLanguage = Read-Host " UI Language (e.g., fr-FR)"
$userLocale = Read-Host " User Locale (e.g., fr-FR)"
$inputLocale = Read-Host " Keyboard (e.g., 040c:0000040c for French)"
$timeZone = Read-Host " Time Zone (e.g., Romance Standard Time)"
}
Write-Host ""
Write-Host " ── Local User Account ──" -ForegroundColor Yellow
$currentUser = $env:USERNAME
$username = Read-Host " Username [$currentUser]"
if ([string]::IsNullOrEmpty($username)) { $username = $currentUser }
$password = Read-Host " Password (leave blank for no password)"
$hasPassword = -not [string]::IsNullOrEmpty($password)
$isAdmin = Read-Host " Make Administrator? (Y/N) [Y]"
if ([string]::IsNullOrEmpty($isAdmin)) { $isAdmin = 'Y' }
$groupName = if ($isAdmin -eq 'Y' -or $isAdmin -eq 'y') { "Administrators" } else { "Users" }
$currentPC = $env:COMPUTERNAME
$computerName = Read-Host " Computer Name [$currentPC]"
if ([string]::IsNullOrEmpty($computerName)) { $computerName = $currentPC }
$autoLogon = Read-Host " Auto-login after restore? (Y/N) [Y]"
if ([string]::IsNullOrEmpty($autoLogon)) { $autoLogon = 'Y' }
# Summary
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────┐" -ForegroundColor White
Write-Host " │ Locale : $locale" -ForegroundColor White
Write-Host " │ Keyboard : $inputLocale" -ForegroundColor White
Write-Host " │ Time Zone : $timeZone" -ForegroundColor White
Write-Host " │ Username : $username" -ForegroundColor White
Write-Host " │ Password : $(if ($hasPassword) {'****'} else {'(none)'})" -ForegroundColor White
Write-Host " │ Admin : $groupName" -ForegroundColor White
Write-Host " │ PC Name : $computerName" -ForegroundColor White
Write-Host " │ Auto-login : $autoLogon" -ForegroundColor White
Write-Host " └──────────────────────────────────────────────────┘" -ForegroundColor White
Write-Host ""
$confirm = Read-Host " Confirm these settings? (Y/N)"
if ($confirm -ne 'Y' -and $confirm -ne 'y') { return $null }
return @{
Locale = $locale
UILanguage = $uiLanguage
UserLocale = $userLocale
InputLocale = $inputLocale
TimeZone = $timeZone
Username = $username
Password = $password
HasPassword = $hasPassword
GroupName = $groupName
ComputerName = $computerName
AutoLogon = ($autoLogon -eq 'Y' -or $autoLogon -eq 'y')
}
}
function New-UnattendXml {
param([hashtable]$S)
$arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "x86" }
$passwordBlock = if ($S.HasPassword) {
$encodedPwd = [Convert]::ToBase64String(
[System.Text.Encoding]::Unicode.GetBytes($S.Password + "Password")
)
"<Password><Value>$encodedPwd</Value><PlainText>false</PlainText></Password>"
} else {
"<Password><Value></Value><PlainText>true</PlainText></Password>"
}
$autoLogonBlock = ""
if ($S.AutoLogon) {
$autoLogonBlock = @"
<AutoLogon>
<Enabled>true</Enabled>
<Username>$($S.Username)</Username>
<Password>
<Value>$($S.Password)</Value>
<PlainText>true</PlainText>
</Password>
<LogonCount>3</LogonCount>
</AutoLogon>
"@
}
return @"
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="$arch"
publicKeyToken="31bf3856ad364e35"
language="neutral" versionScope="nonSxS">
<ComputerName>$($S.ComputerName)</ComputerName>
</component>
<component name="Microsoft-Windows-Deployment"
processorArchitecture="$arch"
publicKeyToken="31bf3856ad364e35"
language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE /v BypassNRO /t REG_DWORD /d 1 /f</Path>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core"
processorArchitecture="$arch"
publicKeyToken="31bf3856ad364e35"
language="neutral" versionScope="nonSxS">
<InputLocale>$($S.InputLocale)</InputLocale>
<SystemLocale>$($S.Locale)</SystemLocale>
<UILanguage>$($S.UILanguage)</UILanguage>
<UILanguageFallback>en-US</UILanguageFallback>
<UserLocale>$($S.UserLocale)</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="$arch"
publicKeyToken="31bf3856ad364e35"
language="neutral" versionScope="nonSxS">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideLocalAccountScreen>true</HideLocalAccountScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>3</ProtectYourPC>
<SkipMachineOOBE>true</SkipMachineOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
</OOBE>
<TimeZone>$($S.TimeZone)</TimeZone>
<UserAccounts>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Name>$($S.Username)</Name>
<Group>$($S.GroupName)</Group>
<DisplayName>$($S.Username)</DisplayName>
$passwordBlock
</LocalAccount>
</LocalAccounts>
</UserAccounts>
$autoLogonBlock
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<CommandLine>reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f</CommandLine>
<RequiresUserInput>false</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>2</Order>
<CommandLine>reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OOBE" /v DisablePrivacyExperience /t REG_DWORD /d 1 /f</CommandLine>
<RequiresUserInput>false</RequiresUserInput>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
</unattend>
"@
}
function Save-UnattendXml {
param([string]$RecoveryPath, [hashtable]$Settings)
if ($null -eq $Settings) {
Write-Log "Unattend configuration skipped." "WARNING"
return
}
$xml = New-UnattendXml -S $Settings
$unattendFile = Join-Path $RecoveryPath "unattend.xml"
$xml | Out-File -FilePath $unattendFile -Encoding UTF8 -Force
Write-Log "Saved: $unattendFile" "SUCCESS"
}
# ============================================================
# STEP 2: CREATE RECOVERY SCRIPTS
# ════════════════════════════════════════════════════════════
# Key design:
# - NO setlocal EnableDelayedExpansion
# - NO !variables! in any DISM/diskpart commands
# - ALL paths hardcoded at generation time
# - ping instead of timeout (WinRE compatible)
# - Separate diskpart calls for re-hide
# - Full logging to C:\temp\restore_log.txt
# ============================================================
function Invoke-Step2_CreateRecoveryScripts {
param(
[string]$RecoveryDrive,
[string]$RecoveryPath,
[string]$WimFile
)
Write-Log "STEP 2: CREATE RECOVERY SCRIPTS" "STEP"
$diskNumber = (Get-Partition -DriveLetter C).DiskNumber
$cPartNum = (Get-Partition -DriveLetter C).PartitionNumber
$partitions = Get-Partition -DiskNumber $diskNumber
$efiPart = $partitions | Where-Object {
$_.Type -eq 'System' -or $_.GptType -eq '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}'
}
$efiPartNum = if ($efiPart) { $efiPart.PartitionNumber } else { 1 }
$recDriveLetter = $RecoveryDrive.TrimEnd(':')
$recPartition = Get-Partition -DriveLetter $recDriveLetter
$recPartNum = $recPartition.PartitionNumber
$recDiskNum = $recPartition.DiskNumber
$osPartSize = [math]::Round((Get-Partition -DriveLetter C).Size / 1GB, 0)
Write-Host ""
Write-Host " ── Partition Layout ──" -ForegroundColor Yellow
Write-Host " OS Disk : $diskNumber, Partition $cPartNum (C:)" -ForegroundColor Gray
Write-Host " EFI : $diskNumber, Partition $efiPartNum" -ForegroundColor Gray
Write-Host " Recovery : $recDiskNum, Partition $recPartNum" -ForegroundColor Gray
Write-Host ""
# ════════════════════════════════════════════════════════
# RESTORE.CMD
#
# Fix: Scans ALL drive letters first to find install.wim
# Only uses diskpart if WIM not found on any letter
# ════════════════════════════════════════════════════════
$restoreScript = @"
@echo off
cls
color 1F
mkdir C:\temp 2>nul
echo ================================================== > C:\temp\restore_log.txt
echo FACTORY RESET LOG >> C:\temp\restore_log.txt
echo Started: %date% %time% >> C:\temp\restore_log.txt
echo ================================================== >> C:\temp\restore_log.txt
echo.
echo ========================================================
echo FACTORY RESET - FULL SYSTEM RESTORE
echo All data on C: will be PERMANENTLY DELETED!
echo Log: C:\temp\restore_log.txt
echo ========================================================
echo.
if /i "%1"=="AUTO" goto SKIP_CONFIRM
echo Type YES to confirm factory reset:
set /p CONFIRM=
if /i not "%CONFIRM%"=="YES" (
echo Cancelled.
echo [%time%] Cancelled >> C:\temp\restore_log.txt
pause
exit /b 0
)
:SKIP_CONFIRM
echo [%time%] Confirmed >> C:\temp\restore_log.txt
REM ──────────────────────────────────────────────────────
REM STEP 1/6: FIND the recovery image
REM
REM Method A: Scan all existing drive letters (D-Z)
REM WinRE may have already assigned a letter
REM Method B: Use diskpart to mount known partition as R:
REM For when partition is truly hidden
REM ──────────────────────────────────────────────────────
echo.
echo [1/6] Searching for recovery image...
echo.
echo [%time%] STEP 1: Finding recovery image >> C:\temp\restore_log.txt
set RECOVERY_DRIVE=
set WIM_FOUND=0
REM ── Method A: Scan all existing drive letters ──
echo Scanning all drive letters...
echo [%time%] Scanning drive letters A-Z >> C:\temp\restore_log.txt
if exist C:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=C:
set WIM_FOUND=1
echo Found on C:
echo [%time%] Found on C: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist D:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=D:
set WIM_FOUND=1
echo Found on D:
echo [%time%] Found on D: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist E:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=E:
set WIM_FOUND=1
echo Found on E:
echo [%time%] Found on E: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist F:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=F:
set WIM_FOUND=1
echo Found on F:
echo [%time%] Found on F: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist G:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=G:
set WIM_FOUND=1
echo Found on G:
echo [%time%] Found on G: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist H:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=H:
set WIM_FOUND=1
echo Found on H:
echo [%time%] Found on H: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist I:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=I:
set WIM_FOUND=1
echo Found on I:
echo [%time%] Found on I: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist J:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=J:
set WIM_FOUND=1
echo Found on J:
echo [%time%] Found on J: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist K:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=K:
set WIM_FOUND=1
echo Found on K:
echo [%time%] Found on K: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist L:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=L:
set WIM_FOUND=1
echo Found on L:
echo [%time%] Found on L: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist M:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=M:
set WIM_FOUND=1
echo Found on M:
echo [%time%] Found on M: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist N:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=N:
set WIM_FOUND=1
echo Found on N:
echo [%time%] Found on N: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist O:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=O:
set WIM_FOUND=1
echo Found on O:
echo [%time%] Found on O: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist P:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=P:
set WIM_FOUND=1
echo Found on P:
echo [%time%] Found on P: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist Q:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=Q:
set WIM_FOUND=1
echo Found on Q:
echo [%time%] Found on Q: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist R:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=R:
set WIM_FOUND=1
echo Found on R:
echo [%time%] Found on R: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist S:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=S:
set WIM_FOUND=1
echo Found on S:
echo [%time%] Found on S: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist T:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=T:
set WIM_FOUND=1
echo Found on T:
echo [%time%] Found on T: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist U:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=U:
set WIM_FOUND=1
echo Found on U:
echo [%time%] Found on U: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist V:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=V:
set WIM_FOUND=1
echo Found on V:
echo [%time%] Found on V: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist W:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=W:
set WIM_FOUND=1
echo Found on W:
echo [%time%] Found on W: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist X:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=X:
set WIM_FOUND=1
echo Found on X:
echo [%time%] Found on X: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist Y:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=Y:
set WIM_FOUND=1
echo Found on Y:
echo [%time%] Found on Y: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
if exist Z:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=Z:
set WIM_FOUND=1
echo Found on Z:
echo [%time%] Found on Z: >> C:\temp\restore_log.txt
goto FOUND_WIM
)
REM ── Method B: Not found on any letter — mount via diskpart ──
echo.
echo Not found on any lettered drive.
echo Mounting hidden partition (Disk $recDiskNum, Part $recPartNum) as R: ...
echo [%time%] Not on any letter. Trying diskpart mount... >> C:\temp\restore_log.txt
(
echo select disk $recDiskNum
echo select partition $recPartNum
echo assign letter=R
echo exit
) > %TEMP%\dp_mount.txt
diskpart /s %TEMP%\dp_mount.txt >> C:\temp\restore_log.txt 2>&1
echo [%time%] diskpart mount exit: %errorlevel% >> C:\temp\restore_log.txt
del %TEMP%\dp_mount.txt 2>nul
ping -n 5 127.0.0.1 > nul
if exist R:\$RecoveryFolderName\install.wim (
set RECOVERY_DRIVE=R:
set WIM_FOUND=1
echo Found after diskpart mount on R:
echo [%time%] Found on R: after diskpart >> C:\temp\restore_log.txt
goto FOUND_WIM
)
REM ── ALL METHODS FAILED ──
echo.
echo ════════════════════════════════════════════════════
echo ERROR: Recovery image not found anywhere!
echo ════════════════════════════════════════════════════
echo.
echo [%time%] FATAL: Recovery image not found! >> C:\temp\restore_log.txt
echo [%time%] Dumping volume list: >> C:\temp\restore_log.txt
(echo list volume
echo exit) > %TEMP%\dp_dbg.txt
diskpart /s %TEMP%\dp_dbg.txt >> C:\temp\restore_log.txt 2>&1
diskpart /s %TEMP%\dp_dbg.txt
del %TEMP%\dp_dbg.txt 2>nul
echo.
echo Check log: C:\temp\restore_log.txt
pause
exit /b 1
:FOUND_WIM
echo.
echo Recovery drive : %RECOVERY_DRIVE%
echo Image file : %RECOVERY_DRIVE%\$RecoveryFolderName\install.wim
echo.
echo [%time%] Using drive: %RECOVERY_DRIVE% >> C:\temp\restore_log.txt
REM ──────────────────────────────────────────────────────
REM STEP 2/6: Format C:
REM ──────────────────────────────────────────────────────
echo [2/6] Formatting C: drive...
echo.
echo [%time%] STEP 2: Formatting C: >> C:\temp\restore_log.txt
REM ── Save log before format ──
mkdir %RECOVERY_DRIVE%\temp 2>nul
copy /y C:\temp\restore_log.txt %RECOVERY_DRIVE%\temp\restore_log.txt > nul 2>nul
format C: /FS:NTFS /Q /Y /V:Windows
set FMT_ERR=%errorlevel%
REM ── Restore log after format ──
mkdir C:\temp 2>nul
copy /y %RECOVERY_DRIVE%\temp\restore_log.txt C:\temp\restore_log.txt > nul 2>nul
echo [%time%] format exit: %FMT_ERR% >> C:\temp\restore_log.txt
if %FMT_ERR% neq 0 (
echo format.exe failed, using diskpart...
echo [%time%] format failed, trying diskpart >> C:\temp\restore_log.txt
(
echo select disk $diskNumber
echo select partition $cPartNum
echo format fs=ntfs quick label=Windows
echo assign letter=C
echo exit
) > %TEMP%\dp_fmt.txt
diskpart /s %TEMP%\dp_fmt.txt >> C:\temp\restore_log.txt 2>&1
del %TEMP%\dp_fmt.txt 2>nul
mkdir C:\temp 2>nul
copy /y %RECOVERY_DRIVE%\temp\restore_log.txt C:\temp\restore_log.txt > nul 2>nul
)
echo C: formatted.
echo [%time%] Format done >> C:\temp\restore_log.txt
REM ──────────────────────────────────────────────────────
REM STEP 3/6: Apply recovery image
REM
REM Uses %RECOVERY_DRIVE% which was set during scan.
REM This is a SIMPLE %var% — works fine in basic cmd.exe
REM (only !var! delayed expansion is broken in WinRE)
REM ──────────────────────────────────────────────────────
echo.
echo [3/6] Applying recovery image (15-30 minutes)...
echo DO NOT turn off your computer!
echo.
echo [%time%] STEP 3: Applying image >> C:\temp\restore_log.txt
echo [%time%] Source: %RECOVERY_DRIVE%\$RecoveryFolderName\install.wim >> C:\temp\restore_log.txt
DISM.exe /Apply-Image /ImageFile:%RECOVERY_DRIVE%\$RecoveryFolderName\install.wim /Index:1 /ApplyDir:C:\
set DISM_ERR=%errorlevel%
echo [%time%] DISM exit: %DISM_ERR% >> C:\temp\restore_log.txt
if %DISM_ERR% neq 0 (
echo.
echo Attempt 1 failed (code: %DISM_ERR%).
echo Trying without trailing backslash...
echo [%time%] Trying /ApplyDir:C: >> C:\temp\restore_log.txt
DISM.exe /Apply-Image /ImageFile:%RECOVERY_DRIVE%\$RecoveryFolderName\install.wim /Index:1 /ApplyDir:C:
set DISM_ERR2=%errorlevel%
echo [%time%] DISM attempt 2 exit: %DISM_ERR2% >> C:\temp\restore_log.txt
if %DISM_ERR2% neq 0 (
echo.
echo ALL DISM ATTEMPTS FAILED!
echo [%time%] ALL DISM FAILED >> C:\temp\restore_log.txt
echo.
echo Source: %RECOVERY_DRIVE%\$RecoveryFolderName\install.wim
echo Target: C:\
echo Log: C:\temp\restore_log.txt
pause
exit /b 1
)
)
echo.
echo Image applied successfully!
echo [%time%] Image applied OK >> C:\temp\restore_log.txt
REM ──────────────────────────────────────────────────────
REM STEP 4/6: Rebuild boot
REM ──────────────────────────────────────────────────────
echo.
echo [4/6] Rebuilding boot configuration...
echo.
echo [%time%] STEP 4: Boot config >> C:\temp\restore_log.txt
(
echo select disk $diskNumber
echo select partition $efiPartNum
echo assign letter=S
echo exit
) > %TEMP%\dp_efi.txt
diskpart /s %TEMP%\dp_efi.txt >> C:\temp\restore_log.txt 2>&1
del %TEMP%\dp_efi.txt 2>nul
ping -n 3 127.0.0.1 > nul
echo [%time%] bcdboot C:\Windows /s S: /f UEFI >> C:\temp\restore_log.txt
bcdboot C:\Windows /s S: /f UEFI >> C:\temp\restore_log.txt 2>&1
set BCD_ERR=%errorlevel%
echo [%time%] bcdboot exit: %BCD_ERR% >> C:\temp\restore_log.txt
if %BCD_ERR% neq 0 (
echo [%time%] Trying without /s S: >> C:\temp\restore_log.txt
bcdboot C:\Windows /f UEFI >> C:\temp\restore_log.txt 2>&1
)
(
echo select disk $diskNumber
echo select partition $efiPartNum
echo remove letter=S
echo exit
) > %TEMP%\dp_efi2.txt
diskpart /s %TEMP%\dp_efi2.txt >> C:\temp\restore_log.txt 2>&1
del %TEMP%\dp_efi2.txt 2>nul
echo Boot rebuilt.
echo [%time%] Boot done >> C:\temp\restore_log.txt
REM ──────────────────────────────────────────────────────
REM STEP 5/6: Unattend.xml
REM ──────────────────────────────────────────────────────
echo.
echo [5/6] Applying unattend.xml...
echo.
echo [%time%] STEP 5: Unattend >> C:\temp\restore_log.txt
if exist %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml (
echo [%time%] unattend.xml found >> C:\temp\restore_log.txt
mkdir C:\Windows\Panther\unattend 2>nul
mkdir C:\Windows\System32\Sysprep 2>nul
copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\Panther\unattend\unattend.xml >> C:\temp\restore_log.txt 2>&1
copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\Panther\unattend.xml >> C:\temp\restore_log.txt 2>&1
copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\System32\Sysprep\unattend.xml >> C:\temp\restore_log.txt 2>&1
reg load HKLM\OFFLINE_SW C:\Windows\System32\config\SOFTWARE >> C:\temp\restore_log.txt 2>&1
reg add "HKLM\OFFLINE_SW\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f >> C:\temp\restore_log.txt 2>&1
reg add "HKLM\OFFLINE_SW\Policies\Microsoft\Windows\OOBE" /v DisablePrivacyExperience /t REG_DWORD /d 1 /f >> C:\temp\restore_log.txt 2>&1
reg unload HKLM\OFFLINE_SW >> C:\temp\restore_log.txt 2>&1
echo Unattend.xml applied.
echo [%time%] Unattend OK >> C:\temp\restore_log.txt
) else (
echo No unattend.xml found.
echo [%time%] No unattend.xml >> C:\temp\restore_log.txt
)
REM ──────────────────────────────────────────────────────
REM STEP 6/6: Re-hide recovery partition
REM Separate diskpart calls for reliability
REM ──────────────────────────────────────────────────────
echo.
echo [6/6] Re-hiding recovery partition...
echo.
echo [%time%] STEP 6: Re-hide >> C:\temp\restore_log.txt
REM ── 6a: Remove whatever letter recovery has ──
echo [%time%] 6a: Removing recovery drive letter >> C:\temp\restore_log.txt
(
echo select disk $recDiskNum
echo select partition $recPartNum
echo remove
echo exit
) > %TEMP%\dp_rh1.txt
diskpart /s %TEMP%\dp_rh1.txt >> C:\temp\restore_log.txt 2>&1
echo [%time%] Remove letter exit: %errorlevel% >> C:\temp\restore_log.txt
del %TEMP%\dp_rh1.txt 2>nul
ping -n 3 127.0.0.1 > nul
REM ── 6b: Set partition type ──
echo [%time%] 6b: Setting partition type >> C:\temp\restore_log.txt
(
echo select disk $recDiskNum
echo select partition $recPartNum
echo set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac override
echo exit
) > %TEMP%\dp_rh2.txt
diskpart /s %TEMP%\dp_rh2.txt >> C:\temp\restore_log.txt 2>&1
echo [%time%] Set type exit: %errorlevel% >> C:\temp\restore_log.txt
del %TEMP%\dp_rh2.txt 2>nul
ping -n 3 127.0.0.1 > nul
REM ── 6c: Set GPT attributes ──
echo [%time%] 6c: Setting GPT attributes >> C:\temp\restore_log.txt
(
echo select disk $recDiskNum
echo select partition $recPartNum
echo gpt attributes=0x8000000000000001
echo exit
) > %TEMP%\dp_rh3.txt
diskpart /s %TEMP%\dp_rh3.txt >> C:\temp\restore_log.txt 2>&1
echo [%time%] Set GPT exit: %errorlevel% >> C:\temp\restore_log.txt
del %TEMP%\dp_rh3.txt 2>nul
echo Re-hide complete.
echo [%time%] Re-hide done >> C:\temp\restore_log.txt
REM ── Verify ──
echo [%time%] Final volume list: >> C:\temp\restore_log.txt
(echo list volume
echo exit) > %TEMP%\dp_vfy.txt
diskpart /s %TEMP%\dp_vfy.txt >> C:\temp\restore_log.txt 2>&1
del %TEMP%\dp_vfy.txt 2>nul
REM ──────────────────────────────────────────────────────
REM DONE
REM ──────────────────────────────────────────────────────
echo. >> C:\temp\restore_log.txt
echo ================================================== >> C:\temp\restore_log.txt
echo COMPLETED: %date% %time% >> C:\temp\restore_log.txt
echo ================================================== >> C:\temp\restore_log.txt
echo.
echo ========================================================
echo.
echo FACTORY RESET COMPLETE!
echo All applications, drivers, and settings restored.
echo Log: C:\temp\restore_log.txt
echo.
echo Restarting in 15 seconds...
echo.
echo ========================================================
echo.
echo 15...
ping -n 4 127.0.0.1 > nul
echo 12...
ping -n 4 127.0.0.1 > nul
echo 8...
ping -n 5 127.0.0.1 > nul
echo 3...
ping -n 4 127.0.0.1 > nul
wpeutil reboot
"@
# ════════════════════════════════════════════════════════
# DIAGNOSE.CMD
# ════════════════════════════════════════════════════════
$diagnoseScript = @"
@echo off
cls
color 1F
mkdir C:\temp 2>nul
echo RECOVERY DIAGNOSTICS LOG > C:\temp\diagnose_log.txt
echo %date% %time% >> C:\temp\diagnose_log.txt
echo.
echo ========================================================
echo RECOVERY DIAGNOSTICS (safe - no changes)
echo Log: C:\temp\diagnose_log.txt
echo ========================================================
echo.
echo ── Current volumes ──
echo.
(echo list volume
echo exit) > %TEMP%\dd1.txt
diskpart /s %TEMP%\dd1.txt
diskpart /s %TEMP%\dd1.txt >> C:\temp\diagnose_log.txt 2>&1
del %TEMP%\dd1.txt 2>nul
echo.
echo ── Partitions on Disk $diskNumber ──
echo.
(echo select disk $diskNumber
echo list partition
echo exit) > %TEMP%\dd2.txt
diskpart /s %TEMP%\dd2.txt
diskpart /s %TEMP%\dd2.txt >> C:\temp\diagnose_log.txt 2>&1
del %TEMP%\dd2.txt 2>nul
echo.
echo ── Expected layout ──
echo Windows : Disk $diskNumber, Partition $cPartNum (C:)
echo EFI : Disk $diskNumber, Partition $efiPartNum
echo Recovery : Disk $recDiskNum, Partition $recPartNum
echo.
echo ── Scanning ALL drives for install.wim ──
echo.
echo Scanning... >> C:\temp\diagnose_log.txt
if exist C:\$RecoveryFolderName\install.wim echo FOUND on C:
if exist D:\$RecoveryFolderName\install.wim echo FOUND on D:
if exist E:\$RecoveryFolderName\install.wim echo FOUND on E:
if exist F:\$RecoveryFolderName\install.wim echo FOUND on F:
if exist G:\$RecoveryFolderName\install.wim echo FOUND on G:
if exist H:\$RecoveryFolderName\install.wim echo FOUND on H:
if exist I:\$RecoveryFolderName\install.wim echo FOUND on I:
if exist J:\$RecoveryFolderName\install.wim echo FOUND on J:
if exist K:\$RecoveryFolderName\install.wim echo FOUND on K:
if exist R:\$RecoveryFolderName\install.wim echo FOUND on R:
echo.
echo ── Trying diskpart mount (Disk $recDiskNum, Part $recPartNum as R:) ──
echo.
(echo select disk $recDiskNum
echo select partition $recPartNum
echo assign letter=R
echo exit) > %TEMP%\dd3.txt
diskpart /s %TEMP%\dd3.txt >> C:\temp\diagnose_log.txt 2>&1
del %TEMP%\dd3.txt 2>nul
ping -n 5 127.0.0.1 > nul
if exist R:\$RecoveryFolderName\install.wim (
echo FOUND on R: after diskpart mount
DISM.exe /Get-ImageInfo /ImageFile:R:\$RecoveryFolderName\install.wim
) else (
echo NOT FOUND on R: after mount
echo R: contents:
dir R:\ 2>nul
)
echo.
echo ── Cleanup ──
(echo select disk $recDiskNum
echo select partition $recPartNum
echo remove
echo exit) > %TEMP%\dd4.txt
diskpart /s %TEMP%\dd4.txt >> C:\temp\diagnose_log.txt 2>&1
del %TEMP%\dd4.txt 2>nul
echo.
echo ========================================================
echo DONE. Log: C:\temp\diagnose_log.txt
echo.
echo restore.cmd will auto-find the WIM on any drive letter.
echo Just run: [drive]:\$RecoveryFolderName\restore.cmd
echo ========================================================
echo.
pause
"@
# ════════════════════════════════════════════════════════
# QUICK_RESTORE.CMD
# ════════════════════════════════════════════════════════
$quickRestoreScript = @"
@echo off
cls
mkdir C:\temp 2>nul
echo QUICK RESTORE %date% %time% > C:\temp\quick_restore_log.txt
echo AUTOMATED FACTORY RESET...
set RECOVERY_DRIVE=
set WIM_FOUND=0
REM ── Scan all letters ──
if exist C:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=C:& set WIM_FOUND=1& goto QF
if exist D:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=D:& set WIM_FOUND=1& goto QF
if exist E:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=E:& set WIM_FOUND=1& goto QF
if exist F:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=F:& set WIM_FOUND=1& goto QF
if exist G:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=G:& set WIM_FOUND=1& goto QF
if exist H:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=H:& set WIM_FOUND=1& goto QF
if exist R:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=R:& set WIM_FOUND=1& goto QF
REM ── Diskpart mount ──
(echo select disk $recDiskNum
echo select partition $recPartNum
echo assign letter=R
echo exit) > %TEMP%\qm.txt
diskpart /s %TEMP%\qm.txt >> C:\temp\quick_restore_log.txt 2>&1
del %TEMP%\qm.txt 2>nul
ping -n 5 127.0.0.1 > nul
if exist R:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=R:& set WIM_FOUND=1& goto QF
echo FATAL: Not found! >> C:\temp\quick_restore_log.txt
echo FATAL: Recovery image not found!
pause
exit /b 1
:QF
echo Found on %RECOVERY_DRIVE% >> C:\temp\quick_restore_log.txt
mkdir %RECOVERY_DRIVE%\temp 2>nul
copy /y C:\temp\quick_restore_log.txt %RECOVERY_DRIVE%\temp\quick_restore_log.txt > nul 2>nul
format C: /FS:NTFS /Q /Y /V:Windows
if errorlevel 1 (
(echo select disk $diskNumber
echo select partition $cPartNum
echo format fs=ntfs quick label=Windows
echo assign letter=C
echo exit) > %TEMP%\qf.txt
diskpart /s %TEMP%\qf.txt > nul 2>&1
del %TEMP%\qf.txt 2>nul
)
mkdir C:\temp 2>nul
copy /y %RECOVERY_DRIVE%\temp\quick_restore_log.txt C:\temp\quick_restore_log.txt > nul 2>nul
DISM.exe /Apply-Image /ImageFile:%RECOVERY_DRIVE%\$RecoveryFolderName\install.wim /Index:1 /ApplyDir:C:\
if errorlevel 1 (
DISM.exe /Apply-Image /ImageFile:%RECOVERY_DRIVE%\$RecoveryFolderName\install.wim /Index:1 /ApplyDir:C:
if errorlevel 1 (
echo DISM FAILED! >> C:\temp\quick_restore_log.txt
echo CRITICAL: DISM failed!
pause
exit /b 1
)
)
(echo select disk $diskNumber
echo select partition $efiPartNum
echo assign letter=S
echo exit) > %TEMP%\qe.txt
diskpart /s %TEMP%\qe.txt > nul 2>&1
del %TEMP%\qe.txt 2>nul
ping -n 3 127.0.0.1 > nul
bcdboot C:\Windows /s S: /f UEFI > nul 2>&1
if exist %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml (
mkdir C:\Windows\Panther\unattend 2>nul
copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\Panther\unattend\unattend.xml
copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\Panther\unattend.xml
copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\System32\Sysprep\unattend.xml
reg load HKLM\OFFLINE_SW C:\Windows\System32\config\SOFTWARE 2>nul
reg add "HKLM\OFFLINE_SW\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f 2>nul
reg add "HKLM\OFFLINE_SW\Policies\Microsoft\Windows\OOBE" /v DisablePrivacyExperience /t REG_DWORD /d 1 /f 2>nul
reg unload HKLM\OFFLINE_SW 2>nul
)
(echo select disk $diskNumber
echo select partition $efiPartNum
echo remove letter=S
echo exit) > %TEMP%\qe2.txt
diskpart /s %TEMP%\qe2.txt > nul 2>&1
del %TEMP%\qe2.txt 2>nul
ping -n 3 127.0.0.1 > nul
(echo select disk $recDiskNum
echo select partition $recPartNum
echo remove
echo exit) > %TEMP%\qr1.txt
diskpart /s %TEMP%\qr1.txt > nul 2>&1
del %TEMP%\qr1.txt 2>nul
ping -n 3 127.0.0.1 > nul
(echo select disk $recDiskNum
echo select partition $recPartNum
echo set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac override
echo exit) > %TEMP%\qr2.txt
diskpart /s %TEMP%\qr2.txt > nul 2>&1
del %TEMP%\qr2.txt 2>nul
ping -n 3 127.0.0.1 > nul
(echo select disk $recDiskNum
echo select partition $recPartNum
echo gpt attributes=0x8000000000000001
echo exit) > %TEMP%\qr3.txt
diskpart /s %TEMP%\qr3.txt > nul 2>&1
del %TEMP%\qr3.txt 2>nul
echo COMPLETE >> C:\temp\quick_restore_log.txt
wpeutil reboot
"@
# ── Save ──
$restoreScript | Out-File (Join-Path $RecoveryPath "restore.cmd") -Encoding ASCII -Force
$diagnoseScript | Out-File (Join-Path $RecoveryPath "diagnose.cmd") -Encoding ASCII -Force
$quickRestoreScript | Out-File (Join-Path $RecoveryPath "quick_restore.cmd") -Encoding ASCII -Force
@"
Recovery Partition Metadata
===========================
Created : $(Get-Date)
OS Disk : $diskNumber
OS Partition : $cPartNum (C:, ~${osPartSize} GB)
EFI Partition : $efiPartNum
Recovery Disk : $recDiskNum
Recovery Part : $recPartNum
Folder : $RecoveryFolderName
How restore.cmd finds the image:
1. Scans ALL drive letters C: through Z:
2. If not found, mounts Disk $recDiskNum Part $recPartNum as R:
3. Uses whichever letter has the WIM
Re-hide uses separate diskpart calls:
Step 1: remove (letter)
Step 2: set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac
Step 3: gpt attributes=0x8000000000000001
"@ | Out-File (Join-Path $RecoveryPath "recovery_metadata.txt") -Encoding ASCII -Force
Write-Log "Created: restore.cmd" "SUCCESS"
Write-Log "Created: diagnose.cmd" "SUCCESS"
Write-Log "Created: quick_restore.cmd" "SUCCESS"
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────────────┐" -ForegroundColor Green
Write-Host " │ ✅ Recovery Scripts Created │" -ForegroundColor Green
Write-Host " │ │" -ForegroundColor Green
Write-Host " │ Image search order: │" -ForegroundColor Green
Write-Host " │ 1. Scan ALL drive letters (C: through Z:) │" -ForegroundColor Green
Write-Host " │ 2. Diskpart mount Disk $recDiskNum Part $recPartNum as R:" -ForegroundColor Green
Write-Host " │ 3. Use whichever letter has install.wim │" -ForegroundColor Green
Write-Host " │ │" -ForegroundColor Green
Write-Host " │ Works whether partition is D:, R:, or hidden! │" -ForegroundColor Green
Write-Host " └──────────────────────────────────────────────────────────┘" -ForegroundColor Green
return $true
}
# ============================================================
# STEP 3: CREATE DESKTOP SHORTCUTS
# ============================================================
function Invoke-Step3_CreateShortcuts {
param(
[string]$RecoveryDrive,
[string]$RecoveryPath
)
Write-Log "STEP 3: CREATE DESKTOP SHORTCUTS" "STEP"
$recPartition = Get-Partition -DriveLetter ($RecoveryDrive.TrimEnd(':'))
$recPartNum = $recPartition.PartitionNumber
$recDiskNum = $recPartition.DiskNumber
$createShortcut = Read-Host " Create desktop shortcuts? (Y/N) [Y]"
if ([string]::IsNullOrEmpty($createShortcut)) { $createShortcut = 'Y' }
if ($createShortcut -ne 'Y' -and $createShortcut -ne 'y') {
Write-Log "Desktop shortcuts skipped." "INFO"
return $true
}
# ── PowerShell Launcher ──
$launcherPS = @"
#Requires -RunAsAdministrator
Add-Type -AssemblyName System.Windows.Forms
`$msg = "WARNING: This will ERASE ALL DATA on C: and restore the factory image with all original apps and drivers.`n`nALL personal files will be DELETED.`n`nProceed?"
`$r = [System.Windows.Forms.MessageBox]::Show(`$msg, "FACTORY RESET", "YesNo", "Warning")
if (`$r -eq "Yes") {
`$r2 = [System.Windows.Forms.MessageBox]::Show(
"FINAL CONFIRMATION`n`nComputer will restart into recovery.`nEstimated time: 20-40 minutes.`n`nContinue?",
"CONFIRM FACTORY RESET", "YesNo", "Stop")
if (`$r2 -eq "Yes") {
Write-Host "Preparing recovery environment..."
reagentc /enable 2>`$null
reagentc /boottore
shutdown /r /t 5 /c "Restarting for Factory Reset..."
}
}
"@
# ── Batch Wrapper (auto-elevates) ──
$batchWrapper = @"
@echo off
net session >nul 2>&1
if %errorlevel% neq 0 (
echo Requesting administrator privileges...
powershell -Command "Start-Process cmd -ArgumentList '/c cd /d \"%~dp0\" && powershell -ExecutionPolicy Bypass -File \"%~dp0FactoryReset_Launcher.ps1\"' -Verb RunAs"
exit /b
)
powershell -ExecutionPolicy Bypass -File "%~dp0FactoryReset_Launcher.ps1"
"@
# ── Instructions ──
$instructions = @"
FACTORY RESET INSTRUCTIONS
===========================
AUTOMATIC (Windows boots normally):
1. Double-click "Factory Reset.bat" on Desktop
2. Confirm twice
3. PC restarts into recovery mode
4. In recovery CMD, mount R: and run restore:
diskpart
select disk $recDiskNum
select partition $recPartNum
assign letter=R
exit
R:\$RecoveryFolderName\restore.cmd
MANUAL (SHIFT + Restart):
1. Hold SHIFT + click Restart
2. Troubleshoot → Command Prompt
3. Run the diskpart + restore commands above
USB BOOT (Windows won't start):
1. Boot from Windows 11 USB
2. Repair → Command Prompt
3. Run the diskpart + restore commands above
DIAGNOSTICS (safe test first):
Mount R: then run: R:\$RecoveryFolderName\diagnose.cmd
LOG FILES:
C:\temp\restore_log.txt (after restore)
C:\temp\diagnose_log.txt (after diagnostics)
PARTITION INFO:
Recovery: Disk $recDiskNum, Partition $recPartNum
"@
# ── Save files ──
$desktop = "$env:USERPROFILE\Desktop"
$launcherPS | Out-File "$desktop\FactoryReset_Launcher.ps1" -Encoding UTF8 -Force
$batchWrapper | Out-File "$desktop\Factory Reset.bat" -Encoding ASCII -Force
$instructions | Out-File "$desktop\Factory Reset - Instructions.txt" -Encoding UTF8 -Force
$instructions | Out-File (Join-Path $RecoveryPath "INSTRUCTIONS.txt") -Encoding UTF8 -Force
Write-Host " ✓ Factory Reset.bat" -ForegroundColor Green
Write-Host " ✓ FactoryReset_Launcher.ps1" -ForegroundColor Green
Write-Host " ✓ Factory Reset - Instructions.txt" -ForegroundColor Green
Write-Host " ✓ INSTRUCTIONS.txt (on recovery partition)" -ForegroundColor Green
Write-Log "Desktop shortcuts created." "SUCCESS"
return $true
}
# ============================================================
# STEP 4: HIDE PARTITION (Optional)
# ============================================================
function Invoke-Step4_HidePartition {
param([string]$RecoveryDrive)
Write-Log "STEP 4: HIDE & PROTECT RECOVERY PARTITION" "STEP"
$driveLetter = $RecoveryDrive.TrimEnd(':')
# Find volume number
$dpListFile = "$env:TEMP\dp_list.txt"
"list volume" | Out-File -FilePath $dpListFile -Encoding ASCII
$dpOutput = & diskpart /s $dpListFile 2>&1
Remove-Item $dpListFile -Force -ErrorAction SilentlyContinue
$volumeNumber = $null
foreach ($line in $dpOutput) {
if ($line -match "Volume\s+(\d+)\s+$driveLetter\s") {
$volumeNumber = $Matches[1]
break
}
}
if ($null -eq $volumeNumber) {
Write-Log "Could not find volume for ${driveLetter}:." "ERROR"
return $false
}
Write-Host ""
Write-Host " This will:" -ForegroundColor Yellow
Write-Host " • Remove drive letter ${driveLetter}:" -ForegroundColor White
Write-Host " • Hide partition from File Explorer" -ForegroundColor White
Write-Host " • Set GPT recovery attributes" -ForegroundColor White
Write-Host " • Restore scripts will auto-mount it as R:" -ForegroundColor White
Write-Host ""
Write-Host " Volume $volumeNumber = ${driveLetter}:" -ForegroundColor Cyan
Write-Host ""
$confirm = Read-Host " Type 'HIDE' to proceed (anything else to cancel)"
if ($confirm -eq 'HIDE') {
# Separate operations for reliability
# Remove letter
$dp1 = @"
select volume $volumeNumber
remove letter=$driveLetter
exit
"@
$dp1 | Out-File "$env:TEMP\dp_h1.txt" -Encoding ASCII
& diskpart /s "$env:TEMP\dp_h1.txt"
Remove-Item "$env:TEMP\dp_h1.txt" -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
# Set partition type
$dp2 = @"
select volume $volumeNumber
set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac override
exit
"@
$dp2 | Out-File "$env:TEMP\dp_h2.txt" -Encoding ASCII
& diskpart /s "$env:TEMP\dp_h2.txt"
Remove-Item "$env:TEMP\dp_h2.txt" -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
# Set GPT attributes
$dp3 = @"
select volume $volumeNumber
gpt attributes=0x8000000000000001
exit
"@
$dp3 | Out-File "$env:TEMP\dp_h3.txt" -Encoding ASCII
& diskpart /s "$env:TEMP\dp_h3.txt"
Remove-Item "$env:TEMP\dp_h3.txt" -Force -ErrorAction SilentlyContinue
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────┐" -ForegroundColor Green
Write-Host " │ ✅ Partition hidden and protected! │" -ForegroundColor Green
Write-Host " │ │" -ForegroundColor Green
Write-Host " │ To unhide later: │" -ForegroundColor Green
Write-Host " │ diskpart → list volume → select volume X → │" -ForegroundColor Green
Write-Host " │ assign letter=R │" -ForegroundColor Green
Write-Host " └──────────────────────────────────────────────────┘" -ForegroundColor Green
Write-Log "Partition hidden successfully." "SUCCESS"
return $true
}
Write-Log "Hide cancelled by user." "WARNING"
return $false
}
# ============================================================
# MAIN
# ============================================================
function Main {
"Recovery Setup Log (Final) - $(Get-Date)" | Out-File -FilePath $LogFile -Force
Show-Banner
# ── Admin check ──
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Log "Must run as Administrator!" "ERROR"
Write-Host " Right-click PowerShell → Run as Administrator" -ForegroundColor Yellow
Read-Host " Press Enter to exit"
exit 1
}
Write-Log "Running with Administrator privileges." "SUCCESS"
# ── Select recovery drive ──
if ([string]::IsNullOrEmpty($RecoveryDriveLetter)) {
$RecoveryDriveLetter = Get-RecoveryDrive
if ($null -eq $RecoveryDriveLetter) {
Write-Log "No recovery partition selected. Exiting." "ERROR"
Read-Host " Press Enter to exit"
exit 1
}
}
Write-Log "Selected recovery drive: $RecoveryDriveLetter" "INFO"
# ── Prerequisites ──
if (-not (Test-Prerequisites -RecoveryDrive $RecoveryDriveLetter)) {
Write-Log "Prerequisites FAILED. Fix issues above and re-run." "ERROR"
Read-Host " Press Enter to exit"
exit 1
}
Write-Log "All prerequisites PASSED." "SUCCESS"
# ── Confirm ──
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────────────┐" -ForegroundColor Yellow
Write-Host " │ Process: │" -ForegroundColor Yellow
Write-Host " │ │" -ForegroundColor Yellow
Write-Host " │ Step 1 : Capture WIM (VSS Shadow Copy) │" -ForegroundColor Yellow
Write-Host " │ Step 1.5: Generate unattend.xml (silent OOBE) │" -ForegroundColor Yellow
Write-Host " │ Step 2 : Create restore scripts (WinRE-compatible) │" -ForegroundColor Yellow
Write-Host " │ Step 3 : Desktop shortcuts │" -ForegroundColor Yellow
Write-Host " │ Step 4 : (Optional) Hide partition │" -ForegroundColor Yellow
Write-Host " │ │" -ForegroundColor Yellow
Write-Host " │ ✅ All apps, drivers, settings preserved on restore │" -ForegroundColor Yellow
Write-Host " │ ⏱ Estimated: 30-75 minutes │" -ForegroundColor Yellow
Write-Host " └──────────────────────────────────────────────────────────┘" -ForegroundColor Yellow
Write-Host ""
$proceed = Read-Host " Proceed? (Y/N)"
if ($proceed -ne 'Y' -and $proceed -ne 'y') {
Write-Log "User cancelled." "WARNING"
exit 0
}
$recoveryFolderPath = Join-Path $RecoveryDriveLetter $RecoveryFolderName
# ═══════════════════════════════════
# STEP 1: Capture WIM
# ═══════════════════════════════════
$step1 = Invoke-Step1_CaptureWIM `
-RecoveryDrive $RecoveryDriveLetter `
-FolderName $RecoveryFolderName `
-Name $ImageName `
-Description $ImageDescription `
-Compression $CompressionType
if (-not $step1.Success) {
Write-Log "Step 1 FAILED. Check DISM log." "ERROR"
Write-Host " notepad C:\Windows\Logs\DISM\dism.log" -ForegroundColor Yellow
Read-Host " Press Enter to exit"
exit 1
}
# ═══════════════════════════════════
# STEP 1.5: Unattend.xml
# ═══════════════════════════════════
Write-Host ""
$doUnattend = Read-Host " Configure silent recovery (skip OOBE screens)? (Y/N) [Y]"
if ([string]::IsNullOrEmpty($doUnattend)) { $doUnattend = 'Y' }
if ($doUnattend -eq 'Y' -or $doUnattend -eq 'y') {
$settings = Get-UnattendSettings
Save-UnattendXml -RecoveryPath $recoveryFolderPath -Settings $settings
}
else {
Write-Log "Unattend.xml skipped. OOBE screens will appear on restore." "INFO"
}
# ═══════════════════════════════════
# STEP 2: Recovery Scripts
# ═══════════════════════════════════
Invoke-Step2_CreateRecoveryScripts `
-RecoveryDrive $RecoveryDriveLetter `
-RecoveryPath $recoveryFolderPath `
-WimFile $step1.WimFile
# ═══════════════════════════════════
# STEP 3: Desktop Shortcuts
# ═══════════════════════════════════
Invoke-Step3_CreateShortcuts `
-RecoveryDrive $RecoveryDriveLetter `
-RecoveryPath $recoveryFolderPath
# ═══════════════════════════════════
# STEP 4: Hide Partition
# ═══════════════════════════════════
Write-Host ""
Write-Host " ┌──────────────────────────────────────────────────────────┐" -ForegroundColor Cyan
Write-Host " │ Hide recovery partition? │" -ForegroundColor Cyan
Write-Host " │ │" -ForegroundColor Cyan
Write-Host " │ [Y] Production — hidden from Explorer, auto-mounted │" -ForegroundColor Cyan
Write-Host " │ [N] Testing — keep visible for debugging │" -ForegroundColor Cyan
Write-Host " └──────────────────────────────────────────────────────────┘" -ForegroundColor Cyan
Write-Host ""
$hide = Read-Host " (Y/N)"
if ($hide -eq 'Y' -or $hide -eq 'y') {
Invoke-Step4_HidePartition -RecoveryDrive $RecoveryDriveLetter
}
else {
Write-Log "Partition not hidden (testing mode)." "INFO"
}
# ═══════════════════════════════════
# COMPLETE
# ═══════════════════════════════════
Write-Host ""
Write-Host " ╔════════════════════════════════════════════════════════════╗" -ForegroundColor Green
Write-Host " ║ ✅ RECOVERY PARTITION SETUP COMPLETE! ║" -ForegroundColor Green
Write-Host " ╠════════════════════════════════════════════════════════════╣" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ║ Recovery Partition Contents: ║" -ForegroundColor Green
Write-Host " ║ ├── install.wim (full system image) ║" -ForegroundColor Green
Write-Host " ║ ├── unattend.xml (silent OOBE) ║" -ForegroundColor Green
Write-Host " ║ ├── restore.cmd (interactive restore + log) ║" -ForegroundColor Green
Write-Host " ║ ├── diagnose.cmd (safe testing) ║" -ForegroundColor Green
Write-Host " ║ ├── quick_restore.cmd (automated) ║" -ForegroundColor Green
Write-Host " ║ ├── recovery_metadata.txt (partition info) ║" -ForegroundColor Green
Write-Host " ║ └── INSTRUCTIONS.txt (how to restore) ║" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ║ Desktop Files: ║" -ForegroundColor Green
Write-Host " ║ ├── Factory Reset.bat ║" -ForegroundColor Green
Write-Host " ║ ├── FactoryReset_Launcher.ps1 ║" -ForegroundColor Green
Write-Host " ║ └── Factory Reset - Instructions.txt ║" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ║ HOW TO RESTORE: ║" -ForegroundColor Green
Write-Host " ║ 1. SHIFT+Restart → Troubleshoot → CMD ║" -ForegroundColor Green
Write-Host " ║ 2. Mount R: (diskpart commands in Instructions.txt) ║" -ForegroundColor Green
Write-Host " ║ 3. R:\$RecoveryFolderName\diagnose.cmd (test first)" -ForegroundColor Green
Write-Host " ║ 4. R:\$RecoveryFolderName\restore.cmd (restore)" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ║ LOG FILES (after restore): ║" -ForegroundColor Green
Write-Host " ║ C:\temp\restore_log.txt ║" -ForegroundColor Green
Write-Host " ║ C:\temp\diagnose_log.txt ║" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ║ Setup log: $LogFile" -ForegroundColor Green
Write-Host " ║ ║" -ForegroundColor Green
Write-Host " ╚════════════════════════════════════════════════════════════╝" -ForegroundColor Green
Write-Host ""
Write-Log "Setup completed successfully." "SUCCESS"
Read-Host " Press Enter to exit"
}
# ── Run ──
Main
Phase 3: How the Restore Scripts Work
How to Perform a Factory Reset
Method 1: From Windows (System Boots Normally)
1. Hold SHIFT + click "Restart"
2. Troubleshoot → Command Prompt
3. Mount the recovery partition:
diskpart
select disk 0
select partition 5 ← your recovery partition number
assign letter=R
exit
4. Run: R:\RecoveryImage\restore.cmd
Method 2: From USB (Windows Won’t Boot)
1. Boot from Windows 11 installation USB
2. Click "Repair your computer"
3. Troubleshoot → Command Prompt
4. Same diskpart + restore commands as above
What restore.cmd Does
Step 1: FIND the recovery image
├── Scans C: through Z: for RecoveryImage\install.wim
├── If found (e.g., on D:) → uses that drive
└── If not found → diskpart mounts known partition as R:
Step 2: FORMAT C: drive
├── format C: /FS:NTFS /Q /Y
└── Fallback: diskpart format
Step 3: APPLY image
└── DISM.exe /Apply-Image /ImageFile:[drive]:\RecoveryImage\install.wim
/Index:1 /ApplyDir:C:\
Step 4: REBUILD boot
├── Assign S: to EFI partition
├── bcdboot C:\Windows /s S: /f UEFI
└── Remove S:
Step 5: APPLY unattend.xml
├── Copy to C:\Windows\Panther\unattend\
├── Copy to C:\Windows\System32\Sysprep\
└── Inject BypassNRO into offline registry
Step 6: RE-HIDE recovery partition
├── Call 1: remove (drive letter)
├── Call 2: set id=de94bba4... (recovery type)
└── Call 3: gpt attributes=0x80... (protected)
Reboot → Windows starts → Silent OOBE → Desktop → Done!
Log Files
After a restore, check these logs:
| Log | Location | Purpose |
|---|---|---|
| Restore log | C:\temp\restore_log.txt |
Every step with timestamps |
| DISM log | C:\Windows\Logs\DISM\dism.log |
DISM operation details |
| Setup log | Desktop\RecoverySetup_Log.txt |
Initial script setup |
Phase 4: Testing & Validation
Pre-Restore Test (Safe)
1. Boot into WinRE (SHIFT + Restart)
2. Command Prompt
3. Mount recovery partition
4. Run: R:\RecoveryImage\diagnose.cmd
(Makes no changes — just verifies everything)
Post-Restore Verification Checklist
After restoring, verify:
[ ] Windows boots successfully
[ ] All applications are present (Start Menu)
[ ] All drivers work (Device Manager — no yellow triangles)
[ ] Network connectivity works
[ ] User account was auto-created (if unattend configured)
[ ] No OOBE setup screens appeared (if unattend configured)
[ ] C:\temp\restore_log.txt shows all steps succeeded
[ ] Recovery partition is hidden (not visible in Explorer)
Lessons Learned & Pitfalls
Pitfall 1: “ClientAccessible” Typo
❌ "ClientAccesible" (one 's') → VSS returns error 5
✅ "ClientAccessible" (two 's') → Works
Pitfall 2: DISM Error 0x80070020
❌ DISM /Capture-Image /CaptureDir:C:\ → Files locked by running OS
✅ VSS Shadow Copy → Mount → DISM captures from snapshot
Pitfall 3: “Reset this PC” Strips Apps
❌ reagentc /setosimage → "Reset this PC" → Apps removed
✅ DISM /Apply-Image → Byte-for-byte restore → Everything preserved
Pitfall 4: DISM Error 87 in WinRE
❌ setlocal EnableDelayedExpansion + !var! → Empty in WinRE
✅ Hardcoded paths, no delayed expansion
Pitfall 5: timeout Doesn’t Exist in WinRE
❌ timeout /t 3 /nobreak > nul → Command not found
✅ ping -n 4 127.0.0.1 > nul → 3-second delay
Pitfall 6: WinRE Reassigns Drive Letters
❌ Assume R: = Recovery → WinRE assigns D: instead
✅ Scan ALL letters C-Z first, then diskpart fallback
Pitfall 7: Combined DiskPart Re-hide Fails
❌ remove letter=R (fails if letter is D:) → set id skipped
✅ Separate calls: remove (no letter specified) → set id → gpt attributes
Pitfall 8: Log Lost When C: Formatted
❌ Log on C: → format C: → Log gone
✅ Copy log to recovery drive before format, restore after
FAQ
Q: How much space does the recovery partition need? A: About 70% of your used space on C:. If you have 40 GB used, you need ~28 GB for the WIM file. Plan for 30-50 GB.
Q: Can I update the recovery image later? A: Yes! Re-run the PowerShell script. When it asks about the existing WIM, choose “Y” to overwrite with a fresh capture.
Q: Does this work with BitLocker? A: You’ll need to suspend BitLocker before capturing and ensure the recovery environment can access the drives. The restore scripts don’t handle BitLocker decryption.
Q: Can I deploy this to multiple identical PCs?
A: For multi-PC deployment, add Sysprep generalization before capture:
C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /shutdown
Then boot from WinPE and capture.
Q: What if the recovery partition gets deleted? A: The WIM file is gone. Always keep a backup copy on an external drive. You can recreate the partition and re-run the script.
Q: Why not use WinPE USB for capture instead of VSS?
A: You can! Boot from WinPE USB, then run:
DISM /Capture-Image /ImageFile:R:\RecoveryImage\install.wim /CaptureDir:C:\ /Name:Recovery /Compress:maximum
No file locks from WinPE. VSS is the “no reboot needed” alternative.
License
This script is provided as-is for educational purposes. Test thoroughly before deploying in production environments. Always maintain backups before performing system modifications.
How to Use This Blog Post
- Copy everything between the outer `
markdown ` and `` tags - Paste into your blog editor (WordPress, Ghost, Hugo, Jekyll, etc.)
- The code blocks inside will render correctly since they use indented fences
- The complete PowerShell script is included inline — readers can copy-paste directly
- All diagrams use ASCII art — no external images needed