Files
DirectWindowsUpgrade/DirectWindowsUpgrade.ps1
2025-03-13 20:25:45 +00:00

996 lines
43 KiB
PowerShell

# Direct Windows 11 Upgrade - No GUI
#
# This script performs a silent in-place upgrade to Windows 11, supporting:
# - Upgrading from Windows 10 to Windows 11
# - Upgrading from older Windows 11 versions to newer versions (e.g., 21H2 to 22H2)
# - Completely silent operation with no user interaction required
# - Bypassing TPM, CPU, and other hardware compatibility checks
#
# PROCESS DETAILS:
# - The script will install 7-Zip if not already present (required to extract ISO contents)
# - Total upgrade process takes approximately 1.5 hours to complete
# - By default, the system will automatically reboot when necessary (configurable)
# - No user intervention is required at any point in the process
#
# USAGE NOTES:
# 1. Edit the configuration section below to specify your Windows 11 ISO source
# 2. Make sure to use an ISO containing the Windows 11 version you want to upgrade to
# 3. Recommended ISO source: https://massgrave.dev/genuine-installation-media.html
# 4. Run this script with administrative privileges
#######################################################################
# CONFIGURATION - MODIFY THESE SETTINGS AS NEEDED
#######################################################################
# Specify the Windows 11 ISO source - MODIFY THIS FOR YOUR ENVIRONMENT
# Options:
# 1. Direct download URL (default)
# 2. Local file path (e.g., "C:\Path\To\Windows11.iso")
# 3. Network share (e.g., "\\server\share\Windows11.iso")
#
# IMPORTANT: The ISO must contain the Windows 11 version you want to upgrade to.
# Business editions are recommended as they have fewer issues with silent upgrades.
# You can download official ISOs from: https://massgrave.dev/genuine-installation-media.html
$WIN11_ISO_SOURCE = "https://replace/this/url/with/your/iso/windows.iso"
# WORKING DIRECTORIES - you can modify these if needed
$WORKING_DIR = "C:\Win11Upgrade" # Main working directory
$TEMP_DIR = "C:\Windows\Temp" # Temporary directory
$LOG_FILE = "C:\Win11_Upgrade_Progress.log" # Main log file
$MONITOR_LOG = "C:\Win11_Monitor.log" # Process monitor log file
# BEHAVIOR SETTINGS
$ALLOW_AUTOMATIC_REBOOT = $true # Set to $false to prevent automatic reboots
#######################################################################
# SCRIPT BEGINS HERE - DO NOT MODIFY BELOW THIS LINE UNLESS NECESSARY
#######################################################################
# Ensure running as admin
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host "ERROR: This script requires administrator privileges." -ForegroundColor Red
Write-Host "Please restart the script with administrator rights." -ForegroundColor Yellow
exit 1
}
# Use the ISO source defined at the top of the script
$isoUrl = $WIN11_ISO_SOURCE
$isoPath = "$TEMP_DIR\windows11.iso"
# Validate the URL or file path before doing anything else
if ($isoUrl -eq "https://replace/this/url/with/your/iso/windows.iso") {
Write-Host "ERROR: You need to replace the placeholder URL in the configuration section." -ForegroundColor Red
Write-Host "Please edit the script and update the `$WIN11_ISO_SOURCE variable with a valid ISO URL or file path." -ForegroundColor Yellow
exit 1
}
# Show confirmation prompt
Write-Host "WARNING: You are about to start an automated Windows 11 upgrade process." -ForegroundColor Yellow
Write-Host ""
Write-Host "The following will occur if you proceed:"
Write-Host "- 7-Zip will be installed (if not already present)"
Write-Host "- System settings will be modified"
Write-Host "- Your PC will reboot when the installation is ready"
Write-Host "- The entire process takes approximately 1.5 hours"
Write-Host ""
Write-Host "IMPORTANT NOTES:" -ForegroundColor Cyan
Write-Host "- Please be patient during the upgrade process"
Write-Host "- As long as SetupHost.exe is running in Task Manager, the upgrade is working"
Write-Host "- The process may appear to stall at times, but this is normal"
Write-Host "- Do not interrupt the process once it has started"
Write-Host ""
$confirmation = Read-Host "Do you want to continue with the Windows 11 upgrade? (y/n)"
if ($confirmation -ne 'y' -and $confirmation -ne 'Y') {
Write-Host "Windows 11 upgrade cancelled by user."
exit 0
}
# Set aggressive compatibility bypass registry keys
Write-Host "Setting comprehensive compatibility bypass registry keys..."
# TPM and basic hardware checks
reg add "HKLM\SYSTEM\Setup\MoSetup" /f /v AllowUpgradesWithUnsupportedTPMorCPU /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\LabConfig" /f /v BypassTPMCheck /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\LabConfig" /f /v BypassSecureBootCheck /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\LabConfig" /f /v BypassRAMCheck /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\LabConfig" /f /v BypassStorageCheck /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\LabConfig" /f /v BypassCPUCheck /d 1 /t reg_dword
# Safeguard overrides
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f /v DisableWUfBSafeguards /d 1 /t reg_dword
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\UpdatePolicy\Settings" /f /v DisableWUfBSafeguards /d 1 /t reg_dword
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f /v DisableSafeguards /d 1 /t reg_dword
# Setup compatibility settings
reg add "HKLM\SYSTEM\Setup\UpgradeCompat" /f /v IgnoreAllWarnings /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\UpgradeCompat" /f /v IgnoreHWRequirements /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\UpgradeCompat" /f /v IgnoreApplicationsOnUpgrade /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\UpgradeCompat" /f /v IgnoreAppsOnUpgrade /d 1 /t reg_dword
reg add "HKLM\SYSTEM\Setup\Status\UninstallWindow" /f /v UninstallActive /d 0 /t reg_dword
# Disable compatibility checks
reg add "HKLM\SYSTEM\Setup" /f /v BypassCompatibilityCheck /d 1 /t reg_dword
# Disable error reporting during upgrade
reg add "HKLM\SOFTWARE\Microsoft\PCHealth\ErrorReporting" /f /v DoReport /d 0 /t reg_dword
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /f /v Disabled /d 1 /t reg_dword
# Skip setup compliance checks
reg add "HKLM\SYSTEM\Setup" /f /v BypassComplianceCheck /d 1 /t reg_dword
# Allow setup to continue despite errors
reg add "HKLM\SYSTEM\Setup" /f /v AllowNonZeroExitStatus /d 1 /t reg_dword
# Disable CEIP during setup
reg add "HKLM\SOFTWARE\Policies\Microsoft\SQMClient\Windows" /f /v CEIPEnable /d 0 /t reg_dword
# Force target platform version
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f /v TargetReleaseVersion /d 1 /t reg_dword
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f /v TargetReleaseVersionInfo /d "25H1" /t reg_sz
# Function to validate URL - ensures the URL is reachable before download
function Test-UrlIsValid {
param (
[string]$Url
)
try {
# Simple HEAD request to check if URL is reachable
$request = [System.Net.WebRequest]::Create($Url)
$request.Method = "HEAD"
$request.Timeout = 15000 # 15 seconds timeout
$request.UserAgent = "Mozilla/5.0 Windows PowerShell Script"
# Get the response
$response = $request.GetResponse()
# Check if we can access the URL
$success = $response.StatusCode -eq [System.Net.HttpStatusCode]::OK
# Show file size if available
$contentLength = $response.Headers["Content-Length"]
if ($contentLength) {
$sizeInMB = [math]::Round([long]$contentLength / 1MB, 2)
if ($sizeInMB -gt 1000) {
Write-Host "File size: $([math]::Round($sizeInMB / 1024, 2)) GB" -ForegroundColor Cyan
} else {
Write-Host "File size: $sizeInMB MB" -ForegroundColor Cyan
}
}
# Close the response
$response.Close()
return $success
}
catch {
# Simple error message without details that could cause red dumps
Write-Host "ERROR: Could not access the URL. Please verify it's correct and accessible." -ForegroundColor Red
return $false
}
}
# If the ISO source is a local file or network share, copy it to the temp directory
if (Test-Path $isoUrl) {
Write-Host "Using local/network ISO file: $isoUrl"
# Verify it's an ISO file
$extension = [System.IO.Path]::GetExtension($isoUrl).ToLower()
if ($extension -ne ".iso") {
Write-Host "Warning: The file does not have an .iso extension. It may not be a valid Windows installation image." -ForegroundColor Yellow
$continue = Read-Host "Do you want to continue anyway? (y/n)"
if ($continue -ne 'y' -and $continue -ne 'Y') {
Write-Host "Operation cancelled by user."
exit 0
}
}
# Verify file size (ISO should be at least 3GB)
$fileInfo = Get-Item $isoUrl
$fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2)
if ($fileSizeMB -lt 3000) {
Write-Host "Warning: The ISO file is only $fileSizeMB MB in size, which is unusually small for a Windows 11 ISO." -ForegroundColor Yellow
Write-Host "A typical Windows 11 ISO is 4-6 GB in size." -ForegroundColor Yellow
$continue = Read-Host "Do you want to continue anyway? (y/n)"
if ($continue -ne 'y' -and $continue -ne 'Y') {
Write-Host "Operation cancelled by user."
exit 0
}
}
# Copy the file with progress indication for large files
Write-Host "Copying ISO file to working location..."
try {
Copy-Item -Path $isoUrl -Destination $isoPath -Force
if (-not (Test-Path $isoPath)) {
throw "Failed to copy ISO file to destination"
}
Write-Host "ISO file copied successfully."
}
catch {
Write-Error "Failed to copy ISO file: $_"
exit 1
}
$isLocalFile = $true
} else {
# Validate the URL before attempting to download
Write-Host "Validating ISO download URL: $isoUrl"
if (-not (Test-UrlIsValid -Url $isoUrl)) {
Write-Error "The specified URL does not appear to be valid or accessible."
Write-Host "Please check the URL and ensure it points to a valid Windows 11 ISO file." -ForegroundColor Yellow
exit 1
}
Write-Host "URL validation successful. Proceeding with download..." -ForegroundColor Green
$isLocalFile = $false
}
# Download ISO if needed
if (-not $isLocalFile) {
Write-Host "Downloading Windows 11 ISO..." -ForegroundColor Cyan
Write-Host "This may take some time depending on your internet connection speed." -ForegroundColor Cyan
Write-Host "Download progress will be displayed..." -ForegroundColor Cyan
try {
# Create a progress-tracking download function
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "Mozilla/5.0 Windows PowerShell Script")
# Register events for download progress tracking
$eventData = @{}
# Define the handler for DownloadProgressChanged
$ProgressChanged = {
$percent = $eventArgs.ProgressPercentage
$totalBytes = $eventArgs.TotalBytesToReceive
$receivedBytes = $eventArgs.BytesReceived
# Calculate download speed
$currentTime = Get-Date
if (-not $eventData.LastTime) {
$eventData.LastTime = $currentTime
$eventData.LastBytes = 0
}
$timeDiff = ($currentTime - $eventData.LastTime).TotalSeconds
if ($timeDiff -ge 1) {
$byteDiff = $receivedBytes - $eventData.LastBytes
$speedMBps = [math]::Round(($byteDiff / $timeDiff) / 1MB, 2)
# Calculate estimated time remaining
$remainingBytes = $totalBytes - $receivedBytes
$estimatedSeconds = 0
if ($byteDiff -gt 0) {
$estimatedSeconds = [math]::Round($remainingBytes / ($byteDiff / $timeDiff))
}
# Format as hours:minutes:seconds
$estimatedTime = [TimeSpan]::FromSeconds($estimatedSeconds)
$estimatedTimeStr = "{0:hh\:mm\:ss}" -f $estimatedTime
# Convert total size to MB or GB for display
if ($totalBytes -ge 1GB) {
$totalSize = [math]::Round($totalBytes / 1GB, 2)
$downloadedSize = [math]::Round($receivedBytes / 1GB, 2)
$sizeUnit = "GB"
} else {
$totalSize = [math]::Round($totalBytes / 1MB, 2)
$downloadedSize = [math]::Round($receivedBytes / 1MB, 2)
$sizeUnit = "MB"
}
Write-Progress -Activity "Downloading Windows 11 ISO" `
-Status "$percent% Complete - $downloadedSize $sizeUnit of $totalSize $sizeUnit - $speedMBps MB/s" `
-PercentComplete $percent `
-CurrentOperation "Estimated time remaining: $estimatedTimeStr"
# Update for next calculation
$eventData.LastTime = $currentTime
$eventData.LastBytes = $receivedBytes
}
}
# Define the handler for DownloadFileCompleted
$Completed = {
Write-Progress -Activity "Downloading Windows 11 ISO" -Completed
$eventData.Clear()
if ($eventArgs.Error -ne $null) {
throw $eventArgs.Error
}
}
# Register the event handlers
$downloadProgressEvent = Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -Action $ProgressChanged
$downloadCompletedEvent = Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted -Action $Completed
# Start the download
$webClient.DownloadFileAsync((New-Object System.Uri($isoUrl)), $isoPath)
# Wait for download to complete
while ($webClient.IsBusy) {
Start-Sleep -Milliseconds 100
}
# Clean up event handlers
Unregister-Event -SourceIdentifier $downloadProgressEvent.Id
Unregister-Event -SourceIdentifier $downloadCompletedEvent.Id
# Verify the download
if (Test-Path $isoPath) {
$fileInfo = Get-Item $isoPath
$fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2)
# Check if file size is reasonable for a Windows ISO
if ($fileSizeMB -lt 3000) {
Write-Host "Warning: The downloaded ISO is only $fileSizeMB MB, which is unusually small for a Windows 11 ISO." -ForegroundColor Yellow
Write-Host "This might indicate a partial download or incorrect ISO source." -ForegroundColor Yellow
$continue = Read-Host "Do you want to continue anyway? (y/n)"
if ($continue -ne 'y' -and $continue -ne 'Y') {
Write-Host "Operation cancelled by user."
exit 0
}
} else {
Write-Host "ISO downloaded successfully ($fileSizeMB MB)." -ForegroundColor Green
}
} else {
throw "ISO download failed: File not found after download"
}
} catch {
Write-Error "Failed to download the ISO: $_"
exit 1
} finally {
# Ensure WebClient is disposed
if ($webClient) {
$webClient.Dispose()
}
}
} else {
Write-Host "ISO file ready: $isoPath" -ForegroundColor Green
}
# Create a working directory for extracted ISO content
$extractDir = $WORKING_DIR
if (Test-Path $extractDir) {
# Clean any existing directory to avoid conflicts
Write-Host "Cleaning existing working directory..."
Remove-Item -Path "$extractDir\*" -Recurse -Force -ErrorAction SilentlyContinue
} else {
# Create new directory
New-Item -Path $extractDir -ItemType Directory -Force | Out-Null
}
# Check for 7-Zip installation
$7zipPath = "C:\Program Files\7-Zip\7z.exe"
if (-not (Test-Path $7zipPath)) {
# Try common alternative path for x86 on x64 systems
$7zipPath = "C:\Program Files (x86)\7-Zip\7z.exe"
if (-not (Test-Path $7zipPath)) {
Write-Host "7-Zip not found. Attempting to download and install it..."
# Download and install 7-Zip using dynamic method to get latest version
try {
# Settings
$downloadPage = "https://www.7-zip.org/download.html"
$downloadPath = "C:\Windows\Temp\7z.msi"
# Download page content
$webClient = New-Object System.Net.WebClient
$htmlContent = $webClient.DownloadString($downloadPage)
# Extract latest x64 MSI link from the current version section
$latestSection = $htmlContent -split '<P><B>Download 7-Zip' | Select-Object -Index 1
# Handle both 32-bit and 64-bit architectures
if ([Environment]::Is64BitOperatingSystem) {
$archPattern = '-x64\.msi'
} else {
$archPattern = '\.msi"'
}
$msiRelativePath = ($latestSection | Select-String -Pattern "href=`"(a/7z\d+.*?$($archPattern))" -AllMatches).Matches[0].Groups[1].Value
# Construct full download URL
$downloadUrl = "https://www.7-zip.org/$msiRelativePath"
Write-Host "Downloading 7-Zip from $downloadUrl"
# Download and install
$webClient.DownloadFile($downloadUrl, $downloadPath)
Start-Process msiexec.exe -ArgumentList "/i `"$downloadPath`" /quiet /norestart" -Wait
# Verify installation
if (-not (Test-Path "C:\Program Files\7-Zip\7z.exe")) {
throw "Failed to install 7-Zip"
}
$7zipPath = "C:\Program Files\7-Zip\7z.exe"
} catch {
Write-Error "Failed to install 7-Zip: $_"
Write-Error "Please install 7-Zip manually and try again."
exit 1
}
}
}
# Extract ISO using 7-Zip
Write-Host "Extracting ISO using 7-Zip (this may take 5-10 minutes)..." -ForegroundColor Cyan
Write-Host "Please be patient while the ISO contents are extracted..." -ForegroundColor Cyan
try {
# Use Start-Process with redirected error streams to capture output without displaying it
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.FileName = $7zipPath
$processInfo.Arguments = "x -y -o`"$extractDir`" `"$isoPath`""
$processInfo.RedirectStandardError = $true
$processInfo.RedirectStandardOutput = $true
$processInfo.UseShellExecute = $false
$processInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
$process.Start() | Out-Null
# Display a simple spinner to show progress
$spinner = @('|', '/', '-', '\')
$spinnerPos = 0
$startTime = Get-Date
Write-Host " " -NoNewline
while (-not $process.HasExited) {
Write-Host "`r $($spinner[$spinnerPos]) Extracting... [$(([TimeSpan]::FromSeconds((Get-Date).Subtract($startTime).TotalSeconds)).ToString("hh\:mm\:ss"))]" -NoNewline
$spinnerPos = ($spinnerPos + 1) % 4
Start-Sleep -Milliseconds 200
}
Write-Host "`r Extraction complete [$(([TimeSpan]::FromSeconds((Get-Date).Subtract($startTime).TotalSeconds)).ToString("hh\:mm\:ss"))] " -ForegroundColor Green
# Get the output without displaying it
$standardOutput = $process.StandardOutput.ReadToEnd()
$standardError = $process.StandardError.ReadToEnd()
# Check the exit code
if ($process.ExitCode -ne 0) {
# Format a user-friendly error message without dumping everything
Write-Host "ERROR: 7-Zip extraction failed with exit code $($process.ExitCode)" -ForegroundColor Red
Write-Host "The ISO file may be corrupted or incompatible." -ForegroundColor Yellow
# Show minimal error information
if (-not [string]::IsNullOrEmpty($standardError)) {
$errorLines = $standardError -split "`n"
$relevantError = ($errorLines | Where-Object { $_ -match "ERROR:" } | Select-Object -First 1)
if ($relevantError) {
Write-Host "Error details: $relevantError" -ForegroundColor Yellow
}
}
exit 1
}
# Verify extraction succeeded
$setupPath = "$extractDir\setup.exe"
if (-not (Test-Path $setupPath)) {
Write-Host "ERROR: Extraction completed but setup.exe was not found." -ForegroundColor Red
Write-Host "This suggests the ISO does not contain a valid Windows installation." -ForegroundColor Yellow
Write-Host "Please verify you are using a proper Windows 11 installation ISO." -ForegroundColor Yellow
exit 1
}
Write-Host "ISO extracted successfully to $extractDir" -ForegroundColor Green
# Create zero-byte appraiserres.dll to bypass TPM check
Write-Host "Creating TPM check bypass..."
$appraiserdllPath = "$extractDir\sources\appraiserres.dll"
if (Test-Path $appraiserdllPath) {
# Make a backup just in case
Copy-Item -Path $appraiserdllPath -Destination "$appraiserdllPath.bak" -Force
# Replace with zero-byte file
Set-Content -Path $appraiserdllPath -Value "" -Force
} else {
# Create new zero-byte file if it doesn't exist
New-Item -Path $appraiserdllPath -ItemType File -Force | Out-Null
}
# Create EI.cfg to avoid product key prompts
$eiCfgPath = "$extractDir\sources\EI.cfg"
Set-Content -Path $eiCfgPath -Value "[Channel]`n_Default" -Force
# Create SetupConfig.ini
$setupConfigPath = "$extractDir\sources\SetupConfig.ini"
$setupConfig = @"
[BeginSetupMode]
SkipPrediagler=1
DeviceEnumeration=1
DiscoverSystemPartition=1
[SetupConfig]
ScratchDir=$env:SystemDrive\`$WINDOWS.~BT
ScratchSpace=12000
PreinstallKitSpace=8000
"@
Set-Content -Path $setupConfigPath -Value $setupConfig -Force
# Run setup.exe with appropriate arguments and stronger compatibility bypass options
Write-Host "Starting Windows 11 upgrade from extracted ISO with maximum compatibility overrides..."
# Create custom answer file to force upgrade
$answerFilePath = "$extractDir\unattend.xml"
$answerFileContent = @"
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<ComplianceCheck>
<DisplayReport>Never</DisplayReport>
</ComplianceCheck>
<Diagnostics>
<OptIn>false</OptIn>
</Diagnostics>
<DynamicUpdate>
<Enable>true</Enable>
<WillShowUI>OnError</WillShowUI>
</DynamicUpdate>
<ImageInstall>
<OSImage>
<InstallFrom>
<MetaData>
<Key>/IMAGE/INDEX</Key>
<Value>1</Value>
</MetaData>
</InstallFrom>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>1</PartitionID>
</InstallTo>
<WillShowUI>OnError</WillShowUI>
<InstallToAvailablePartition>true</InstallToAvailablePartition>
</OSImage>
</ImageInstall>
<UserData>
<AcceptEula>true</AcceptEula>
</UserData>
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /d 1 /t reg_dword /f</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /d 1 /t reg_dword /f</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>3</Order>
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /d 1 /t reg_dword /f</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>4</Order>
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassCPUCheck /d 1 /t reg_dword /f</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>5</Order>
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassStorageCheck /d 1 /t reg_dword /f</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>6</Order>
<Path>reg add HKLM\SYSTEM\Setup\MoSetup /v AllowUpgradesWithUnsupportedTPMorCPU /d 1 /t reg_dword /f</Path>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
</unattend>
"@
Set-Content -Path $answerFilePath -Value $answerFileContent -Force
Write-Host "Created custom unattend.xml file for compatibility bypass"
# Try the alternate 'Server' product trick - update setup files
Write-Host "Applying 'Server' product trick to bypass hardware checks..."
try {
$setupConfigDatPath = "$extractDir\sources\setupconfig.dat"
if (Test-Path $setupConfigDatPath) {
$content = Get-Content -Path $setupConfigDatPath -Encoding Byte
$clientPattern = [System.Text.Encoding]::Unicode.GetBytes("Client")
$serverPattern = [System.Text.Encoding]::Unicode.GetBytes("Server")
$found = $false
for ($i = 0; $i -lt $content.Length - $clientPattern.Length; $i++) {
$matched = $true
for ($j = 0; $j -lt $clientPattern.Length; $j++) {
if ($content[$i + $j] -ne $clientPattern[$j]) {
$matched = $false
break
}
}
if ($matched) {
$found = $true
for ($j = 0; $j -lt $serverPattern.Length; $j++) {
$content[$i + $j] = $serverPattern[$j]
}
}
}
if ($found) {
[System.IO.File]::WriteAllBytes($setupConfigDatPath, $content)
Write-Host "Successfully modified setupconfig.dat to use Server edition bypass"
}
}
} catch {
Write-Host "Warning: Could not modify setupconfig.dat: $_"
}
# Setup command line arguments
$arguments = @(
"/auto", "upgrade",
"/quiet",
"/compat", "ignorewarning",
"/migratedrivers", "all",
"/showoobe", "none",
"/telemetry", "disable",
"/dynamicupdate", "enable",
"/eula", "accept",
"/unattend:$answerFilePath",
"/product", "server", # Add server product parameter to bypass hardware checks
"/pkey", "VK7JG-NPHTM-C97JM-9MPGT-3V66T" # Generic Windows 11 Pro key
)
# Add /noreboot switch if automatic reboots are disabled
if (-not $ALLOW_AUTOMATIC_REBOOT) {
$arguments += "/noreboot"
Write-Host "Automatic reboots are disabled. The system will need to be manually rebooted to complete the upgrade."
} else {
Write-Host "Automatic reboots are enabled. The system will reboot automatically when needed."
}
# Create a log file to track progress
$logFile = $LOG_FILE
Set-Content -Path $logFile -Value "Windows 11 Upgrade started at $(Get-Date)`r`n" -Force
# Function to log progress
function Write-ProgressLog {
param(
[string]$Message
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logMessage = "[$timestamp] $Message"
Write-Host $logMessage
Add-Content -Path $logFile -Value $logMessage
}
# Function to check if Windows Setup is running
function Is-SetupRunning {
$setupProcesses = @(
"SetupHost", # This is the critical process for actual Windows upgrade execution
"setupprep",
"setup",
"Windows10UpgraderApp"
)
foreach ($proc in $setupProcesses) {
$running = Get-Process -Name $proc -ErrorAction SilentlyContinue
if ($running) {
if ($proc -eq "SetupHost") {
Write-ProgressLog "CRITICAL SUCCESS: SetupHost.exe is running! This confirms the upgrade is properly underway."
return @{Success = $true; Critical = $true}
}
return @{Success = $true; Critical = $false}
}
}
return @{Success = $false; Critical = $false}
}
# Function to wait for SetupHost.exe to appear (the definitive sign that upgrade is working)
function Wait-ForSetupHost {
param (
[int]$TimeoutSeconds = 300, # Wait up to 5 minutes by default
[int]$CheckIntervalSeconds = 5
)
Write-ProgressLog "Waiting for SetupHost.exe to start (the critical process for Windows upgrades)..."
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$timeoutMs = $TimeoutSeconds * 1000
while ($stopwatch.ElapsedMilliseconds -lt $timeoutMs) {
$setupHost = Get-Process -Name "SetupHost" -ErrorAction SilentlyContinue
if ($setupHost) {
$stopwatch.Stop()
Write-ProgressLog "SUCCESS: SetupHost.exe started after $([math]::Round($stopwatch.ElapsedMilliseconds / 1000)) seconds."
Write-ProgressLog "SetupHost.exe PID: $($setupHost.Id), Started at: $(Get-Date)"
return $true
}
# Check for setupprep - it should start first and launch SetupHost
$setupPrep = Get-Process -Name "setupprep" -ErrorAction SilentlyContinue
if ($setupPrep) {
Write-ProgressLog "setupprep.exe is running (PID: $($setupPrep.Id)). Waiting for it to launch SetupHost.exe..."
}
# Sleep before checking again
Start-Sleep -Seconds $CheckIntervalSeconds
}
$stopwatch.Stop()
Write-ProgressLog "WARNING: SetupHost.exe did not start within $TimeoutSeconds seconds."
return $false
}
Write-ProgressLog "Starting Windows 11 upgrade from extracted ISO..."
Write-ProgressLog "To monitor progress, check the log file at: $logFile"
Write-ProgressLog "You can also look for these processes: SetupHost.exe, setupprep.exe, setup.exe"
# Start setup without waiting
$arguments += "/PostOOBE", "$extractDir\PostInstall.cmd"
# Create a post-install script to log completion
$postInstallScript = @"
@echo off
echo Windows 11 Upgrade completed at %DATE% %TIME% > C:\Win11_Upgrade_Completed.log
"@
Set-Content -Path "$extractDir\PostInstall.cmd" -Value $postInstallScript -Force
# Start the setup process
Write-ProgressLog "Launching setup.exe with arguments: $($arguments -join ' ')"
$process = Start-Process -FilePath $setupPath -ArgumentList $arguments -PassThru -NoNewWindow
# Wait briefly to see if initial processes start
Start-Sleep -Seconds 5
# Check if setup is running and log process ID
$setupStatus = Is-SetupRunning
if ($setupStatus.Success) {
$setupProcesses = Get-Process | Where-Object { $_.Name -match "setup|SetupHost" }
foreach ($proc in $setupProcesses) {
Write-ProgressLog "Setup process running: $($proc.Name) (PID: $($proc.Id))"
}
if ($setupStatus.Critical) {
Write-ProgressLog "VERIFIED: SetupHost.exe is running! The upgrade is confirmed to be properly underway."
} else {
# SetupHost is not yet running - wait for it as it's the critical indicator
Write-ProgressLog "Initial setup processes started, but waiting for SetupHost.exe (the critical component)..."
# Wait for SetupHost to appear - this is the definitive test
$setupHostStarted = Wait-ForSetupHost -TimeoutSeconds 600 # Wait up to 10 minutes
if ($setupHostStarted) {
Write-ProgressLog "UPGRADE CONFIRMED: SetupHost.exe is running. The Windows 11 upgrade is now definitely underway."
Write-ProgressLog "This is the critical process that indicates the actual upgrade is proceeding correctly."
} else {
Write-ProgressLog "WARNING: SetupHost.exe did not start within the expected timeframe."
Write-ProgressLog "The upgrade may still proceed, but you should monitor it carefully."
Write-ProgressLog "If the upgrade does not complete, you may need to run the script again."
}
}
Write-ProgressLog "Setup initiated. The upgrade is now running in the background."
Write-ProgressLog "To check if it's running, use Task Manager to look for SetupHost.exe."
} else {
Write-ProgressLog "Warning: Setup may not have started correctly."
Write-ProgressLog "Checking exit code: $($process.ExitCode)"
# Try alternative approach - direct setupprep.exe execution with Server trick
Write-ProgressLog "Trying alternative approach with setupprep.exe and Server trick..."
$setupPrepPath = "$extractDir\sources\setupprep.exe"
if (Test-Path $setupPrepPath) {
# Prepare $WINDOWS.~BT directory
$btDir = "$env:SystemDrive\`$WINDOWS.~BT\Sources"
if (-not (Test-Path $btDir)) {
New-Item -Path $btDir -ItemType Directory -Force | Out-Null
}
# Copy critical files to ensure Windows.~BT has what it needs
Write-ProgressLog "Copying setup files to Windows.~BT directory..."
Copy-Item -Path "$extractDir\sources\*" -Destination $btDir -Force -Recurse
# Create zero-byte appraiserres.dll in Windows.~BT
Set-Content -Path "$btDir\appraiserres.dll" -Value "" -Force
# Add additional bypass files
Set-Content -Path "$btDir\Skip.cmd" -Value @"
@echo off
reg add HKLM\SYSTEM\Setup\MoSetup /f /v AllowUpgradesWithUnsupportedTPMorCPU /d 1 /t reg_dword
reg add HKLM\SYSTEM\Setup\LabConfig /f /v BypassTPMCheck /d 1 /t reg_dword
reg add HKLM\SYSTEM\Setup\LabConfig /f /v BypassSecureBootCheck /d 1 /t reg_dword
reg add HKLM\SYSTEM\Setup\LabConfig /f /v BypassRAMCheck /d 1 /t reg_dword
reg add HKLM\SYSTEM\Setup\LabConfig /f /v BypassStorageCheck /d 1 /t reg_dword
reg add HKLM\SYSTEM\Setup\LabConfig /f /v BypassCPUCheck /d 1 /t reg_dword
reg add HKLM\SYSTEM\Setup /f /v BypassComponentCheck /d 1 /t reg_dword
"@ -Force
# Modify the EditionID to ensure compatibility
$regScript = @"
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion]
"EditionID_undo"="Professional"
"EditionID"="Professional"
"ProductName"="Windows 11 Pro"
"@
Set-Content -Path "$btDir\edition.reg" -Value $regScript -Force
# Create batch file to run registry changes and launch setup
$setupBatchPath = "$btDir\RunSetup.cmd"
$setupCommand = "setupprep.exe /product server /auto upgrade /quiet /compat ignorewarning /migratedrivers all /dynamicupdate enable /eula accept"
# Add noreboot switch if automatic reboots are disabled
if (-not $ALLOW_AUTOMATIC_REBOOT) {
$setupCommand += " /noreboot"
}
$setupBatch = @"
@echo off
cd /d "%~dp0"
call Skip.cmd
regedit /s edition.reg
$setupCommand
"@
Set-Content -Path $setupBatchPath -Value $setupBatch -Force
# Run the batch file to execute setup with all bypasses
Write-ProgressLog "Launching setup with all compatibility bypasses..."
Start-Process -FilePath "cmd.exe" -ArgumentList "/c $setupBatchPath" -PassThru -NoNewWindow
# Wait briefly to check if it started
Start-Sleep -Seconds 5
# Check again if setup is running
$setupStatus = Is-SetupRunning
if ($setupStatus.Success) {
$setupProcesses = Get-Process | Where-Object { $_.Name -match "setup|SetupHost" }
foreach ($proc in $setupProcesses) {
Write-ProgressLog "Setup process running: $($proc.Name) (PID: $($proc.Id))"
}
if ($setupStatus.Critical) {
Write-ProgressLog "VERIFIED: SetupHost.exe is running via fallback method! The upgrade is confirmed to be properly underway."
} else {
# SetupHost is not yet running - wait for it as it's the critical indicator
Write-ProgressLog "Initial setup processes started via fallback method, waiting for SetupHost.exe..."
# Wait for SetupHost to appear - this is the definitive test
$setupHostStarted = Wait-ForSetupHost -TimeoutSeconds 600 # Wait up to 10 minutes
if ($setupHostStarted) {
Write-ProgressLog "UPGRADE CONFIRMED: SetupHost.exe is running. The Windows 11 upgrade is now definitely underway."
} else {
Write-ProgressLog "WARNING: SetupHost.exe did not start within the expected timeframe."
Write-ProgressLog "The upgrade may still proceed, but monitoring is recommended."
}
}
Write-ProgressLog "Setup initiated via fallback method. The upgrade is now running in the background."
} else {
Write-ProgressLog "WARNING: Both setup methods failed to start the upgrade process."
Write-ProgressLog "Please check the logs and consider running the script again."
}
}
# Create a simple monitor script that only logs progress
$monitorScript = @"
@echo off
echo Windows 11 upgrade monitor started at %DATE% %TIME% > "%MONITOR_LOG%"
:check
echo ------------------------------------------------ >> "%MONITOR_LOG%"
echo Checking processes at %DATE% %TIME% >> "%MONITOR_LOG%"
tasklist /fi "imagename eq setuphost.exe" >> "%MONITOR_LOG%"
tasklist /fi "imagename eq setupprep.exe" >> "%MONITOR_LOG%"
tasklist /fi "imagename eq setup.exe" >> "%MONITOR_LOG%"
REM Check if RebootRequired registry exists (for logging only)
reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" > nul 2>&1
if %ERRORLEVEL% EQU 0 (
echo Reboot Required registry key found! Windows should reboot automatically. >> "%MONITOR_LOG%"
)
REM Check if installation phase registry indicates readiness (for logging only)
reg query "HKLM\SYSTEM\Setup" /v SystemSetupInProgress > nul 2>&1
if %ERRORLEVEL% EQU 0 (
for /f "tokens=3" %%a in ('reg query "HKLM\SYSTEM\Setup" /v SystemSetupInProgress ^| find "SystemSetupInProgress"') do set SETUP_PROGRESS=%%a
echo SystemSetupInProgress value: !SETUP_PROGRESS! >> "%MONITOR_LOG%"
)
REM Log recent activity in setup logs
echo Recent setup logs: >> "%MONITOR_LOG%"
dir /a-d /od C:\$WINDOWS.~BT\Sources\Panther\*.log >> "%MONITOR_LOG%" 2>&1
REM Log memory info to monitor system health
echo Memory status: >> "%MONITOR_LOG%"
systeminfo | find "Physical Memory" >> "%MONITOR_LOG%"
echo. >> "%MONITOR_LOG%"
timeout /t 300 > nul
goto check
"@
Set-Content -Path "$extractDir\MonitorSetup.cmd" -Value $monitorScript -Force
# Start the monitor script in a hidden window
Start-Process -FilePath "cmd.exe" -ArgumentList "/c $extractDir\MonitorSetup.cmd" -WindowStyle Hidden
}
# Clean up - delete the downloaded ISO but keep extracted files for debugging
Remove-Item -Path $isoPath -Force -ErrorAction SilentlyContinue
} catch {
# Extract only the essential error message without the full stack trace
$errorMessage = $_.Exception.Message
if ($errorMessage.Length -gt 150) {
$errorMessage = $errorMessage.Substring(0, 150) + "..."
}
# Create a clean, user-friendly error message
$detailedMessage = "The Windows 11 upgrade process encountered an issue and could not continue.`nTry again or check the log file at $logFile for more details."
# Try to clean up
try {
Remove-Item -Path $isoPath -Force -ErrorAction SilentlyContinue
} catch {
# Ignore cleanup errors
}
# Show clean error message
Write-Host "ERROR: Windows 11 upgrade process failed." -ForegroundColor Red
Write-Host "The Windows 11 upgrade process encountered an issue and could not continue." -ForegroundColor Yellow
Write-Host "Try again or check the log file at $logFile for more details." -ForegroundColor Yellow
exit 1
}
# Final progress information and verification
if ($ALLOW_AUTOMATIC_REBOOT) {
Write-ProgressLog "Windows 11 upgrade process initiated. The system will reboot automatically when the upgrade is complete."
} else {
Write-ProgressLog "Windows 11 upgrade process initiated. Manual reboot will be required when the upgrade preparation is complete."
}
Write-ProgressLog ""
Write-ProgressLog "To verify the upgrade is running, check for these files:"
Write-ProgressLog "- $logFile - Contains detailed progress information"
Write-ProgressLog "- $MONITOR_LOG - Contains periodic process checks every 5 minutes"
Write-ProgressLog "- C:\Win11_Upgrade_Completed.log - Will be created when upgrade completes"
Write-ProgressLog ""
Write-ProgressLog "You should also see one or more of these processes in Task Manager:"
Write-ProgressLog "- SetupHost.exe - Main upgrade process"
Write-ProgressLog "- setupprep.exe - Preparation process"
Write-ProgressLog "- setup.exe - Initial setup launcher"
Write-ProgressLog ""
Write-ProgressLog "Process monitoring is active but no automatic intervention will occur."
Write-ProgressLog "Windows Setup will handle the reboot process organically when ready."
# One final check to make absolutely sure SetupHost is running
Write-ProgressLog "Performing final verification to ensure SetupHost.exe is running..."
Start-Sleep -Seconds 15
$setupHost = Get-Process -Name "SetupHost" -ErrorAction SilentlyContinue
if ($setupHost) {
Write-ProgressLog "FINAL VERIFICATION PASSED: SetupHost.exe is running (PID: $($setupHost.Id))."
Write-ProgressLog "The Windows 11 upgrade is definitely underway and proceeding properly."
Write-ProgressLog "This is the CRITICAL process that confirms the upgrade will complete successfully."
# Log all running setup processes for completeness
$setupProcesses = Get-Process | Where-Object { $_.Name -match "setup|SetupHost" }
Write-ProgressLog "All running setup processes:"
foreach ($proc in $setupProcesses) {
Write-ProgressLog "- $($proc.Name) (PID: $($proc.Id))"
}
Write-ProgressLog "UPGRADE STATUS: SUCCESS - The script has successfully initiated the Windows 11 upgrade."
} else {
# SetupHost is still not running - check for any setup processes
$setupStatus = Is-SetupRunning
if ($setupStatus.Success) {
Write-ProgressLog "WARNING: Setup processes are running, but SetupHost.exe has not started yet."
Write-ProgressLog "The upgrade may still proceed, but it's recommended to monitor the process."
Write-ProgressLog "If needed, check the C:\$WINDOWS.~BT\Sources\Panther directory for logs."
# One last attempt to wait for SetupHost
Write-ProgressLog "Making final attempt to wait for SetupHost.exe to start..."
$finalAttempt = Wait-ForSetupHost -TimeoutSeconds 300 # Wait 5 more minutes
if ($finalAttempt) {
Write-ProgressLog "SUCCESS: SetupHost.exe has finally started. The upgrade is now properly underway."
} else {
Write-ProgressLog "CAUTION: SetupHost.exe still not detected. The upgrade process may be abnormal."
Write-ProgressLog "Please monitor the system to ensure the upgrade completes successfully."
}
} else {
Write-ProgressLog "CRITICAL WARNING: No setup processes are running. The upgrade has likely failed to start."
Write-ProgressLog "Check C:\$WINDOWS.~BT\Sources\Panther directory for setupact.log and setuperr.log files"
Write-ProgressLog "You may need to run the script again or try a different approach."
}
}