From 954834d79d0645235c4193d7d7894a51be1521b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Thu, 27 Nov 2025 18:23:08 +0100 Subject: [PATCH 01/79] chore: build Windows 2022 images from a Windows Server Core 2025 agent --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index bd28e5c5..a01c29ec 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,9 +17,9 @@ def agentSelector(String imageType) { return 'docker-highmem' } } - // Windows Server Core 2022 agent - if (imageType.contains('2022')) { - return 'windows-2022' + // Windows Server Core 2025 can build both 2022 and 2025 images + if (imageType.contains('2022') || imageType.contains('2025')) { + return 'windows-2025' } // Windows Server Core 2019 agent (for nanoserver 1809 & ltsc2019 and for windowservercore ltsc2019) return 'windows-2019' From 435bb652009a4f5f4dacf89a110e5dac88060fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Thu, 27 Nov 2025 18:23:50 +0100 Subject: [PATCH 02/79] feat: add Windows 2025 images --- Jenkinsfile | 4 +++- docker-bake.hcl | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a01c29ec..7bcb38aa 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -47,9 +47,11 @@ def parallelStages = [failFast: false] 'nanoserver-1809', 'nanoserver-ltsc2019', 'nanoserver-ltsc2022', + 'nanoserver-ltsc2025', 'windowsservercore-1809', 'windowsservercore-ltsc2019', - 'windowsservercore-ltsc2022' + 'windowsservercore-ltsc2022', + 'windowsservercore-ltsc2025' ].each { imageType -> parallelStages[imageType] = { withEnv([ diff --git a/docker-bake.hcl b/docker-bake.hcl index 3acfd3da..94bff0e9 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -128,7 +128,7 @@ function "windowsversions" { ? [WINDOWS_VERSION_OVERRIDE] : (equal(flavor, "windowsservercore") ? ["ltsc2019", "ltsc2022"] - : ["1809", "ltsc2019", "ltsc2022"])) + : ["1809", "ltsc2019", "ltsc2022", "ltsc2025"])) } # Return the Windows version to use as base image for the Windows version passed as parameter From 11b2e9e7eff1a0217d1b8545c34e3c8fecbe6bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 11:55:58 +0100 Subject: [PATCH 03/79] build only nanoserver & windows-server-core 2022 & 2025 images for now --- Jenkinsfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7bcb38aa..192ffe27 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -43,13 +43,13 @@ def spotAgentSelector(String agentLabel, int counter) { // Specify parallel stages def parallelStages = [failFast: false] [ - 'linux', - 'nanoserver-1809', - 'nanoserver-ltsc2019', + // 'linux', + // 'nanoserver-1809', + // 'nanoserver-ltsc2019', 'nanoserver-ltsc2022', 'nanoserver-ltsc2025', - 'windowsservercore-1809', - 'windowsservercore-ltsc2019', + // 'windowsservercore-1809', + // 'windowsservercore-ltsc2019', 'windowsservercore-ltsc2022', 'windowsservercore-ltsc2025' ].each { imageType -> From daf83367d109783be04926ccceee400b63622f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 11:56:32 +0100 Subject: [PATCH 04/79] add `docker info` to `init-docker` Makefile target --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index c92ea616..47680aff 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ else docker buildx create --use --bootstrap --driver docker-container endif docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + docker info build: check-reqs @set -x; $(bake_cli) $(shell make --silent list) --set '*.platform=linux/$(ARCH)' From dcf111865eabb98a80d40a02674ce6b16ffd5edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 11:56:58 +0100 Subject: [PATCH 05/79] add `docker-init` target to `build.ps1` script --- build.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build.ps1 b/build.ps1 index 6da2568b..b7c8b8b6 100644 --- a/build.ps1 +++ b/build.ps1 @@ -116,6 +116,14 @@ function Test-Image { return $failed } +function Initialize-Docker() { + Get-ComputerInfo | Select-Object OsName, OsBuildNumber, WindowsVersion + Get-WindowsFeature Containers + Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -NoRestart + Get-WindowsFeature Containers + Invoke-Expression docker info +} + function Initialize-DockerComposeFile { $baseDockerBakeCmd = 'docker buildx bake --progress=plain --file=docker-bake.hcl' @@ -157,6 +165,10 @@ Test-CommandExists 'docker-compose' Test-CommandExists 'docker buildx' Test-CommandExists 'yq' +if($target -eq 'docker-init') { + Initialize-Docker +} + # Generate the docker compose file if it doesn't exists or if the parameter OverwriteDockerComposeFile is set if ((Test-Path $dockerComposeFile) -and -not $OverwriteDockerComposeFile) { Write-Host "= PREPARE: The docker compose file '$dockerComposeFile' containing the image definitions already exists." From f65648077b354325521def6e3bf86b6742cebf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 11:57:26 +0100 Subject: [PATCH 06/79] call `docker-init` for both Linux & Windows --- Jenkinsfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 192ffe27..81a15985 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -66,9 +66,11 @@ def parallelStages = [failFast: false] node(resolvedAgentLabel) { timeout(time: 60, unit: 'MINUTES') { checkout scm - if (imageType == "linux") { - stage('Prepare Docker') { + stage('Prepare Docker') { + if (isUnix()) { sh 'make docker-init' + } else { + powershell './build.ps1 docker-init' } } // This function is defined in the jenkins-infra/pipeline-library From 7cba9ebcf15b95b3fa84dcf9ca2db831d42ed03b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 11:58:00 +0100 Subject: [PATCH 07/79] correct test description --- tests/sshAgent.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sshAgent.Tests.ps1 b/tests/sshAgent.Tests.ps1 index 695bc3e8..b7e8f130 100644 --- a/tests/sshAgent.Tests.ps1 +++ b/tests/sshAgent.Tests.ps1 @@ -62,7 +62,7 @@ $global:GITLFSVERSION = '3.7.1' Cleanup($global:CONTAINERNAME) -Describe "[$global:IMAGE_NAME] image is present" { +Describe "[$global:IMAGE_NAME] image can be built" { It 'builds image' { $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg `"TOOLS_WINDOWS_VERSION=${global:TOOLSWINDOWSVERSION}`" --build-arg `"JAVA_VERSION=${global:JAVA_VERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --tag=${global:IMAGE_TAG} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ." $exitCode | Should -Be 0 From f8ea469fe5021a8e06ecfe66be1739b98dbd7090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 11:59:08 +0100 Subject: [PATCH 08/79] show `IMAGE_TAG` instead of `IMAGE_NAME` in Windows test descriptions --- tests/sshAgent.Tests.ps1 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/sshAgent.Tests.ps1 b/tests/sshAgent.Tests.ps1 index b7e8f130..1ac2fe3c 100644 --- a/tests/sshAgent.Tests.ps1 +++ b/tests/sshAgent.Tests.ps1 @@ -62,14 +62,14 @@ $global:GITLFSVERSION = '3.7.1' Cleanup($global:CONTAINERNAME) -Describe "[$global:IMAGE_NAME] image can be built" { +Describe "[$global:IMAGE_TAG] image can be built" { It 'builds image' { $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg `"TOOLS_WINDOWS_VERSION=${global:TOOLSWINDOWSVERSION}`" --build-arg `"JAVA_VERSION=${global:JAVA_VERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --tag=${global:IMAGE_TAG} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ." $exitCode | Should -Be 0 } } -Describe "[$global:IMAGE_NAME] image has setup-sshd.ps1 in the correct location" { +Describe "[$global:IMAGE_TAG] image has setup-sshd.ps1 in the correct location" { BeforeAll { $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name=`"$global:CONTAINERNAME`" --publish-all `"$global:IMAGE_NAME`" `"$global:CONTAINERSHELL`"" $exitCode | Should -Be 0 @@ -86,7 +86,7 @@ Describe "[$global:IMAGE_NAME] image has setup-sshd.ps1 in the correct location" } } -Describe "[$global:IMAGE_NAME] image has no pre-existing SSH host keys" { +Describe "[$global:IMAGE_TAG] image has no pre-existing SSH host keys" { BeforeAll { $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name=`"$global:CONTAINERNAME`" --publish-all `"$global:IMAGE_NAME`" `"$global:CONTAINERSHELL`"" $exitCode | Should -Be 0 @@ -103,7 +103,7 @@ Describe "[$global:IMAGE_NAME] image has no pre-existing SSH host keys" { } } -Describe "[$global:IMAGE_NAME] checking image metadata" { +Describe "[$global:IMAGE_TAG] checking image metadata" { It 'has correct volumes' { $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format '{{.Config.Volumes}}' $global:IMAGE_NAME" $exitCode | Should -Be 0 @@ -119,7 +119,7 @@ Describe "[$global:IMAGE_NAME] checking image metadata" { } } -Describe "[$global:IMAGE_NAME] image has correct version of java and git-lfs installed and in the PATH" { +Describe "[$global:IMAGE_TAG] image has correct version of java and git-lfs installed and in the PATH" { BeforeAll { $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name=`"$global:CONTAINERNAME`" --publish-all `"$global:IMAGE_NAME`" `"$global:PUBLIC_SSH_KEY`"" $exitCode | Should -Be 0 @@ -151,7 +151,7 @@ Describe "[$global:IMAGE_NAME] image has correct version of java and git-lfs ins } } -Describe "[$global:IMAGE_NAME] create agent container with pubkey as argument" { +Describe "[$global:IMAGE_TAG] create agent container with pubkey as argument" { BeforeAll { $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name=`"$global:CONTAINERNAME`" --publish-all `"$global:IMAGE_NAME`" `"$global:PUBLIC_SSH_KEY`"" $exitCode | Should -Be 0 @@ -169,7 +169,7 @@ Describe "[$global:IMAGE_NAME] create agent container with pubkey as argument" { } } -Describe "[$global:IMAGE_NAME] create agent container with pubkey as envvar" { +Describe "[$global:IMAGE_TAG] create agent container with pubkey as envvar" { BeforeAll { $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name=`"$global:CONTAINERNAME`" --publish-all `"$global:IMAGE_NAME`" `"$global:PUBLIC_SSH_KEY`"" $exitCode | Should -Be 0 @@ -189,7 +189,7 @@ Describe "[$global:IMAGE_NAME] create agent container with pubkey as envvar" { $global:DOCKER_PLUGIN_DEFAULT_ARG="/usr/sbin/sshd -D -p 22" -Describe "[$global:IMAGE_NAME] create agent container like docker-plugin with '$global:DOCKER_PLUGIN_DEFAULT_ARG' as argument" { +Describe "[$global:IMAGE_TAG] create agent container like docker-plugin with '$global:DOCKER_PLUGIN_DEFAULT_ARG' as argument" { BeforeAll { [string]::IsNullOrWhiteSpace($global:DOCKER_PLUGIN_DEFAULT_ARG) | Should -BeFalse $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name=`"$global:CONTAINERNAME`" --publish-all --env=`"JENKINS_AGENT_SSH_PUBKEY=$global:PUBLIC_SSH_KEY`" `"$global:IMAGE_NAME`" `"$global:DOCKER_PLUGIN_DEFAULT_ARG`"" @@ -208,7 +208,7 @@ Describe "[$global:IMAGE_NAME] create agent container like docker-plugin with '$ } } -Describe "[$global:IMAGE_NAME] build args" { +Describe "[$global:IMAGE_TAG] build args" { BeforeAll { Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." } From 30f1c86a53a823c7a8d8597e12925550a5463f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 12:00:04 +0100 Subject: [PATCH 09/79] move slow Windows image build test after the quicker ones --- tests/sshAgent.Tests.ps1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/sshAgent.Tests.ps1 b/tests/sshAgent.Tests.ps1 index 1ac2fe3c..74cfeb71 100644 --- a/tests/sshAgent.Tests.ps1 +++ b/tests/sshAgent.Tests.ps1 @@ -62,13 +62,6 @@ $global:GITLFSVERSION = '3.7.1' Cleanup($global:CONTAINERNAME) -Describe "[$global:IMAGE_TAG] image can be built" { - It 'builds image' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg `"TOOLS_WINDOWS_VERSION=${global:TOOLSWINDOWSVERSION}`" --build-arg `"JAVA_VERSION=${global:JAVA_VERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --tag=${global:IMAGE_TAG} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ." - $exitCode | Should -Be 0 - } -} - Describe "[$global:IMAGE_TAG] image has setup-sshd.ps1 in the correct location" { BeforeAll { $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name=`"$global:CONTAINERNAME`" --publish-all `"$global:IMAGE_NAME`" `"$global:CONTAINERSHELL`"" @@ -208,6 +201,13 @@ Describe "[$global:IMAGE_TAG] create agent container like docker-plugin with '$g } } +Describe "[$global:IMAGE_TAG] image can be built" { + It 'builds image' { + $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg `"TOOLS_WINDOWS_VERSION=${global:TOOLSWINDOWSVERSION}`" --build-arg `"JAVA_VERSION=${global:JAVA_VERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --tag=${global:IMAGE_TAG} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ." + $exitCode | Should -Be 0 + } +} + Describe "[$global:IMAGE_TAG] build args" { BeforeAll { Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." From 2dae8e797d8874ff560500ef50b72c2b6cb56764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 12:00:43 +0100 Subject: [PATCH 10/79] better Windows image build with custom buid args test description --- tests/sshAgent.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sshAgent.Tests.ps1 b/tests/sshAgent.Tests.ps1 index 74cfeb71..61cda852 100644 --- a/tests/sshAgent.Tests.ps1 +++ b/tests/sshAgent.Tests.ps1 @@ -208,7 +208,7 @@ Describe "[$global:IMAGE_TAG] image can be built" { } } -Describe "[$global:IMAGE_TAG] build args" { +Describe "[$global:IMAGE_TAG] image can be built with custom build args" { BeforeAll { Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." } From eeaa260dd64813e6b9c53297e25b303869f89667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 12:10:00 +0100 Subject: [PATCH 11/79] quote invoked expression in `Initialize-Docker` --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index b7c8b8b6..80a82cb0 100644 --- a/build.ps1 +++ b/build.ps1 @@ -121,7 +121,7 @@ function Initialize-Docker() { Get-WindowsFeature Containers Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -NoRestart Get-WindowsFeature Containers - Invoke-Expression docker info + Invoke-Expression 'docker info' } function Initialize-DockerComposeFile { From f3d5c498b576199fe4306f34856648c9d61688a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Sun, 30 Nov 2025 12:40:06 +0100 Subject: [PATCH 12/79] debug: add Debug-ContainerHost.ps1 from https://github.com/MicrosoftDocs/Virtualization-Documentation --- Debug-ContainerHost.ps1 | 310 ++++++++++++++++++++++++++++++++++++++++ build.ps1 | 1 + 2 files changed, 311 insertions(+) create mode 100644 Debug-ContainerHost.ps1 diff --git a/Debug-ContainerHost.ps1 b/Debug-ContainerHost.ps1 new file mode 100644 index 00000000..de64acab --- /dev/null +++ b/Debug-ContainerHost.ps1 @@ -0,0 +1,310 @@ +# Source: https://github.com/MicrosoftDocs/Virtualization-Documentation/blob/82a6a47b44d2f9eb1936acbd01d133b9531ab8af/windows-server-container-tools/Debug-ContainerHost/Debug-ContainerHost.ps1 +Write-Output "Checking for common problems" + +$filesToDump = @{} +$currentVersion = Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' +$OSProductName = $currentVersion.GetValue('ProductName') +$OSBuildLabel = $currentVersion.GetValue('BuildLabEx') +Write-Output "Container Host OS Product Name: $OSProductName" +Write-Output "Container Host OS Build Label: $OSBuildLabel" + +Describe "Windows Version and Prerequisites" { + $buildNumber = (Get-CimInstance -Namespace root\cimv2 Win32_OperatingSystem).BuildNumber + It "Is Windows 10 Anniversary Update or Windows Server 2016" { + $buildNumber -ge 14393 | Should Be $true + } + It "Has KB3192366, KB3194496, or later installed if running Windows build 14393" { + if ($buildNumber -eq 14393) + { + (Get-ItemProperty -Path 'HKLM:\software\Microsoft\Windows NT\CurrentVersion' -Name UBR).UBR | Should Not BeLessThan 351 + } + } + It "Is not a build with blocking issues" { + $buildNumber | Should Not Be 14931 + $buildNumber | Should Not Be 14936 + } + + It "Has 'Containers' feature installed" { + if (((Get-ComputerInfo).WindowsInstallationType) -eq "Client") { + (Get-WindowsOptionalFeature -Online -FeatureName Containers).State | Should Be "Enabled" + } + else { + (Get-WindowsFeature -Name Containers).InstallState | Should Be "Installed" + } + } + + # TODO Check on SKU support - Home, Pro, Education, ... +} + +Describe "Docker is installed" { + + $services = Get-Service | Where-Object {($_.Name -eq "Docker") -or ($_.Name -eq "com.Docker.Service")} + It "A Docker service is installed - 'Docker' or 'com.Docker.Service' " { + $services| Should Not BeNullOrEmpty + } + It "Service is running" { + $AtLeastOneRunning = $false; + foreach ($service in $services) + { + #if there is more than 1 only one can be running + if ($service.Status -eq "Running") + { + $AtLeastOneRunning = $true + } + } + $AtLeastOneRunning | Should Be $true + } + It "Docker.exe is in path" { + # This also captures 'docker info' and 'docker version' output to be shown later + { + Start-Process -NoNewWindow ` + -Wait ` + -FilePath docker.exe ` + -ArgumentList "info" ` + -RedirectStandardError err.txt ` + -RedirectStandardOutput dockerinfo.txt + $filesToDump["docker info"] = "dockerinfo.txt" + Start-Process -NoNewWindow ` + -Wait ` + -FilePath docker.exe ` + -ArgumentList "version" ` + -RedirectStandardError err.txt ` + -RedirectStandardOutput dockerversion.txt + $filesToDump["docker version"] = "dockerversion.txt" + } | Should Not Throw + } + It "Docker is registered in the EventLog service" { + (Test-Path "HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\docker") -or (Test-Path "HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\DockerService") | Should Be $true + } +} + +Describe "User has permissions to use Docker daemon" { + It "docker.exe should not return access denied" { + "err.txt" | Should Not Contain "access is denied" + } +} + +Describe "Windows container settings are correct" { + It "Do not have DisableVSmbOplock set to 1" { + $regvalue = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers" -Name VSmbDisableOplocks -ErrorAction Ignore + if ($regvalue) { + $regvalue.VSmbDisableOplocks | Should Be 0 + } + } + It "Do not have zz values set" { + $regvalues = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers" + ($regvalues | Get-Member zz* | Measure-Object).Count | Should Be 0 + } + It "Do not have FDVDenyWriteAccess set to 1" { + $regvalue = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Policies\Microsoft\FVE" -Name FDVDenyWriteAccess -ErrorAction Ignore + if ($regvalue) { + $regvalue.FDVDenyWriteAccess | Should Be 0 + } + } + } + +Describe "The right container base images are installed" { + $imageListOutput = docker.exe images + $images = $imageListOutput | Foreach-Object { + if (($_ -match "^REPOSITORY") -eq $false) { + $trimmed = [regex]::Replace($_, "\s{2,}"," ") + $split = $trimmed.Split(" ") + New-Object -Typename PSObject -Property @{Repository=$split[0]; + Tag=$split[1]; + ImageId=$split[2]} + } + } + + It "At least one of 'mcr.microsoft.com/windows/servercore', 'mcr.microsoft.com/windows/nanoserver', 'mcr.microsoft.com/windows' or deprecated microsoft/windowsservercore, microsoft/nanoserver should be installed" { + $baseImages = $images | Where-Object { ($_.Repository -eq "mcr.microsoft.com/windows/servercore") ` + -or ($_.Repository -eq "mcr.microsoft.com/windows/nanoserver") ` + -or ($_.Repository -eq "mcr.microsoft.com/windows")} + if (($baseImages | Measure-Object).Count -eq 0) + { + Write-Warning "No mcr.microsoft.com/* base images found. Checking for deprecated images." + $baseImages = $images | Where-Object { ($_.Repository -eq "microsoft/windowsservercore") ` + -or ($_.Repository -eq "microsoft/nanoserver")} + (Measure-Object $baseImages).Count | Should Not BeNullOrEmpty + } + } +} + +Describe "Container network is created" { + Start-Process -NoNewWindow ` + -Wait ` + -FilePath docker.exe ` + -ArgumentList "network ls" ` + -RedirectStandardError err.txt ` + -RedirectStandardOutput dockernetworkls.txt + $filesToDump["docker network ls"] = "dockernetworkls.txt" + + $networksListOutput = Get-Content .\dockernetworkls.txt + + $networks = $networksListOutput | Foreach-object { + if (($_ -match "^NETWORK") -eq $false) { + $trimmed = [regex]::Replace($_, "\s{2,}", " ") + $split = $trimmed.Split(" ") + New-Object -Typename PSObject -Property @{Driver=$split[2]; + NetName=$split[1]; + Scope=$split[3]} + } + } + + # Get all NAT networks + $natNetworks = $networks | Where-Object { ($_.Driver -eq "nat")} + + # Get all Transparent networks + $transparentNetworks = $networks | Where-Object { ($_.Driver -eq "transparent")} + + # Get all l2bridge networks + $l2bridgeNetworks = $networks | Where-Object { ($_.Driver -eq "l2bridge")} + + $hostips = @() + if ($natNetworks -ne $null) + { + # Get VMSwitch for NAT network + if ($natNetworks[0].NetName -eq "nat") + { + $natVMSwitchName = "nat" + } + else + { + $natVMSwitchName = docker.exe network inspect --format="{{.Id}}" $natNetworks[0].NetName + } + + $natGatewayIP = docker.exe network inspect --format="{{range .IPAM.Config }}{{.Gateway}}{{end}}" $natNetworks[0].NetName + + #$switchType = (Get-VMSwitch -SwitchName $natNetworks[0].NetName).SwitchType + $switchType = (Get-VMSwitch -SwitchName $natVMSwitchName).SwitchType + + # TODO - Add checks for the case where there are no (default) nat networks, everything will need to be user-defined + $natInternalPrefix = docker.exe network inspect --format="{{range .IPAM.Config }}{{.Subnet}}{{end}}" $natNetworks[0].NetName + if ($natInternalPrefix.Contains("/")) + { + $Temp = $natInternalPrefix.Split("/") + $Prefix = $Temp[0] + $Length = $Temp[1] + } + $IPSubnet = [Net.IPAddress]::Parse($Prefix) + $BinaryIPSubnet = [String]::Join('', $( $IPSubnet.GetAddressBytes() | %{ + [Convert]::ToString($_, 2).PadLeft(8, '0') } )) + + # Get all Host IP Addresses from Container Host + $hostips = Get-NetIPAddress -AddressFamily IPv4 | where { $_.InterfaceAlias -notmatch "Loopback" -And $_.InterfaceAlias -notmatch "HNS" -And $_.InterfaceAlias -notmatch "NAT" } | Select IPAddress + } + + It "At least one local container network is available" { + $localNetworks = $networks | Where-Object { ($_.Scope -eq "local")} + ($localNetworks | Measure-Object).Count | Should Not BeNullOrEmpty + } + + # Either need NAT, L2bridge, or Transparent for external network access. + It "At least one NAT, Transparent, or L2Bridge Network exists" { + $totalnets = 0 + if ($natNetworks -ne $null) + { + $totalnets += ($natNetworks | Measure-Object).Count + } + + if ($transparentNetworks -ne $null) + { + $totalnets += ($transparentNetworks | Measure-Object).Count + } + + if ($l2bridgeNetworks -ne $null) + { + $totalnets += ($l2bridgeNetworks | Measure-Object).Count + } + + $totalnets | Should BeGreaterThan 0 + } + + # TODO: Need a way to skip these next two tests if no NAT networks exist on the system + It "NAT Network's vSwitch is internal" { + $switchType | Should Be "Internal" + } + + It "A Windows NAT is configured if a Docker NAT network exists" { + $winnatCount = (Get-NetNat | Measure-Object).Count + $natCount = 0 + if ($natNetworks -ne $null) + { + $natCount += ($natNetworks | Measure-Object).Count + } + $winnatCount | Should Not BeLessThan $natCount + } + + It "Specified Network Gateway IP for NAT network is assigned to Host vNIC" { + $natGatewayIP | Should Not BeNullOrEmpty + + $vmnicIps = Get-NetIPAddress -AddressFamily IPv4 | where { $_.InterfaceAlias -notmatch "Loopback" -And $_.InterfaceAlias -match "vEthernet" } | Select IPAddress + + $vmNicGatewayIPExists = $false + $vmnicIps | Foreach-object { + if ($_ -match $natGatewayIP) { + $vmNicGatewayIPExists = $true + } + } + $vmNicGatewayIPExists | Should Be $true + + } + + It "NAT Network's internal prefix does not overlap with external IP'" { + if ( ($hostips | measure-object).Count -gt 0) + { + $hostips | Foreach-object { + $testip = [Net.IPAddress]::Parse( ($_.IPAddress) ) + $BinaryIP = [String]::Join('', $( $testip.GetAddressBytes() | %{ + [Convert]::ToString($_, 2).PadLeft(8, '0') } )) + + $BinaryIP.Substring(0, $Length) | Should Not Be $BinaryIPSubnet.Substring(0, $Length) + } + } + else + { + $hostips.Count | Should BeGreaterThan 0 + } + } + + # TODO: Add a test to validate the Host vNIC exists with NAT Network's Default Gateawy IP assigned. +} + +# Dump & Cleanup temporary files used during Pester tests +$filesToDump.Keys | ForEach-Object { + if (Test-Path $filesToDump[$_]) { + Write-Output "Showing output from: $($_)" + Get-Content $filesToDump[$_] | Write-Output + Remove-Item $filesToDump[$_] + Write-Output "" + } +} + +if (Test-Path err.txt) { Remove-Item err.txt} + +Write-Output "Getting Warnings & errors in the Windows event logs from the last 24 hours" +$logStartTime = (Get-Date).AddHours(-24) + +$logNames = "Microsoft-Windows-Containers-Wcifs/Operational", + "Microsoft-Windows-Containers-Wcnfs/Operational", + "Microsoft-Windows-Hyper-V-Compute-Admin", + "Microsoft-Windows-Hyper-V-Compute-Operational", + "Application" +$levels = 3,2,1,0 +$providers = "Docker", "Microsoft-Windows-Hyper-V-Compute" + +$events = Get-WinEvent -FilterHashtable @{Logname=$logNames; StartTime=$logStartTime; Level=$levels; ProviderName=$providers} -ErrorAction Ignore + +$eventCsv = "logs_$((get-date).ToString("yyyyMMdd'-'HHmmss")).csv" +$events | Format-Table +$events | Export-CSV $eventCsv +Write-Host "Logs saved to $($PWD)\$($eventCsv)`n`n" + +Write-Output "Getting Docker for Windows daemon logs from the last execution" +Write-Output " Note: More logs may be available at $($ENV:LOCALAPPDATA)\Docker. Only showing the latest 100 lines." +if (Test-Path "$($ENV:LOCALAPPDATA)\Docker\log.txt") +{ + Get-Content -Tail 100 "$($ENV:LOCALAPPDATA)\Docker\log.txt" | Select-String "WindowsDockerDaemon" +} +else { + Write-Output " $($ENV:LOCALAPPDATA)\Docker\log.txt does not exist." +} diff --git a/build.ps1 b/build.ps1 index 80a82cb0..80d52041 100644 --- a/build.ps1 +++ b/build.ps1 @@ -121,6 +121,7 @@ function Initialize-Docker() { Get-WindowsFeature Containers Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -NoRestart Get-WindowsFeature Containers + & $PSScriptRoot/Debug-ContainerHost.ps1 Invoke-Expression 'docker info' } From 3eb61655ee4b73eca1dc622ce48c4675d505d45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Mon, 1 Dec 2025 23:35:01 +0100 Subject: [PATCH 13/79] debug: remove docker daemon config of the docker-root pointing to Z:\docker --- build.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/build.ps1 b/build.ps1 index 80d52041..95ac34a4 100644 --- a/build.ps1 +++ b/build.ps1 @@ -117,11 +117,17 @@ function Test-Image { } function Initialize-Docker() { + Get-ChildItem env: | Select-Object Name, Value + # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) + # Cf https://github.com/jenkins-infra/jenkins-infra/blob/production/modules/profile/templates/jenkinscontroller/casc/clouds-ec2.yaml.erb + $dockerDaemonConfig = 'C:\ProgramData\Docker\config\daemon.json' + if ($path | Test-Path) { + Write-Host "${dockerDaemon} docker daemon config file content before deletion:" + Get-Content -Path $dockerDaemonConfig + Remove-Item -Path $dockerDaemonConfig + } Get-ComputerInfo | Select-Object OsName, OsBuildNumber, WindowsVersion - Get-WindowsFeature Containers - Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -NoRestart - Get-WindowsFeature Containers - & $PSScriptRoot/Debug-ContainerHost.ps1 + Get-WindowsFeature Containers | Out-String Invoke-Expression 'docker info' } From 52049bd55b98b3e07cd55d4684c5d698c81acadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Mon, 1 Dec 2025 23:36:00 +0100 Subject: [PATCH 14/79] comment out `disableConcurrentBuilds(abortPrevious: true)` for now --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 81a15985..d1c9c939 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ final String cronExpr = env.BRANCH_IS_PRIMARY ? '@daily' : '' properties([ buildDiscarder(logRotator(numToKeepStr: '10')), - disableConcurrentBuilds(abortPrevious: true), + // disableConcurrentBuilds(abortPrevious: true), pipelineTriggers([cron(cronExpr)]), ]) From 809e4a438dd610a3cef1ce31d8c0a491b9717461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Mon, 1 Dec 2025 23:36:19 +0100 Subject: [PATCH 15/79] move comment --- build.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index 95ac34a4..6c285f21 100644 --- a/build.ps1 +++ b/build.ps1 @@ -118,12 +118,12 @@ function Test-Image { function Initialize-Docker() { Get-ChildItem env: | Select-Object Name, Value - # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) # Cf https://github.com/jenkins-infra/jenkins-infra/blob/production/modules/profile/templates/jenkinscontroller/casc/clouds-ec2.yaml.erb $dockerDaemonConfig = 'C:\ProgramData\Docker\config\daemon.json' if ($path | Test-Path) { - Write-Host "${dockerDaemon} docker daemon config file content before deletion:" + Write-Host "${dockerDaemon} docker daemon config file content:" Get-Content -Path $dockerDaemonConfig + # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 Remove-Item -Path $dockerDaemonConfig } Get-ComputerInfo | Select-Object OsName, OsBuildNumber, WindowsVersion From 7aefe0264a9320575dd4d87780085498241d0805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Mon, 1 Dec 2025 23:40:16 +0100 Subject: [PATCH 16/79] set new SystemTemp instead of deleting docker daemon config --- build.ps1 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index 6c285f21..7c9087c7 100644 --- a/build.ps1 +++ b/build.ps1 @@ -123,8 +123,13 @@ function Initialize-Docker() { if ($path | Test-Path) { Write-Host "${dockerDaemon} docker daemon config file content:" Get-Content -Path $dockerDaemonConfig - # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 - Remove-Item -Path $dockerDaemonConfig + # # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 + # Remove-Item -Path $dockerDaemonConfig + + cd 'C:\Windows' + ren SystemTemp SystemTemp.old + mklink /D SystemTemp $dockerDaemonConfig + cd - } Get-ComputerInfo | Select-Object OsName, OsBuildNumber, WindowsVersion Get-WindowsFeature Containers | Out-String From 781303db2de61b4836591dda5ee277c6aacb5795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Mon, 1 Dec 2025 23:43:37 +0100 Subject: [PATCH 17/79] space --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index 7c9087c7..42eb03aa 100644 --- a/build.ps1 +++ b/build.ps1 @@ -97,7 +97,7 @@ function Test-Image { $env:JAVA_VERSION = "$javaVersion" $targetPath = '.\target\{0}' -f $imageTag - if(Test-Path $targetPath) { + if (Test-Path $targetPath) { Remove-Item -Recurse -Force $targetPath } New-Item -Path $targetPath -Type Directory | Out-Null From 4a0a26c4de0d6b64f68c450619e3aed98d2e10c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Mon, 1 Dec 2025 23:44:13 +0100 Subject: [PATCH 18/79] fixup path --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index 42eb03aa..a8a0c276 100644 --- a/build.ps1 +++ b/build.ps1 @@ -120,7 +120,7 @@ function Initialize-Docker() { Get-ChildItem env: | Select-Object Name, Value # Cf https://github.com/jenkins-infra/jenkins-infra/blob/production/modules/profile/templates/jenkinscontroller/casc/clouds-ec2.yaml.erb $dockerDaemonConfig = 'C:\ProgramData\Docker\config\daemon.json' - if ($path | Test-Path) { + if (Test-Path $dockerDaemonConfig) { Write-Host "${dockerDaemon} docker daemon config file content:" Get-Content -Path $dockerDaemonConfig # # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 From 503b79c16e3979ed0936067976567394460378c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Mon, 1 Dec 2025 23:44:24 +0100 Subject: [PATCH 19/79] rename --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index a8a0c276..af9115e0 100644 --- a/build.ps1 +++ b/build.ps1 @@ -127,7 +127,7 @@ function Initialize-Docker() { # Remove-Item -Path $dockerDaemonConfig cd 'C:\Windows' - ren SystemTemp SystemTemp.old + Rename-Item SystemTemp SystemTemp.old mklink /D SystemTemp $dockerDaemonConfig cd - } From e5c9048c838bcda8491c9584c5034207f2c50ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Mon, 1 Dec 2025 23:56:24 +0100 Subject: [PATCH 20/79] fixup mklink --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index af9115e0..6d85ec1c 100644 --- a/build.ps1 +++ b/build.ps1 @@ -128,7 +128,7 @@ function Initialize-Docker() { cd 'C:\Windows' Rename-Item SystemTemp SystemTemp.old - mklink /D SystemTemp $dockerDaemonConfig + cmd.exe /c "mklink /D SystemTemp 'Z:\docker'" cd - } Get-ComputerInfo | Select-Object OsName, OsBuildNumber, WindowsVersion From e39fa6d2fffeb3e5803b96c0eea79e41d29dfb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 00:03:32 +0100 Subject: [PATCH 21/79] Push/Pop-Location --- build.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index 6d85ec1c..1a64a9d9 100644 --- a/build.ps1 +++ b/build.ps1 @@ -126,10 +126,10 @@ function Initialize-Docker() { # # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 # Remove-Item -Path $dockerDaemonConfig - cd 'C:\Windows' + Push-Location -Path 'C:\Windows' Rename-Item SystemTemp SystemTemp.old cmd.exe /c "mklink /D SystemTemp 'Z:\docker'" - cd - + Pop-Location } Get-ComputerInfo | Select-Object OsName, OsBuildNumber, WindowsVersion Get-WindowsFeature Containers | Out-String From cc7c07e55ae464f6d6a3ce4923c98cce684ec573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 00:10:06 +0100 Subject: [PATCH 22/79] data-root value from docker daemon config as new SystemTemp folder --- build.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/build.ps1 b/build.ps1 index 1a64a9d9..d81d824b 100644 --- a/build.ps1 +++ b/build.ps1 @@ -119,16 +119,15 @@ function Test-Image { function Initialize-Docker() { Get-ChildItem env: | Select-Object Name, Value # Cf https://github.com/jenkins-infra/jenkins-infra/blob/production/modules/profile/templates/jenkinscontroller/casc/clouds-ec2.yaml.erb - $dockerDaemonConfig = 'C:\ProgramData\Docker\config\daemon.json' - if (Test-Path $dockerDaemonConfig) { - Write-Host "${dockerDaemon} docker daemon config file content:" - Get-Content -Path $dockerDaemonConfig + $dockerDaemonConfigPath = 'C:\ProgramData\Docker\config\daemon.json' + if (Test-Path $dockerDaemonConfigPath) { + $dockerDaemonConfig = Get-Content -Path $dockerDaemonConfigPath -Raw | ConvertFrom-Json # # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 # Remove-Item -Path $dockerDaemonConfig Push-Location -Path 'C:\Windows' Rename-Item SystemTemp SystemTemp.old - cmd.exe /c "mklink /D SystemTemp 'Z:\docker'" + cmd.exe /c 'mklink /D SystemTemp {0}' -f $dockerDaemonConfig.PSObject.Properties['data-root'].Value Pop-Location } Get-ComputerInfo | Select-Object OsName, OsBuildNumber, WindowsVersion From 2b0820fa756295e0b624d048757177e365628a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 00:13:29 +0100 Subject: [PATCH 23/79] delete docker daemon config for now --- build.ps1 | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/build.ps1 b/build.ps1 index d81d824b..fd64281f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -122,13 +122,15 @@ function Initialize-Docker() { $dockerDaemonConfigPath = 'C:\ProgramData\Docker\config\daemon.json' if (Test-Path $dockerDaemonConfigPath) { $dockerDaemonConfig = Get-Content -Path $dockerDaemonConfigPath -Raw | ConvertFrom-Json - # # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 - # Remove-Item -Path $dockerDaemonConfig - - Push-Location -Path 'C:\Windows' - Rename-Item SystemTemp SystemTemp.old - cmd.exe /c 'mklink /D SystemTemp {0}' -f $dockerDaemonConfig.PSObject.Properties['data-root'].Value - Pop-Location + Write-Host "${dockerDaemonConfigPath} file content:" + $dockerDaemonConfig | ConvertTo-Json + # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 + Remove-Item -Path $dockerDaemonConfig + + # Push-Location -Path 'C:\Windows' + # Rename-Item SystemTemp SystemTemp.old + # cmd.exe /c 'mklink /D SystemTemp {0}' -f $dockerDaemonConfig.PSObject.Properties['data-root'].Value + # Pop-Location } Get-ComputerInfo | Select-Object OsName, OsBuildNumber, WindowsVersion Get-WindowsFeature Containers | Out-String From 815eff6b8fe9642b41081a877697c872f1085250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 00:18:43 +0100 Subject: [PATCH 24/79] fixup remove path not content --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index fd64281f..f6618b33 100644 --- a/build.ps1 +++ b/build.ps1 @@ -125,7 +125,7 @@ function Initialize-Docker() { Write-Host "${dockerDaemonConfigPath} file content:" $dockerDaemonConfig | ConvertTo-Json # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 - Remove-Item -Path $dockerDaemonConfig + Remove-Item -Path $dockerDaemonConfigPath # Push-Location -Path 'C:\Windows' # Rename-Item SystemTemp SystemTemp.old From 6646c61bf635bceb8108a6aff814b7f832ae7149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 00:29:29 +0100 Subject: [PATCH 25/79] restart docker service after docker daemon config deletion --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index f6618b33..ba77b240 100644 --- a/build.ps1 +++ b/build.ps1 @@ -126,7 +126,7 @@ function Initialize-Docker() { $dockerDaemonConfig | ConvertTo-Json # Remove docker daemon config setting "data-root" to Z:\docker (NVMe mount) to avoid hitting moby/moby#48093 Remove-Item -Path $dockerDaemonConfigPath - + Restart-Service docker # Push-Location -Path 'C:\Windows' # Rename-Item SystemTemp SystemTemp.old # cmd.exe /c 'mklink /D SystemTemp {0}' -f $dockerDaemonConfig.PSObject.Properties['data-root'].Value From 2b9540709132ac769adfe9778c6165481054ecac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 01:02:09 +0100 Subject: [PATCH 26/79] retrieve powershell from wsc base image instead of powershell:nanoserver (no 2025 image, there won't be) --- windows/nanoserver/Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index 13614d0a..607b21a8 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -38,8 +38,6 @@ RUN New-Item -ItemType Directory -Path C:\temp | Out-Null ; ` $proc.WaitForExit() ; ` Remove-Item -Path C:\temp -Recurse | Out-Null -FROM mcr.microsoft.com/powershell:nanoserver-"${TOOLS_WINDOWS_VERSION}" AS pwsh-source - FROM mcr.microsoft.com/windows/nanoserver:"${WINDOWS_VERSION_TAG}" ARG JAVA_HOME @@ -50,7 +48,7 @@ ENV PATH="C:\Windows\system32;C:\Windows;${PSHOME};" COPY --from=jdk-core /windows/system32/netapi32.dll /windows/system32/netapi32.dll COPY --from=jdk-core /windows/system32/whoami.exe /windows/system32/whoami.exe COPY --from=jdk-core $JAVA_HOME $JAVA_HOME -COPY --from=pwsh-source $PSHOME $PSHOME +COPY --from=jdk-core $PSHOME $PSHOME SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] USER ContainerAdministrator From 889a4171ea2077324037d233dec0ed7e4023ef7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 01:19:01 +0100 Subject: [PATCH 27/79] allow passing multiple versions with `WINDOWS_VERSION_OVERRIDE` --- docker-bake.hcl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-bake.hcl b/docker-bake.hcl index 94bff0e9..c0e05c6c 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -80,6 +80,7 @@ variable "DEBIAN_RELEASE" { } # Set this value to a specific Windows version to override Windows versions to build returned by windowsversions function +# Accept multiple coma-separated versions, ex: ltsc2022,ltsc2025 variable "WINDOWS_VERSION_OVERRIDE" { default = "" } @@ -120,12 +121,12 @@ function "debian_platforms" { # Return array of Windows version(s) to build # There is no mcr.microsoft.com/windows/servercore:1809 image -# Can be overriden by setting WINDOWS_VERSION_OVERRIDE to a specific Windows version +# Can be overriden by setting WINDOWS_VERSION_OVERRIDE to one or many specific Windows version # Ex: WINDOWS_VERSION_OVERRIDE=1809 docker buildx bake windows function "windowsversions" { params = [flavor] result = (notequal(WINDOWS_VERSION_OVERRIDE, "") - ? [WINDOWS_VERSION_OVERRIDE] + ? split(",", WINDOWS_VERSION_OVERRIDE) : (equal(flavor, "windowsservercore") ? ["ltsc2019", "ltsc2022"] : ["1809", "ltsc2019", "ltsc2022", "ltsc2025"])) From c30b84d4e33017d750a6ebecfaecb8dc2b9cd0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 01:41:26 +0100 Subject: [PATCH 28/79] remove Debug-ContainerHost.ps1 --- Debug-ContainerHost.ps1 | 310 ---------------------------------------- 1 file changed, 310 deletions(-) delete mode 100644 Debug-ContainerHost.ps1 diff --git a/Debug-ContainerHost.ps1 b/Debug-ContainerHost.ps1 deleted file mode 100644 index de64acab..00000000 --- a/Debug-ContainerHost.ps1 +++ /dev/null @@ -1,310 +0,0 @@ -# Source: https://github.com/MicrosoftDocs/Virtualization-Documentation/blob/82a6a47b44d2f9eb1936acbd01d133b9531ab8af/windows-server-container-tools/Debug-ContainerHost/Debug-ContainerHost.ps1 -Write-Output "Checking for common problems" - -$filesToDump = @{} -$currentVersion = Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -$OSProductName = $currentVersion.GetValue('ProductName') -$OSBuildLabel = $currentVersion.GetValue('BuildLabEx') -Write-Output "Container Host OS Product Name: $OSProductName" -Write-Output "Container Host OS Build Label: $OSBuildLabel" - -Describe "Windows Version and Prerequisites" { - $buildNumber = (Get-CimInstance -Namespace root\cimv2 Win32_OperatingSystem).BuildNumber - It "Is Windows 10 Anniversary Update or Windows Server 2016" { - $buildNumber -ge 14393 | Should Be $true - } - It "Has KB3192366, KB3194496, or later installed if running Windows build 14393" { - if ($buildNumber -eq 14393) - { - (Get-ItemProperty -Path 'HKLM:\software\Microsoft\Windows NT\CurrentVersion' -Name UBR).UBR | Should Not BeLessThan 351 - } - } - It "Is not a build with blocking issues" { - $buildNumber | Should Not Be 14931 - $buildNumber | Should Not Be 14936 - } - - It "Has 'Containers' feature installed" { - if (((Get-ComputerInfo).WindowsInstallationType) -eq "Client") { - (Get-WindowsOptionalFeature -Online -FeatureName Containers).State | Should Be "Enabled" - } - else { - (Get-WindowsFeature -Name Containers).InstallState | Should Be "Installed" - } - } - - # TODO Check on SKU support - Home, Pro, Education, ... -} - -Describe "Docker is installed" { - - $services = Get-Service | Where-Object {($_.Name -eq "Docker") -or ($_.Name -eq "com.Docker.Service")} - It "A Docker service is installed - 'Docker' or 'com.Docker.Service' " { - $services| Should Not BeNullOrEmpty - } - It "Service is running" { - $AtLeastOneRunning = $false; - foreach ($service in $services) - { - #if there is more than 1 only one can be running - if ($service.Status -eq "Running") - { - $AtLeastOneRunning = $true - } - } - $AtLeastOneRunning | Should Be $true - } - It "Docker.exe is in path" { - # This also captures 'docker info' and 'docker version' output to be shown later - { - Start-Process -NoNewWindow ` - -Wait ` - -FilePath docker.exe ` - -ArgumentList "info" ` - -RedirectStandardError err.txt ` - -RedirectStandardOutput dockerinfo.txt - $filesToDump["docker info"] = "dockerinfo.txt" - Start-Process -NoNewWindow ` - -Wait ` - -FilePath docker.exe ` - -ArgumentList "version" ` - -RedirectStandardError err.txt ` - -RedirectStandardOutput dockerversion.txt - $filesToDump["docker version"] = "dockerversion.txt" - } | Should Not Throw - } - It "Docker is registered in the EventLog service" { - (Test-Path "HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\docker") -or (Test-Path "HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\DockerService") | Should Be $true - } -} - -Describe "User has permissions to use Docker daemon" { - It "docker.exe should not return access denied" { - "err.txt" | Should Not Contain "access is denied" - } -} - -Describe "Windows container settings are correct" { - It "Do not have DisableVSmbOplock set to 1" { - $regvalue = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers" -Name VSmbDisableOplocks -ErrorAction Ignore - if ($regvalue) { - $regvalue.VSmbDisableOplocks | Should Be 0 - } - } - It "Do not have zz values set" { - $regvalues = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers" - ($regvalues | Get-Member zz* | Measure-Object).Count | Should Be 0 - } - It "Do not have FDVDenyWriteAccess set to 1" { - $regvalue = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Policies\Microsoft\FVE" -Name FDVDenyWriteAccess -ErrorAction Ignore - if ($regvalue) { - $regvalue.FDVDenyWriteAccess | Should Be 0 - } - } - } - -Describe "The right container base images are installed" { - $imageListOutput = docker.exe images - $images = $imageListOutput | Foreach-Object { - if (($_ -match "^REPOSITORY") -eq $false) { - $trimmed = [regex]::Replace($_, "\s{2,}"," ") - $split = $trimmed.Split(" ") - New-Object -Typename PSObject -Property @{Repository=$split[0]; - Tag=$split[1]; - ImageId=$split[2]} - } - } - - It "At least one of 'mcr.microsoft.com/windows/servercore', 'mcr.microsoft.com/windows/nanoserver', 'mcr.microsoft.com/windows' or deprecated microsoft/windowsservercore, microsoft/nanoserver should be installed" { - $baseImages = $images | Where-Object { ($_.Repository -eq "mcr.microsoft.com/windows/servercore") ` - -or ($_.Repository -eq "mcr.microsoft.com/windows/nanoserver") ` - -or ($_.Repository -eq "mcr.microsoft.com/windows")} - if (($baseImages | Measure-Object).Count -eq 0) - { - Write-Warning "No mcr.microsoft.com/* base images found. Checking for deprecated images." - $baseImages = $images | Where-Object { ($_.Repository -eq "microsoft/windowsservercore") ` - -or ($_.Repository -eq "microsoft/nanoserver")} - (Measure-Object $baseImages).Count | Should Not BeNullOrEmpty - } - } -} - -Describe "Container network is created" { - Start-Process -NoNewWindow ` - -Wait ` - -FilePath docker.exe ` - -ArgumentList "network ls" ` - -RedirectStandardError err.txt ` - -RedirectStandardOutput dockernetworkls.txt - $filesToDump["docker network ls"] = "dockernetworkls.txt" - - $networksListOutput = Get-Content .\dockernetworkls.txt - - $networks = $networksListOutput | Foreach-object { - if (($_ -match "^NETWORK") -eq $false) { - $trimmed = [regex]::Replace($_, "\s{2,}", " ") - $split = $trimmed.Split(" ") - New-Object -Typename PSObject -Property @{Driver=$split[2]; - NetName=$split[1]; - Scope=$split[3]} - } - } - - # Get all NAT networks - $natNetworks = $networks | Where-Object { ($_.Driver -eq "nat")} - - # Get all Transparent networks - $transparentNetworks = $networks | Where-Object { ($_.Driver -eq "transparent")} - - # Get all l2bridge networks - $l2bridgeNetworks = $networks | Where-Object { ($_.Driver -eq "l2bridge")} - - $hostips = @() - if ($natNetworks -ne $null) - { - # Get VMSwitch for NAT network - if ($natNetworks[0].NetName -eq "nat") - { - $natVMSwitchName = "nat" - } - else - { - $natVMSwitchName = docker.exe network inspect --format="{{.Id}}" $natNetworks[0].NetName - } - - $natGatewayIP = docker.exe network inspect --format="{{range .IPAM.Config }}{{.Gateway}}{{end}}" $natNetworks[0].NetName - - #$switchType = (Get-VMSwitch -SwitchName $natNetworks[0].NetName).SwitchType - $switchType = (Get-VMSwitch -SwitchName $natVMSwitchName).SwitchType - - # TODO - Add checks for the case where there are no (default) nat networks, everything will need to be user-defined - $natInternalPrefix = docker.exe network inspect --format="{{range .IPAM.Config }}{{.Subnet}}{{end}}" $natNetworks[0].NetName - if ($natInternalPrefix.Contains("/")) - { - $Temp = $natInternalPrefix.Split("/") - $Prefix = $Temp[0] - $Length = $Temp[1] - } - $IPSubnet = [Net.IPAddress]::Parse($Prefix) - $BinaryIPSubnet = [String]::Join('', $( $IPSubnet.GetAddressBytes() | %{ - [Convert]::ToString($_, 2).PadLeft(8, '0') } )) - - # Get all Host IP Addresses from Container Host - $hostips = Get-NetIPAddress -AddressFamily IPv4 | where { $_.InterfaceAlias -notmatch "Loopback" -And $_.InterfaceAlias -notmatch "HNS" -And $_.InterfaceAlias -notmatch "NAT" } | Select IPAddress - } - - It "At least one local container network is available" { - $localNetworks = $networks | Where-Object { ($_.Scope -eq "local")} - ($localNetworks | Measure-Object).Count | Should Not BeNullOrEmpty - } - - # Either need NAT, L2bridge, or Transparent for external network access. - It "At least one NAT, Transparent, or L2Bridge Network exists" { - $totalnets = 0 - if ($natNetworks -ne $null) - { - $totalnets += ($natNetworks | Measure-Object).Count - } - - if ($transparentNetworks -ne $null) - { - $totalnets += ($transparentNetworks | Measure-Object).Count - } - - if ($l2bridgeNetworks -ne $null) - { - $totalnets += ($l2bridgeNetworks | Measure-Object).Count - } - - $totalnets | Should BeGreaterThan 0 - } - - # TODO: Need a way to skip these next two tests if no NAT networks exist on the system - It "NAT Network's vSwitch is internal" { - $switchType | Should Be "Internal" - } - - It "A Windows NAT is configured if a Docker NAT network exists" { - $winnatCount = (Get-NetNat | Measure-Object).Count - $natCount = 0 - if ($natNetworks -ne $null) - { - $natCount += ($natNetworks | Measure-Object).Count - } - $winnatCount | Should Not BeLessThan $natCount - } - - It "Specified Network Gateway IP for NAT network is assigned to Host vNIC" { - $natGatewayIP | Should Not BeNullOrEmpty - - $vmnicIps = Get-NetIPAddress -AddressFamily IPv4 | where { $_.InterfaceAlias -notmatch "Loopback" -And $_.InterfaceAlias -match "vEthernet" } | Select IPAddress - - $vmNicGatewayIPExists = $false - $vmnicIps | Foreach-object { - if ($_ -match $natGatewayIP) { - $vmNicGatewayIPExists = $true - } - } - $vmNicGatewayIPExists | Should Be $true - - } - - It "NAT Network's internal prefix does not overlap with external IP'" { - if ( ($hostips | measure-object).Count -gt 0) - { - $hostips | Foreach-object { - $testip = [Net.IPAddress]::Parse( ($_.IPAddress) ) - $BinaryIP = [String]::Join('', $( $testip.GetAddressBytes() | %{ - [Convert]::ToString($_, 2).PadLeft(8, '0') } )) - - $BinaryIP.Substring(0, $Length) | Should Not Be $BinaryIPSubnet.Substring(0, $Length) - } - } - else - { - $hostips.Count | Should BeGreaterThan 0 - } - } - - # TODO: Add a test to validate the Host vNIC exists with NAT Network's Default Gateawy IP assigned. -} - -# Dump & Cleanup temporary files used during Pester tests -$filesToDump.Keys | ForEach-Object { - if (Test-Path $filesToDump[$_]) { - Write-Output "Showing output from: $($_)" - Get-Content $filesToDump[$_] | Write-Output - Remove-Item $filesToDump[$_] - Write-Output "" - } -} - -if (Test-Path err.txt) { Remove-Item err.txt} - -Write-Output "Getting Warnings & errors in the Windows event logs from the last 24 hours" -$logStartTime = (Get-Date).AddHours(-24) - -$logNames = "Microsoft-Windows-Containers-Wcifs/Operational", - "Microsoft-Windows-Containers-Wcnfs/Operational", - "Microsoft-Windows-Hyper-V-Compute-Admin", - "Microsoft-Windows-Hyper-V-Compute-Operational", - "Application" -$levels = 3,2,1,0 -$providers = "Docker", "Microsoft-Windows-Hyper-V-Compute" - -$events = Get-WinEvent -FilterHashtable @{Logname=$logNames; StartTime=$logStartTime; Level=$levels; ProviderName=$providers} -ErrorAction Ignore - -$eventCsv = "logs_$((get-date).ToString("yyyyMMdd'-'HHmmss")).csv" -$events | Format-Table -$events | Export-CSV $eventCsv -Write-Host "Logs saved to $($PWD)\$($eventCsv)`n`n" - -Write-Output "Getting Docker for Windows daemon logs from the last execution" -Write-Output " Note: More logs may be available at $($ENV:LOCALAPPDATA)\Docker. Only showing the latest 100 lines." -if (Test-Path "$($ENV:LOCALAPPDATA)\Docker\log.txt") -{ - Get-Content -Tail 100 "$($ENV:LOCALAPPDATA)\Docker\log.txt" | Select-String "WindowsDockerDaemon" -} -else { - Write-Output " $($ENV:LOCALAPPDATA)\Docker\log.txt does not exist." -} From 5ad8847338931c4482ff503a0f4d67659c0c445f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 01:49:36 +0100 Subject: [PATCH 29/79] move Get-ChildItem env: to its own powershell call so it doesn't overflow on other command outputs --- Jenkinsfile | 1 + build.ps1 | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index d1c9c939..a35317e7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -70,6 +70,7 @@ def parallelStages = [failFast: false] if (isUnix()) { sh 'make docker-init' } else { + powershell 'Get-ChildItem env: | Select-Object Name, Value' powershell './build.ps1 docker-init' } } diff --git a/build.ps1 b/build.ps1 index ba77b240..fcce57f6 100644 --- a/build.ps1 +++ b/build.ps1 @@ -117,7 +117,6 @@ function Test-Image { } function Initialize-Docker() { - Get-ChildItem env: | Select-Object Name, Value # Cf https://github.com/jenkins-infra/jenkins-infra/blob/production/modules/profile/templates/jenkinscontroller/casc/clouds-ec2.yaml.erb $dockerDaemonConfigPath = 'C:\ProgramData\Docker\config\daemon.json' if (Test-Path $dockerDaemonConfigPath) { From 2d5356d00cc11f495c990ec6c4cc23a44b465181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 02:59:12 +0100 Subject: [PATCH 30/79] install powershell 7 in nanoserver --- windows/nanoserver/Dockerfile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index 01f437c3..f9b7568c 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -48,7 +48,15 @@ ENV PATH="C:\Windows\system32;C:\Windows;${PSHOME};" COPY --from=jdk-core /windows/system32/netapi32.dll /windows/system32/netapi32.dll COPY --from=jdk-core /windows/system32/whoami.exe /windows/system32/whoami.exe COPY --from=jdk-core $JAVA_HOME $JAVA_HOME -COPY --from=jdk-core $PSHOME $PSHOME + +# Download and install PowerShell 7 +RUN curl.exe -L https://github.com/PowerShell/PowerShell/releases/download/v7.5.4/PowerShell-7.5.4-win-x64.zip -o pwsh.zip && \ + mkdir C:\PowerShell7 && \ + tar.exe -xf pwsh.zip -C C:\PowerShell7 && \ + del pwsh.zip + +# Add PowerShell to PATH +RUN setx /M PATH "%PATH%;C:\PowerShell7" SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] USER ContainerAdministrator From 9006052f93d202b21e8e1b6cb4ed98db98b6ad18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 03:10:56 +0100 Subject: [PATCH 31/79] fixup --- windows/nanoserver/Dockerfile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index f9b7568c..7d9b96fc 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -40,9 +40,9 @@ RUN New-Item -ItemType Directory -Path C:\temp | Out-Null ; ` FROM mcr.microsoft.com/windows/nanoserver:"${WINDOWS_VERSION_TAG}" +SHELL ["cmd.exe"] + ARG JAVA_HOME -ENV PSHOME="C:\Program Files\PowerShell" -ENV PATH="C:\Windows\system32;C:\Windows;${PSHOME};" # The nanoserver image is nice and small, but we need a couple of things to get SSH working COPY --from=jdk-core /windows/system32/netapi32.dll /windows/system32/netapi32.dll @@ -50,14 +50,14 @@ COPY --from=jdk-core /windows/system32/whoami.exe /windows/system32/whoami.exe COPY --from=jdk-core $JAVA_HOME $JAVA_HOME # Download and install PowerShell 7 -RUN curl.exe -L https://github.com/PowerShell/PowerShell/releases/download/v7.5.4/PowerShell-7.5.4-win-x64.zip -o pwsh.zip && \ - mkdir C:\PowerShell7 && \ - tar.exe -xf pwsh.zip -C C:\PowerShell7 && \ +ARG POWERSHELL_VERSION=7.5.4 +ENV PSHOME="C:\PowerShell7" +ENV PATH="C:\Windows\system32;C:\Windows;%PSHOME%;" +RUN curl.exe --location "https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_VERSION%/PowerShell-%POWERSHELL_VERSION%-win-x64.zip" --output pwsh.zip && \ + mkdir %PSHOME% && \ + tar.exe -xf pwsh.zip -C %PSHOME% && \ del pwsh.zip -# Add PowerShell to PATH -RUN setx /M PATH "%PATH%;C:\PowerShell7" - SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] USER ContainerAdministrator From b5e68a846c44b1f6350eee81d5e22973c90064e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 03:25:12 +0100 Subject: [PATCH 32/79] use CMD continuation syntax --- windows/nanoserver/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index 7d9b96fc..2113ef6f 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -53,9 +53,9 @@ COPY --from=jdk-core $JAVA_HOME $JAVA_HOME ARG POWERSHELL_VERSION=7.5.4 ENV PSHOME="C:\PowerShell7" ENV PATH="C:\Windows\system32;C:\Windows;%PSHOME%;" -RUN curl.exe --location "https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_VERSION%/PowerShell-%POWERSHELL_VERSION%-win-x64.zip" --output pwsh.zip && \ - mkdir %PSHOME% && \ - tar.exe -xf pwsh.zip -C %PSHOME% && \ +RUN curl.exe --location "https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_VERSION%/PowerShell-%POWERSHELL_VERSION%-win-x64.zip" --output pwsh.zip ^ + mkdir %PSHOME% ^ + tar.exe -xf pwsh.zip -C %PSHOME% ^ del pwsh.zip SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] From cd5e23edb377b1ef1c1f8ad5fceff9614128d8ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 03:35:04 +0100 Subject: [PATCH 33/79] prevent docker bake progress stderr (output by default) --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index fcce57f6..6e89cca6 100644 --- a/build.ps1 +++ b/build.ps1 @@ -166,7 +166,7 @@ function Initialize-DockerComposeFile { Write-Host "= PREPARE: Docker compose file generation command`n$generateDockerComposeFileCmd" - Invoke-Expression $generateDockerComposeFileCmd + Invoke-Expression $generateDockerComposeFileCmd | Out-Null # Remove override Remove-Item env:\WINDOWS_VERSION_OVERRIDE From 64f52b38872e8df4f0a69bb5aaa0e8fa0641a658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 03:37:52 +0100 Subject: [PATCH 34/79] cmd.exe /c mkdir --- windows/nanoserver/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index 2113ef6f..dd7ba898 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -54,7 +54,7 @@ ARG POWERSHELL_VERSION=7.5.4 ENV PSHOME="C:\PowerShell7" ENV PATH="C:\Windows\system32;C:\Windows;%PSHOME%;" RUN curl.exe --location "https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_VERSION%/PowerShell-%POWERSHELL_VERSION%-win-x64.zip" --output pwsh.zip ^ - mkdir %PSHOME% ^ + cmd.exe /c mkdir mkdir %PSHOME% ^ tar.exe -xf pwsh.zip -C %PSHOME% ^ del pwsh.zip From 8c67bdfa940941f14569ef0748bd495c56382387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 03:47:40 +0100 Subject: [PATCH 35/79] fixup ^ && --- windows/nanoserver/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index dd7ba898..8b56df30 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -54,9 +54,9 @@ ARG POWERSHELL_VERSION=7.5.4 ENV PSHOME="C:\PowerShell7" ENV PATH="C:\Windows\system32;C:\Windows;%PSHOME%;" RUN curl.exe --location "https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_VERSION%/PowerShell-%POWERSHELL_VERSION%-win-x64.zip" --output pwsh.zip ^ - cmd.exe /c mkdir mkdir %PSHOME% ^ - tar.exe -xf pwsh.zip -C %PSHOME% ^ - del pwsh.zip + && mkdir %PSHOME% ^ + && tar.exe -xf pwsh.zip -C %PSHOME% ^ + && del pwsh.zip SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] USER ContainerAdministrator From 789bf5808f7aa8185fd3a8b2b2f6fc7360dc8f3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 03:49:50 +0100 Subject: [PATCH 36/79] fixup last PATH --- windows/nanoserver/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index 8b56df30..4937a78b 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -80,7 +80,7 @@ RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tl # Add java & git to PATH ENV ProgramFiles="C:\Program Files" ENV WindowsPATH="C:\Windows\system32;C:\Windows" -ENV PATH="${WindowsPATH};${ProgramFiles}\PowerShell;${JAVA_HOME}\bin;C:\mingit\cmd" +ENV PATH="${PATH};${WindowsPATH};${JAVA_HOME}\bin;C:\mingit\cmd" # Install git-lfs ARG GIT_LFS_VERSION=3.7.1 From d23361ffd98cf5285691a4d74f5c87f83fa1b307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 03:59:17 +0100 Subject: [PATCH 37/79] cmd multiline --- windows/nanoserver/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index 4937a78b..7f6c90cd 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -53,10 +53,10 @@ COPY --from=jdk-core $JAVA_HOME $JAVA_HOME ARG POWERSHELL_VERSION=7.5.4 ENV PSHOME="C:\PowerShell7" ENV PATH="C:\Windows\system32;C:\Windows;%PSHOME%;" -RUN curl.exe --location "https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_VERSION%/PowerShell-%POWERSHELL_VERSION%-win-x64.zip" --output pwsh.zip ^ - && mkdir %PSHOME% ^ - && tar.exe -xf pwsh.zip -C %PSHOME% ^ - && del pwsh.zip +RUN curl.exe --location "https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_VERSION%/PowerShell-%POWERSHELL_VERSION%-win-x64.zip" --output pwsh.zip & ^ + mkdir %PSHOME% & ^ + tar.exe -xf pwsh.zip -C %PSHOME% & ^ + del pwsh.zip SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] USER ContainerAdministrator From 24552a6538324cfe441a2d3b38b940c326a84501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Le=20Meur?= Date: Tue, 2 Dec 2025 04:11:05 +0100 Subject: [PATCH 38/79] heredoc --- windows/nanoserver/Dockerfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index 7f6c90cd..2ef0e35b 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -53,10 +53,12 @@ COPY --from=jdk-core $JAVA_HOME $JAVA_HOME ARG POWERSHELL_VERSION=7.5.4 ENV PSHOME="C:\PowerShell7" ENV PATH="C:\Windows\system32;C:\Windows;%PSHOME%;" -RUN curl.exe --location "https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_VERSION%/PowerShell-%POWERSHELL_VERSION%-win-x64.zip" --output pwsh.zip & ^ - mkdir %PSHOME% & ^ - tar.exe -xf pwsh.zip -C %PSHOME% & ^ - del pwsh.zip +RUN <