From 3267c992aadcb58b7051677142c6164fb207e7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20QUEROL?= <2+kawa@not.obvious> Date: Wed, 10 Jun 2026 15:18:23 +0200 Subject: [PATCH] Upload files to "/" --- Install-DockerDesktop-LTSC-Force.ps1 | 434 +++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 Install-DockerDesktop-LTSC-Force.ps1 diff --git a/Install-DockerDesktop-LTSC-Force.ps1 b/Install-DockerDesktop-LTSC-Force.ps1 new file mode 100644 index 0000000..8d47dd2 --- /dev/null +++ b/Install-DockerDesktop-LTSC-Force.ps1 @@ -0,0 +1,434 @@ +#Requires -RunAsAdministrator +<# +.SYNOPSIS + Force-installs Docker Desktop on Windows 10 LTSC / IoT LTSC editions. + +.DESCRIPTION + Docker Desktop's installer rejects non-Enterprise/Pro editions at launch. + This script works around that using three layered techniques: + + Technique A -- Registry EditionID spoof (all builds) + Temporarily sets HKLM\...\CurrentVersion\EditionID to "Enterprise" + so the installer's edition check passes, then restores the original + value after installation. + + Technique B -- WSL2 backend (build 19041+, recommended) + Installs WSL2 manually (wsl --install or the MSI path for LTSC), + then launches Docker Desktop with --backend=wsl2. Gives you full + Linux container support without Hyper-V. + + Technique C -- Hyper-V feature injection (build 17763+) + Enables Hyper-V on SKUs where the GUI won't show it (IoT Enterprise, + IoT Enterprise LTSC) by directly enabling the optional features and + writing the required BCD entry. Used as fallback for the Windows + containers backend or when WSL2 is unavailable. + + All three techniques are applied in sequence; you can disable any of + them with the switches below. + +.PARAMETER DockerDesktopVersion + Version of Docker Desktop to install, e.g. "4.30.0". + Defaults to "latest" which queries the Docker Hub release API. + +.PARAMETER Backend + "wsl2" -- Linux containers via WSL2 (default, recommended) + "hyper-v" -- Windows/Linux containers via Hyper-V + "windows" -- Windows containers only, no Hyper-V / WSL2 needed + +.PARAMETER SkipEditionSpoof + Skip the temporary EditionID registry spoof. + Use only if your SKU already passes Docker Desktop's check. + +.PARAMETER SkipWSL2 + Skip WSL2 installation (ignored when -Backend is not wsl2). + +.PARAMETER SkipHyperV + Skip Hyper-V feature enablement. + +.PARAMETER KeepDownloads + Do not delete downloaded installers after use. + +.EXAMPLE + # Fully automatic, WSL2 backend (recommended for LTSC 2021) + .\Install-DockerDesktop-LTSC-Force.ps1 + + # Hyper-V backend on LTSC 2019 without WSL2 + .\Install-DockerDesktop-LTSC-Force.ps1 -Backend hyper-v -SkipWSL2 + + # Windows-containers-only, no virtualisation layer at all + .\Install-DockerDesktop-LTSC-Force.ps1 -Backend windows -SkipWSL2 -SkipHyperV + +.NOTES + [!] This script modifies system registry keys and Windows features. + Take a snapshot / backup before running on production machines. + [!] A reboot is almost certainly required after the first run. + Tested on: + Windows 10 IoT Enterprise LTSC 2019 (build 17763) + Windows 10 IoT Enterprise LTSC 2021 (build 19044) + Windows 10 Enterprise LTSC 2021 (build 19044) +#> + +[CmdletBinding()] +param( + [string] $DockerDesktopVersion = "latest", + [ValidateSet("wsl2","hyper-v","windows")] + [string] $Backend = "wsl2", + [switch] $SkipEditionSpoof, + [switch] $SkipWSL2, + [switch] $SkipHyperV, + [switch] $KeepDownloads +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# -- Helpers ------------------------------------------------------------------- + +function Write-Step { param($m) Write-Host "`n==> $m" -ForegroundColor Cyan } +function Write-OK { param($m) Write-Host " [OK] $m" -ForegroundColor Green } +function Write-Warn { param($m) Write-Host " [WARN] $m" -ForegroundColor Yellow } +function Write-Fail { param($m) Write-Host "`n[FAIL] $m" -ForegroundColor Red; exit 1 } + +function Invoke-WithRetry { + param([scriptblock]$Action, [int]$Retries = 3, [int]$DelaySeconds = 5) + for ($i = 1; $i -le $Retries; $i++) { + try { return & $Action } + catch { + if ($i -eq $Retries) { throw } + Write-Warn "Attempt $i failed -- retrying in $DelaySeconds s..." + Start-Sleep $DelaySeconds + } + } +} + +function Enable-WindowsFeatureSafe { + param([string]$Name) + $f = Get-WindowsOptionalFeature -Online -FeatureName $Name -ErrorAction SilentlyContinue + if ($null -eq $f) { Write-Warn "Feature '$Name' not found on this SKU -- skipping"; return } + if ($f.State -eq "Enabled") { Write-OK "'$Name' already enabled"; return } + Write-Host " Enabling '$Name'..." + Enable-WindowsOptionalFeature -Online -FeatureName $Name -All -NoRestart | Out-Null + Write-OK "'$Name' enabled (reboot required)" + $script:RebootRequired = $true +} + +$script:RebootRequired = $false +$script:OriginalEdition = $null # used by spoof/restore pair + +# -- 1. OS check --------------------------------------------------------------- + +Write-Step "Validating operating system" + +$regCV = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" +$build = [int](Get-ItemProperty $regCV).CurrentBuildNumber +$curEd = (Get-ItemProperty $regCV).EditionID +$os = Get-WmiObject Win32_OperatingSystem + +Write-OK "Caption : $($os.Caption)" +Write-OK "Build : $build" +Write-OK "EditionID: $curEd" +Write-OK "SKU : $($os.OperatingSystemSKU)" + +if ($build -lt 17763) { + Write-Fail "Minimum supported build is 17763 (LTSC 2019). Found $build." +} +if ($Backend -eq "wsl2" -and $build -lt 19041) { + Write-Warn "WSL2 requires build 19041+. Your build is $build -- switching backend to 'hyper-v'." + $Backend = "hyper-v" +} + +Write-OK "Selected backend: $Backend" + +# -- 2. Resolve Docker Desktop download URL ------------------------------------ + +Write-Step "Resolving Docker Desktop installer" + +if ($DockerDesktopVersion -eq "latest") { + try { + $rel = Invoke-RestMethod "https://desktop.docker.com/win/main/amd64/appcast.xml" ` + -UseBasicParsing -ErrorAction Stop + # appcast is XML; the url contains the version + $url = ($rel.rss.channel.item.enclosure | Select-Object -First 1).url + if (-not $url) { throw "empty" } + $DockerDesktopVersion = [regex]::Match($url, 'Docker%20Desktop%20(\d+\.\d+\.\d+)').Groups[1].Value + Write-OK "Latest Docker Desktop: $DockerDesktopVersion" + } catch { + # Fallback: direct stable URL (always points to current release) + $url = "https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe" + Write-Warn "Could not determine latest version -- using rolling latest URL" + } +} + +if (-not $url) { + # Build versioned URL + $escaped = "Docker%20Desktop%20$DockerDesktopVersion.exe" + $url = "https://desktop.docker.com/win/main/amd64/$escaped" +} + +Write-OK "Installer URL: $url" + +$tmpDir = Join-Path $Env:TEMP "docker-desktop-install" +New-Item -ItemType Directory -Force $tmpDir | Out-Null +$installer = Join-Path $tmpDir "DockerDesktopInstaller.exe" + +# -- 3. TECHNIQUE A -- EditionID registry spoof -------------------------------- + +function Set-EditionSpoof { + $cv = Get-ItemProperty $regCV + + # Edition fields + $script:OriginalEdition = $cv.EditionID + $script:OriginalProductName = $cv.ProductName + + # Build / version fields (Docker Desktop requires build 19045 / 22H2) + $script:OriginalCurrentBuild = $cv.CurrentBuild + $script:OriginalCurrentBuildNumber = $cv.CurrentBuildNumber + $script:OriginalDisplayVersion = $cv.DisplayVersion + $script:OriginalReleaseId = $cv.ReleaseId + $script:OriginalUBR = $cv.UBR + + $needsEditionSpoof = $script:OriginalEdition -notmatch "Enterprise|Pro|Home" + $needsBuildSpoof = ([int]$script:OriginalCurrentBuildNumber) -lt 19045 + + if (-not $needsEditionSpoof -and -not $needsBuildSpoof) { + Write-OK "Edition and build already pass Docker Desktop checks -- spoof not needed" + $script:OriginalEdition = $null # signal: nothing to restore + return + } + + if ($needsEditionSpoof) { + Write-Host " Spoofing EditionID: '$script:OriginalEdition' -> 'Enterprise' ..." + Set-ItemProperty -Path $regCV -Name "EditionID" -Value "Enterprise" + Set-ItemProperty -Path $regCV -Name "ProductName" -Value "Windows 10 Enterprise" + } + + if ($needsBuildSpoof) { + # Spoof to Windows 10 22H2 (build 19045) -- minimum accepted by Docker Desktop + Write-Host " Spoofing build: '$script:OriginalCurrentBuildNumber' -> '19045' (22H2) ..." + Set-ItemProperty -Path $regCV -Name "CurrentBuild" -Value "19045" + Set-ItemProperty -Path $regCV -Name "CurrentBuildNumber" -Value "19045" + Set-ItemProperty -Path $regCV -Name "DisplayVersion" -Value "22H2" + Set-ItemProperty -Path $regCV -Name "ReleaseId" -Value "2009" + Set-ItemProperty -Path $regCV -Name "UBR" -Value 4170 + } + + Write-OK "Registry spoofed (will be fully restored after install)" +} + +function Restore-EditionSpoof { + if ($null -eq $script:OriginalEdition) { return } + Write-Host " Restoring original registry values ..." + Set-ItemProperty -Path $regCV -Name "EditionID" -Value $script:OriginalEdition + Set-ItemProperty -Path $regCV -Name "ProductName" -Value $script:OriginalProductName + Set-ItemProperty -Path $regCV -Name "CurrentBuild" -Value $script:OriginalCurrentBuild + Set-ItemProperty -Path $regCV -Name "CurrentBuildNumber" -Value $script:OriginalCurrentBuildNumber + Set-ItemProperty -Path $regCV -Name "DisplayVersion" -Value $script:OriginalDisplayVersion + Set-ItemProperty -Path $regCV -Name "ReleaseId" -Value $script:OriginalReleaseId + Set-ItemProperty -Path $regCV -Name "UBR" -Value $script:OriginalUBR + Write-OK "Registry restored to original values" +} + +# Register restore as a finally-block safeguard at the top level +$spoofApplied = $false +if (-not $SkipEditionSpoof) { + Write-Step "Technique A: EditionID spoof" + Set-EditionSpoof + $spoofApplied = $true +} + +# -- 4. TECHNIQUE B -- WSL2 ----------------------------------------------------- + +if ($Backend -eq "wsl2" -and -not $SkipWSL2) { + Write-Step "Technique B: WSL2 installation" + + # Enable required features + Enable-WindowsFeatureSafe "Microsoft-Windows-Subsystem-Linux" + Enable-WindowsFeatureSafe "VirtualMachinePlatform" + + # Check if WSL2 kernel is already present + $wslVer = wsl --status 2>$null | Select-String "Kernel" + if ($wslVer) { + Write-OK "WSL2 kernel already installed: $wslVer" + } else { + # On LTSC, 'wsl --install' may not work -- download the kernel MSI directly + try { + Write-Host " Trying 'wsl --install --no-distribution' ..." + wsl --install --no-distribution 2>&1 | Out-Null + Write-OK "WSL2 installed via wsl --install" + } catch { + Write-Warn "wsl --install failed -- downloading WSL2 kernel MSI manually ..." + $wslMsi = Join-Path $tmpDir "wsl_update_x64.msi" + Invoke-WithRetry { + Invoke-WebRequest ` + -Uri "https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi" ` + -OutFile $wslMsi -UseBasicParsing + } + Start-Process msiexec.exe -ArgumentList "/i `"$wslMsi`" /quiet /norestart" -Wait + Write-OK "WSL2 kernel MSI installed" + } + } + + # Set WSL default version + try { + wsl --set-default-version 2 2>&1 | Out-Null + Write-OK "WSL default version set to 2" + } catch { + Write-Warn "Could not set WSL default version (may need reboot first)" + $script:RebootRequired = $true + } +} + +# -- 5. TECHNIQUE C -- Hyper-V feature injection -------------------------------- + +if (($Backend -eq "hyper-v" -or $Backend -eq "wsl2") -and -not $SkipHyperV) { + Write-Step "Technique C: Hyper-V feature injection" + + $hvFeatures = @( + "Microsoft-Hyper-V-All", + "Microsoft-Hyper-V", + "Microsoft-Hyper-V-Tools-All", + "Microsoft-Hyper-V-Management-PowerShell", + "Microsoft-Hyper-V-Hypervisor", + "Microsoft-Hyper-V-Services" + ) + foreach ($f in $hvFeatures) { Enable-WindowsFeatureSafe $f } + + # On IoT SKUs Hyper-V may need a BCD entry to actually activate + Write-Host " Verifying BCD hypervisor launch type ..." + $bcdOut = bcdedit /enum "{current}" 2>&1 + if ($bcdOut -match "hypervisorlaunchtype\s+Auto") { + Write-OK "BCD hypervisorlaunchtype already set to Auto" + } else { + bcdedit /set hypervisorlaunchtype Auto | Out-Null + Write-OK "BCD hypervisorlaunchtype set to Auto" + $script:RebootRequired = $true + } +} + +# -- 6. Download Docker Desktop installer -------------------------------------- + +Write-Step "Downloading Docker Desktop installer" + +if (Test-Path $installer) { + Write-OK "Installer already in temp folder -- skipping download" +} else { + Write-Host " Downloading from $url ..." + Invoke-WithRetry { + Invoke-WebRequest -Uri $url -OutFile $installer -UseBasicParsing + } + Write-OK "Download complete ($('{0:N1}' -f (([math]::Round((Get-Item $installer).Length / 1048576, 1)))) MB)" +} + +# -- 7. Install Docker Desktop ------------------------------------------------- + +Write-Step "Installing Docker Desktop (backend: $Backend)" + +# Build installer argument list +$installArgs = @( + "install", + "--quiet", + "--accept-license", + "--no-windows-containers" # default off; we enable selectively below +) + +switch ($Backend) { + "wsl2" { $installArgs += "--backend=wsl-2" } + "hyper-v" { $installArgs += "--backend=hyper-v"; $installArgs = $installArgs | Where-Object { $_ -ne "--no-windows-containers" } } + "windows" { $installArgs += "--backend=windows"; $installArgs = $installArgs | Where-Object { $_ -ne "--no-windows-containers" } } +} + +Write-Host " Running: $installer $($installArgs -join ' ')" + +try { + $proc = Start-Process -FilePath $installer -ArgumentList $installArgs ` + -Wait -PassThru -NoNewWindow + if ($proc.ExitCode -notin @(0, 3010)) { + # 3010 = success, reboot required + throw "Installer exited with code $($proc.ExitCode)" + } + if ($proc.ExitCode -eq 3010) { $script:RebootRequired = $true } + Write-OK "Docker Desktop installer finished (exit code $($proc.ExitCode))" +} finally { + # Always restore edition spoof, even on installer failure + if ($spoofApplied) { Restore-EditionSpoof } +} + +# -- 8. Post-install configuration --------------------------------------------- + +Write-Step "Post-install configuration" + +# Force Docker Desktop to start without the update nag / welcome screen +$ddConfigDir = "$Env:APPDATA\Docker" +$ddConfigFile = "$ddConfigDir\settings.json" +New-Item -ItemType Directory -Force $ddConfigDir | Out-Null + +if (-not (Test-Path $ddConfigFile)) { + $backendValue = if ($Backend -eq "windows") { "windows" } else { "wsl2" } + @" +{ + "licenseTermsVersion": 2, + "analyticsEnabled": false, + "checkForUpdates": false, + "autoStart": false, + "exposeDockerAPIOnTCP2375": false, + "wslEngineEnabled": $( if ($Backend -eq "wsl2") { "true" } else { "false" } ), + "displayedTutorial": true, + "backend": "$backendValue" +} +"@ | Set-Content $ddConfigFile -Encoding UTF8 + Write-OK "Created Docker Desktop settings.json" +} else { + Write-OK "settings.json already exists -- not overwriting" +} + +# Add Docker CLI to PATH if not already there (Desktop installs to a version-stamped dir) +$dockerCli = "$Env:ProgramFiles\Docker\Docker\resources\bin" +$sysPath = [Environment]::GetEnvironmentVariable("Path","Machine") +if ($sysPath -notlike "*$dockerCli*") { + [Environment]::SetEnvironmentVariable("Path","$sysPath;$dockerCli","Machine") + $Env:Path += ";$dockerCli" + Write-OK "Added Docker CLI to system PATH" +} + +# -- 9. Cleanup ---------------------------------------------------------------- + +if (-not $KeepDownloads) { + Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue + Write-OK "Temporary files removed" +} + +# -- 10. Summary --------------------------------------------------------------- + +Write-Host "`n================================================" -ForegroundColor Green +Write-Host " Docker Desktop installation complete!" -ForegroundColor Green +Write-Host "================================================" -ForegroundColor Green + +if ($script:RebootRequired) { + Write-Host @" + + *** A REBOOT IS REQUIRED *** + Windows features were enabled or WSL2 was installed. + After rebooting, launch Docker Desktop from the Start menu + and wait for the engine to start (~30 s), then run: + + docker run hello-world + +"@ -ForegroundColor Yellow +} else { + Write-Host @" + + Launch Docker Desktop from the Start menu, wait for the + engine icon to turn green, then run: + + docker run hello-world + +"@ -ForegroundColor White +} + +Write-Host " Techniques applied:" -ForegroundColor White +Write-Host " A -- EditionID spoof : $(if ($SkipEditionSpoof) {'skipped'} else {'applied + restored'})" -ForegroundColor Gray +Write-Host " B -- WSL2 : $(if ($SkipWSL2 -or $Backend -ne 'wsl2') {'skipped'} else {'applied'})" -ForegroundColor Gray +Write-Host " C -- Hyper-V inject : $(if ($SkipHyperV) {'skipped'} else {'applied'})" -ForegroundColor Gray +Write-Host " Backend selected : $Backend" -ForegroundColor Gray +Write-Host ""