<# ConvertToXOS.ps1 - Convert this computer to xOS (version 2, 2026-09-16) What this script does, in order, and nothing else: 1. WELCOME explains the plan; you choose "Let Lumi do it" (guided) or "I'll do it myself" (same steps, you confirm each). 2. SYSTEM CHECK UEFI or BIOS, free disk, RAM, Secure Boot, BitLocker, Fast Startup. 3. DOWNLOAD fetches the xOS image with real progress, verifies its SHA-256, retries once, then stops if it still differs. Also fetches the small boot bundle (xos-boot-efi.zip) used by mode [2], verified the same way. 4. BACKUP copies your personal folders to a drive you choose, hashes every file, re-reads the copy to verify it. Skipping requires you to type I UNDERSTAND. 5. INSTALL MODE tells you plainly what each mode does and which ones this version can do: [1] USB stick or second disk: writes the installer there (after you type ERASE ...), re-reads it, tells you how to boot. [2] No stick (this drive, UEFI, 8 GB+ RAM): shrinks C: by about the image size (after you type MAKE ROOM), writes the installer into that new space, re-reads it, puts one file set under EFI\xos on the EFI partition and adds a ONE-TIME boot entry, then restarts. The xOS installer copies itself to memory first, so it can offer this drive. 6./7. the chosen staging, then the restart instructions. In both modes the xOS installer makes the full block-level copy of your old drive and asks three more times before it erases anything. If you stop at the installer, Windows boots as before. What it never does: erase anything without your typed word; send anything anywhere. Mode [2] is the only step that touches the Windows EFI partition (it adds EFI\xos\bootx64.efi + grub.cfg and a one-time firmware boot entry; Windows' own entries stay). Everything runs on this machine. The only network use is downloading the image, the boot bundle and the release manifest from xscash.net. Run it: right-click -> Run with PowerShell (it asks for administrator rights itself) Read it: it is plain text on purpose. #> param([switch]$Auto, [switch]$NoElevate) $ErrorActionPreference = 'Stop' $ReleaseUrl = 'https://xscash.net/xos/release.json' $Work = 'C:\xOS-install' $LogPath = Join-Path $Work 'ConvertToXOS.log' $MinRamGB = 8 $MinDiskGB = 32 $MinStickGB = 8 function Say($text, $color) { if ($color) { Write-Host $text -ForegroundColor $color } else { Write-Host $text } try { Add-Content -Path $LogPath -Value ("[{0}] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $text) -ErrorAction SilentlyContinue } catch {} } function Lumi($text) { Say (" Lumi: " + $text) Cyan } function Ask($prompt) { $answer = Read-Host $prompt try { Add-Content -Path $LogPath -Value ("[{0}] ? {1} -> {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $prompt, $answer) -ErrorAction SilentlyContinue } catch {} return $answer } function Stop-Here($why, $code) { Say "" Say ("Stopped: " + $why) Yellow Say ("Nothing on your drives was changed by this script beyond what is listed above. Log: " + $LogPath) exit $code } function Fmt-GB($bytes) { return ("{0:N1} GB" -f ($bytes / 1GB)) } # ---------------------------------------------------------------- elevation $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin -and -not $NoElevate) { Write-Host "Asking Windows for administrator rights (needed to read drive information and to write a USB stick)..." $argList = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ('"' + $PSCommandPath + '"')) if ($Auto) { $argList += '-Auto' } Start-Process -FilePath 'powershell.exe' -ArgumentList $argList -Verb RunAs exit 0 } New-Item -ItemType Directory -Path $Work -Force | Out-Null Say "==================================================================" Say " Convert to xOS - version 1 - " + (Get-Date -Format 'yyyy-MM-dd HH:mm') Say "==================================================================" Say "" # ---------------------------------------------------------------- 1. welcome Say "This will prepare xOS for this computer. Your files are backed up first." Say "Nothing is erased until you confirm it in writing, and the xOS installer asks" Say "three more times on the next boot before it touches a drive." Say "" Say " [1] Let Lumi do it - guided; Lumi explains each step and picks safe defaults" Say " [2] I'll do it myself - the same steps; you confirm each one" Say " [q] Quit" $mode = 'lumi' if (-not $Auto) { $choice = Ask "Choose 1, 2 or q" if ($choice -eq 'q') { Stop-Here "you chose to quit at the welcome step." 0 } if ($choice -eq '2') { $mode = 'manual' } } if ($mode -eq 'lumi') { Lumi "Hi. I'll walk you through it. I read from this machine only; nothing is sent anywhere." } # ---------------------------------------------------------------- 2. system check Say ""; Say "--- 2. System check ---" Green $firmware = 'BIOS' try { $null = Confirm-SecureBootUEFI -ErrorAction Stop; $firmware = 'UEFI' } catch { if ($_.Exception.Message -match 'not supported on this platform') { $firmware = 'BIOS' } else { $firmware = 'UEFI' } } $secureBoot = $false if ($firmware -eq 'UEFI') { try { $secureBoot = [bool](Confirm-SecureBootUEFI) } catch { $secureBoot = $false } } $cs = Get-CimInstance Win32_ComputerSystem $ramGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 1) $arch = $env:PROCESSOR_ARCHITECTURE $sys = Get-CimInstance Win32_OperatingSystem $sysDrive = ($sys.SystemDrive) $free = (Get-PSDrive -Name $sysDrive.TrimEnd(':')).Free $freeGB = [math]::Round($free / 1GB, 1) $bitlocker = 'unknown' try { $bde = & manage-bde -status $sysDrive 2>$null if ($bde -match 'Protection On') { $bitlocker = 'on' } elseif ($bde -match 'Protection Off') { $bitlocker = 'off' } } catch { $bitlocker = 'unknown' } $fastStartup = 'unknown' try { $hb = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power' -Name HiberbootEnabled -ErrorAction Stop if ($hb.HiberbootEnabled -eq 1) { $fastStartup = 'on' } else { $fastStartup = 'off' } } catch { $fastStartup = 'unknown' } Say (" Firmware: " + $firmware) Say (" Architecture: " + $arch) Say (" Memory: " + $ramGB + " GB (minimum " + $MinRamGB + " GB)") Say (" Free on " + $sysDrive + ": " + $freeGB + " GB (the image needs about 5 GB here; xOS itself needs " + $MinDiskGB + " GB on the drive it will use)") Say (" Secure Boot: " + $(if ($firmware -eq 'UEFI') { $(if ($secureBoot) { 'on' } else { 'off' }) } else { 'n/a (BIOS)' })) Say (" BitLocker: " + $bitlocker + " (" + $sysDrive + ")") Say (" Fast Startup: " + $fastStartup) $blockers = @() if ($arch -ne 'AMD64') { $blockers += "xOS needs an x86_64 (Intel or AMD 64-bit) processor; this machine reports " + $arch + "." } if ($ramGB -lt $MinRamGB) { $blockers += ("xOS needs " + $MinRamGB + " GB of memory; this machine has " + $ramGB + " GB.") } if ($freeGB -lt 6) { $blockers += ("The download needs about 5 GB free on " + $sysDrive + "; only " + $freeGB + " GB is free.") } if ($blockers.Count -gt 0) { foreach ($b in $blockers) { Say (" BLOCKER: " + $b) Red }; Stop-Here "this computer does not meet the minimum right now." 3 } if ($secureBoot) { Say "" Say " Secure Boot is ON. The xOS installer is not signed yet, so the firmware would refuse to start it." Yellow Say " You turn it off in the firmware setup (usually F2, Del or Esc at power-on -> Security or Boot -> Secure Boot -> Disabled)." Say " This script cannot flip that switch for you, and it will not pretend to." if ($mode -eq 'lumi') { Lumi "I'll keep going so the download and backup are ready; turn Secure Boot off before you reboot into the installer." } } if ($bitlocker -eq 'on') { Say "" Say (" BitLocker is ON for " + $sysDrive + ". The xOS installer copies your whole drive block by block; an encrypted drive copies fine,") Yellow Say " but the copy is only readable with your recovery key. Save the key now: Settings -> Privacy & security -> Device encryption," Say " or run: manage-bde -protectors -get " + $sysDrive Say " To decrypt first (recommended, takes a while): manage-bde -off " + $sysDrive $go = Ask "Continue anyway? (yes / no)" if ($go -ne 'yes') { Stop-Here "BitLocker is on and you chose to handle it first." 0 } } if ($fastStartup -eq 'on') { Say "" Say " Fast Startup is ON. It leaves Windows half-asleep on the drive, which breaks a clean copy. It must be off." $turnOff = 'yes' if ($mode -ne 'lumi') { $turnOff = Ask "Turn Fast Startup off now? (yes / no)" } if ($turnOff -eq 'yes') { Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power' -Name HiberbootEnabled -Value 0 Say " Changed: Fast Startup turned off (HiberbootEnabled=0). To undo: set it back to 1 in the same registry key." Yellow } else { Stop-Here "Fast Startup must be off before a clean copy can be made." 0 } } # ---------------------------------------------------------------- 3. download Say ""; Say "--- 3. Download ---" Green [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $release = $null try { $release = Invoke-RestMethod -Uri $ReleaseUrl -Headers @{ 'Cache-Control' = 'no-store' } -TimeoutSec 30 } catch { Stop-Here ("could not read the release manifest at " + $ReleaseUrl + " (" + $_.Exception.Message + ")") 4 } if (-not $release.published) { Say " The public xOS image is not published yet. The manifest says:" Yellow Say (" " + $release.notes) Stop-Here "no image to download yet. Run this again when xscash.net shows the image as published." 5 } $imagePath = Join-Path $Work $release.name $parts = @() if ($release.parts -and $release.parts.Count -gt 0) { $parts = @($release.parts) } else { $parts = @(@{ url = $release.url; sha256 = $release.sha256; size = $release.size_bytes }) } Say (" Image: " + $release.name + " " + (Fmt-GB $release.size_bytes) + " built " + $release.built) Say (" Expected SHA-256: " + $release.sha256) Say (" Saving to: " + $imagePath) function Download-File($url, $dest, $expectedBytes) { $req = [Net.HttpWebRequest]::Create($url); $req.AllowAutoRedirect = $true; $req.Timeout = 60000; $req.UserAgent = 'ConvertToXOS/1' $resp = $req.GetResponse() $total = $resp.ContentLength if ($total -le 0 -and $expectedBytes) { $total = [long]$expectedBytes } $in = $resp.GetResponseStream(); $out = [IO.File]::Create($dest) $buf = New-Object byte[] 1048576; $done = [long]0; $sw = [Diagnostics.Stopwatch]::StartNew(); $lastShown = -1 try { while (($n = $in.Read($buf, 0, $buf.Length)) -gt 0) { $out.Write($buf, 0, $n); $done += $n $secs = $sw.Elapsed.TotalSeconds if ($secs -ge 1 -and [int]$secs -ne $lastShown) { $lastShown = [int]$secs $rate = $done / $secs $line = " " + (Fmt-GB $done) + " received at " + ("{0:N1}" -f ($rate / 1MB)) + " MB/s" if ($total -gt 0) { $pct = [math]::Floor(100.0 * $done / $total) $remaining = ($total - $done) / [math]::Max($rate, 1) if ($secs -ge 5) { $line += " " + $pct + "% about " + [math]::Round($remaining / 60) + " min left (measured)" } else { $line += " " + $pct + "%" } } Write-Host ("`r" + $line.PadRight(90)) -NoNewline } } } finally { $out.Close(); $in.Close(); $resp.Close() } Write-Host "" return $done } function Verify-Sha256($path, $expected) { Say (" Verifying SHA-256 of " + (Split-Path $path -Leaf) + " (this reads the whole file)...") $h = (Get-FileHash -Path $path -Algorithm SHA256).Hash.ToLower() Say (" Computed: " + $h) return ($h -eq $expected.ToLower()) } $attempt = 0; $ok = $false while (-not $ok -and $attempt -lt 2) { $attempt++ $partPaths = @() $i = 0 foreach ($p in $parts) { $i++ $dest = if ($parts.Count -gt 1) { Join-Path $Work ($release.name + ".part" + $i) } else { $imagePath } $partPaths += $dest $skip = $false if (Test-Path $dest) { if ((Get-Item $dest).Length -eq [long]$p.size) { Say (" Part " + $i + " already present with the right size; checking it..."); if (Verify-Sha256 $dest $p.sha256) { $skip = $true } } } if (-not $skip) { Say (" Downloading part " + $i + " of " + $parts.Count + ": " + $p.url) $got = Download-File $p.url $dest $p.size Say (" Part " + $i + ": " + (Fmt-GB $got) + " received.") if (-not (Verify-Sha256 $dest $p.sha256)) { Say (" Part " + $i + " does not match its checksum.") Red; Remove-Item $dest -Force -ErrorAction SilentlyContinue; $ok = $false; break } } $ok = $true } if ($ok -and $parts.Count -gt 1) { Say " Joining parts..." $outStream = [IO.File]::Create($imagePath) try { foreach ($pp in $partPaths) { $s = [IO.File]::OpenRead($pp); try { $s.CopyTo($outStream) } finally { $s.Close() } } } finally { $outStream.Close() } $ok = Verify-Sha256 $imagePath $release.sha256 if ($ok) { foreach ($pp in $partPaths) { Remove-Item $pp -Force -ErrorAction SilentlyContinue } } } elseif ($ok -and $parts.Count -eq 1 -and -not (Verify-Sha256 $imagePath $release.sha256)) { $ok = $false } if (-not $ok) { if ($attempt -lt 2) { Say " The download did not verify. Trying once more." Yellow } else { Stop-Here "the image did not verify after two attempts. The file at " + $imagePath + " must not be used." 6 } } } Say (" Image verified: " + $imagePath) Green $bootEfiZip = Join-Path $Work 'xos-boot-efi.zip' $bootEfiOk = $false if ($release.boot_efi_url -and $release.boot_efi_sha256) { try { if (-not ((Test-Path $bootEfiZip) -and (Verify-Sha256 $bootEfiZip $release.boot_efi_sha256))) { Say (" Downloading the boot bundle for mode [2]: " + $release.boot_efi_url) $null = Download-File $release.boot_efi_url $bootEfiZip $release.boot_efi_size } $bootEfiOk = Verify-Sha256 $bootEfiZip $release.boot_efi_sha256 } catch { $bootEfiOk = $false; Say (" Boot bundle not available (" + $_.Exception.Message + "); mode [2] will be off.") Yellow } if ($bootEfiOk) { Say " Boot bundle verified." Green } } else { Say " The manifest has no boot bundle; mode [2] (no stick) is off for this release." } # ---------------------------------------------------------------- 4. backup Say ""; Say "--- 4. Backup ---" Green Say " Windows is running from " + $sysDrive + ", so this script copies your personal folders file by file (with a" Say " SHA-256 for every file, then re-reads the copy). The full block-by-block copy of the whole drive is made by the" Say " xOS installer on the next boot, when nothing is running from the drive." $systemDiskNumber = (Get-Partition -DriveLetter $sysDrive.TrimEnd(':') | Get-Disk).Number $targets = @() foreach ($d in Get-Disk | Where-Object { $_.Number -ne $systemDiskNumber -and $_.OperationalStatus -eq 'Online' }) { foreach ($v in ($d | Get-Partition -ErrorAction SilentlyContinue | Get-Volume -ErrorAction SilentlyContinue | Where-Object { $_.DriveLetter })) { $kind = if ($d.BusType -eq 'USB') { 'external (USB)' } else { 'second internal disk' } $targets += [pscustomobject]@{ Letter = $v.DriveLetter; Kind = $kind; Free = $v.SizeRemaining; Label = $v.FileSystemLabel; Disk = $d.Number } } } foreach ($m in (Get-SmbMapping -ErrorAction SilentlyContinue)) { if ($m.LocalPath) { $targets += [pscustomobject]@{ Letter = $m.LocalPath.TrimEnd(':'); Kind = 'LAN share ' + $m.RemotePath; Free = 0; Label = ''; Disk = -1 } } } $folders = @('Desktop', 'Documents', 'Pictures', 'Downloads', 'Videos', 'Music') $profileRoot = $env:USERPROFILE $estimate = 0 foreach ($f in $folders) { $p = Join-Path $profileRoot $f; if (Test-Path $p) { $estimate += (Get-ChildItem $p -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum } } Say (" Your personal folders (" + ($folders -join ', ') + ") hold about " + (Fmt-GB $estimate) + ".") if ($targets.Count -eq 0) { Say " No external drive, second disk or LAN share is connected right now." Yellow } else { Say " Places I can copy them to:" $n = 0; foreach ($t in $targets) { $n++; Say (" [" + $n + "] " + $t.Letter + ": " + $t.Kind + " " + $(if ($t.Free -gt 0) { (Fmt-GB $t.Free) + " free" } else { '' }) + " " + $t.Label) } } Say " [s] Skip the backup (you will have to type I UNDERSTAND)" $pick = Ask "Choose a number or s" $backupDone = $false if ($pick -eq 's') { $typed = Ask "Type I UNDERSTAND to continue without a backup" if ($typed -cne 'I UNDERSTAND') { Stop-Here "backup skipped without the exact confirmation." 0 } Say " Backup skipped by your typed confirmation. The xOS installer will offer the whole-drive copy again on the next boot." Yellow } else { $t = $targets[[int]$pick - 1] if (-not $t) { Stop-Here "that was not a listed choice." 0 } if ($t.Free -gt 0 -and $t.Free -lt $estimate) { Stop-Here ("the chosen drive has " + (Fmt-GB $t.Free) + " free but the folders need " + (Fmt-GB $estimate) + ".") 0 } $dest = ($t.Letter + ':\xOS-backup-' + $env:COMPUTERNAME + '-' + (Get-Date -Format 'yyyyMMdd-HHmm')) Say (" Copying to " + $dest + " ...") New-Item -ItemType Directory -Path $dest -Force | Out-Null $manifest = @() foreach ($f in $folders) { $src = Join-Path $profileRoot $f; if (-not (Test-Path $src)) { continue } $null = & robocopy $src (Join-Path $dest $f) /E /COPY:DAT /R:1 /W:1 /XJ /NFL /NDL /NJH /NJS /NP foreach ($file in Get-ChildItem $src -Recurse -File -Force -ErrorAction SilentlyContinue) { $rel = $file.FullName.Substring($profileRoot.Length + 1) $copy = Join-Path $dest $rel $h1 = (Get-FileHash -Path $file.FullName -Algorithm SHA256 -ErrorAction SilentlyContinue).Hash $h2 = if (Test-Path $copy) { (Get-FileHash -Path $copy -Algorithm SHA256 -ErrorAction SilentlyContinue).Hash } else { $null } $manifest += [pscustomobject]@{ path = $rel; bytes = $file.Length; sha256 = $h1; copy_sha256 = $h2; verified = ($h1 -and $h1 -eq $h2) } } } $bad = @($manifest | Where-Object { -not $_.verified }) $manifest | ConvertTo-Json -Depth 3 | Set-Content -Path (Join-Path $dest 'MANIFEST.json') -Encoding UTF8 Say (" Files copied and re-read: " + $manifest.Count + ", verified: " + ($manifest.Count - $bad.Count) + ", mismatched: " + $bad.Count) if ($bad.Count -gt 0) { foreach ($b in ($bad | Select-Object -First 10)) { Say (" mismatch: " + $b.path) Red }; Stop-Here "the backup did not verify completely. Fix the drive or choose another and run again." 7 } Say (" Backup verified. Manifest: " + (Join-Path $dest 'MANIFEST.json')) Green $backupDone = $true } # ---------------------------------------------------------------- raw write + verify helpers function Write-RawImage($dev, $path, $length) { $src = [IO.File]::OpenRead($path) $dst = New-Object IO.FileStream($dev, [IO.FileMode]::Open, [IO.FileAccess]::Write, [IO.FileShare]::None, 1048576, $false) $buf = New-Object byte[] 4194304; $done = [long]0; $sw = [Diagnostics.Stopwatch]::StartNew(); $last = -1 try { while (($n = $src.Read($buf, 0, $buf.Length)) -gt 0) { if ($n % 512 -ne 0) { $pad = 512 - ($n % 512); [Array]::Clear($buf, $n, $pad); $n += $pad } $dst.Write($buf, 0, $n); $done += $n $secs = $sw.Elapsed.TotalSeconds if ([int]$secs -ne $last) { $last = [int]$secs; Write-Host ("`r " + (Fmt-GB $done) + " of " + (Fmt-GB $length) + " " + [math]::Floor(100.0 * $done / $length) + "% " + ("{0:N1}" -f ($done / [math]::Max($secs, 1) / 1MB)) + " MB/s").PadRight(90) -NoNewline } } $dst.Flush() } finally { $dst.Close(); $src.Close(); Write-Host "" } } function Read-RawHash($dev, $length) { $rd = New-Object IO.FileStream($dev, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite, 1048576, $false) $sha = [Security.Cryptography.SHA256]::Create(); $remaining = [long]$length; $buf = New-Object byte[] 4194304 try { while ($remaining -gt 0) { $want = [int][math]::Min($buf.Length, $remaining); $want = $want - ($want % 512); if ($want -le 0) { $want = 512 } $n = $rd.Read($buf, 0, $want); if ($n -le 0) { break } $take = [int][math]::Min($n, $remaining) $null = $sha.TransformBlock($buf, 0, $take, $null, 0); $remaining -= $take } $null = $sha.TransformFinalBlock($buf, 0, 0) } finally { $rd.Close() } return ([BitConverter]::ToString($sha.Hash) -replace '-', '').ToLower() } # ---------------------------------------------------------------- 5. install mode Say ""; Say "--- 5. Install mode ---" Green $imgSize = (Get-Item $imagePath).Length $roomBytes = [long]([math]::Ceiling(($imgSize + 512MB) / 1MB) * 1MB) $noStickWhy = '' if ($firmware -ne 'UEFI') { $noStickWhy = 'this computer boots in BIOS mode; a one-time boot entry needs UEFI' } elseif ($ramGB -lt 8) { $noStickWhy = 'the installer needs 8 GB of memory to copy itself into memory; this computer has ' + $ramGB + ' GB' } elseif (-not $bootEfiOk) { $noStickWhy = 'the boot bundle for this release is not available' } elseif ($bitlocker -eq 'on') { $noStickWhy = 'BitLocker is on for ' + $sysDrive + '; turn it off first (Settings > Privacy & security > Device encryption)' } $noStick = ($noStickWhy -eq '') Say " [1] Replace Windows from a USB stick or a second disk - this script writes the installer there; on the next boot" Say " the xOS installer erases the drive you choose. This drive is untouched until then." if ($noStick) { Say (" [2] Replace Windows, no stick (this drive) - this script shrinks " + $sysDrive + " by " + (Fmt-GB $roomBytes) + ", writes the installer into") Say " that space, adds a ONE-TIME boot entry and restarts. The xOS installer copies itself to memory," Say " offers to copy this whole drive to a backup drive, asks three times, then erases this drive." } else { Say (" [2] Replace Windows, no stick - NOT possible here: " + $noStickWhy + ".") Yellow } Say " [3] Alongside Windows - NOT available in this version. The xOS installer can only take a whole drive today;" Say " a partition-level install (a section of your disk) is being built. Nothing is faked here." Say " [4] Try in a VM first - NOT available in this version. The image is a normal hybrid ISO; you can boot it in" Say (" any virtual machine program (QEMU, VirtualBox) from " + $imagePath + ".") Say " [q] Stop here, keep the downloaded image and the backup." $modeChoice = '1' if ($mode -eq 'lumi') { $haveStick = Ask "Do you have a USB stick of 8 GB or more, or a second disk, to carry the installer? (yes / no)" if ($haveStick -eq 'yes') { $modeChoice = '1'; Lumi "Good. A stick keeps this drive untouched until you say otherwise on the next boot." } elseif ($noStick) { $modeChoice = '2'; Lumi "Then I'll make room on this drive and boot the installer from there. Nothing is erased until you confirm at the installer." } else { Stop-Here ("without a stick this computer cannot start the installer: " + $noStickWhy + ". The image is at " + $imagePath + ".") 0 } } else { $modeChoice = Ask "Choose 1, 2 or q" } if ($modeChoice -eq '2' -and -not $noStick) { Stop-Here ("mode 2 is not possible here: " + $noStickWhy + ".") 0 } if ($modeChoice -ne '1' -and $modeChoice -ne '2') { Stop-Here ("you chose to stop after the download and backup. The image is at " + $imagePath + ".") 0 } if ($modeChoice -eq '1') { # ---------------------------------------------------------------- 6a. stage the installer on a stick or second disk Say ""; Say "--- 6. Stage the installer ---" Green $candidates = @() foreach ($d in Get-Disk | Where-Object { $_.Number -ne $systemDiskNumber -and $_.OperationalStatus -eq 'Online' -and $_.Size -ge ($MinStickGB * 1GB) }) { $kind = if ($d.BusType -eq 'USB') { 'USB' } else { 'internal (' + $d.BusType + ')' } $candidates += [pscustomobject]@{ Number = $d.Number; Model = $d.FriendlyName; Size = $d.Size; Kind = $kind } } if ($candidates.Count -eq 0) { Say " No USB stick (8 GB or more) or second internal disk is connected." Yellow Say " Plug in a stick of 8 GB or more and run this again; the download and backup are kept and will not repeat." Stop-Here "nothing to stage the installer on." 8 } Say " Drives that can carry the installer (EVERYTHING on the chosen one will be erased):" $n = 0; foreach ($c in $candidates) { $n++; Say (" [" + $n + "] Disk " + $c.Number + " " + $c.Model + " " + (Fmt-GB $c.Size) + " " + $c.Kind) } $pick = Ask "Choose a number (or q to stop)" if ($pick -eq 'q') { Stop-Here "you stopped before any drive was written." 0 } $c = $candidates[[int]$pick - 1] if (-not $c) { Stop-Here "that was not a listed choice." 0 } Say "" Say (" You chose Disk " + $c.Number + ": " + $c.Model + " (" + (Fmt-GB $c.Size) + "). Every file on it will be gone.") Yellow $typed = Ask ("Type ERASE DISK " + $c.Number + " to continue") if ($typed -cne ("ERASE DISK " + $c.Number)) { Stop-Here "the exact confirmation was not typed; the drive was not touched." 0 } Say " Clearing the drive's partition table..." Clear-Disk -Number $c.Number -RemoveData -RemoveOEM -Confirm:$false -ErrorAction Stop Set-Disk -Number $c.Number -IsOffline $true -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 $dev = '\\.\PhysicalDrive' + $c.Number Say (" Writing " + (Fmt-GB $imgSize) + " to " + $dev + " ...") Write-RawImage $dev $imagePath $imgSize Say " Written. Re-reading the drive to verify the image bytes..." $diskHash = Read-RawHash $dev $imgSize Say (" Drive SHA-256 over the image length: " + $diskHash) if ($diskHash -ne $release.sha256.ToLower()) { Stop-Here "the drive does not read back what was written. Do not boot from it; try another stick." 9 } Set-Disk -Number $c.Number -IsOffline $false -ErrorAction SilentlyContinue Say " Verified: the drive carries the xOS installer, byte for byte." Green # ---------------------------------------------------------------- 7a. reboot Say ""; Say "--- 7. Ready ---" Green Say " What happens next:" Say " 1. Restart and open the boot menu (usually F12, F2, Esc or Del as the logo appears)." Say (" 2. Pick the drive named like '" + $c.Model + "' (" + $(if ($firmware -eq 'UEFI') { 'the UEFI entry' } else { 'the plain entry' }) + ")." ) if ($secureBoot) { Say " 3. Secure Boot is still ON on this machine; turn it off in the same firmware setup first." Yellow } Say " Then Lumi takes over: she copies your whole drive block by block to a drive you choose, asks three times, installs xOS," Say " and on the first boot you create your account (Argon2id) and she greets you by name." Say "" Say (" Everything this script did is in " + $LogPath + ".") if ($firmware -eq 'UEFI') { $r = Ask "Restart now into the firmware setup so you can pick the drive? (yes / no)" if ($r -eq 'yes') { Say " Restarting into firmware setup in 10 seconds..."; & shutdown /r /fw /t 10 } } else { $r = Ask "Restart now? (yes / no)" if ($r -eq 'yes') { Say " Restarting in 10 seconds..."; & shutdown /r /t 10 } } exit 0 } # ---------------------------------------------------------------- 6b. no stick: make room on this drive, stage, one-time boot entry Say ""; Say "--- 6. Make room on this drive ---" Green $letter = $sysDrive.TrimEnd(':') $sysPart = Get-Partition -DriveLetter $letter -ErrorAction Stop $systemDiskNumber = $sysPart.DiskNumber $supported = Get-PartitionSupportedSize -DriveLetter $letter -ErrorAction Stop $canShrink = [long]$sysPart.Size - [long]$supported.SizeMin Say (" " + $sysDrive + " is " + (Fmt-GB $sysPart.Size) + "; Windows says it can shrink by up to " + (Fmt-GB $canShrink) + ".") Say (" The installer needs " + (Fmt-GB $roomBytes) + " of that.") if ($canShrink -lt ($roomBytes + 1GB)) { Say " Not enough shrinkable space. Windows keeps unmovable files near the end of the drive; a restart, turning off hibernation" Yellow Say " (powercfg /h off) and the page file, or Disk Cleanup usually frees it. Or use a USB stick (mode 1)." Yellow Stop-Here "cannot make room on this drive yet. Nothing was changed." 10 } $typed = Ask ("Type MAKE ROOM to shrink " + $sysDrive + " by " + (Fmt-GB $roomBytes) + " (your files stay; only free space moves)") if ($typed -cne 'MAKE ROOM') { Stop-Here "the exact confirmation was not typed; the drive was not touched." 0 } $newSize = [long]$sysPart.Size - $roomBytes Say (" Shrinking " + $sysDrive + " to " + (Fmt-GB $newSize) + " ...") Resize-Partition -DriveLetter $letter -Size $newSize -ErrorAction Stop Start-Sleep -Seconds 2 Say " Creating the installer partition (no drive letter; Windows will not touch it)..." $linuxType = '{0FC63DAF-8483-4772-8E79-3D69D8477DE4}' $stage = New-Partition -DiskNumber $systemDiskNumber -Size $roomBytes -GptType $linuxType -ErrorAction Stop Start-Sleep -Seconds 2 $dev = '\\.\Harddisk' + $systemDiskNumber + 'Partition' + $stage.PartitionNumber Say (" Writing " + (Fmt-GB $imgSize) + " to " + $dev + " (partition " + $stage.PartitionNumber + ") ...") Write-RawImage $dev $imagePath $imgSize Say " Written. Re-reading the partition to verify the image bytes..." $diskHash = Read-RawHash $dev $imgSize Say (" Partition SHA-256 over the image length: " + $diskHash) if ($diskHash -ne $release.sha256.ToLower()) { Stop-Here "the partition does not read back what was written. Nothing else was changed; delete the new partition in Disk Management and extend C: to undo." 9 } Say " Verified: the partition carries the xOS installer, byte for byte." Green Say ""; Say "--- 6b. One-time boot entry ---" Green $efiDir = Join-Path $Work 'boot-efi' if (Test-Path $efiDir) { Remove-Item $efiDir -Recurse -Force } Expand-Archive -Path $bootEfiZip -DestinationPath $efiDir -Force $efiBin = Get-ChildItem -Path $efiDir -Recurse -Filter 'bootx64.efi' | Select-Object -First 1 $efiCfg = Get-ChildItem -Path $efiDir -Recurse -Filter 'grub.cfg' | Select-Object -First 1 if (-not $efiBin -or -not $efiCfg) { Stop-Here "the boot bundle is missing bootx64.efi or grub.cfg. The installer partition is in place; use a USB stick (mode 1) or run again." 11 } $esp = $null foreach ($cand in @('S', 'T', 'U', 'V', 'W')) { if (-not (Test-Path ($cand + ':\'))) { $esp = $cand; break } } if (-not $esp) { Stop-Here "no free drive letter to mount the EFI partition." 12 } $mv = & mountvol ($esp + ':') /S 2>&1 if (-not (Test-Path ($esp + ':\EFI'))) { Stop-Here ("could not mount the EFI system partition (" + ($mv -join ' ') + ").") 12 } try { $dest = $esp + ':\EFI\xos' New-Item -ItemType Directory -Path $dest -Force | Out-Null Copy-Item $efiBin.FullName (Join-Path $dest 'bootx64.efi') -Force Copy-Item $efiCfg.FullName (Join-Path $dest 'grub.cfg') -Force Say (" Copied bootx64.efi and grub.cfg to " + $dest + " (Windows' own EFI files are untouched).") $copyOut = & bcdedit /copy '{bootmgr}' /d 'xOS installer' 2>&1 $guid = [regex]::Match(($copyOut -join ' '), '\{[0-9a-fA-F-]{36}\}').Value if (-not $guid) { Stop-Here ("bcdedit could not create the boot entry (" + ($copyOut -join ' ') + ").") 13 } $null = & bcdedit /set $guid device ('partition=' + $esp + ':') 2>&1 $null = & bcdedit /set $guid path '\EFI\xos\bootx64.efi' 2>&1 $null = & bcdedit /set $guid description 'xOS installer (one time)' 2>&1 $seq = & bcdedit /set '{fwbootmgr}' bootsequence $guid 2>&1 Say (" One-time firmware boot entry " + $guid + " set for the next start only (" + ($seq -join ' ').Trim() + ").") Add-Content -Path (Join-Path $Work 'boot-entry.txt') -Value ($guid + ' xOS installer ' + (Get-Date -Format s)) } finally { $null = & mountvol ($esp + ':') /D 2>&1 } # ---------------------------------------------------------------- 7b. reboot Say ""; Say "--- 7. Ready ---" Green Say " What happens next:" Say " 1. This computer restarts once into the xOS installer (no key to press). If it comes back to Windows instead," Say " open the boot menu (F12, F2, Esc or Del) and pick 'xOS installer'." if ($secureBoot) { Say " 2. Secure Boot is still ON; turn it off in the firmware setup first or the entry will not start." Yellow } Say " Then Lumi takes over: the installer copies itself into memory, copies this whole drive block by block to a drive you" Say " choose, asks three times, installs xOS, and on the first boot you create your account (Argon2id)." Say (" To undo without installing: choose Stop at the installer; Windows boots as before. Then delete the '" + (Fmt-GB $roomBytes) + "' partition") Say (" in Disk Management and extend " + $sysDrive + ". Remove the entry with: bcdedit /delete " + $guid) Say "" Say (" Everything this script did is in " + $LogPath + ".") $r = Ask "Restart now? (yes / no)" if ($r -eq 'yes') { Say " Restarting in 10 seconds..."; & shutdown /r /t 10 } exit 0