Upload files to "/"

This commit is contained in:
2026-06-10 15:18:23 +02:00
commit 3267c992aa
+434
View File
@@ -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 <enclosure> 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 ""