Upload files to "/"
This commit is contained in:
@@ -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 ""
|
||||
Reference in New Issue
Block a user