Repository: actions/runner-images Branch: main Commit: 563ad669b646 Files: 504 Total size: 1.5 MB Directory structure: gitextract_ol0xcjy2/ ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── announcement.yml │ │ ├── bug-report.yml │ │ ├── config.yml │ │ └── tool-request.yml │ ├── copilot-instructions.md │ ├── pull_request_template.md │ └── workflows/ │ ├── check-pinned-versions.yml │ ├── codeql-analysis.yml │ ├── create_github_release.yml │ ├── create_pull_request.yml │ ├── create_sbom_report.yml │ ├── docker-images.yml │ ├── linter.yml │ ├── merge_pull_request.yml │ ├── powershell-tests.yml │ ├── trigger-ubuntu-win-build.yml │ ├── ubuntu2204.yml │ ├── ubuntu2404.yml │ ├── update_github_release.yml │ ├── validate-json-schema.yml │ ├── windows2022.yml │ ├── windows2025-vs2026.yml │ └── windows2025.yml ├── .gitignore ├── .vscode/ │ ├── extensions.json │ ├── settings.json │ └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── docs/ │ ├── create-image-and-azure-resources.md │ └── dotnet-ubuntu.md ├── helpers/ │ ├── CheckJsonSchema.ps1 │ ├── CheckOutdatedVersionPinning.ps1 │ ├── CreateAzureVMFromPackerTemplate.ps1 │ ├── GenerateResourcesAndImage.ps1 │ ├── GitHubApi.psm1 │ ├── WaitWorkflowCompletion.ps1 │ └── software-report-base/ │ ├── Calculate-ImagesDifference.ps1 │ ├── SoftwareReport.BaseNodes.psm1 │ ├── SoftwareReport.DifferenceCalculator.psm1 │ ├── SoftwareReport.DifferenceRender.psm1 │ ├── SoftwareReport.Nodes.psm1 │ ├── SoftwareReport.psm1 │ └── tests/ │ ├── SoftwareReport.Difference.E2E.Tests.ps1 │ ├── SoftwareReport.DifferenceCalculator.Unit.Tests.ps1 │ ├── SoftwareReport.DifferenceRender.Unit.Tests.ps1 │ ├── SoftwareReport.E2E.Tests.ps1 │ ├── SoftwareReport.Nodes.Unit.Tests.ps1 │ └── TestHelpers.psm1 ├── images/ │ ├── macos/ │ │ ├── assets/ │ │ │ ├── add-certificate.swift │ │ │ ├── auto-software-update-arm64.exp │ │ │ ├── bashprofile │ │ │ ├── bashrc │ │ │ └── bootstrap-provisioner/ │ │ │ ├── change_password │ │ │ ├── installNewProvisioner.sh │ │ │ ├── kcpassword.py │ │ │ └── setAutoLogin.sh │ │ ├── macos-14-Readme.md │ │ ├── macos-14-arm64-Readme.md │ │ ├── macos-15-Readme.md │ │ ├── macos-15-arm64-Readme.md │ │ ├── macos-26-Readme.md │ │ ├── macos-26-arm64-Readme.md │ │ ├── scripts/ │ │ │ ├── build/ │ │ │ │ ├── Configure-Toolset.ps1 │ │ │ │ ├── Configure-Xcode-Simulators.ps1 │ │ │ │ ├── Install-Toolset.ps1 │ │ │ │ ├── Install-Xcode.ps1 │ │ │ │ ├── Update-XcodeSimulators.ps1 │ │ │ │ ├── configure-auto-updates.sh │ │ │ │ ├── configure-autologin.sh │ │ │ │ ├── configure-hostname.sh │ │ │ │ ├── configure-machine.sh │ │ │ │ ├── configure-ntpconf.sh │ │ │ │ ├── configure-preimagedata.sh │ │ │ │ ├── configure-shell.sh │ │ │ │ ├── configure-ssh.sh │ │ │ │ ├── configure-system.sh │ │ │ │ ├── configure-tccdb-macos.sh │ │ │ │ ├── configure-windows.sh │ │ │ │ ├── configure-xcode.sh │ │ │ │ ├── install-actions-cache.sh │ │ │ │ ├── install-android-sdk.sh │ │ │ │ ├── install-audiodevice.sh │ │ │ │ ├── install-aws-tools.sh │ │ │ │ ├── install-azcopy.sh │ │ │ │ ├── install-bicep.sh │ │ │ │ ├── install-chrome.sh │ │ │ │ ├── install-cocoapods.sh │ │ │ │ ├── install-codeql-bundle.sh │ │ │ │ ├── install-common-utils.sh │ │ │ │ ├── install-dotnet.sh │ │ │ │ ├── install-edge.sh │ │ │ │ ├── install-firefox.sh │ │ │ │ ├── install-gcc.sh │ │ │ │ ├── install-git.sh │ │ │ │ ├── install-homebrew.sh │ │ │ │ ├── install-llvm.sh │ │ │ │ ├── install-mono.sh │ │ │ │ ├── install-nginx.sh │ │ │ │ ├── install-node.sh │ │ │ │ ├── install-openjdk.sh │ │ │ │ ├── install-openssl.sh │ │ │ │ ├── install-php.sh │ │ │ │ ├── install-postgresql.sh │ │ │ │ ├── install-powershell.sh │ │ │ │ ├── install-python.sh │ │ │ │ ├── install-rosetta.sh │ │ │ │ ├── install-ruby.sh │ │ │ │ ├── install-rubygems.sh │ │ │ │ ├── install-rust.sh │ │ │ │ ├── install-safari.sh │ │ │ │ ├── install-swiftlint.sh │ │ │ │ ├── install-unxip.sh │ │ │ │ ├── install-vcpkg.sh │ │ │ │ └── install-xcode-clt.sh │ │ │ ├── docs-gen/ │ │ │ │ ├── Generate-SoftwareReport.ps1 │ │ │ │ ├── SoftwareReport.Android.psm1 │ │ │ │ ├── SoftwareReport.Browsers.psm1 │ │ │ │ ├── SoftwareReport.Common.psm1 │ │ │ │ ├── SoftwareReport.Helpers.psm1 │ │ │ │ ├── SoftwareReport.Java.psm1 │ │ │ │ ├── SoftwareReport.Toolcache.psm1 │ │ │ │ └── SoftwareReport.Xcode.psm1 │ │ │ ├── helpers/ │ │ │ │ ├── Common.Helpers.psm1 │ │ │ │ ├── Xcode.Helpers.psm1 │ │ │ │ ├── Xcode.Installer.psm1 │ │ │ │ ├── confirm-identified-developers-macos14.scpt │ │ │ │ ├── confirm-identified-developers-macos15.scpt │ │ │ │ ├── invoke-tests.sh │ │ │ │ └── utils.sh │ │ │ └── tests/ │ │ │ ├── ActionArchiveCache.Tests.ps1 │ │ │ ├── Android.Tests.ps1 │ │ │ ├── BasicTools.Tests.ps1 │ │ │ ├── Browsers.Tests.ps1 │ │ │ ├── Common.Tests.ps1 │ │ │ ├── Git.Tests.ps1 │ │ │ ├── Helpers.psm1 │ │ │ ├── Java.Tests.ps1 │ │ │ ├── LLVM.Tests.ps1 │ │ │ ├── Linters.Tests.ps1 │ │ │ ├── Mono.Tests.ps1 │ │ │ ├── Node.Tests.ps1 │ │ │ ├── OpenSSL.Tests.ps1 │ │ │ ├── PHP.Tests.ps1 │ │ │ ├── Powershell.Tests.ps1 │ │ │ ├── Python.Tests.ps1 │ │ │ ├── Rosetta.Tests.ps1 │ │ │ ├── Ruby.Tests.ps1 │ │ │ ├── RubyGem.Tests.ps1 │ │ │ ├── RunAll-Tests.ps1 │ │ │ ├── Rust.Tests.ps1 │ │ │ ├── System.Tests.ps1 │ │ │ ├── Toolcache.Tests.ps1 │ │ │ ├── Toolset.Tests.ps1 │ │ │ └── Xcode.Tests.ps1 │ │ ├── templates/ │ │ │ ├── macOS-14.anka.pkr.hcl │ │ │ ├── macOS-14.arm64.anka.pkr.hcl │ │ │ ├── macOS-15.anka.pkr.hcl │ │ │ ├── macOS-15.arm64.anka.pkr.hcl │ │ │ ├── macOS-26.anka.pkr.hcl │ │ │ └── macOS-26.arm64.anka.pkr.hcl │ │ └── toolsets/ │ │ ├── Readme.md │ │ ├── toolset-14.json │ │ ├── toolset-15.json │ │ └── toolset-26.json │ ├── ubuntu/ │ │ ├── Ubuntu2204-Readme.md │ │ ├── Ubuntu2404-Readme.md │ │ ├── assets/ │ │ │ ├── post-gen/ │ │ │ │ ├── cleanup-logs.sh │ │ │ │ ├── environment-variables.sh │ │ │ │ └── systemd-linger.sh │ │ │ └── ubuntu2204.conf │ │ ├── scripts/ │ │ │ ├── build/ │ │ │ │ ├── Configure-Toolset.ps1 │ │ │ │ ├── Install-PowerShellAzModules.ps1 │ │ │ │ ├── Install-PowerShellModules.ps1 │ │ │ │ ├── Install-Toolset.ps1 │ │ │ │ ├── cleanup.sh │ │ │ │ ├── configure-apt-mock.sh │ │ │ │ ├── configure-apt-sources.sh │ │ │ │ ├── configure-apt.sh │ │ │ │ ├── configure-dpkg.sh │ │ │ │ ├── configure-environment.sh │ │ │ │ ├── configure-image-data.sh │ │ │ │ ├── configure-limits.sh │ │ │ │ ├── configure-snap.sh │ │ │ │ ├── configure-system.sh │ │ │ │ ├── install-actions-cache.sh │ │ │ │ ├── install-aliyun-cli.sh │ │ │ │ ├── install-android-sdk.sh │ │ │ │ ├── install-apache.sh │ │ │ │ ├── install-apt-common.sh │ │ │ │ ├── install-apt-vital.sh │ │ │ │ ├── install-aws-tools.sh │ │ │ │ ├── install-azcopy.sh │ │ │ │ ├── install-azure-cli.sh │ │ │ │ ├── install-azure-devops-cli.sh │ │ │ │ ├── install-bazel.sh │ │ │ │ ├── install-bicep.sh │ │ │ │ ├── install-clang.sh │ │ │ │ ├── install-cmake.sh │ │ │ │ ├── install-codeql-bundle.sh │ │ │ │ ├── install-container-tools.sh │ │ │ │ ├── install-docker.sh │ │ │ │ ├── install-dotnetcore-sdk.sh │ │ │ │ ├── install-firefox.sh │ │ │ │ ├── install-gcc-compilers.sh │ │ │ │ ├── install-gfortran.sh │ │ │ │ ├── install-git-lfs.sh │ │ │ │ ├── install-git.sh │ │ │ │ ├── install-github-cli.sh │ │ │ │ ├── install-google-chrome.sh │ │ │ │ ├── install-google-cloud-cli.sh │ │ │ │ ├── install-haskell.sh │ │ │ │ ├── install-heroku.sh │ │ │ │ ├── install-homebrew.sh │ │ │ │ ├── install-java-tools.sh │ │ │ │ ├── install-julia.sh │ │ │ │ ├── install-kotlin.sh │ │ │ │ ├── install-kubernetes-tools.sh │ │ │ │ ├── install-leiningen.sh │ │ │ │ ├── install-microsoft-edge.sh │ │ │ │ ├── install-miniconda.sh │ │ │ │ ├── install-mono.sh │ │ │ │ ├── install-ms-repos.sh │ │ │ │ ├── install-mssql-tools.sh │ │ │ │ ├── install-mysql.sh │ │ │ │ ├── install-nginx.sh │ │ │ │ ├── install-ninja.sh │ │ │ │ ├── install-nodejs.sh │ │ │ │ ├── install-nvm.sh │ │ │ │ ├── install-oc-cli.sh │ │ │ │ ├── install-oras-cli.sh │ │ │ │ ├── install-packer.sh │ │ │ │ ├── install-php.sh │ │ │ │ ├── install-pipx-packages.sh │ │ │ │ ├── install-postgresql.sh │ │ │ │ ├── install-powershell.sh │ │ │ │ ├── install-pulumi.sh │ │ │ │ ├── install-pypy.sh │ │ │ │ ├── install-python.sh │ │ │ │ ├── install-rlang.sh │ │ │ │ ├── install-ruby.sh │ │ │ │ ├── install-rust.sh │ │ │ │ ├── install-sbt.sh │ │ │ │ ├── install-selenium.sh │ │ │ │ ├── install-sqlpackage.sh │ │ │ │ ├── install-swift.sh │ │ │ │ ├── install-terraform.sh │ │ │ │ ├── install-vcpkg.sh │ │ │ │ ├── install-yq.sh │ │ │ │ ├── install-zstd.sh │ │ │ │ ├── list-dpkg.sh │ │ │ │ └── post-build-validation.sh │ │ │ ├── docs-gen/ │ │ │ │ ├── Generate-SoftwareReport.ps1 │ │ │ │ ├── SoftwareReport.Android.psm1 │ │ │ │ ├── SoftwareReport.Browsers.psm1 │ │ │ │ ├── SoftwareReport.CachedTools.psm1 │ │ │ │ ├── SoftwareReport.Common.psm1 │ │ │ │ ├── SoftwareReport.Databases.psm1 │ │ │ │ ├── SoftwareReport.Helpers.psm1 │ │ │ │ ├── SoftwareReport.Java.psm1 │ │ │ │ ├── SoftwareReport.Rust.psm1 │ │ │ │ ├── SoftwareReport.Tools.psm1 │ │ │ │ └── SoftwareReport.WebServers.psm1 │ │ │ ├── helpers/ │ │ │ │ ├── Common.Helpers.psm1 │ │ │ │ ├── etc-environment.sh │ │ │ │ ├── install.sh │ │ │ │ ├── invoke-tests.sh │ │ │ │ └── os.sh │ │ │ └── tests/ │ │ │ ├── ActionArchiveCache.Tests.ps1 │ │ │ ├── Android.Tests.ps1 │ │ │ ├── Apt.Tests.ps1 │ │ │ ├── Browsers.Tests.ps1 │ │ │ ├── CLI.Tools.Tests.ps1 │ │ │ ├── Common.Tests.ps1 │ │ │ ├── Databases.Tests.ps1 │ │ │ ├── DotnetSDK.Tests.ps1 │ │ │ ├── Haskell.Tests.ps1 │ │ │ ├── Helpers.psm1 │ │ │ ├── Java.Tests.ps1 │ │ │ ├── Node.Tests.ps1 │ │ │ ├── PowerShellModules.Tests.ps1 │ │ │ ├── RunAll-Tests.ps1 │ │ │ ├── System.Tests.ps1 │ │ │ ├── Tools.Tests.ps1 │ │ │ ├── Toolset.Tests.ps1 │ │ │ └── WebServers.Tests.ps1 │ │ ├── templates/ │ │ │ ├── build.ubuntu-22_04.pkr.hcl │ │ │ ├── build.ubuntu-24_04.pkr.hcl │ │ │ ├── locals.ubuntu.pkr.hcl │ │ │ ├── source.ubuntu.pkr.hcl │ │ │ └── variable.ubuntu.pkr.hcl │ │ └── toolsets/ │ │ ├── toolset-2204.json │ │ └── toolset-2404.json │ ├── ubuntu-slim/ │ │ ├── Dockerfile │ │ ├── generate-software-report.sh │ │ ├── scripts/ │ │ │ ├── build/ │ │ │ │ ├── configure-apt-sources.sh │ │ │ │ ├── configure-apt.sh │ │ │ │ ├── configure-dpkg.sh │ │ │ │ ├── configure-environment.sh │ │ │ │ ├── configure-image-data-file.sh │ │ │ │ ├── configure-system.sh │ │ │ │ ├── install-actions-cache.sh │ │ │ │ ├── install-apt-common.sh │ │ │ │ ├── install-apt-vital.sh │ │ │ │ ├── install-aws-tools.sh │ │ │ │ ├── install-azcopy.sh │ │ │ │ ├── install-azure-cli.sh │ │ │ │ ├── install-azure-devops-cli.sh │ │ │ │ ├── install-bicep.sh │ │ │ │ ├── install-docker-cli.sh │ │ │ │ ├── install-git-lfs.sh │ │ │ │ ├── install-git.sh │ │ │ │ ├── install-github-cli.sh │ │ │ │ ├── install-google-cloud-cli.sh │ │ │ │ ├── install-ms-repos.sh │ │ │ │ ├── install-nodejs.sh │ │ │ │ ├── install-nvm.sh │ │ │ │ ├── install-pipx-packages.sh │ │ │ │ ├── install-powershell.sh │ │ │ │ ├── install-python.sh │ │ │ │ ├── install-yq.sh │ │ │ │ └── install-zstd.sh │ │ │ ├── docs-gen/ │ │ │ │ ├── Common.Helpers.psm1 │ │ │ │ ├── Generate-SoftwareReport.ps1 │ │ │ │ ├── SoftwareReport.Common.psm1 │ │ │ │ ├── SoftwareReport.Helpers.psm1 │ │ │ │ └── SoftwareReport.Tools.psm1 │ │ │ ├── entrypoint.sh │ │ │ └── helpers/ │ │ │ ├── cleanup.sh │ │ │ ├── etc-environment.sh │ │ │ ├── install.sh │ │ │ └── os.sh │ │ ├── test.sh │ │ ├── toolsets/ │ │ │ └── toolset.json │ │ ├── ubuntu-slim-Readme.md │ │ └── ubuntu-slim-Report.json │ └── windows/ │ ├── Windows2022-Readme.md │ ├── Windows2025-Readme.md │ ├── Windows2025-VS2026-Readme.md │ ├── assets/ │ │ └── post-gen/ │ │ ├── GenerateIISExpressCertificate.ps1 │ │ ├── InternetExplorerConfiguration.ps1 │ │ ├── Msys2FirstLaunch.ps1 │ │ ├── VSConfiguration.ps1 │ │ └── warmup.vdproj │ ├── scripts/ │ │ ├── build/ │ │ │ ├── Configure-BaseImage.ps1 │ │ │ ├── Configure-DeveloperMode.ps1 │ │ │ ├── Configure-Diagnostics.ps1 │ │ │ ├── Configure-DotnetSecureChannel.ps1 │ │ │ ├── Configure-DynamicPort.ps1 │ │ │ ├── Configure-GDIProcessHandleQuota.ps1 │ │ │ ├── Configure-ImageDataFile.ps1 │ │ │ ├── Configure-PowerShell.ps1 │ │ │ ├── Configure-Shell.ps1 │ │ │ ├── Configure-System.ps1 │ │ │ ├── Configure-SystemEnvironment.ps1 │ │ │ ├── Configure-Toolset.ps1 │ │ │ ├── Configure-User.ps1 │ │ │ ├── Configure-WindowsDefender.ps1 │ │ │ ├── Install-AWSTools.ps1 │ │ │ ├── Install-ActionsCache.ps1 │ │ │ ├── Install-AliyunCli.ps1 │ │ │ ├── Install-AndroidSDK.ps1 │ │ │ ├── Install-Apache.ps1 │ │ │ ├── Install-AzureCli.ps1 │ │ │ ├── Install-AzureCosmosDbEmulator.ps1 │ │ │ ├── Install-AzureDevOpsCli.ps1 │ │ │ ├── Install-Bazel.ps1 │ │ │ ├── Install-Chocolatey.ps1 │ │ │ ├── Install-ChocolateyPackages.ps1 │ │ │ ├── Install-Chrome.ps1 │ │ │ ├── Install-CodeQLBundle.ps1 │ │ │ ├── Install-DACFx.ps1 │ │ │ ├── Install-Docker.ps1 │ │ │ ├── Install-DockerCompose.ps1 │ │ │ ├── Install-DockerWinCred.ps1 │ │ │ ├── Install-DotnetSDK.ps1 │ │ │ ├── Install-EdgeDriver.ps1 │ │ │ ├── Install-Firefox.ps1 │ │ │ ├── Install-Git.ps1 │ │ │ ├── Install-GitHub-CLI.ps1 │ │ │ ├── Install-Haskell.ps1 │ │ │ ├── Install-IEWebDriver.ps1 │ │ │ ├── Install-JavaTools.ps1 │ │ │ ├── Install-Kotlin.ps1 │ │ │ ├── Install-KubernetesTools.ps1 │ │ │ ├── Install-LLVM.ps1 │ │ │ ├── Install-Mercurial.ps1 │ │ │ ├── Install-Mingw64.ps1 │ │ │ ├── Install-Miniconda.ps1 │ │ │ ├── Install-MongoDB.ps1 │ │ │ ├── Install-Msys2.ps1 │ │ │ ├── Install-MysqlCli.ps1 │ │ │ ├── Install-NSIS.ps1 │ │ │ ├── Install-NativeImages.ps1 │ │ │ ├── Install-Nginx.ps1 │ │ │ ├── Install-NodeJS.ps1 │ │ │ ├── Install-OpenSSL.ps1 │ │ │ ├── Install-PHP.ps1 │ │ │ ├── Install-Pipx.ps1 │ │ │ ├── Install-PostgreSQL.ps1 │ │ │ ├── Install-PowerShellModules.ps1 │ │ │ ├── Install-PowershellAzModules.ps1 │ │ │ ├── Install-PowershellCore.ps1 │ │ │ ├── Install-PyPy.ps1 │ │ │ ├── Install-R.ps1 │ │ │ ├── Install-RootCA.ps1 │ │ │ ├── Install-Ruby.ps1 │ │ │ ├── Install-Rust.ps1 │ │ │ ├── Install-SQLOLEDBDriver.ps1 │ │ │ ├── Install-SQLPowerShellTools.ps1 │ │ │ ├── Install-Sbt.ps1 │ │ │ ├── Install-Selenium.ps1 │ │ │ ├── Install-ServiceFabricSDK.ps1 │ │ │ ├── Install-Stack.ps1 │ │ │ ├── Install-Toolset.ps1 │ │ │ ├── Install-TortoiseSvn.ps1 │ │ │ ├── Install-VSExtensions.ps1 │ │ │ ├── Install-Vcpkg.ps1 │ │ │ ├── Install-VisualStudio.ps1 │ │ │ ├── Install-WDK.ps1 │ │ │ ├── Install-WSL2.ps1 │ │ │ ├── Install-WebPlatformInstaller.ps1 │ │ │ ├── Install-WinAppDriver.ps1 │ │ │ ├── Install-WindowsFeatures.ps1 │ │ │ ├── Install-WindowsUpdates.ps1 │ │ │ ├── Install-WindowsUpdatesAfterReboot.ps1 │ │ │ ├── Install-Wix.ps1 │ │ │ ├── Install-Zstd.ps1 │ │ │ ├── Invoke-Cleanup.ps1 │ │ │ └── Post-Build-Validation.ps1 │ │ ├── docs-gen/ │ │ │ ├── Generate-SoftwareReport.ps1 │ │ │ ├── SoftwareReport.Android.psm1 │ │ │ ├── SoftwareReport.Browsers.psm1 │ │ │ ├── SoftwareReport.CachedTools.psm1 │ │ │ ├── SoftwareReport.Common.psm1 │ │ │ ├── SoftwareReport.Databases.psm1 │ │ │ ├── SoftwareReport.Helpers.psm1 │ │ │ ├── SoftwareReport.Java.psm1 │ │ │ ├── SoftwareReport.Tools.psm1 │ │ │ ├── SoftwareReport.VisualStudio.psm1 │ │ │ └── SoftwareReport.WebServers.psm1 │ │ ├── helpers/ │ │ │ ├── AndroidHelpers.ps1 │ │ │ ├── ChocoHelpers.ps1 │ │ │ ├── ImageHelpers.psd1 │ │ │ ├── ImageHelpers.psm1 │ │ │ ├── InstallHelpers.ps1 │ │ │ ├── PathHelpers.ps1 │ │ │ ├── VisualStudioHelpers.ps1 │ │ │ └── test/ │ │ │ └── ImageHelpers.Tests.ps1 │ │ └── tests/ │ │ ├── ActionArchiveCache.Tests.ps1 │ │ ├── Android.Tests.ps1 │ │ ├── Apache.Tests.ps1 │ │ ├── Browsers.Tests.ps1 │ │ ├── CLI.Tools.Tests.ps1 │ │ ├── ChocoPackages.Tests.ps1 │ │ ├── Databases.Tests.ps1 │ │ ├── Docker.Tests.ps1 │ │ ├── DotnetSDK.Tests.ps1 │ │ ├── Git.Tests.ps1 │ │ ├── Haskell.Tests.ps1 │ │ ├── Helpers.psm1 │ │ ├── Java.Tests.ps1 │ │ ├── LLVM.Tests.ps1 │ │ ├── MSYS2.Tests.ps1 │ │ ├── Miniconda.Tests.ps1 │ │ ├── Nginx.Tests.ps1 │ │ ├── Node.Tests.ps1 │ │ ├── PHP.Tests.ps1 │ │ ├── PipxPackages.Tests.ps1 │ │ ├── PowerShellAzModules.Tests.ps1 │ │ ├── PowerShellModules.Tests.ps1 │ │ ├── RunAll-Tests.ps1 │ │ ├── Rust.Tests.ps1 │ │ ├── SSDTExtensions.Tests.ps1 │ │ ├── Shell.Tests.ps1 │ │ ├── Tools.Tests.ps1 │ │ ├── Toolset.Tests.ps1 │ │ ├── VisualStudio.Tests.ps1 │ │ ├── Vsix.Tests.ps1 │ │ ├── WDK.Tests.ps1 │ │ ├── WinAppDriver.Tests.ps1 │ │ ├── WindowsFeatures.Tests.ps1 │ │ └── Wix.Tests.ps1 │ ├── templates/ │ │ ├── build.windows-2022.pkr.hcl │ │ ├── build.windows-2025-vs2026.pkr.hcl │ │ ├── build.windows-2025.pkr.hcl │ │ ├── locals.windows.pkr.hcl │ │ ├── source.windows.pkr.hcl │ │ └── variable.windows.pkr.hcl │ └── toolsets/ │ ├── toolset-2022.json │ ├── toolset-2025-vs2026.json │ └── toolset-2025.json ├── images.CI/ │ ├── credscan-exclusions.json │ ├── linux-and-win/ │ │ ├── build-image.ps1 │ │ ├── cleanup.ps1 │ │ └── create-release.ps1 │ ├── measure-provisioners-duration.ps1 │ └── shebang-linter.ps1 └── schemas/ └── toolset-schema.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/CODEOWNERS ================================================ * @actions/runner-images-team ================================================ FILE: .github/ISSUE_TEMPLATE/announcement.yml ================================================ name: Announcement description: Submit an announcement labels: [Announcement] body: - type: textarea attributes: label: Breaking changes placeholder: Short description of the upcoming change validations: required: true - type: textarea attributes: label: Target date placeholder: Date of changes propagation start validations: required: true - type: textarea attributes: label: The motivation for the changes placeholder: Description of main reasons for this change validations: required: true - type: textarea attributes: label: Possible impact placeholder: Description of who might be impacted by this change validations: required: true - type: checkboxes attributes: label: Platforms affected options: - label: Azure DevOps - label: GitHub Actions - type: checkboxes attributes: label: Runner images affected options: - label: Ubuntu 22.04 - label: Ubuntu 24.04 - label: Ubuntu Slim - label: macOS 14 - label: macOS 14 Arm64 - label: macOS 15 - label: macOS 15 Arm64 - label: macOS 26 - label: macOS 26 Arm64 - label: Windows Server 2022 - label: Windows Server 2025 - label: Windows Server 2025 with Visual Studio 2026 - type: textarea attributes: label: Mitigation ways description: Steps or options for impact mitigation validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: Bug Report description: Submit a bug report. labels: [bug report, needs triage] body: - type: textarea attributes: label: Description description: A clear and concise description of what the bug is, and why you consider it to be a bug. validations: required: true - type: checkboxes attributes: label: Platforms affected options: - label: Azure DevOps - label: GitHub Actions - Standard Runners - label: GitHub Actions - Larger Runners - type: checkboxes attributes: label: Runner images affected options: - label: Ubuntu 22.04 - label: Ubuntu 24.04 - label: Ubuntu Slim - label: macOS 14 - label: macOS 14 Arm64 - label: macOS 15 - label: macOS 15 Arm64 - label: macOS 26 - label: macOS 26 Arm64 - label: Windows Server 2022 - label: Windows Server 2025 - label: Windows Server 2025 with Visual Studio 2026 - type: textarea attributes: label: Image version and build link description: | Image version where you are experiencing the issue. Where to find image version in build logs: 1. For GitHub Actions, under "Set up job" -> "Runner Image" -> "Version". 2. For Azure DevOps, under "Initialize job" -> "Runner Image" -> "Version". If you have a public example, please, provide a link to the failed build. validations: required: true - type: input attributes: label: Is it regression? description: If yes, please, provide the latest image version where the issue didn't persist, and a link to the latest successful build. validations: required: true - type: textarea attributes: label: Expected behavior description: A description of what you expected to happen. validations: required: true - type: textarea attributes: label: Actual behavior description: A description of what is actually happening. validations: required: true - type: textarea attributes: label: Repro steps placeholder: | A description with steps to reproduce the issue. 1. Step 1 2. Step 2 validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Get help in GitHub Discussions url: https://github.com/actions/runner-images/discussions about: Have a question? Feel free to ask in the runner-images GitHub Discussions! ================================================ FILE: .github/ISSUE_TEMPLATE/tool-request.yml ================================================ name: Tool request description: Request a new tool or update to a tool title: Update/Add [tool name] labels: [feature request, needs triage] body: - type: markdown attributes: value: "## Tool information" - type: input attributes: label: Tool name validations: required: true - type: input attributes: label: Tool license description: Type of licensing for desired tool. validations: required: true - type: checkboxes attributes: label: Add or update? options: - label: Add - label: Update - type: input attributes: label: Desired version description: Let us know if you're requesting a specific version, dev/RC, whatever is latest, etc. validations: required: true - type: input attributes: label: Approximate size description: Leave blank if unknown. - type: markdown attributes: value: "## If this is an add request" - type: textarea attributes: label: Brief description of tool - type: input attributes: label: URL for tool's homepage - type: textarea attributes: label: Provide a basic test case to validate the tool's functionality. description: This will be automatically formatted into code. render: bash - type: checkboxes attributes: label: Platforms where you need the tool options: - label: Azure DevOps - label: GitHub Actions - type: checkboxes attributes: label: Runner images where you need the tool options: - label: Ubuntu 22.04 - label: Ubuntu 24.04 - label: Ubuntu Slim - label: macOS 14 - label: macOS 14 Arm64 - label: macOS 15 - label: macOS 15 Arm64 - label: macOS 26 - label: macOS 26 Arm64 - label: Windows Server 2022 - label: Windows Server 2025 - label: Windows Server 2025 with Visual Studio 2026 - type: textarea attributes: label: Can this tool be installed during the build? description: If so, please provide a description with required steps. This will be automatically formatted into code. render: bash - type: input attributes: label: Tool installation time in runtime description: How long does it take to install the tool? - type: input attributes: label: Are you willing to submit a PR? description: We accept contributions! ================================================ FILE: .github/copilot-instructions.md ================================================ # GitHub Copilot Instructions for Actions Runner Images Repository ## Scope and goals - This repository serves as the source for building GitHub Actions runner and Azure DevOps agent images for Windows, Ubuntu, and macOS. You can find exact versions in the [Available Images](../README.md#available-images) section of README.md. Windows and Ubuntu images build on Azure infrastructure using Packer; macOS images use Anka virtualization. - Emphasize best practices for contributing to open-source projects, including code style, commit messages, and pull request etiquette. - Prefer clarity and correctness over creativity. If information is missing, ask clarifying questions or insert TODOs instead of guessing. ## Code and command instructions - Follow the code style guide in [CONTRIBUTING.md](../CONTRIBUTING.md#code-style-guide) for Bash and PowerShell scripts, including naming conventions, file structure, and indentation rules. - Focus on re-using helpers when writing scripts. Windows, Linux and Ubuntu scripts have helper functions available to simplify installation and validation. - Always confirm versions and installation paths against existing toolset files and installation scripts. ## Output format - Use GitHub Flavored Markdown only. Avoid raw HTML unless necessary. - One H1 (`#`) per page, followed by logical, sequential headings (`##`, `###`, …). - Use fenced code blocks with language identifiers (` ```bash `, ` ```json `, ` ```yaml `, etc.). - Use blockquote callouts for notes: > [!NOTE] Context or nuance > [!TIP] Helpful hint > [!WARNING] Risks or breaking changes > [!IMPORTANT] Critical requirement for functionality ## Style and tone - Audience: Open-source contributors, GitHub Actions maintainers, and developers building custom runner images. Assume familiarity with CI/CD concepts, Packer, and basic infrastructure provisioning, but explain platform-specific details (Azure for Windows/Ubuntu, Anka for macOS) when relevant. - Voice: Second person ("you"), active voice, imperative for operational steps. - Be concise: short paragraphs and sentences. Prefer lists and step-by-steps, especially for operational procedures and troubleshooting. - Use inclusive, accessible language. Avoid idioms, sarcasm, and culturally specific references. - English: en-US (spelling, punctuation, and units). ## Safety and integrity - Do not expose sensitive credentials (API tokens, Azure subscription IDs, etc.) in code examples. - Do not fabricate tool versions, installation paths, or software availability without verifying against toolset files or actual installation scripts. - Always call out assumptions and limitations explicitly, especially for changes affecting runner image behavior or software availability. - If ambiguous requests are made about image modifications, ask clarifying questions about target OS, tool versions, and compatibility requirements before proceeding. ================================================ FILE: .github/pull_request_template.md ================================================ # Description New tool, Bug fixing, or Improvement? Please include a summary of the change and which issue is fixed. Also include relevant motivation and context. **For new tools, please provide total size and installation time.** #### Related issue: ## Check list - [ ] Related issue / work item is attached - [ ] Tests are written (if applicable) - [ ] Documentation is updated (if applicable) - [ ] Changes are tested and related VM images are successfully generated ================================================ FILE: .github/workflows/check-pinned-versions.yml ================================================ name: Check Outdated Version Pinning on: schedule: - cron: '0 12 * * 1' # Run at 12:00 UTC every Monday permissions: issues: write contents: read jobs: check-pinning-dates: runs-on: ubuntu-slim steps: - name: Checkout repository uses: actions/checkout@v5 - name: Validate JSON Schema shell: pwsh run: ./helpers/CheckOutdatedVersionPinning.ps1 env: GH_TOKEN: ${{ github.token }} ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ main ] pull_request: # The branches below must be a subset of the branches above branches: [ main ] schedule: - cron: '32 4 * * 0' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: # only required for workflows in private repositories actions: read contents: read # required for all workflows security-events: write strategy: fail-fast: false matrix: language: [ 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 ================================================ FILE: .github/workflows/create_github_release.yml ================================================ name: Create GitHub release on: repository_dispatch: types: [create-github-release] jobs: Create_GitHub_release: runs-on: ubuntu-latest steps: - name: Create release for ${{ github.event.client_payload.ReleaseBranchName }} uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b #v1.20.0 with: tag: ${{ github.event.client_payload.ReleaseBranchName }} name: ${{ github.event.client_payload.ReleaseTitle }} body: ${{ github.event.client_payload.ReleaseBody }} prerelease: ${{ github.event.client_payload.Prerelease }} commit: ${{ github.event.client_payload.Commitish }} allowUpdates: true ================================================ FILE: .github/workflows/create_pull_request.yml ================================================ name: Create Pull Request on: repository_dispatch: types: [create-pr] jobs: Create_pull_request: runs-on: ubuntu-slim steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Clone release branch to create pull request run: | git checkout ${{ github.event.client_payload.ReleaseBranchName }} git branch ${{ github.event.client_payload.ReleaseBranchName }}-docs git push origin ${{ github.event.client_payload.ReleaseBranchName }}-docs --force - name: Create pull request for ${{ github.event.client_payload.ReleaseBranchName }} id: create-pr uses: actions/github-script@v8 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | const pulls = await github.rest.pulls.list({ owner: context.repo.owner, repo: context.repo.repo, head: `${context.repo.owner}:${{ github.event.client_payload.ReleaseBranchName }}-docs`, base: "${{ github.event.client_payload.PullRequestBase }}", state: 'open' }); if (pulls.data.length > 0) { console.log(`Pull request already exists: ${pulls.data[0].html_url}`); return pulls.data[0].number; } else { console.log('No existing pull request found, creating new one'); let response = await github.rest.pulls.create({ owner: context.repo.owner, repo: context.repo.repo, title: "${{ github.event.client_payload.PullRequestTitle }}", head: "${{ github.event.client_payload.ReleaseBranchName }}-docs", base: "${{ github.event.client_payload.PullRequestBase }}", body: `${{ github.event.client_payload.PullRequestBody }}` }); return response.data.number; } - name: Request reviewers uses: actions/github-script@v8 with: github-token: ${{secrets.PRAPPROVAL_SECRET}} script: | github.rest.pulls.requestReviewers({ owner: context.repo.owner, repo: context.repo.repo, pull_number: ${{ steps.create-pr.outputs.result }}, team_reviewers: ['runner-images-team'] }) ================================================ FILE: .github/workflows/create_sbom_report.yml ================================================ name: Create SBOM for the release run-name: Collecting SBOM for ${{ github.event.client_payload.agentSpec || 'unknown image' }} - ${{ github.event.client_payload.imageVersion || 'unknown version' }} on: repository_dispatch: types: [generate-sbom] defaults: run: shell: pwsh jobs: sbom-check: outputs: check_status: ${{ steps.check.outputs.status }} runs-on: ubuntu-latest env: RELEASE_ID: ${{ github.event.client_payload.ReleaseID }} steps: - name: Check SBOM asset for release ${{ env.RELEASE_ID }} id: check shell: pwsh run: | $apiUrl = "https://api.github.com/repos/actions/runner-images/releases/$env:RELEASE_ID" $response = Invoke-RestMethod -Uri $apiUrl -Method Get -SkipHttpErrorCheck if ($response.message -ilike "Not Found") { echo "status=release_not_found" >> $env:GITHUB_OUTPUT Write-Error "Release $env:RELEASE_ID wasn't found" exit 1 } foreach ($asset in $response.assets) { if ($asset.name -like '*sbom*') { echo "status=sbom_exists" >> $env:GITHUB_OUTPUT return "Release $env:RELEASE_ID already contains a SBOM" } } Write-Host "Release has been found, SBOM is not attached, starting generation." echo "status=okay" >> $env:GITHUB_OUTPUT building-sbom: needs: sbom-check if: ${{ needs.sbom-check.outputs.check_status == 'okay' }} runs-on: ${{ github.event.client_payload.agentSpec }} env: AGENT_SPEC: ${{ github.event.client_payload.agentSpec }} RELEASE_ID: ${{ github.event.client_payload.ReleaseID }} IMAGE_VERSION: ${{ github.event.client_payload.imageVersion }} steps: - name: Available image version check run: | $expectedVersion = $env:IMAGE_VERSION $runnerVersion = $env:ImageVersion # Split versions by dot $expectedParts = $expectedVersion.Split('.') $runnerParts = $runnerVersion.Split('.') # Determine what parts to compare $minLength = [Math]::Min($expectedParts.Length, $runnerParts.Length) $expectedComparable = $expectedParts[0..($minLength-1)] -join '.' $runnerComparable = $runnerParts[0..($minLength-1)] -join '.' # Perform the comparison if ($expectedComparable -ne $runnerComparable) { throw "Version mismatch: Expected version '$expectedVersion' doesn't match runner version '$runnerVersion'" } - name: Install SYFT tool on Windows if: ${{ runner.os == 'Windows' }} run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b C:/syft - name: Install SYFT tool on Ubuntu if: ${{ runner.os == 'Linux' }} run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin - name: Install SYFT v1.24.0 on macOS if: ${{ runner.os == 'macOS' }} run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v1.24.0 - name: Run SYFT on Windows if: ${{ runner.os == 'Windows' }} run: C:/syft/syft dir:C:/ -vv -o spdx-json=sbom.json - name: Run SYFT on Ubuntu if: ${{ runner.os == 'Linux' }} run: syft dir:/ -vv -o spdx-json=sbom.json - name: Run SYFT on macOS if: ${{ runner.os == 'macOS' }} # Skip protected folders to avoid prompt privileges that block process indefinitely (https://github.com/anchore/syft/issues/1367) run: sudo syft dir:/ -vv -o spdx-json=sbom.json --exclude ./Users --exclude ./System/Volumes --exclude ./private shell: bash - name: Compress SBOM file run: Compress-Archive sbom.json sbom.json.zip - uses: actions/upload-artifact@v4 with: name: sbom-${{ env.AGENT_SPEC }}-${{ env.IMAGE_VERSION }} path: sbom.json.zip if-no-files-found: warn - name: Upload release asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: "https://uploads.github.com/repos/actions/runner-images/releases/${{ env.RELEASE_ID }}/assets{?name,label}" asset_path: ./sbom.json.zip asset_name: sbom.${{ env.AGENT_SPEC }}.json.zip asset_content_type: application/zip ================================================ FILE: .github/workflows/docker-images.yml ================================================ name: Test Docker Images on: push: branches: - main paths: - 'images/ubuntu-slim/**' - '.github/workflows/docker-images.yml' pull_request: paths: - 'images/ubuntu-slim/**' - '.github/workflows/docker-images.yml' workflow_dispatch: permissions: contents: read jobs: test-images: runs-on: ubuntu-latest strategy: matrix: directory: - images/ubuntu-slim steps: - uses: actions/checkout@v6 - name: Run test.sh working-directory: ${{ matrix.directory }} run: ./test.sh ================================================ FILE: .github/workflows/linter.yml ================================================ # CI Validation name: Linter on: pull_request: branches: [ main ] paths: - '**.json' - '**.md' - '**.sh' jobs: build: name: Lint JSON & MD files runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v5 with: fetch-depth: 0 - name: Lint Code Base uses: github/super-linter/slim@v7 env: VALIDATE_ALL_CODEBASE: false GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_JSON: true VALIDATE_MARKDOWN: true DEFAULT_BRANCH: ${{ github.base_ref }} FILTER_REGEX_EXCLUDE: .*images/*/.*-Readme.md - name: Checking shebang lines in MacOS and Ubuntu releases. run: ./images.CI/shebang-linter.ps1 shell: pwsh ================================================ FILE: .github/workflows/merge_pull_request.yml ================================================ name: Merge pull request on: repository_dispatch: types: [merge-pr] jobs: Merge_pull_request: runs-on: ubuntu-slim steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Resolve possible conflicts ${{ github.event.client_payload.ReleaseBranchName }} with main run: | git config --global user.email "no-reply@github.com" git config --global user.name "Actions service account" git checkout ${{ github.event.client_payload.ReleaseBranchName }}-docs git merge --no-edit --strategy-option=ours main git push origin ${{ github.event.client_payload.ReleaseBranchName }}-docs sleep 30 - name: Approve pull request by GitHub-Actions bot uses: actions/github-script@v8 with: github-token: ${{secrets.PRAPPROVAL_SECRET}} script: | github.rest.pulls.createReview({ owner: context.repo.owner, repo: context.repo.repo, pull_number: ${{ github.event.client_payload.PullRequestNumber }}, event: "APPROVE" }); - name: Merge pull request for ${{ github.event.client_payload.ReleaseBranchName }} uses: actions/github-script@v8 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | github.rest.pulls.merge({ owner: context.repo.owner, repo: context.repo.repo, pull_number: ${{ github.event.client_payload.PullRequestNumber }}, merge_method: "squash" }) ================================================ FILE: .github/workflows/powershell-tests.yml ================================================ # CI Validation name: PowerShell Tests on: pull_request: branches: [ main ] paths: - 'helpers/software-report-base/**' jobs: powershell-tests: name: PowerShell tests runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v5 - name: Run Software Report module tests shell: pwsh run: | $ErrorActionPreference = "Stop" Invoke-Pester -Output Detailed "helpers/software-report-base/tests" ================================================ FILE: .github/workflows/trigger-ubuntu-win-build.yml ================================================ name: Trigger Build workflow on: workflow_call: inputs: image_type: required: true type: string defaults: run: shell: pwsh jobs: trigger-workflow: runs-on: ubuntu-latest outputs: ci_workflow_run_id: ${{ steps.resolve.outputs.ci_workflow_run_id }} ci_workflow_run_url: ${{ steps.resolve.outputs.ci_workflow_run_url }} env: CI_PR_TOKEN: ${{ secrets.CI_PR_TOKEN }} PR_TITLE: ${{ github.event.pull_request.title }} CI_REPO: ${{ vars.CI_REPO }} steps: - name: Checkout Code uses: actions/checkout@v5 - name: Trigger Build workflow run: | Import-Module ./helpers/GitHubApi.psm1 $gitHubApi = Get-GithubApi -Repository "${env:CI_REPO}" -AccessToken "${env:CI_PR_TOKEN}" $eventType = "trigger-${{ inputs.image_type }}-build" [string] $prGuid = New-Guid $clientPayload = @{ pr_title = "${env:PR_TITLE} - " + $prGuid custom_repo = "${{ github.event.pull_request.head.repo.full_name }}" custom_repo_commit_hash = "${{ github.event.pull_request.head.sha }}" } $gitHubApi.DispatchWorkflow($eventType, $clientPayload) "PR_GUID=$prGuid" | Out-File -Append -FilePath $env:GITHUB_ENV - name: Resolve Workflow Run ID id: resolve run: | Import-Module ./helpers/GitHubApi.psm1 $gitHubApi = Get-GithubApi -Repository "${env:CI_REPO}" -AccessToken "${env:CI_PR_TOKEN}" $workflowFileName = $("{0}.yml" -f "${{ inputs.image_type }}").ToLower() $WorkflowSearchPattern = "${env:PR_GUID}" # It might take a few minutes for the action to start $attempt = 1 do { $workflowRuns = $gitHubApi.GetWorkflowRuns($WorkflowFileName).workflow_runs $workflowRunId = ($workflowRuns | Where-Object {$_.display_title -match $WorkflowSearchPattern}).id | Select-Object -First 1 if (-not ([string]::IsNullOrEmpty($workflowRunId))) { $workflowRun = $gitHubApi.GetWorkflowRun($workflowRunId) Write-Host "Found the workflow run with ID $workflowRunId on attempt $attempt. Workflow run link: $($workflowRun.html_url)" "ci_workflow_run_id=$workflowRunId" | Out-File -Append -FilePath $env:GITHUB_OUTPUT "ci_workflow_run_url=$($workflowRun.html_url)" | Out-File -Append -FilePath $env:GITHUB_OUTPUT break } Write-Host "Workflow run for $WorkflowSearchPattern pattern not found on attempt $attempt." $attempt += 1 Start-Sleep 30 } until ($attempt -eq 10) if ([string]::IsNullOrEmpty($workflowRunId)) { throw "Failed to find a workflow run for '$WorkflowSearchPattern'." } wait-completion: runs-on: ubuntu-latest needs: trigger-workflow steps: - name: Checkout Code uses: actions/checkout@v5 - name: Wait for workflow completion env: CI_PR_TOKEN: ${{ secrets.CI_PR_TOKEN }} CI_REPO: ${{ vars.CI_REPO }} run: | ./helpers/WaitWorkflowCompletion.ps1 ` -WorkflowRunId "${{ needs.trigger-workflow.outputs.ci_workflow_run_id }}" ` -Repository "${env:CI_REPO}" ` -AccessToken "${env:CI_PR_TOKEN}" - name: Add Summary if: always() run: | "# Test Partner Image" >> $env:GITHUB_STEP_SUMMARY "| Key | Value |" >> $env:GITHUB_STEP_SUMMARY "| :-----------: | :--------: |" >> $env:GITHUB_STEP_SUMMARY "| Workflow Run | [Link](${{ needs.trigger-workflow.outputs.ci_workflow_run_url }}) |" >> $env:GITHUB_STEP_SUMMARY "| Workflow Result | $env:CI_WORKFLOW_RUN_RESULT |" >> $env:GITHUB_STEP_SUMMARY " " >> $env:GITHUB_STEP_SUMMARY cancel-workflow: runs-on: ubuntu-latest needs: [trigger-workflow, wait-completion] if: cancelled() steps: - name: Checkout Code uses: actions/checkout@v5 - name: Cancel workflow env: CI_PR_TOKEN: ${{ secrets.CI_PR_TOKEN }} CI_REPO: ${{ vars.CI_REPO }} run: | Import-Module ./helpers/GitHubApi.psm1 $gitHubApi = Get-GithubApi -Repository "${env:CI_REPO}" -AccessToken "${env:CI_PR_TOKEN}" $gitHubApi.CancelWorkflowRun("${{ needs.trigger-workflow.outputs.ci_workflow_run_id }}") ================================================ FILE: .github/workflows/ubuntu2204.yml ================================================ name: Trigger Ubuntu22.04 CI run-name: Ubuntu22.04 - ${{ github.event.pull_request.title }} on: pull_request_target: types: labeled paths: - 'images/ubuntu/**' defaults: run: shell: pwsh jobs: Ubuntu_2204: if: github.event.label.name == 'CI ubuntu-all' || github.event.label.name == 'CI ubuntu-2204' uses: ./.github/workflows/trigger-ubuntu-win-build.yml with: image_type: 'ubuntu2204' secrets: inherit ================================================ FILE: .github/workflows/ubuntu2404.yml ================================================ name: Trigger Ubuntu24.04 CI run-name: Ubuntu24.04 - ${{ github.event.pull_request.title }} on: pull_request_target: types: labeled paths: - 'images/ubuntu/**' defaults: run: shell: pwsh jobs: Ubuntu_2404: if: github.event.label.name == 'CI ubuntu-all' || github.event.label.name == 'CI ubuntu-2404' uses: ./.github/workflows/trigger-ubuntu-win-build.yml with: image_type: 'ubuntu2404' secrets: inherit ================================================ FILE: .github/workflows/update_github_release.yml ================================================ name: Update release on: repository_dispatch: types: [update-github-release] jobs: Update_GitHub_release: runs-on: ubuntu-slim steps: - name: Update release for ${{ github.event.client_payload.ReleaseBranchName }} uses: actions/github-script@v8 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | const response = await github.rest.repos.getReleaseByTag({ owner: context.repo.owner, repo: context.repo.repo, tag: "${{ github.event.client_payload.ReleaseBranchName }}" }); github.rest.repos.updateRelease({ owner: context.repo.owner, repo: context.repo.repo, release_id: response.data.id, prerelease: ${{ github.event.client_payload.Prerelease }} }); ================================================ FILE: .github/workflows/validate-json-schema.yml ================================================ name: Validate JSON Schema on: push: branches: - main pull_request: branches: - main jobs: validate-json-schema: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v5 - name: Validate JSON Schema shell: pwsh run: ./helpers/CheckJsonSchema.ps1 ================================================ FILE: .github/workflows/windows2022.yml ================================================ name: Trigger Windows22 CI run-name: Windows2022 - ${{ github.event.pull_request.title }} on: pull_request_target: types: labeled paths: - 'images/windows/**' defaults: run: shell: pwsh jobs: Windows_2022: if: github.event.label.name == 'CI windows-all' || github.event.label.name == 'CI windows-2022' uses: ./.github/workflows/trigger-ubuntu-win-build.yml with: image_type: 'windows2022' secrets: inherit ================================================ FILE: .github/workflows/windows2025-vs2026.yml ================================================ name: Trigger Windows25 with VS 2026 CI run-name: Windows2025 with VS 2026 - ${{ github.event.pull_request.title }} on: pull_request_target: types: labeled paths: - 'images/windows/**' defaults: run: shell: pwsh jobs: Windows_2025_vs_2026: if: github.event.label.name == 'CI windows-all' || github.event.label.name == 'CI windows-2025-vs2026' uses: ./.github/workflows/trigger-ubuntu-win-build.yml with: image_type: 'windows2025-vs2026' secrets: inherit ================================================ FILE: .github/workflows/windows2025.yml ================================================ name: Trigger Windows25 CI run-name: Windows2025 - ${{ github.event.pull_request.title }} on: pull_request_target: types: labeled paths: - 'images/windows/**' defaults: run: shell: pwsh jobs: Windows_2025: if: github.event.label.name == 'CI windows-all' || github.event.label.name == 'CI windows-2025' uses: ./.github/workflows/trigger-ubuntu-win-build.yml with: image_type: 'windows2025' secrets: inherit ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # .NET Core project.lock.json project.fragment.lock.json artifacts/ **/Properties/launchSettings.json *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Typescript v1 declaration files typings/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # VSCode settings .vscode/** !.vscode/extensions.json !.vscode/settings.json !.vscode/tasks.json # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # Ignore files generated by packer InstalledSoftware.md # Desktop Service Store .DS_Store # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # gatsby files .cache/ public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # visual studio code launch configuration launch.json # Ignore dynamic template images/*/*-temp.json ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "streetsidesoftware.code-spell-checker", "hashicorp.hcl", "davidanson.vscode-markdownlint", "ms-vscode.powershell", "timonwong.shellcheck" ] } ================================================ FILE: .vscode/settings.json ================================================ { "files.trimFinalNewlines": true, "files.insertFinalNewline": true, "powershell.codeFormatting.addWhitespaceAroundPipe": true, "powershell.codeFormatting.alignPropertyValuePairs": true, "powershell.codeFormatting.autoCorrectAliases": true, "powershell.codeFormatting.newLineAfterCloseBrace": false, "powershell.codeFormatting.newLineAfterOpenBrace": true, "powershell.codeFormatting.openBraceOnSameLine": true, "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline", "powershell.codeFormatting.preset": "OTBS", "powershell.codeFormatting.trimWhitespaceAroundPipe": true, "powershell.codeFormatting.whitespaceAfterSeparator": true, "powershell.codeFormatting.whitespaceAroundOperator": true, "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, "powershell.codeFormatting.whitespaceBeforeOpenParen": true, "powershell.codeFormatting.whitespaceBetweenParameters": true, "powershell.codeFormatting.whitespaceInsideBrace": true, "shellcheck.exclude": [ "SC1090","SC2096" ], "shellcheck.customArgs": [ "-x" ], "json.schemas": [ { "fileMatch": [ "**/toolset-*.json" ], "url": "./schemas/toolset-schema.json" } ] } ================================================ FILE: .vscode/tasks.json ================================================ // Available variables which can be used inside of strings. // ${workspaceRoot}: the root folder of the team // ${file}: the current opened file // ${relativeFile}: the current opened file relative to workspaceRoot // ${fileBasename}: the current opened file's basename // ${fileDirname}: the current opened file's dirname // ${fileExtname}: the current opened file's extension // ${cwd}: the current working directory of the spawned process { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", // Start PowerShell "windows": { "command": "${env:windir}/System32/WindowsPowerShell/v1.0/powershell.exe", //"command": "${env:ProgramFiles}/PowerShell/6.0.0/powershell.exe", "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] }, "linux": { "command": "/usr/bin/powershell", "args": [ "-NoProfile" ] }, "osx": { "command": "/usr/local/bin/powershell", "args": [ "-NoProfile" ] }, // Associate with test task runner "tasks": [ { "taskName": "Test", "suppressTaskName": true, "isTestCommand": true, "args": [ "Write-Host 'Invoking Pester...'; $ProgressPreference = 'SilentlyContinue'; Invoke-Pester -Script test -PesterOption @{IncludeVSCodeMarker=$true};", "Invoke-Command { Write-Host 'Completed Test task in task runner.' }" ], "problemMatcher": "$pester" } ] } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource@github.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing [fork]: https://github.com/actions/runner-images/fork [pr]: https://github.com//actions/runner-images/compare [code-of-conduct]: CODE_OF_CONDUCT.md Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [MIT](LICENSE.md) license. Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project, you agree to abide by its terms. ## Contents - [Submitting a pull request](#submitting-a-pull-request) - [Adding a new tool to an image](#adding-a-new-tool-to-an-image) - [Code style guide](#code-style-guide) ## Submitting a pull request 1. [Fork][fork] and clone the repository. 1. Create a new branch: `git checkout -b my-branch-name`. 1. Make your changes, ensuring that they include steps to install, validate post-install, and update the software report (please see [Adding a new tool to an image](#adding-a-new-tool-to-an-image) for details). 1. Test your changes by [creating an image and deploying a VM](docs/create-image-and-azure-resources.md). 1. Push to your fork and [submit a pull request][pr]. Here are a few things you can do that will increase the likelihood of your pull request being accepted: - Follow the style guide for [Powershell](https://github.com/PoshCode/PowerShellPracticeAndStyle) when writing Windows scripts. There is currently no set style for the Shell scripts that run Linux installs :soon:. - Include complete details of why this is needed in the PR description. - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. - Write [good commit messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). - For new tools: - Make sure that the tool satisfies the [Software Guidelines](README.md#software-guidelines). - Create an issue and get approval from us to add this tool to the image before creating the pull request. ## Adding a new tool to an image ### General rules - For every new tool, add validation scripts and update the software report script to ensure that it is included in the documentation. - If the tool is available on multiple platforms (macOS, Windows, Linux), make sure you include it on as many as possible. - If installing multiple versions of the tool, consider putting the list of versions in the corresponding `toolset.json` file. This will help other customers configure their builds flexibly. See [toolset-windows-2022.json](images/windows/toolsets/toolset-2022.json) as an example. - Use consistent naming across all files. - Validation scripts should be simple and shouldn't change the image content. ### Windows - Add a script that will install the tool and put the script in the `scripts/build` folder. There are a bunch of helper functions that could simplify your code: `Install-ChocoPackage`, `Install-Binary`, `Install-VSIXFromFile`, `Install-VSIXFromUrl`, `Invoke-DownloadWithRetry`, `Test-IsWin22`, `Test-IsWin25` (find the full list of helpers in [ImageHelpers.psm1](images/windows/scripts/helpers/ImageHelpers.psm1)). - Add a script that will validate the tool installation and put the script in the `scripts/tests` folder. We use [Pester v5](https://github.com/pester/pester) for validation scripts. If the tests for the tool are complex enough, create a separate `*.Tests.ps1`. Otherwise, use `Tools.Tests.ps1` for simple tests. Add `Invoke-PesterTests -TestFile [-TestName ]` at the end of the installation script to ensure that your tests will be run. - Add changes to the software report generator `images/windows/scripts/docs-gen/Generate-SoftwareReport.ps1`. The software report generator is used to generate an image's README file, e.g. [Windows2022-Readme.md](images/windows/Windows2022-Readme.md) and uses [MarkdownPS](https://github.com/Sarafian/MarkdownPS). ### Ubuntu - Add a script that will install and validate the tool and put the script in the `scripts/build` folder. Use existing scripts such as [github-cli.sh](images/ubuntu/scripts/build/github-cli.sh) as a starting point. - Use [helpers](images/ubuntu/scripts/helpers/install.sh) to simplify the installation process. - The validation part should `exit 1` if there is any issue with the installation. - Add changes to the software report generator `images/ubuntu/scripts/docs-gen/Generate-SoftwareReport.ps1`. The software report generator is used to generate an image's README file, e.g. [Ubuntu2204-Readme.md](images/ubuntu/Ubuntu2204-Readme.md) and it uses [MarkdownPS](https://github.com/Sarafian/MarkdownPS). ### macOS The macOS source lives in this repository and is available for everyone. However, the macOS image-generation CI doesn't support external contributions yet, so we are not able to accept pull requests for now. We are in the process of preparing the macOS CI to accept contributions. Until then, we appreciate your patience and ask that you continue to make tool requests by filing issues. ## Code style guide The principles of clean code apply to all languages. The main points are: - Use meaningful names for variables, functions, files, etc. - Keep functions short and simple. - Use comments to explain what the code does. - Use a consistent code style, naming convention, and file structure. ### File structure - Each file should have a header with a title and a short description of the file. - Each file should have a newline at the end. - Use blank lines to separate logical blocks of code, but don't abuse blank lines: - Don't add a blank line in the beginning and end of a block or function. - Don't add blank lines between logically connected statements. - Avoid trailing whitespace. ### Bash scripts #### Naming convention for bash scripts - Use lowercase letters for variable names. - Use uppercase letters for constants. - Use underscores to separate words in variable names. #### Bash script structure Each script should start with the following shebang: ```bash #!/bin/bash -e ``` > TODO: do we need to set pipefail? This will make the script exit if any command fails. After the shebang, add a header with the following format: ```bash ################################################################################ ## File: ## Desc: ################################################################################ ``` Then import helpers that are used in the script. For Linux: ```bash source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh ``` For macOS: ```bash source ~/utils/utils.sh ``` > [!NOTE] > You don't need to import all helpers, only the ones that are used in the script. After that, add the script code. ### Indentations and line breaks in bash scripts - Use 4 spaces for indentation. - Use 1 space between `if`/`for`/`while` and `[[` and between `[[` and the condition. - Place `then`/`do` on the new line. - For short `if`/`for`/`while` statements, use the one-line format. - Break long pipelines using `\`. ### Other recommendations for bash scripts - For command substitution, use `$()` instead of backticks. - Use `[[` instead of `[` for conditional expressions. - Prefer using long options instead of short keys, but there are exceptions, e.g.: - `tar -xzf` - `apt-get -yqq` - `curl -sSLf` - `wget -qO-` ### PowerShell scripts #### Naming convention for PowerShell scripts - Use camelCase for variable names. - Use uppercase letters for constants. - Use `Verb-Noun` and PascalCase for function names. ### PowerShell script structure Each script should start with the following header: ```powershell ################################################################################ ## File: ## Desc: ################################################################################ ``` Then declare functions that are used in the script. > TODO: do we need to set the error action preference and progress preference? > > ```powershell > $ErrorActionPreference = "Stop" > $ProgressPreference = "SilentlyContinue" > ``` For Linux and macOS, import helpers that are used in the script: For Linux: ```powershell Import-Module "$env:HELPER_SCRIPTS/Tests.Helpers.psm1" -DisableNameChecking ``` For macOS: ```powershell Import-Module "$env:HOME/image-generation/helpers/Common.Helpers.psm1" Import-Module "$env:HOME/image-generation/helpers/Xcode.Helpers.psm1" -DisableNameChecking ``` > [!NOTE] > You don't need to import all helpers, only the ones that are used in the script. After that, add the script code. ### Indentations and line breaks in PowerShell scripts - Use 4 spaces for indentation. - Use 1 space between `if`/`elseif`/`foreach` and `(` but not between `(` and the condition. - Add a space before and after pipe `|` and redirection `>` operators. - Align properties in hash tables. - Use [1TBS](https://en.wikipedia.org/wiki/Indentation_style#Variant:_1TBS_(OTBS)) style for curly braces: - If block of statement is long, then place it on the new line, indent it, and add a closing curly brace on the new line. - If block of statement is short, then place it on the same line as the statement. ```powershell function Show-Example1 { $exampleVariable = Get-ChildItem $env:TEMP $exampleVariable | ForEach-Object { $itemName = $_.Name $itemPath = $_.FullName } } $Example2 | Some-Function -Arguments @{Parameter1 = "Disabled"} ``` - Avoid using aliases. - Break long pipelines using backticks or use [splatting](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-7.3): ```powershell # Instead of this Copy-Item -Path "test.txt" -Destination "test2.txt" -WhatIf # you can use this $HashArguments = @{ Path = "test.txt" Destination = "test2.txt" WhatIf = $true } Copy-Item @HashArguments ``` When using backticks be extra careful with trailing whitespace as they can cause errors. ### Other recommendations for PowerShell scripts - Verify exit codes of commands. - When writing a function, provide a docstring that describes what the function does. ## Resources - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) - [GitHub Help](https://help.github.com) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2026 GitHub Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # GitHub Actions Runner Images **Table of Contents** - [About](#about) - [Available Images](#available-images) - [Announcements](#announcements) - [Image Definitions](#image-definitions) - [Image Releases](#image-releases) - [Software and Image Support](#software-and-image-support) - [How to Interact with the Repo](#how-to-interact-with-the-repo) - [FAQs](#faqs) ## About This repository contains the source code used to create the VM images for [GitHub-hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners) used for Actions, as well as for [Microsoft-hosted agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops#use-a-microsoft-hosted-agent) used for Azure Pipelines. To build a VM machine from this repo's source, see the [instructions](docs/create-image-and-azure-resources.md). ## Available Images | Image | Architecture | YAML Label | Included Software | | --------------------|--------------|---------------------|------------------| | Ubuntu 24.04
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fubuntu24.json) | x64 | `ubuntu-latest` or `ubuntu-24.04` | [ubuntu-24.04] | | Ubuntu 22.04
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fubuntu22.json) | x64 | `ubuntu-22.04` | [ubuntu-22.04] | | Ubuntu Slim ![preview](https://img.shields.io/badge/preview-0969DA?style=flat&logoColor=white)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fubuntu-slim.json) | x64 | `ubuntu-slim` | [ubuntu-slim] | | macOS 26 Arm64
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fmacos-26-arm64.json) | arm64 | `macos-26` or `macos-26-xlarge` | [macOS-26-arm64] | | macOS 26
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fmacos-26.json) | x64 | `macos-26-intel`, `macos-26-large` | [macOS-26] | | macOS 15
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fmacos-15.json) | x64 | `macos-latest-large`, `macos-15-large`, or `macos-15-intel` | [macOS-15] | | macOS 15 Arm64
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fmacos-15-arm64.json) | arm64 | `macos-latest`, `macos-15`, or `macos-15-xlarge` | [macOS-15-arm64] | | macOS 14
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fmacos-14.json) | x64 | `macos-14-large`| [macOS-14] | | macOS 14 Arm64
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fmacos-14-arm64.json) | arm64 | `macos-14` or `macos-14-xlarge`| [macOS-14-arm64] | | Windows Server 2025 with Visual Studio 2026 ![beta](https://img.shields.io/badge/beta-yellow)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fwin25-vs2026.json) | x64 | `windows-2025-vs2026` | [windows-2025-vs2026] | | Windows Server 2025
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fwin25.json) | x64 | `windows-latest` or `windows-2025` | [windows-2025] | | Windows Server 2022
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhosted-runners-images-bot%2F79267492faab096d04cdd25ce7014cec%2Fraw%2Fwin22.json) | x64 | `windows-2022` | [windows-2022] | ### Label scheme - In general the `-latest` label is used for the latest OS image version that is GA. - Before moving the `-latest` label to a new OS version we will announce the change and give sufficient lead time for users to update their workflows. - The `-xlarge` and `-large` suffixes are unique to macOS images and are only available for GitHub Actions. Learn more about [GitHub Actions larger runners](https://docs.github.com/en/actions/reference/runners/larger-runners#available-macos-larger-runners-and-labels). [ubuntu-24.04]: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md [ubuntu-22.04]: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md [ubuntu-slim]: https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md [windows-2025]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md [windows-2025-vs2026]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-VS2026-Readme.md [windows-2022]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md [macOS-14]: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md [macOS-14-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md [macOS-15]: https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md [macOS-15-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md [macOS-26]: https://github.com/actions/runner-images/blob/main/images/macos/macos-26-Readme.md [macOS-26-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md [self-hosted runners]: https://help.github.com/en/actions/hosting-your-own-runners ## Announcements See notable upcoming changes by viewing issues with the [Announcement](https://github.com/actions/runner-images/labels/Announcement) label. ## Image Definitions ### Beta The purpose of a Beta is to collect feedback on an image before it is released to GA. The goal of a Beta is to identify and fix any potential issues that exist on that image. Images are updated on a weekly cadence. Any workflows that run on a beta image do not fall under the customer [SLA](https://github.com/customer-terms/github-online-services-sla) in place for Actions. Customers choosing to use Beta images are encouraged to provide feedback in the runner-images repo by creating an issue. A Beta may take on different availability, i.e. public vs private. ### GA A GA (General Availability) image has been through a Beta period and is deemed ready for general use. Images are updated on a weekly cadence. In order to be moved to GA the image must meet the following criteria: 1. Has been through a Beta period (public or private) 2. Most major software we install on the image has a compatible version for the underlying OS and 3. All major bugs reported during the Beta period have been addressed. This image type falls under the customer [SLA](https://github.com/customer-terms/github-online-services-sla) for actions. GA images are eventually deprecated according to our guidelines as we only support the latest 2 versions of an OS. #### Latest Migration Process GitHub Actions and Azure DevOps use the `-latest` YAML label (ex: `ubuntu-latest`, `windows-latest`, and `macos-latest`). These labels point towards the newest stable OS version available. The `-latest` migration process is gradual and happens over 1-2 months in order to allow customers to adapt their workflows to the newest OS version. During this process, any workflow using the `-latest` label, may see changes in the OS version in their workflows or pipelines. To avoid unwanted migration, users can specify a specific OS version in the yaml file (ex: macos-14, windows-2022, ubuntu-22.04). ## Image Releases *How to best follow along with changes* 1. Find the latest releases for this repository [here](https://github.com/actions/runner-images/releases). 2. Subscribe to the releases coming out of this repository, instructions [here](https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#configuring-your-watch-settings-for-an-individual-repository). 3. Upcoming changes: A pre-release is created when the deployment of an image has started. As soon as the deployment is finished, the pre-release is converted to a release. If you have subscribed to releases, you will get notified of pre-releases as well. - You can also track upcoming changes using the [awaiting-deployment](https://github.com/actions/runner-images/labels/awaiting-deployment) label. 4. For high impact changes, we will post these in advance to the GitHub Changelog on our [blog](https://github.blog/changelog/) and on [X](https://x.com/GHchangelog). - Ex: breaking changes, GA or deprecation of images *Cadence* - We typically deploy weekly updates to the software on the runner images. ## Software and Image Support ### Support Policy - Tools and versions will typically be removed 6 months after they are deprecated or have reached end-of-life - We support (at maximum) 2 GA images and 1 beta image at a time. We begin the deprecation process of the oldest image label once the newest OS image label has been released to GA. - The images generally contain the latest versions of packages installed except for Ubuntu LTS where we mostly rely on the Canonical-provided repositories. - Popular tools can have several versions installed side-by-side with the following strategy: | Tool name | Installation strategy | |-----------|-----------------------| | Docker images | not more than 3 latest LTS OS\tool versions. New images or new versions of current images are added using the standard tool request process | | Java | all LTS versions | | Node.js | 3 latest LTS versions | | Go | 3 latest minor versions | | Python
Ruby | 5 most popular `major.minor` versions | | PyPy | 3 most popular `major.minor` versions | | .NET Core | 2 latest LTS versions and 1 latest version. For each feature version only latest patch is installed. Note for [Ubuntu images see details.](./docs/dotnet-ubuntu.md) | | GCC
GNU Fortran
Clang
GNU C++ | 3 latest major versions | | Android NDK | 1 latest non-LTS, 2 latest LTS versions | | Xcode | - only one major version of Xcode will be supported per macOS version
- all minor versions of the supported major version will be available
- beta and RC versions will be provided "as-is" in the latest available macOS image only no matter of beta/GA status of the image
- when a new patch version is released, the previous patch version will be replaced | | Xcode Platforms | - only three major.minor versions of platform tools and simulator runtimes will be available for installed Xcode, including beta/RC versions | ### Package managers usage We use third-party package managers to install software during the image generation process. The table below lists the package managers and the software installed. > [!NOTE] > Third-party repositories are re-evaluated every year to identify if they are still useful and secure. | Operating system | Package manager | Third-party repos and packages | | :--- | :---: | ---: | | Ubuntu | [APT](https://wiki.debian.org/Apt) | [docker](https://download.docker.com/linux/ubuntu)
[Eclipse-Temurin (Adoptium)](https://packages.adoptium.net/artifactory/deb/)
[Erlang](https://packages.erlang-solutions.com/ubuntu)
[Firefox](https://ppa.launchpad.net/mozillateam/ppa/ubuntu)
[git-lfs](https://packagecloud.io/install/repositories/github/git-lfs)
[git](https://launchpad.net/~git-core/+archive/ubuntu/ppa)
[Google Cloud CLI](https://packages.cloud.google.com/apt)
[Heroku](https://cli-assets.heroku.com/channels/stable/apt)
[HHvm](https://dl.hhvm.com/ubuntu)
[MongoDB](https://repo.mongodb.org/apt/ubuntu)
[Mono](https://download.mono-project.com/repo/ubuntu)
[MS Edge](https://packages.microsoft.com/repos/edge)
[PostgreSQL](https://apt.postgresql.org/pub/repos/apt/)
[R](https://cloud.r-project.org/bin/linux/ubuntu) | | | [pipx](https://pypa.github.io/pipx) | ansible-core
yamllint | | Windows | [Chocolatey](https://chocolatey.org) | No third-party repos installed | | macOS | [Homebrew](https://brew.sh) | [aws-cli v2](https://github.com/aws/homebrew-tap)
[azure/bicep](https://github.com/Azure/homebrew-bicep)
[mongodb/brew](https://github.com/mongodb/homebrew-brew) | | | [pipx](https://pypa.github.io/pipx/) | yamllint | ### Image Deprecation Policy - Images begin the deprecation process of the oldest image label once a new GA OS version has been released. - Deprecation process begins with an announcement that sets a date for deprecation. - As it gets closer to the date, GitHub begins doing scheduled brownouts of the image. - During this time there will be an Announcement pinned in the repo to remind users of the deprecation. - Finally, GitHub will deprecate the image and it will no longer be available. ### Preinstallation Policy In general, these are the guidelines we follow when deciding what to pre-install on our images: - Popularity: widely-used tools and ecosystems will be given priority. - Latest Technology: recent versions of tools will be given priority. - Deprecation: end-of-life tools and versions will not be added. - Licensing: MIT, Apache, or GNU licenses are allowed. - Time & Space on the Image: we will evaluate how much time is saved and how much space is used by having the tool pre-installed. - Support: If a tool requires the support of more than one version, we will consider the cost of this maintenance. ### Default Version Update Policy - In general, once a new version is installed on the image, we announce the default version update 2 weeks prior to deploying it. - For potentially dangerous updates, we may extend the timeline up to 1 month between the announcement and deployment. ## How to Interact with the Repo - **Issues**: To file a bug report, or request tools to be added/updated, please [open an issue using the appropriate template](https://github.com/actions/runner-images/issues/new/choose) - **Discussions**: If you want to share your thoughts about image configuration, installed software, or bring a new idea, please create a [new discussion](https://github.com/orgs/community/discussions/new?category=actions). Before making a new discussion, please make sure no similar topics were created earlier in the [actions category](https://github.com/orgs/community/discussions/categories/actions). - For general questions about using the runner images or writing your Actions workflow, please open requests in the [GitHub Community discussion Actions category](https://github.com/orgs/community/discussions/categories/actions). ## FAQs
What images are available for GitHub Actions and Azure DevOps? The availability of images for GitHub Actions and Azure DevOps is the same. However, deprecation policies may differ. See documentation for more details: - [GitHub Actions](https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners#supported-runners-and-hardware-resources) - [Azure DevOps](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#software)
What image version is used in my build? Usually, image deployment takes 2-3 days, and documentation in the `main` branch is only updated when deployment is finished. To find out which image version and what software versions are used in a specific build, see `Set up job` (GitHub Actions) or `Initialize job` (Azure DevOps) step log. actions-runner-image
Looking for other Linux distributions? We do not plan to offer other Linux distributions. We recommend using Docker if you'd like to build using other distributions with the hosted runner images. Alternatively, you can leverage [self-hosted runners] and fully customize your VM image to your needs.
How do I contribute to the macOS source? macOS source lives in this repository and is available for everyone. However, macOS image-generation CI doesn't support external contributions yet so we are not able to accept pull-requests for now. We are in the process of preparing macOS CI to accept contributions. Until then, we appreciate your patience and ask you to continue to make tool requests by filing issues.
How does GitHub determine what tools are installed on the images? For some tools, we always install the latest at the time of the deployment; for others, we pin the tool to specific version(s). For more details please see the [Preinstallation Policy](#preinstallation-policy)
How do I request that a new tool be pre-installed on the image? Please create an issue and get an approval from us to add this tool to the image before creating the pull request.
What branch should I use to build custom image? We strongly encourage customers to build their own images using the main branch. This repository contains multiple branches and releases that serve as document milestones to reflect what software is installed in the images at certain point of time. Current builds are not idempotent and if one tries to build a runner image using the specific tag it is not guaranteed that the build will succeed.
================================================ FILE: SECURITY.md ================================================ If you discover a security issue in this repo, please submit it through the [GitHub Security Bug Bounty](https://hackerone.com/github) Thanks for helping make GitHub Actions safe for everyone. ================================================ FILE: docs/create-image-and-azure-resources.md ================================================ # GitHub Actions Runner Images The runner-images project uses [Packer](https://www.packer.io/) to generate disk images for Windows 2022/2025 and Ubuntu 22.04/24.04. Each image is configured by a HCL2 Packer template that specifies where to build the image (Azure, in this case), and what steps to run to install software and prepare the disk. The Packer process initializes a connection to the Azure subscription using Azure CLI and creates temporary resources required for the build process: a resource group, network interfaces and a virtual machine from the "clean" image specified in the template. If the VM deployment succeeds, Packer connects to it using SSH or WinRM and begins executing installation steps from the template one-by-one. If any step fails, image generation is aborted, and the temporary VM is terminated. Packer also attempts to clean up all the temporary resources it created (unless otherwise configured). After successful completion of all installation steps, Packer creates a managed image from the temporary VM's disk and deletes the VM. - [Build Agent Preparation](#build-agent-preparation) - [Manual image generation](#manual-image-generation) - [Manual Image Generation Customization](#manual-image-generation-customization) - [Network Security](#network-security) - [Azure Subscription Authentication](#azure-subscription-authentication) - [Generated Machine Deployment](#generated-machine-deployment) - [Automated image generation](#automated-image-generation) - [Required variables](#required-variables) - [Optional variables](#optional-variables) - [Builder variables](#builder-variables) - [Toolset](#toolset) - [Post-generation scripts](#post-generation-scripts) - [Running scripts](#running-scripts) - [Script Details: Ubuntu](#script-details-ubuntu) - [Script Details: Windows](#script-details-windows) ## Build Agent Preparation The build agent is a machine where the Packer process will be started. You can use any physical or virtual machine running Windows or Linux OS. Of course, you may also use an [Azure VM](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/quick-create-cli). In any case, you will need these software installed: - Packer 1.8.2 or higher. Download and install it manually from [here](https://www.packer.io/downloads) or use [Chocolatey](https://chocolatey.org/): ```powershell choco install packer ``` - Git. For Linux - install the latest version from your distro's package repo. For Windows - download and install it from [here](https://gitforwindows.org/) or use [Chocolatey](https://chocolatey.org/): ```powershell choco install git -params '"/GitAndUnixToolsOnPath"' ``` - Powershell 5.0 or higher. In Windows you already have it. For Linux follow instructions [here](https://learn.microsoft.com/en-us/windows-server/administration/linux-package-repository-for-microsoft-software) to add Microsoft's Linux Software Repository and then install the `powershell` package. - Azure CLI. Follow the instructions [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli). Or if you use Windows, you may run this command in Powershell instead: ```powershell Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; rm .\AzureCLI.msi ``` ## Manual image generation This repository includes a script that assists in generating images in Azure. All you need is an Azure subscription, a resource group in that subscription and a build agent configured as described above. All the commands below should be executed in PowerShell. First, clone the runner-images repository and set the current directory to it: ```powershell git clone https://github.com/actions/runner-images.git Set-Location runner-images ``` Then, import the [GenerateResourcesAndImage](../helpers/GenerateResourcesAndImage.ps1) script from the `helpers` subdirectory: ```powershell Import-Module .\helpers\GenerateResourcesAndImage.ps1 ``` Finally, run the `GenerateResourcesAndImage` function, setting the mandatory arguments: image type and where to build and store the resulting managed image: - `SubscriptionId` - your Azure Subscription ID; - `ResourceGroupName` - the name of the resource group that will store the resulting artifact (e.g., "imagegen-test"). The resource group must already exist in your Azure subscription; - `AzureLocation` - the location where resources will be created (e.g., "East US"); - `ImageType` - the type of image to build (valid options are "Windows2022", "Windows2025", "Ubuntu2204", "Ubuntu2404"). This function automatically creates all required Azure resources and initiates the Packer image generation for the selected image type. When the image is ready, you may proceed to [deployment](#generated-machine-deployment). ## Manual Image Generation Customization The `GenerateResourcesAndImage` function accepts a number of arguments that may assist you in generating an image in your specific environment. For example, you may want all the resources involved in the image generation process to be tagged. In this case, pass a HashTable of tags as a value for the `Tags` parameter. If you don't want the function to authenticate interactively, you should create a Service Principal and invoke the function with the parameters `AzureClientId`, `AzureClientSecret` and `AzureTenantId`. You can find more details in the [corresponding section below](#azure-subscription-authentication). Use `get-help GenerateResourcesAndImage -Detailed` for the complete list of available parameters. ### Network Security To connect to a temporary virtual machine, Packer uses WinRM or SSH. If your build agent is located outside of the Azure subscription where the temporary VM is created, a public network interface and public IP address are used. Make sure that firewalls are configured properly and that WinRM (TCP port 5986) and SSH (TCP port 22) connections are allowed both outgoing for the build agent and incoming for the temporary VM. Also, if you don't want the temporary VM to be accessible from everywhere, set the `RestrictToAgentIpAddress` parameter value to `$true` to set up firewall rules allowing access only from your build agent's public IP address. If your build agent and temporary VM are in the same subscription, you can configure Packer to connect using a private virtual network. To achieve this, set proper values for the environment variables `VNET_RESOURCE_GROUP`, `VNET_NAME` and `VNET_SUBNET`. ### Azure Subscription Authentication Packer uses a Service Principal to authenticate in Azure infrastructure. For more information about Service Principals, refer to the [Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal). The `GenerateResourcesAndImage` function is able to create a Service Principal to be used by Packer. It uses the Connect-AzAccount cmdlet that invokes an interactive authentication process by default. If you don't want to use interactive authentication, you should create a Service Principal with full read-write permissions for the selected Azure subscription on your own and provide proper values for the parameters `AzureClientId`, `AzureClientSecret` and `AzureTenantId`. Here is an example of how to create a Service Principal using the Az PowerShell module: ```powershell $credentials = [Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential]@{ StartDateTime = Get-Date EndDateTime = (Get-Date).AddDays(7) } $sp = New-AzADServicePrincipal -DisplayName "imagegen-app" $appCred = New-AzADAppCredential -ApplicationId $sp.AppId -PasswordCredentials $credentials Start-Sleep -Seconds 30 New-AzRoleAssignment -RoleDefinitionName "Contributor" -PrincipalId $sp.Id Start-Sleep -Seconds 30 @{ ClientId = $sp.AppId ClientSecret = $appCred.SecretText TenantId = (Get-AzSubscription -SubscriptionId $SubscriptionId).TenantId } ``` ## Generated Machine Deployment After successful image generation, a Virtual Machine can be created from the generated image using the [CreateAzureVMFromPackerTemplate](../helpers/CreateAzureVMFromPackerTemplate.ps1) script. ```powershell Import-Module .\helpers\CreateAzureVMFromPackerTemplate.ps1 CreateAzureVMFromPackerTemplate -SubscriptionId {YourSubscriptionId} -ResourceGroupName {ResourceGroupName} -ManagedImageName "Runner-Image-Ubuntu2204" -VirtualMachineName "testvm1" -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus" ``` Where: - `SubscriptionId` - the Azure subscription ID where resources will be created; - `ResourceGroupName` - the Azure resource group name where the Azure virtual machine will be created; - `ManagedImageName` - the name of the managed image to be used for the virtual machine creation; - `VirtualMachineName` - the name of the virtual machine to be generated; - `AdminUserName` - the administrator username for the virtual machine to be created; - `AdminPassword` - the administrator password for the virtual machine to be created; - `AzureLocation` - the location where the Azure virtual machine will be provisioned (e.g., "eastus"). This function creates an Azure VM and generates network resources in Azure to make the VM accessible. ## Automated image generation If you want to generate images automatically (e.g., as a part of a CI/CD pipeline), you can use Packer directly. To do this, you will need: - a build agent configured as described in the [Build agent preparation](#build-agent-preparation) section; - an Azure subscription and Service Principal configured as described in the [Azure subscription authentication](#azure-subscription-authentication) section; - a resource group created in your Azure subscription where the managed image will be stored; - a string to be used as a password for the user used to install software (Windows only). Then, you can invoke Packer in your CI/CD pipeline using the following commands: ```powershell packer plugins install github.com/hashicorp/azure 2.2.1 packer build -only "$BuildName*" ` -var "subscription_id=$SubscriptionId" ` -var "client_id=$ClientId" ` -var "client_secret=$ClientSecret" ` -var "install_password=$InstallPassword" ` -var "location=$Location" ` -var "image_os=$ImageOS" ` -var "managed_image_name=$ImageName" ` -var "managed_image_resource_group_name=$ImageResourceGroupName" ` -var "tenant_id=$TenantId" ` $TemplatePath ``` Where: - `BuildName` - name of the build defined in Packer template's `build{}` block (e.g. "ubuntu-24_04", "windows-2025"); - `SubscriptionId` - your Azure Subscription ID; - `ClientId` and `ClientSecret` - Service Principal credentials; - `TenantId` - Azure Tenant ID; - `InstallPassword` - password for the user used to install software (Windows only); - `Location` - location where resources will be created (e.g., "East US"); - `ImageOS` - the type of OS that will be deployed as a temporary VM (e.g. "ubuntu24", "win25"); - `ImageName` and `ImageResourceGroupName` - name of the resource group where the managed image will be stored; - `TemplatePath` - path to the folder with Packer template files (e.g., "images/windows/templates"). ### Required variables The following variables are required to be passed to the Packer process: | Template var | Env var | Description | ------------ | ------- | ----------- | `subscription_id` | `ARM_SUBSCRIPTION_ID` | The subscription under which the build will be performed. | `client_id` | `ARM_CLIENT_ID` | The Active Directory service principal associated with your builder. | `client_secret` | `ARM_CLIENT_SECRET` | The password or secret for your service principal; may be omitted if `client_cert_path` is set. | `client_cert_path` | `ARM_CLIENT_CERT_PATH` | The location of a PEM file containing a certificate and private key for the service principal; may be omitted if `client_secret` is set. | `location` | `ARM_RESOURCE_LOCATION` | The Azure datacenter in which your VM will be built. | `managed_image_resource_group_name` | `ARM_RESOURCE_GROUP` | The resource group under which the final artifact will be stored. ### Optional variables The following variables are optional: - `managed_image_name` - the name of the managed image to create. If not specified, "Runner-Image-{{ImageType}}" will be used; - `build_resource_group_name` - specify an existing resource group to run the build in; by default, a temporary resource group will be created and destroyed as part of the build; if you do not have permission to do so, use `build_resource_group_name` to specify an existing resource group to run the build in; - `object_id` - the object ID for the AAD SP; will be derived from the oAuth token if empty; - `tenant_id` - the Active Directory tenant identifier with which your `client_id` and `subscription_id` are associated; if not specified, `tenant_id` will be looked up using `subscription_id`; - `temp_resource_group_name` - the name assigned to the temporary resource group created during the build; if this value is not set, a random value will be assigned; this resource group is deleted at the end of the build; - `private_virtual_network_with_public_ip` - this value allows you to set a `virtual_network_name` and obtain a public IP; if this value is not set and `virtual_network_name` is defined, Packer is only allowed to be executed from a host on the same subnet / virtual network; - `virtual_network_name` - use a pre-existing virtual network for the VM; this option enables private communication with the VM, no public IP address is used or provisioned (unless you set `private_virtual_network_with_public_ip`); - `virtual_network_resource_group_name` - if `virtual_network_name` is set, this value may also be set; if `virtual_network_name` is set, and this value is not set, the builder attempts to determine the resource group containing the virtual network; if the resource group cannot be found, or it cannot be disambiguated, this value should be set; - `virtual_network_subnet_name` - if `virtual_network_name` is set, this value may also be set; if `virtual_network_name` is set, and this value is not set, the builder attempts to determine the subnet to use with the virtual network; if the subnet cannot be found, or it cannot be disambiguated, this value should be set. ## Builder variables The `builders` section contains variables for the `azure-arm` builder used in the project. Most of the builder variables are inherited from the `user variables` section, however, the variables can be overwritten to adjust image-generation performance. - `vm_size` - the size of the VM used for building; this can be changed when you deploy a VM from your image; - `image_os` - the type of OS that will be deployed as a temporary VM; - `image_version` - specify the version of an OS to boot from. **Detailed Azure builders documentation can be found in the [packer documentation](https://www.packer.io/docs/builders/azure).** ## Toolset The configuration for some installed software is located in `toolset.json` files. These files define the list of Ruby, Python, Go versions, the list of PowerShell modules and VS components that will be installed on the image. They can be changed if these tools are not required, to reduce image generation time or image size. Generated tool versions and details can be found in related projects: - [Python](https://github.com/actions/python-versions/) - [Go](https://github.com/actions/go-versions) - [Node](https://github.com/actions/node-versions) ## Post-generation scripts > :warning: These scripts are intended to be run on a VM deployed in Azure The user, created during the image generation, does not exist in the resulting image. Hence, some configuration files related to the user's home directory need to be changed, as well as the file permissions for some directories. Scripts for that are located in the `post-gen` folder in the repository: - Windows: - Linux: **Note:** The default user for Linux should have `sudo privileges`. The scripts are copied to the image during the generation process to the following paths: - Windows: `C:\post-generation` - Linux: `/opt/post-generation` ### Running scripts - Ubuntu ```bash sudo su -c "find /opt/post-generation -mindepth 1 -maxdepth 1 -type f -name '*.sh' -exec bash {} \;" ``` - Windows ```powershell Get-ChildItem C:\post-generation -Filter *.ps1 | ForEach-Object { & $_.FullName } ``` ### Script Details: Ubuntu - **cleanup-logs.sh** - removes all build process logs from the machine; - **environment-variables.sh** - replaces `$HOME` with the default user's home directory for environment variables related to the default user home directory; - **homebrew-permissions.sh** - resets the Homebrew repository directory by running `git reset --hard` to make the working tree clean after changing permissions in /home and changes the repository directory owner to the current user; - **rust-permissions.sh** - fixes permissions for the Rust folder; a detailed issue explanation is provided in [runner-images/issues/572](https://github.com/actions/runner-images/issues/572). ### Script Details: Windows - **GenerateIISExpressCertificate.ps1** - generates and imports a certificate to run applications with IIS Express through HTTPS; - **InternetExplorerConfiguration.ps1** - turns off the Internet Explorer Enhanced Security feature; - **Msys2FirstLaunch.ps1** - initializes the bash user profile in MSYS2; - **VSConfiguration.ps1** - performs initial Visual Studio configuration. ================================================ FILE: docs/dotnet-ubuntu.md ================================================ # Ubuntu .NET Core Versions .NET has changed the recommended install methods for Ubuntu from 2404. This document gives an overview of these change and the impact this has on the `runner-images`. ## .NET Core for Ubuntu 2204 2204 uses the [Microsoft Package repository](https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu-install?tabs=dotnet8&pivots=os-linux-ubuntu-2204) to install .NET deb files built and published by the .NET team. ## .NET Core Versions from Ubuntu 2404 The .NET Core team have worked with Canonical and Ubuntu now provides its own .NET packages. These are the recommended install path and, as-such what is installed on the image. > The release of Ubuntu 24.04 is just around the corner. Canonical-produced .NET 6, 7, and 8 packages will be available on day one, for "Noble Numbat". Microsoft will not be publishing .NET packages to the 24.04 feed at packages.microsoft.com. You can read the [full announcement from .NET team here](https://github.com/dotnet/core/discussions/9258). We'll briefly summarize how this change may impact users of the image. ### [`Feature Bands`](https://learn.microsoft.com/dotnet/core/porting/versioning-sdk-msbuild-vs) Going forward only the `1xx` feature band will be present in the image as Ubuntu only build and publish this band. > Most distros, including Ubuntu, stick to the .1xx feature band for the lifetime of a major .NET version. They make this choice because .1xx is (effectively) the "compatibility band". Higher bands can have breaking changes. > This means there will no longer be packages available for .2xx and later feature bands. Such packages have been exclusively available from Microsoft. If users see an incompatibility between .1xx and higher feature bands, we ask that you please report it in the dotnet/sdk repo. [link: dotnet/core discussion](https://github.com/dotnet/core/discussions/9258) If you need a higher feature band for your Actions the recommendation is to use the [`setup-dotnet`](https://github.com/actions/setup-dotnet) action to install the desired version. ### .NET MAUI .NET MAUI is [not included](https://github.com/dotnet/core/discussions/9258#discussioncomment-9548857) in the Ubuntu .NET package. There is work [ongoing to fix.](https://github.com/dotnet/core/discussions/9258#discussioncomment-9548857) You should be able to resolve this by using the [`setup-dotnet`](https://github.com/actions/setup-dotnet) action to install the desired version. ================================================ FILE: helpers/CheckJsonSchema.ps1 ================================================ $ErrorActionPreference = 'Stop' # A JSON schema validator which supports outputting line numbers for errors # this allows us to put annotations on builds for errors in the JSON files # `Test-Json` built in cmdline doesn't. No existing cli tool supports this # that I could find either. See: https://github.com/lawrencegripper/gripdev-json-schema-validator Install-Module -Name GripDevJsonSchemaValidator -Force -Scope CurrentUser # Find all toolset JSON files $toolsetFiles = Get-ChildItem -Recurse -Filter "toolset-*.json" | Where-Object { $_.Name -notlike "*schema.json" } $schemaFilePath = "./schemas/toolset-schema.json" $toolsetHasErrors = $false foreach ($file in $toolsetFiles) { Write-Host "" Write-Host "🔍 Validating $($file.FullName)" -ForegroundColor Cyan $validationResult = Test-JsonSchema -SchemaPath $schemaFilePath -JsonPath $file.FullName -PrettyPrint $false if ($validationResult.Valid) { Write-Host "✅ JSON is valid." -ForegroundColor Green } else { # File has been modified since the commit, enforce validation $toolsetHasErrors = $true Write-Host "`n❌ JSON validation failed!" -ForegroundColor Red Write-Host " Found the following errors:`n" -ForegroundColor Yellow $validationResult.Errors | ForEach-Object { Write-Host $_.UserMessage if ($env:GITHUB_ACTIONS -eq 'true') { Write-Host "Adding annotation" Write-Host "::error file=$($file.Name),line=$($_.LineNumber)::$($_.UserMessage.Replace("`n", '%0A'))" } } } } if ($toolsetHasErrors) { Write-Error "One or more toolset JSON files failed schema validation. See the error output above for more details." } else { Write-Host "Schema validation completed successfully" } ================================================ FILE: helpers/CheckOutdatedVersionPinning.ps1 ================================================ $ErrorActionPreference = 'Stop' # Find all toolset JSON files $toolsetFiles = Get-ChildItem -Recurse -Filter "toolset-*.json" | Where-Object { $_.Name -notlike "*schema.json" } $expiringPins = @() $now = Get-Date $warningDays = 30 # Warn if expiring within 30 days foreach ($file in $toolsetFiles) { Write-Host "Processing $($file.Name)" $content = Get-Content $file.FullName | ConvertFrom-Json # Recursively search for pinnedDetails in the JSON function Search-PinnedDetails { param($obj, $path) $foundPins = @() if ($obj -is [System.Management.Automation.PSCustomObject]) { foreach ($prop in $obj.PSObject.Properties) { if ($prop.Name -eq "pinnedDetails") { Write-Host "Found pinned version at $path" $reviewAt = [DateTime]::Parse($prop.Value.'review-at') $daysUntilExpiry = ($reviewAt - $now).Days if ($daysUntilExpiry -lt $warningDays) { Write-Host "Adding to expiringPins array" $foundPins += @{ Path = $path File = $file.Name ReviewAt = $reviewAt DaysUntilExpiry = $daysUntilExpiry Reason = $prop.Value.reason Link = $prop.Value.link } } } else { $foundPins += Search-PinnedDetails -obj $prop.Value -path "$path.$($prop.Name)" } } } elseif ($obj -is [Array]) { for ($i = 0; $i -lt $obj.Count; $i++) { $foundPins += Search-PinnedDetails -obj $obj[$i] -path "$path[$i]" } } return $foundPins } $expiringPins += Search-PinnedDetails -obj $content -path $file.Name } if ($expiringPins) { $issueBody = "# Version Pinning Review Required`n`n" $issueBody += "The following pinned versions need review:`n`n" foreach ($pin in $expiringPins) { $status = if ($pin.DaysUntilExpiry -lt 0) { "EXPIRED" } else { "Expiring Soon" } $issueBody += "## $($status) - $($pin.Path)`n" $issueBody += "- **File**: $($pin.File)`n" $issueBody += "- **Review Date**: $($pin.ReviewAt.ToString('yyyy-MM-dd'))`n" $issueBody += "- **Days until expiry**: $($pin.DaysUntilExpiry)`n" $issueBody += "- **Reason**: $($pin.Reason)`n" $issueBody += "- **Original PR**: $($pin.Link)`n`n" } if ($env:GITHUB_ACTIONS -eq 'true') { # In GitHub Actions, create an issue Write-Host "Creating issue" $tempFile = [System.IO.Path]::GetTempFileName() Set-Content -Path $tempFile -Value $issueBody gh issue create --title "Version Pinning Review Found Expired Pinned Versions" --body-file $tempFile Remove-Item -Path $tempFile } Write-Host "`nIssue Content:`n" Write-Host $issueBody } else { Write-Host "No expiring pins found." if ($env:GITHUB_ACTIONS -eq 'true') { "expired_pins=0" >> $env:GITHUB_OUTPUT } } ================================================ FILE: helpers/CreateAzureVMFromPackerTemplate.ps1 ================================================ Function CreateAzureVMFromPackerTemplate { <# .SYNOPSIS A helper function to deploy a VM from a generated image. .DESCRIPTION Creates an Azure VM from a template. Also generates network resources in Azure to make the VM accessible. .PARAMETER SubscriptionId The Azure subscription Id where resources will be created. .PARAMETER ResourceGroupName The Azure resource group name where the Azure virtual machine will be created. .PARAMETER ManagedImageName The name of the managed image to be used to create the virtual machine. .PARAMETER VirtualMachineName The name of the virtual machine to be generated. .PARAMETER AdminUserName The administrator username for the virtual machine to be created. .PARAMETER AdminPassword The administrator password for the virtual machine to be created. .PARAMETER AzureLocation The location where the Azure virtual machine will be provisioned. Example: "eastus" .EXAMPLE CreateAzureVMFromPackerTemplate -SubscriptionId {SubscriptionId} -ResourceGroupName {ResourceGroupName} -VirtualMachineName "testvm1" -ManagedImageName {ManagedImageName} -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus" #> param ( [Parameter(Mandatory = $True)] [string] $SubscriptionId, [Parameter(Mandatory = $True)] [string] $ResourceGroupName, [Parameter(Mandatory = $True)] [string] $ManagedImageName, [Parameter(Mandatory = $True)] [string] $VirtualMachineName, [Parameter(Mandatory = $True)] [string] $AdminUsername, [Parameter(Mandatory = $True)] [string] $AdminPassword, [Parameter(Mandatory = $True)] [string] $AzureLocation ) $vmSize = "Standard_DS2_v2" $guid = [System.GUID]::NewGuid().ToString().ToUpper() $vnetName = $env:UserName + "vnet-" + $guid $subnetName = $env:UserName + "subnet-" + $guid $nicName = $env:UserName + "nic-" + $guid $publicIpName = $env:UserName + "pip-" + $guid Write-Host "Creating a virtual network and subnet" ($vnet = az network vnet create -g $ResourceGroupName -l $AzureLocation -n $vnetName --address-prefixes 10.0.0.0/16 --subnet-name $subnetName --subnet-prefixes 10.0.1.0/24 --subscription $subscriptionId -o json) $subnetId = ($vnet | ConvertFrom-Json).newVNet.subnets[0].id Write-Host "`nCreating a network interface controller (NIC)" ($nic = az network nic create -g $ResourceGroupName -l $AzureLocation -n $nicName --subnet $subnetId --subscription $subscriptionId -o json) $networkId = ($nic | ConvertFrom-Json).NewNIC.id Write-Host "`nCreating a public IP address" ($publicIp = az network public-ip create -g $ResourceGroupName -l $AzureLocation -n $publicIpName --allocation-method Static --sku Basic --version IPv4 --subscription $subscriptionId -o json) $publicIpId = ($publicIp | ConvertFrom-Json).publicIp.id Write-Host "`nAdding the public IP to the NIC" az network nic ip-config update -g $ResourceGroupName -n ipconfig1 --nic-name $nicName --public-ip-address $publicIpId --subscription $subscriptionId Write-Host "`nCreating the VM" az vm create ` --resource-group $ResourceGroupName ` --name $VirtualMachineName ` --image $ManagedImageName ` --size $vmSize ` --admin-username $AdminUsername ` --admin-password $AdminPassword ` --nics $networkId ` --subscription $subscriptionId ` --location $AzureLocation Write-Host "`nCreated in ${ResourceGroupName}:`n vnet ${vnetName}`n subnet ${subnetName}`n nic ${nicName}`n publicip ${publicIpName}`n vm ${VirtualMachineName}" } ================================================ FILE: helpers/GenerateResourcesAndImage.ps1 ================================================ $ErrorActionPreference = 'Stop' enum ImageType { Windows2022 = 1 Windows2025 = 2 Windows2025_vs2026 = 3 Ubuntu2204 = 4 Ubuntu2404 = 5 } Function Get-PackerTemplate { param ( [Parameter(Mandatory = $True)] [string] $RepositoryRoot, [Parameter(Mandatory = $True)] [ImageType] $ImageType ) switch ($ImageType) { # Note: Double Join-Path is required to support PowerShell 5.1 ([ImageType]::Windows2022) { $relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "build.windows-2022.pkr.hcl" $imageOS = "win22" } ([ImageType]::Windows2025) { $relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "build.windows-2025.pkr.hcl" $imageOS = "win25" } ([ImageType]::Windows2025_vs2026) { $relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "build.windows-2025-vs2026.pkr.hcl" $imageOS = "win25-vs2026" } ([ImageType]::Ubuntu2204) { $relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "build.ubuntu-22_04.pkr.hcl" $imageOS = "ubuntu22" } ([ImageType]::Ubuntu2404) { $relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "build.ubuntu-24_04.pkr.hcl" $imageOS = "ubuntu24" } default { throw "Unknown type of image" } } $imageTemplatePath = [IO.Path]::Combine($RepositoryRoot, "images", $relativeTemplatePath) # Specific template selection using Packer's "-only" functionality $buildName = [IO.Path]::GetFileName($imageTemplatePath).Split(".")[1] if (-not (Test-Path $imageTemplatePath)) { throw "Template for image '$ImageType' doesn't exist on path '$imageTemplatePath'." } return [PSCustomObject] @{ "BuildName" = $buildName "ImageOS" = $imageOS "Path" = [IO.Path]::GetDirectoryName($imageTemplatePath) } } Function Show-LatestCommit { [CmdletBinding()] param() process { $latestCommit = (git --no-pager log --pretty=format:"Date: %cd; Commit: %H - %s; Author: %an <%ae>" -1) Write-Host "Latest commit: $latestCommit." } } function Get-GitHubActionsOidcIdToken { [CmdletBinding()] param( [Parameter(Mandatory = $True)] [string] $RequestUrl, [Parameter(Mandatory = $True)] [string] $RequestToken, [Parameter(Mandatory = $False)] [string] $Audience = 'api://AzureADTokenExchange' ) $separator = if ($RequestUrl -match '\?') { '&' } else { '?' } $urlWithAudience = "${RequestUrl}${separator}audience=$([System.Uri]::EscapeDataString($Audience))" $headers = @{ Authorization = "Bearer $RequestToken" } try { $response = Invoke-RestMethod -Method Get -Uri $urlWithAudience -Headers $headers } catch { throw "Failed to request GitHub Actions OIDC ID token. Ensure workflow permissions include 'id-token: write'. Details: $($_.Exception.Message)" } if ([string]::IsNullOrEmpty($response.value)) { throw "GitHub Actions OIDC token response did not contain a 'value' field." } return $response.value } function Start-Sleep($seconds) { $doneDT = (Get-Date).AddSeconds($seconds) while ($doneDT -gt (Get-Date)) { $secondsLeft = $doneDT.Subtract((Get-Date)).TotalSeconds $percent = ($seconds - $secondsLeft) / $seconds * 100 Write-Progress -Activity "Sleeping" -Status "Sleeping..." -SecondsRemaining $secondsLeft -PercentComplete $percent [System.Threading.Thread]::Sleep(500) } Write-Progress -Activity "Sleeping" -Status "Sleeping..." -SecondsRemaining 0 -Completed } Function GenerateResourcesAndImage { <# .SYNOPSIS A helper function to help generate an image. .DESCRIPTION This function will generate the Azure resources and image for the specified image type. .PARAMETER SubscriptionId The Azure subscription id where the Azure resources will be created. .PARAMETER ResourceGroupName The name of the resource group to store the resulting artifact. Resource group must already exist. .PARAMETER ImageType The type of image to generate. Valid values are: Windows2022, Windows2025, Windows2025_vs2026, Ubuntu2204, Ubuntu2404. .PARAMETER ManagedImageName The name of the managed image to create. The default is "Runner-Image-{{ImageType}}". .PARAMETER AzureLocation The Azure location where the Azure resources will be created. For example: "East US" .PARAMETER ImageGenerationRepositoryRoot The root directory of the image generation repository. This is used to locate the packer template. .PARAMETER SecondsToWaitForServicePrincipalSetup The number of seconds to wait for the service principal to be setup. The default is 120 seconds. .PARAMETER AzureClientId The Azure client id to use to authenticate with Azure. If not specified, the current user's credentials will be used. .PARAMETER AzureClientSecret The Azure client secret to use to authenticate with Azure. If not specified, the current user's credentials will be used. .PARAMETER AzureTenantId The Azure tenant id to use to authenticate with Azure. If not specified, the current user's credentials will be used. .PARAMETER UseOidc If set, authenticate using GitHub Actions OIDC (federated credentials) instead of a client secret. Requires AzureClientId and AzureTenantId, and OidcRequestToken/OidcRequestUrl parameters. .PARAMETER OidcRequestToken GitHub Actions OIDC request token. .PARAMETER OidcRequestUrl GitHub Actions OIDC request URL. .PARAMETER RestrictToAgentIpAddress If set, access to the VM used by packer to generate the image is restricted to the public IP address this script is run from. This parameter cannot be used in combination with the virtual_network_name packer parameter. .PARAMETER OnError Specify how packer handles an error during image creation. Options: abort - abort immediately ask - ask user for input cleanup - attempt to cleanup and then abort run-cleanup-provisioner - run the cleanup provisioner and then abort The default is 'ask'. .PARAMETER Tags Tags to be applied to the Azure resources created. .PARAMETER PluginVersion Specify the version of the packer Azure plugin to use. The default is "2.2.1". .EXAMPLE GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "shsamytest1" -ImageGenerationRepositoryRoot "C:\runner-images" -ImageType Ubuntu2204 -AzureLocation "East US" #> param ( [Parameter(Mandatory = $True)] [string] $SubscriptionId, [Parameter(Mandatory = $True)] [string] $ResourceGroupName, [Parameter(Mandatory = $True)] [ImageType] $ImageType, [Parameter(Mandatory = $False)] [string] $ManagedImageName = "Runner-Image-$($ImageType)", [Parameter(Mandatory = $True)] [string] $AzureLocation, [Parameter(Mandatory = $False)] [string] $ImageGenerationRepositoryRoot = $pwd, [Parameter(Mandatory = $False)] [int] $SecondsToWaitForServicePrincipalSetup = 120, [Parameter(Mandatory = $False)] [string] $AzureClientId, [Parameter(Mandatory = $False)] [string] $AzureClientSecret, [Parameter(Mandatory = $False)] [string] $AzureTenantId, [Parameter(Mandatory = $False)] [switch] $UseOidc, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [string] $OidcRequestToken, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [string] $OidcRequestUrl, [Parameter(Mandatory = $False)] [string] $PluginVersion = "2.2.1", [Parameter(Mandatory = $False)] [switch] $RestrictToAgentIpAddress, [Parameter(Mandatory = $False)] [ValidateSet("abort", "ask", "cleanup", "run-cleanup-provisioner")] [string] $OnError = "ask", [Parameter(Mandatory = $False)] [hashtable] $Tags = @{} ) Show-LatestCommit -ErrorAction SilentlyContinue # Validate packer is installed $PackerBinary = Get-Command "packer" if (-not ($PackerBinary)) { throw "'packer' binary is not found on PATH." } # Get template path $PackerTemplate = Get-PackerTemplate -RepositoryRoot $ImageGenerationRepositoryRoot -ImageType $ImageType Write-Debug "Template path: $($PackerTemplate.Path)." # Prepare list of allowed inbound IP addresses if ($RestrictToAgentIpAddress) { $AgentIp = (Invoke-RestMethod https://ipinfo.io/json).ip if (-not $AgentIp) { throw "Unable to determine agent IP address." } Write-Host "Access to packer generated VM will be restricted to agent IP Address: $AgentIp." if ($PSVersionTable.PSVersion.Major -eq 5) { Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in allowed inbound IP addresses." $AllowedInboundIpAddresses = '[\"{0}\"]' -f $AgentIp } elseif ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) { Write-Verbose "PowerShell 7.0-7.2 detected. Replacing double quotes with escaped double quotes in allowed inbound IP addresses." $AllowedInboundIpAddresses = '[\"{0}\"]' -f $AgentIp } else { $AllowedInboundIpAddresses = '["{0}"]' -f $AgentIp } } else { $AllowedInboundIpAddresses = "[]" } Write-Debug "Allowed inbound IP addresses: $AllowedInboundIpAddresses." # Prepare tags $TagsList = $Tags.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" } Write-Debug "Tags list: $TagsList." $TagsJson = $Tags | ConvertTo-Json -Compress if ($PSVersionTable.PSVersion.Major -eq 5) { Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in tags JSON." $TagsJson = $TagsJson -replace '"', '\"' } elseif ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) { Write-Verbose "PowerShell 7.0-7.2 detected. Replacing double quotes with escaped double quotes in tags JSON." $TagsJson = $TagsJson -replace '"', '\"' } Write-Debug "Tags JSON: $TagsJson." $InstallPassword = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper() Write-Host "Downloading packer plugins..." & $PackerBinary plugins install github.com/hashicorp/azure $PluginVersion if ($LastExitCode -ne 0) { throw "Packer plugins download failed." } Write-Host "Validating packer template..." $validateClientSecret = "fake" if ($UseOidc) { $validateClientSecret = "" } & $PackerBinary validate ` "-only=$($PackerTemplate.BuildName).*" ` "-var=client_id=fake" ` "-var=client_secret=$($validateClientSecret)" ` "-var=oidc_request_token=fake" ` "-var=oidc_request_url=fake" ` "-var=subscription_id=$($SubscriptionId)" ` "-var=tenant_id=fake" ` "-var=location=$($AzureLocation)" ` "-var=image_os=$($PackerTemplate.ImageOS)" ` "-var=managed_image_name=$($ManagedImageName)" ` "-var=managed_image_resource_group_name=$($ResourceGroupName)" ` "-var=install_password=$($InstallPassword)" ` "-var=allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" ` "-var=azure_tags=$($TagsJson)" ` $PackerTemplate.Path if ($LastExitCode -ne 0) { throw "Packer template validation failed." } try { # Login to Azure subscription if ([string]::IsNullOrEmpty($AzureClientId)) { Write-Verbose "No AzureClientId was provided, will use interactive login." az login --output none } elseif ($UseOidc) { if ([string]::IsNullOrEmpty($AzureTenantId)) { throw "AzureTenantId is required for OIDC authentication." } Write-Verbose "Using OIDC service principal login (federated credentials)." $idToken = Get-GitHubActionsOidcIdToken -RequestUrl $OidcRequestUrl -RequestToken $OidcRequestToken az login --service-principal --username $AzureClientId --tenant $AzureTenantId --federated-token $idToken --output none } else { if ([string]::IsNullOrEmpty($AzureClientSecret) -or [string]::IsNullOrEmpty($AzureTenantId)) { throw "AzureClientSecret and AzureTenantId are required for service principal login unless -UseOidc is specified." } Write-Verbose "AzureClientId was provided, will use service principal login (client secret)." az login --service-principal --username $AzureClientId --password=$AzureClientSecret --tenant $AzureTenantId --output none } az account set --subscription $SubscriptionId if ($LastExitCode -ne 0) { throw "Failed to login to Azure subscription '$SubscriptionId'." } # Check resource group $ResourceGroupExists = [System.Convert]::ToBoolean((az group exists --name $ResourceGroupName)); if ($ResourceGroupExists) { Write-Verbose "Resource group '$ResourceGroupName' already exists." } else { throw "Resource group '$ResourceGroupName' does not exist." } # Create / choose authentication for packer if ([string]::IsNullOrEmpty($AzureClientId)) { Write-Host "Creating service principal for packer..." $ADCleanupRequired = $true $ServicePrincipalName = "packer-" + [System.GUID]::NewGuid().ToString().ToUpper() $ServicePrincipal = az ad sp create-for-rbac --name $ServicePrincipalName --role Contributor --scopes /subscriptions/$SubscriptionId --only-show-errors | ConvertFrom-Json if ($LastExitCode -ne 0) { throw "Failed to create service principal '$ServicePrincipalName'." } $ServicePrincipalAppId = $ServicePrincipal.appId $ServicePrincipalPassword = $ServicePrincipal.password $TenantId = $ServicePrincipal.tenant Write-Verbose "Waiting for service principal to propagate..." Start-Sleep $SecondsToWaitForServicePrincipalSetup Write-Host "Service principal created with id '$ServicePrincipalAppId'. It will be deleted after the build." } else { if ($UseOidc) { if ([string]::IsNullOrEmpty($AzureTenantId)) { throw "AzureTenantId is required for OIDC authentication." } $ServicePrincipalAppId = $AzureClientId $ServicePrincipalPassword = "" $TenantId = $AzureTenantId # Avoid leaking OIDC request values via command line arguments. $env:PKR_VAR_oidc_request_token = $OidcRequestToken $env:PKR_VAR_oidc_request_url = $OidcRequestUrl } else { if ([string]::IsNullOrEmpty($AzureClientSecret) -or [string]::IsNullOrEmpty($AzureTenantId)) { throw "AzureClientSecret and AzureTenantId are required for service principal authentication unless -UseOidc is specified." } $ServicePrincipalAppId = $AzureClientId $ServicePrincipalPassword = $AzureClientSecret $TenantId = $AzureTenantId } } Write-Debug "Service principal app id: $ServicePrincipalAppId." Write-Debug "Tenant id: $TenantId." & $PackerBinary build -on-error="$($OnError)" ` -only "$($PackerTemplate.BuildName).*" ` -var "client_id=$($ServicePrincipalAppId)" ` -var "client_secret=$($ServicePrincipalPassword)" ` -var "oidc_request_token=$($env:PKR_VAR_oidc_request_token)" ` -var "oidc_request_url=$($env:PKR_VAR_oidc_request_url)" ` -var "subscription_id=$($SubscriptionId)" ` -var "tenant_id=$($TenantId)" ` -var "location=$($AzureLocation)" ` -var "image_os=$($PackerTemplate.ImageOS)" ` -var "managed_image_name=$($ManagedImageName)" ` -var "managed_image_resource_group_name=$($ResourceGroupName)" ` -var "install_password=$($InstallPassword)" ` -var "allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" ` -var "azure_tags=$($TagsJson)" ` $PackerTemplate.Path if ($LastExitCode -ne 0) { throw "Failed to build image." } } catch { Write-Error $_ } finally { Write-Verbose "`nCleaning up..." # Remove ADServicePrincipal and ADApplication if ($ADCleanupRequired) { Write-Host "Removing ADServicePrincipal..." if (az ad sp show --id $ServicePrincipalAppId --query id) { az ad sp delete --id $ServicePrincipalAppId } Write-Host "Removing ADApplication..." if (az ad app show --id $ServicePrincipalAppId --query id) { az ad app delete --id $ServicePrincipalAppId } } Write-Verbose "Cleanup completed." } } ================================================ FILE: helpers/GitHubApi.psm1 ================================================ class GithubApi { [string] $Repository [object] hidden $AuthHeader GithubApi( [string] $Repository, [string] $AccessToken ) { $this.Repository = $Repository $this.AuthHeader = $this.BuildAuth($AccessToken) } [object] hidden BuildAuth([string]$AccessToken) { if ([string]::IsNullOrEmpty($AccessToken)) { return $null } $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("'':${AccessToken}")) return @{ Authorization = "Basic ${base64AuthInfo}" } } [string] hidden BuildBaseUrl([string]$Repository, [string]$ApiPrefix) { return "https://$ApiPrefix.github.com/repos/$Repository" } [object] GetWorkflowRuns([string]$WorkflowId) { $url = "actions/workflows/$WorkflowId/runs" $response = $this.InvokeRestMethod($url, 'GET', $null, $null) return $response } [object] GetWorkflowRun([string]$WorkflowRunId) { $url = "actions/runs/$WorkflowRunId" $response = $this.InvokeRestMethod($url, 'GET', $null, $null) return $response } [object] DispatchWorkflow([string]$EventType, [object]$EventPayload) { $url = "dispatches" $body = @{ "event_type" = $EventType "client_payload" = $EventPayload } | ConvertTo-Json $response = $this.InvokeRestMethod($url, 'POST', $null, $body) return $response } [object] CancelWorkflowRun([string]$workflowRunId) { $url = "actions/runs/$workflowRunId/cancel" $response = $this.InvokeRestMethod($url, 'POST', $null, $null) return $response } [string] hidden BuildUrl([string]$url, [string]$RequestParams, [string]$ApiPrefix) { $baseUrl = $this.BuildBaseUrl($this.Repository, $ApiPrefix) if ([string]::IsNullOrEmpty($RequestParams)) { return "$($baseUrl)/$($url)" } else { return "$($baseUrl)/$($url)?$($requestParams)" } } [object] hidden InvokeRestMethod( [string] $url, [string] $Method, [string] $RequestParams, [string] $body ) { $requestUrl = $this.BuildUrl($url, $RequestParams, "api") $params = @{ Method = $Method ContentType = "application/json" Uri = $requestUrl Headers = @{} } if ($this.AuthHeader) { $params.Headers += $this.AuthHeader } if (![string]::IsNullOrEmpty($body)) { $params.Body = $body } $response = Invoke-RestMethod @params return $response } } function Get-GithubApi { param ( [string] $Repository, [string] $AccessToken ) return [GithubApi]::New($Repository, $AccessToken) } ================================================ FILE: helpers/WaitWorkflowCompletion.ps1 ================================================ Param ( [Parameter(Mandatory)] [string] $WorkflowRunId, [Parameter(Mandatory)] [string] $Repository, [Parameter(Mandatory)] [string] $AccessToken, [int] $RetryIntervalSeconds = 300, [int] $MaxRetryCount = 0 ) Import-Module (Join-Path $PSScriptRoot "GitHubApi.psm1") function Wait-ForWorkflowCompletion($WorkflowRunId, $RetryIntervalSeconds) { do { Start-Sleep -Seconds $RetryIntervalSeconds $workflowRun = $gitHubApi.GetWorkflowRun($WorkflowRunId) } until ($workflowRun.status -eq "completed") return $workflowRun } $gitHubApi = Get-GithubApi -Repository $Repository -AccessToken $AccessToken $attempt = 1 do { $finishedWorkflowRun = Wait-ForWorkflowCompletion -WorkflowRunId $WorkflowRunId -RetryIntervalSeconds $RetryIntervalSeconds Write-Host "Workflow run finished with result: $($finishedWorkflowRun.conclusion)" if ($finishedWorkflowRun.conclusion -in ("success", "cancelled", "timed_out")) { break } elseif ($finishedWorkflowRun.conclusion -eq "failure") { if ($attempt -le $MaxRetryCount) { Write-Host "Workflow run will be restarted. Attempt $attempt of $MaxRetryCount" $gitHubApi.ReRunFailedJobs($WorkflowRunId) $attempt += 1 } else { break } } } while ($true) Write-Host "Last result: $($finishedWorkflowRun.conclusion)." "CI_WORKFLOW_RUN_RESULT=$($finishedWorkflowRun.conclusion)" | Out-File -Append -FilePath $env:GITHUB_ENV if ($finishedWorkflowRun.conclusion -in ("failure", "cancelled", "timed_out")) { exit 1 } ================================================ FILE: helpers/software-report-base/Calculate-ImagesDifference.ps1 ================================================ using module ./SoftwareReport.psm1 using module ./SoftwareReport.DifferenceCalculator.psm1 <# .SYNOPSIS Calculates the difference between two software reports and saves it to a file. .PARAMETER PreviousJsonReportPath Path to the previous software report. .PARAMETER CurrentJsonReportPath Path to the current software report. .PARAMETER OutputFile Path to the file where the difference will be saved. .PARAMETER ReleaseBranchName Name of the release branch to build image docs URL. .PARAMETER ReadmePath Path to the README file in repository to build image docs URL. #> Param ( [Parameter(Mandatory=$true)] [string] $PreviousJsonReportPath, [Parameter(Mandatory=$true)] [string] $CurrentJsonReportPath, [Parameter(Mandatory=$true)] [string] $OutputFile, [Parameter(Mandatory=$false)] [string] $ReleaseBranchName, [Parameter(Mandatory=$false)] [string] $ReadmePath ) $ErrorActionPreference = "Stop" $global:ErrorView = "NormalView" function Read-SoftwareReport { Param ( [Parameter(Mandatory=$true)] [string] $JsonReportPath ) if (-not (Test-Path $JsonReportPath)) { throw "File '$JsonReportPath' does not exist" } $jsonReport = Get-Content -Path $JsonReportPath -Raw $report = [SoftwareReport]::FromJson($jsonReport) return $report } $previousReport = Read-SoftwareReport -JsonReportPath $PreviousJsonReportPath $currentReport = Read-SoftwareReport -JsonReportPath $CurrentJsonReportPath $comparer = [SoftwareReportDifferenceCalculator]::new($previousReport, $currentReport) $comparer.CompareReports() $diff = $comparer.GetMarkdownReport() if ($ReleaseBranchName -and $ReadmePath) { # https://github.com/actions/runner-images/blob/releases/macOS-12/20221215/images/macos/macos-12-Readme.md $ImageDocsUrl = "https://github.com/actions/runner-images/blob/${ReleaseBranchName}/${ReadmePath}" $diff += "`n`n`nFor comprehensive list of software installed on this image please click [here]($ImageDocsUrl)." } $parentDirectory = Split-Path $OutputFile -Parent if (-not (Test-Path $parentDirectory)) { New-Item -Path $parentDirectory -ItemType Directory | Out-Null } $diff | Out-File -Path $OutputFile -Encoding utf8NoBOM ================================================ FILE: helpers/software-report-base/SoftwareReport.BaseNodes.psm1 ================================================ ############################ ### Abstract base nodes #### ############################ # Abstract base class for all nodes class BaseNode { [Boolean] ShouldBeIncludedToDiff() { return $false } [String] ToMarkdown() { return $this.ToMarkdown(1) } [String] ToMarkdown([Int32] $Level) { throw "Abstract method 'ToMarkdown(level)' is not implemented for '$($this.GetType().Name)'" } [Boolean] IsSimilarTo([BaseNode] $OtherNode) { throw "Abstract method 'IsSimilarTo' is not implemented for '$($this.GetType().Name)'" } [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { throw "Abstract method 'IsIdenticalTo' is not implemented for '$($this.GetType().Name)'" } } # Abstract base class for all nodes that describe a tool and should be rendered inside diff table class BaseToolNode: BaseNode { [ValidateNotNullOrEmpty()] [String] $ToolName BaseToolNode([String] $ToolName) { $this.ToolName = $ToolName } [Boolean] ShouldBeIncludedToDiff() { return $true } [String] GetValue() { throw "Abstract method 'GetValue' is not implemented for '$($this.GetType().Name)'" } [Boolean] IsSimilarTo([BaseNode] $OtherNode) { if ($this.GetType() -ne $OtherNode.GetType()) { return $false } return $this.ToolName -eq $OtherNode.ToolName } [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { return $this.IsSimilarTo($OtherNode) -and ($this.GetValue() -eq $OtherNode.GetValue()) } } ================================================ FILE: helpers/software-report-base/SoftwareReport.DifferenceCalculator.psm1 ================================================ using module ./SoftwareReport.psm1 using module ./SoftwareReport.BaseNodes.psm1 using module ./SoftwareReport.Nodes.psm1 using module ./SoftwareReport.DifferenceRender.psm1 class SoftwareReportDifferenceCalculator { [ValidateNotNullOrEmpty()] hidden [SoftwareReport] $PreviousReport [ValidateNotNullOrEmpty()] hidden [SoftwareReport] $CurrentReport hidden [Collections.Generic.List[ReportDifferenceItem]] $AddedItems hidden [Collections.Generic.List[ReportDifferenceItem]] $ChangedItems hidden [Collections.Generic.List[ReportDifferenceItem]] $DeletedItems SoftwareReportDifferenceCalculator([SoftwareReport] $PreviousReport, [SoftwareReport] $CurrentReport) { $this.PreviousReport = $PreviousReport $this.CurrentReport = $CurrentReport } [void] CompareReports() { $this.AddedItems = @() $this.ChangedItems = @() $this.DeletedItems = @() $this.CompareInternal($this.PreviousReport.Root, $this.CurrentReport.Root, @()) } [String] GetMarkdownReport() { $reporter = [SoftwareReportDifferenceRender]::new() $report = $reporter.GenerateMarkdownReport($this.CurrentReport, $this.PreviousReport, $this.AddedItems, $this.ChangedItems, $this.DeletedItems) return $report } hidden [void] CompareInternal([HeaderNode] $previousReportPointer, [HeaderNode] $currentReportPointer, [String[]] $Headers) { $currentReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object { $currentReportNode = $_ $sameNodeInPreviousReport = $previousReportPointer ? $previousReportPointer.FindSimilarChildNode($currentReportNode) : $null if ($currentReportNode -is [HeaderNode]) { # Compare HeaderNode recursively $this.CompareInternal($sameNodeInPreviousReport, $currentReportNode, $Headers + $currentReportNode.Title) } else { if ($sameNodeInPreviousReport -and ($currentReportNode.IsIdenticalTo($sameNodeInPreviousReport))) { # Nodes are identical, nothing changed, just ignore it } elseif ($sameNodeInPreviousReport) { # Nodes are equal but not identical, something was changed if ($currentReportNode -is [TableNode]) { $this.CompareSimilarTableNodes($sameNodeInPreviousReport, $currentReportNode, $Headers) } elseif ($currentReportNode -is [ToolVersionsListNode]) { $this.CompareSimilarToolVersionsListNodes($sameNodeInPreviousReport, $currentReportNode, $Headers) } else { $this.ChangedItems.Add([ReportDifferenceItem]::new($sameNodeInPreviousReport, $currentReportNode, $Headers)) } } else { # Node was not found in previous report, new node was added $this.AddedItems.Add([ReportDifferenceItem]::new($null, $currentReportNode, $Headers)) } } } # Detecting nodes that were removed $previousReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object { $previousReportNode = $_ $sameNodeInCurrentReport = $currentReportPointer ? $currentReportPointer.FindSimilarChildNode($previousReportNode) : $null if (-not $sameNodeInCurrentReport) { if ($previousReportNode -is [HeaderNode]) { # Compare removed HeaderNode recursively $this.CompareInternal($previousReportNode, $null, $Headers + $previousReportNode.Title) } else { # Node was not found in current report, node was removed $this.DeletedItems.Add([ReportDifferenceItem]::new($previousReportNode, $null, $Headers)) } } } } hidden [void] CompareSimilarTableNodes([TableNode] $PreviousReportNode, [TableNode] $CurrentReportNode, [String[]] $Headers) { $addedRows = $CurrentReportNode.Rows | Where-Object { $_ -notin $PreviousReportNode.Rows } $deletedRows = $PreviousReportNode.Rows | Where-Object { $_ -notin $CurrentReportNode.Rows } if (($addedRows.Count -eq 0) -and ($deletedRows.Count -eq 0)) { # Unexpected state: TableNodes are identical return } if ($PreviousReportNode.Headers -ne $CurrentReportNode.Headers) { # If headers are changed and rows are changed at the same time, we should track it as removing table and adding new one $this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $null, $Headers)) $this.AddedItems.Add([ReportDifferenceItem]::new($null, $CurrentReportNode, $Headers)) } elseif (($addedRows.Count -gt 0) -and ($deletedRows.Count -eq 0)) { # If new rows were added and no rows were deleted, then it is AddedItem $this.AddedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers)) } elseif (($deletedRows.Count -gt 0) -and ($addedRows.Count -eq 0)) { # If no rows were added and some rows were deleted, then it is DeletedItem $this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers)) } else { # If some rows were added and some rows were removed, then it is UpdatedItem $this.ChangedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers)) } } hidden [void] CompareSimilarToolVersionsListNodes([ToolVersionsListNode] $PreviousReportNode, [ToolVersionsListNode] $CurrentReportNode, [String[]] $Headers) { $previousReportMajorVersions = $PreviousReportNode.Versions | ForEach-Object { $PreviousReportNode.ExtractMajorVersion($_) } $currentReportMajorVersion = $CurrentReportNode.Versions | ForEach-Object { $CurrentReportNode.ExtractMajorVersion($_) } $addedVersions = $CurrentReportNode.Versions | Where-Object { $CurrentReportNode.ExtractMajorVersion($_) -notin $previousReportMajorVersions } $deletedVersions = $PreviousReportNode.Versions | Where-Object { $PreviousReportNode.ExtractMajorVersion($_) -notin $currentReportMajorVersion } $changedPreviousVersions = $PreviousReportNode.Versions | Where-Object { ($PreviousReportNode.ExtractMajorVersion($_) -in $currentReportMajorVersion) -and ($_ -notin $CurrentReportNode.Versions) } $changedCurrentVersions = $CurrentReportNode.Versions | Where-Object { ($CurrentReportNode.ExtractMajorVersion($_) -in $previousReportMajorVersions) -and ($_ -notin $PreviousReportNode.Versions) } if ($addedVersions.Count -gt 0) { $this.AddedItems.Add([ReportDifferenceItem]::new($null, [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $addedVersions, $CurrentReportNode.MajorVersionRegex, "List"), $Headers)) } if ($deletedVersions.Count -gt 0) { $this.DeletedItems.Add([ReportDifferenceItem]::new([ToolVersionsListNode]::new($PreviousReportNode.ToolName, $deletedVersions, $PreviousReportNode.MajorVersionRegex, "List"), $null, $Headers)) } $previousChangedNode = ($changedPreviousVersions.Count -gt 0) ? [ToolVersionsListNode]::new($PreviousReportNode.ToolName, $changedPreviousVersions, $PreviousReportNode.MajorVersionRegex, "List") : $null $currentChangedNode = ($changedCurrentVersions.Count -gt 0) ? [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $changedCurrentVersions, $CurrentReportNode.MajorVersionRegex, "List") : $null if ($previousChangedNode -and $currentChangedNode) { $this.ChangedItems.Add([ReportDifferenceItem]::new($previousChangedNode, $currentChangedNode, $Headers)) } } hidden [Boolean] FilterExcludedNodes([BaseNode] $Node) { # We shouldn't show "Image Version" diff because it is already shown in report header if (($Node -is [ToolVersionNode]) -and ($Node.ToolName -eq "Image Version:")) { return $false } return $true } } ================================================ FILE: helpers/software-report-base/SoftwareReport.DifferenceRender.psm1 ================================================ using module ./SoftwareReport.psm1 using module ./SoftwareReport.BaseNodes.psm1 using module ./SoftwareReport.Nodes.psm1 class SoftwareReportDifferenceRender { [String] GenerateMarkdownReport([SoftwareReport] $CurrentReport, [SoftwareReport] $PreviousReport, [ReportDifferenceItem[]] $AddedItems, [ReportDifferenceItem[]] $ChangedItems, [ReportDifferenceItem[]] $DeletedItems) { $sb = [System.Text.StringBuilder]::new() $rootNode = $CurrentReport.Root $imageVersion = $CurrentReport.GetImageVersion() $previousImageVersion = $PreviousReport.GetImageVersion() ############################# ### Render report header #### ############################# $sb.AppendLine("# :desktop_computer: Actions Runner Image: $($rootNode.Title)") # ToolVersionNodes on root level contains main image description so just copy-paste them to final report $rootNode.Children | Where-Object { $_ -is [ToolVersionNode] } | ForEach-Object { $sb.AppendLine($_.ToMarkdown()) } $sb.AppendLine() $sb.AppendLine("## :mega: What's changed?").AppendLine() ########################### ### Render added items #### ########################### [ReportDifferenceItem[]] $addedItemsBaseTools = $AddedItems | Where-Object { $_.IsBaseToolNode() } [ReportDifferenceItem[]] $addedItemsTables = $AddedItems | Where-Object { $_.IsTableNode() } if ($addedItemsBaseTools.Count + $addedItemsTables.Count -gt 0) { $sb.AppendLine("### Added :heavy_plus_sign:").AppendLine() } if ($addedItemsBaseTools.Count -gt 0) { $tableItems = $addedItemsBaseTools | ForEach-Object { [PSCustomObject]@{ "Category" = $this.RenderCategory($_.Headers, $true); "Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName); "Current ($imageVersion)" = $_.CurrentReportNode.GetValue(); } } $sb.AppendLine($this.RenderHtmlTable($tableItems, "Category")) } if ($addedItemsTables.Count -gt 0) { $addedItemsTables | ForEach-Object { $sb.AppendLine($this.RenderTableNodesDiff($_)) } } ############################# ### Render deleted items #### ############################# [ReportDifferenceItem[]] $deletedItemsBaseTools = $DeletedItems | Where-Object { $_.IsBaseToolNode() } [ReportDifferenceItem[]] $deletedItemsTables = $DeletedItems | Where-Object { $_.IsTableNode() } if ($deletedItemsBaseTools.Count + $deletedItemsTables.Count -gt 0) { $sb.AppendLine("### Deleted :heavy_minus_sign:").AppendLine() } if ($deletedItemsBaseTools.Count -gt 0) { $tableItems = $deletedItemsBaseTools | ForEach-Object { [PSCustomObject]@{ "Category" = $this.RenderCategory($_.Headers, $true); "Tool name" = $this.RenderToolName($_.PreviousReportNode.ToolName); "Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue(); } } $sb.AppendLine($this.RenderHtmlTable($tableItems, "Category")) } if ($deletedItemsTables.Count -gt 0) { $deletedItemsTables | ForEach-Object { $sb.AppendLine($this.RenderTableNodesDiff($_)) } } ############################# ### Render updated items #### ############################# [ReportDifferenceItem[]] $changedItemsBaseTools = $ChangedItems | Where-Object { $_.IsBaseToolNode() } [ReportDifferenceItem[]] $changedItemsTables = $ChangedItems | Where-Object { $_.IsTableNode() } if ($changedItemsBaseTools.Count + $changedItemsTables.Count -gt 0) { $sb.AppendLine("### Updated").AppendLine() } if ($changedItemsBaseTools.Count -gt 0) { $tableItems = $changedItemsBaseTools | ForEach-Object { [PSCustomObject]@{ "Category" = $this.RenderCategory($_.Headers, $true); "Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName); "Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue(); "Current ($imageVersion)" = $_.CurrentReportNode.GetValue(); } } $sb.AppendLine($this.RenderHtmlTable($tableItems, "Category")) } if ($changedItemsTables.Count -gt 0) { $changedItemsTables | ForEach-Object { $sb.AppendLine($this.RenderTableNodesDiff($_)) } } return $sb.ToString() } [String] RenderHtmlTable([PSCustomObject[]] $Table, [String] $RowSpanColumnName) { $headers = $Table[0].PSObject.Properties.Name $sb = [System.Text.StringBuilder]::new() $sb.AppendLine("") $sb.AppendLine(" ") $headers | ForEach-Object { $sb.AppendLine(" ") } $sb.AppendLine(" ") $sb.AppendLine(" ") $tableRowSpans = $this.CalculateHtmlTableRowSpan($Table, $RowSpanColumnName) for ($rowIndex = 0; $rowIndex -lt $Table.Count; $rowIndex++) { $row = $Table[$rowIndex] $sb.AppendLine(" ") $headers | ForEach-Object { if ($_ -eq $RowSpanColumnName) { if ($tableRowSpans[$rowIndex] -gt 0) { $sb.AppendLine(" ") } else { # Skip rendering this cell at all } } else { $sb.AppendLine(" ") } } $sb.AppendLine(" ") } $sb.AppendLine(" ") $sb.AppendLine("
$_
$($row.$_)$($row.$_)
") return $sb.ToString() } [int[]] CalculateHtmlTableRowSpan([PSCustomObject[]] $Table, [String] $keyColumn) { $result = @(0) * $Table.Count for ($rowIndex = $Table.Count - 1; $rowIndex -ge 0; $rowIndex--) { if (($rowIndex -lt ($Table.Count - 1)) -and ($Table[$rowIndex].$keyColumn -eq $Table[$rowIndex + 1].$keyColumn)) { # If the current row is the same as the next row # Then rowspan of current row should be equal to rowspan of the next row + 1 # And rowspan of the next row should be 0 because it is already included in the rowspan of the current row $result[$rowIndex] = $result[$rowIndex + 1] + 1 $result[$rowIndex + 1] = 0 } else { $result[$rowIndex] = 1 } } return $result } [String] RenderTableNodesDiff([ReportDifferenceItem] $DiffItem) { # Use the simplest approach for now: first, print all removed lines. Then print added lines # It will work well for most cases like changing existing rows, adding new rows and removing rows # But can produce not so pretty results for cases when some rows are changed and some rows are added at the same time # Let's see how it works in practice and improve it later if needed [String] $tableHeaders = ($DiffItem.CurrentReportNode ?? $DiffItem.PreviousReportNode).Headers [Collections.Generic.List[String]] $tableRows = @() $DiffItem.PreviousReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.CurrentReportNode.Rows } | ForEach-Object { $tableRows.Add($this.StrikeTableRow($_)) } $DiffItem.CurrentReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.PreviousReportNode.Rows } | ForEach-Object { $tableRows.Add($_) } $sb = [System.Text.StringBuilder]::new() $sb.AppendLine("#### $($this.RenderCategory($DiffItem.Headers, $false))") $sb.AppendLine([TableNode]::new($tableHeaders, $tableRows).ToMarkdown()) return $sb.ToString() } [String] RenderCategory([String[]] $Headers, [Boolean] $AddLineSeparator) { # Always skip the first header because it is "Installed Software" [String[]] $takeHeaders = $Headers | Select-Object -Skip 1 if ($takeHeaders.Count -eq 0) { return "" } $lineSeparator = $AddLineSeparator ? "
": "" return [String]::Join(" >$lineSeparator ", $takeHeaders) } [String] RenderToolName([String] $ToolName) { return $ToolName.TrimEnd(":") } [String] StrikeTableRow([String] $Row) { # Convert "a|b|c" to "~~a~~|~~b~~|~~c~~ $cells = $Row.Split("|") $strikedCells = $cells | ForEach-Object { "~~$($_)~~"} return [String]::Join("|", $strikedCells) } } # Temporary structure to store the single difference between two reports class ReportDifferenceItem { [BaseNode] $PreviousReportNode [BaseNode] $CurrentReportNode [String[]] $Headers ReportDifferenceItem([BaseNode] $PreviousReportNode, [BaseNode] $CurrentReportNode, [String[]] $Headers) { $this.PreviousReportNode = $PreviousReportNode $this.CurrentReportNode = $CurrentReportNode $this.Headers = $Headers } [Boolean] IsBaseToolNode() { $node = $this.CurrentReportNode ?? $this.PreviousReportNode return $node -is [BaseToolNode] } [Boolean] IsTableNode() { $node = $this.CurrentReportNode ?? $this.PreviousReportNode return $node -is [TableNode] } } ================================================ FILE: helpers/software-report-base/SoftwareReport.Nodes.psm1 ================================================ using module ./SoftwareReport.BaseNodes.psm1 ######################################### ### Nodes to describe image software #### ######################################### # NodesFactory is used to simplify parsing different types of notes # Every node has own logic of parsing and this method just invokes "FromJsonObject" of correct node type class NodesFactory { static [BaseNode] ParseNodeFromObject([object] $JsonObj) { if ($JsonObj.NodeType -eq [HeaderNode].Name) { return [HeaderNode]::FromJsonObject($JsonObj) } elseif ($JsonObj.NodeType -eq [ToolVersionNode].Name) { return [ToolVersionNode]::FromJsonObject($JsonObj) } elseif ($JsonObj.NodeType -eq [ToolVersionsListNode].Name) { return [ToolVersionsListNode]::FromJsonObject($JsonObj) } elseif ($JsonObj.NodeType -eq [TableNode].Name) { return [TableNode]::FromJsonObject($JsonObj) } elseif ($JsonObj.NodeType -eq [NoteNode].Name) { return [NoteNode]::FromJsonObject($JsonObj) } throw "Unknown node type in ParseNodeFromObject '$($JsonObj.NodeType)'" } } class HeaderNode: BaseNode { [ValidateNotNullOrEmpty()] [String] $Title [Collections.Generic.List[BaseNode]] $Children HeaderNode([String] $Title) { $this.Title = $Title $this.Children = @() } [Boolean] ShouldBeIncludedToDiff() { return $true } [void] AddNode([BaseNode] $node) { $similarNode = $this.FindSimilarChildNode($node) if ($similarNode) { throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.`nFound node: $($similarNode.ToJsonObject() | ConvertTo-Json)`nNew node: $($node.ToJsonObject() | ConvertTo-Json)" } if (-not $this.IsNodeHasMarkdownHeader($node)) { # If the node doesn't print own header to markdown, we should check that there is no other nodes that print header to markdown before it. # It is done to avoid unexpected situation like this: # # HeaderNode A -> # A # HeaderNode B -> ## B # ToolVersionNode C -> - C # ToolVersionNode D -> - D # # In this example, we add 'HeaderNode B" to 'HeaderNode A' and add 'ToolVersionNode C' to 'HeaderNode B'. # Then we add 'ToolVersionNode D' to 'HeaderNode A'. # But the result markdown will look like 'ToolVersionNode D' belongs to 'HeaderNode B' instead of 'HeaderNode A'. $this.Children | Where-Object { $this.IsNodeHasMarkdownHeader($_) } | ForEach-Object { throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node" } } $this.Children.Add($node) } [void] AddNodes([BaseNode[]] $nodes) { $nodes | ForEach-Object { $this.AddNode($_) } } [HeaderNode] AddHeader([String] $Title) { $node = [HeaderNode]::new($Title) $this.AddNode($node) return $node } [void] AddToolVersion([String] $ToolName, [String] $Version) { $this.AddNode([ToolVersionNode]::new($ToolName, $Version)) } [void] AddToolVersionsList([String] $ToolName, [String[]] $Version, [String] $MajorVersionRegex) { $this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, "List")) } [void] AddToolVersionsListInline([String] $ToolName, [String[]] $Version, [String] $MajorVersionRegex) { $this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, "Inline")) } [void] AddTable([PSCustomObject[]] $Table) { $this.AddNode([TableNode]::FromObjectsArray($Table)) } [void] AddNote([String] $Content) { $this.AddNode([NoteNode]::new($Content)) } [String] ToMarkdown([Int32] $Level) { $sb = [System.Text.StringBuilder]::new() $sb.AppendLine() $sb.AppendLine("$("#" * $Level) $($this.Title)") $this.Children | ForEach-Object { $sb.AppendLine($_.ToMarkdown($Level + 1)) } return $sb.ToString().TrimEnd() } [PSCustomObject] ToJsonObject() { return [PSCustomObject]@{ NodeType = $this.GetType().Name Title = $this.Title Children = $this.Children | ForEach-Object { $_.ToJsonObject() } } } static [HeaderNode] FromJsonObject([Object] $JsonObj) { $node = [HeaderNode]::new($JsonObj.Title) $JsonObj.Children | Where-Object { $_ } | ForEach-Object { $node.AddNode([NodesFactory]::ParseNodeFromObject($_)) } return $node } [Boolean] IsSimilarTo([BaseNode] $OtherNode) { if ($OtherNode.GetType() -ne [HeaderNode]) { return $false } return $this.Title -eq $OtherNode.Title } [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { return $this.IsSimilarTo($OtherNode) } [BaseNode] FindSimilarChildNode([BaseNode] $Find) { foreach ($childNode in $this.Children) { if ($childNode.IsSimilarTo($Find)) { return $childNode } } return $null } hidden [Boolean] IsNodeHasMarkdownHeader([BaseNode] $node) { if ($node -is [HeaderNode]) { return $true } if (($node -is [ToolVersionsListNode]) -and ($node.ListType -eq "List")) { return $true } return $false } } class ToolVersionNode: BaseToolNode { [ValidateNotNullOrEmpty()] [String] $Version ToolVersionNode([String] $ToolName, [String] $Version): base($ToolName) { if ([String]::IsNullOrEmpty($Version)) { throw "ToolVersionNode '$($this.ToolName)' has empty version" } $this.Version = $Version } [String] ToMarkdown([Int32] $Level) { return "- $($this.ToolName) $($this.Version)" } [String] GetValue() { return $this.Version } [PSCustomObject] ToJsonObject() { return [PSCustomObject]@{ NodeType = $this.GetType().Name ToolName = $this.ToolName Version = $this.Version } } static [BaseNode] FromJsonObject([Object] $JsonObj) { return [ToolVersionNode]::new($JsonObj.ToolName, $JsonObj.Version) } } class ToolVersionsListNode: BaseToolNode { [ValidateNotNullOrEmpty()] [String[]] $Versions [Regex] $MajorVersionRegex [ValidateSet("List", "Inline")] [String] $ListType ToolVersionsListNode([String] $ToolName, [String[]] $Versions, [String] $MajorVersionRegex, [String] $ListType): base($ToolName) { $this.Versions = $Versions if ([String]::IsNullOrEmpty($Versions)) { throw "ToolVersionsListNode '$($this.ToolName)' has empty versions list" } $this.MajorVersionRegex = [Regex]::new($MajorVersionRegex) $this.ListType = $ListType $this.ValidateMajorVersionRegex() } [String] ToMarkdown([Int32] $Level) { if ($this.ListType -eq "Inline") { return "- $($this.ToolName): $($this.Versions -join ', ')" } $sb = [System.Text.StringBuilder]::new() $sb.AppendLine() $sb.AppendLine("$("#" * $Level) $($this.ToolName)") $this.Versions | ForEach-Object { $sb.AppendLine("- $_") } return $sb.ToString().TrimEnd() } [String] GetValue() { return $this.Versions -join ', ' } [String] ExtractMajorVersion([String] $Version) { $match = $this.MajorVersionRegex.Match($Version) if (($match.Success -ne $true) -or [String]::IsNullOrEmpty($match.Groups[0].Value)) { throw "Version '$Version' doesn't match regex '$($this.PrimaryVersionRegex)'" } return $match.Groups[0].Value } [PSCustomObject] ToJsonObject() { return [PSCustomObject]@{ NodeType = $this.GetType().Name ToolName = $this.ToolName Versions = $this.Versions MajorVersionRegex = $this.MajorVersionRegex.ToString() ListType = $this.ListType } } static [ToolVersionsListNode] FromJsonObject([Object] $JsonObj) { return [ToolVersionsListNode]::new($JsonObj.ToolName, $JsonObj.Versions, $JsonObj.MajorVersionRegex, $JsonObj.ListType) } hidden [void] ValidateMajorVersionRegex() { $this.Versions | Group-Object { $this.ExtractMajorVersion($_) } | ForEach-Object { if ($_.Count -gt 1) { throw "Multiple versions from list '$($this.GetValue())' return the same result from regex '$($this.MajorVersionRegex)': $($_.Name)" } } } } class TableNode: BaseNode { # It is easier to store the table as rendered lines because it will simplify finding differences in rows later [ValidateNotNullOrEmpty()] [String] $Headers [ValidateNotNullOrEmpty()] [String[]] $Rows TableNode([String] $Headers, [String[]] $Rows) { $this.Headers = $Headers $this.Rows = $Rows $columnsCount = $this.Headers.Split("|").Count $this.Rows | ForEach-Object { if ($_.Split("|").Count -ne $columnsCount) { throw "Table has different number of columns in different rows" } } } [Boolean] ShouldBeIncludedToDiff() { return $true } [String] ToMarkdown([Int32] $Level) { $maxColumnWidths = $this.CalculateColumnsWidth() $columnsCount = $maxColumnWidths.Count $delimiterLine = [String]::Join("|", @("-") * $columnsCount) $sb = [System.Text.StringBuilder]::new() @($this.Headers) + @($delimiterLine) + $this.Rows | ForEach-Object { $sb.Append("|") $row = $_.Split("|") for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) { $padSymbol = $row[$colIndex] -eq "-" ? "-" : " " $cellContent = $row[$colIndex].PadRight($maxColumnWidths[$colIndex], $padSymbol) $sb.Append(" $($cellContent) |") } $sb.AppendLine() } return $sb.ToString().TrimEnd() } hidden [Int32[]] CalculateColumnsWidth() { $maxColumnWidths = $this.Headers.Split("|") | ForEach-Object { $_.Length } $columnsCount = $maxColumnWidths.Count $this.Rows | ForEach-Object { $columnWidths = $_.Split("|") | ForEach-Object { $_.Length } for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) { $maxColumnWidths[$colIndex] = [Math]::Max($maxColumnWidths[$colIndex], $columnWidths[$colIndex]) } } return $maxColumnWidths } [PSCustomObject] ToJsonObject() { return [PSCustomObject]@{ NodeType = $this.GetType().Name Headers = $this.Headers Rows = $this.Rows } } static [TableNode] FromJsonObject([Object] $JsonObj) { return [TableNode]::new($JsonObj.Headers, $JsonObj.Rows) } [Boolean] IsSimilarTo([BaseNode] $OtherNode) { if ($OtherNode.GetType() -ne [TableNode]) { return $false } # We don't support having multiple TableNode instances on the same header level so such check is fine return $true } [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { if (-not $this.IsSimilarTo($OtherNode)) { return $false } # We don't compare $this.Headers intentionally # It is fine to ignore the tables where headers are changed but rows are not changed if ($this.Rows.Count -ne $OtherNode.Rows.Count) { return $false } for ($rowIndex = 0; $rowIndex -lt $this.Rows.Count; $rowIndex++) { if ($this.Rows[$rowIndex] -ne $OtherNode.Rows[$rowIndex]) { return $false } } return $true } static [TableNode] FromObjectsArray([PSCustomObject[]] $Table) { if ($Table.Count -eq 0) { throw "Failed to create TableNode from empty objects array" } [String] $tableHeaders = [TableNode]::ArrayToTableRow($Table[0].PSObject.Properties.Name) [Collections.Generic.List[String]] $tableRows = @() $Table | ForEach-Object { $rowHeaders = [TableNode]::ArrayToTableRow($_.PSObject.Properties.Name) if (($rowHeaders -ne $tableHeaders)) { throw "Failed to create TableNode from objects array because objects have different properties" } $tableRows.Add([TableNode]::ArrayToTableRow($_.PSObject.Properties.Value)) } return [TableNode]::new($tableHeaders, $tableRows) } hidden static [String] ArrayToTableRow([String[]] $Values) { if ($Values.Count -eq 0) { throw "Failed to create TableNode because some objects are empty" } $Values | ForEach-Object { if ($_.Contains("|")) { throw "Failed to create TableNode because some cells '$_' contains forbidden symbol '|'" } } return [String]::Join("|", $Values) } } class NoteNode: BaseNode { [ValidateNotNullOrEmpty()] [String] $Content NoteNode([String] $Content) { $this.Content = $Content } [String] ToMarkdown([Int32] $Level) { return @( '```', $this.Content, '```' ) -join "`n" } [PSCustomObject] ToJsonObject() { return [PSCustomObject]@{ NodeType = $this.GetType().Name Content = $this.Content } } static [NoteNode] FromJsonObject([Object] $JsonObj) { return [NoteNode]::new($JsonObj.Content) } [Boolean] IsSimilarTo([BaseNode] $OtherNode) { if ($OtherNode.GetType() -ne [NoteNode]) { return $false } return $this.Content -eq $OtherNode.Content } [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { return $this.IsSimilarTo($OtherNode) } } ================================================ FILE: helpers/software-report-base/SoftwareReport.psm1 ================================================ using module ./SoftwareReport.BaseNodes.psm1 using module ./SoftwareReport.Nodes.psm1 class SoftwareReport { [ValidateNotNullOrEmpty()] [HeaderNode] $Root SoftwareReport([String] $Title) { $this.Root = [HeaderNode]::new($Title) } SoftwareReport([HeaderNode] $Root) { $this.Root = $Root } [String] ToJson() { return $this.Root.ToJsonObject() | ConvertTo-Json -Depth 10 } static [SoftwareReport] FromJson([String] $JsonString) { $jsonObj = $JsonString | ConvertFrom-Json $rootNode = [NodesFactory]::ParseNodeFromObject($jsonObj) return [SoftwareReport]::new($rootNode) } [String] ToMarkdown() { return $this.Root.ToMarkdown().Trim() } [String] GetImageVersion() { $imageVersionNode = $this.Root.Children ?? @() | Where-Object { ($_ -is [ToolVersionNode]) -and ($_.ToolName -eq "Image Version:") } | Select-Object -First 1 return $imageVersionNode.Version ?? "Unknown version" } } ================================================ FILE: helpers/software-report-base/tests/SoftwareReport.Difference.E2E.Tests.ps1 ================================================ using module ../SoftwareReport.psm1 using module ../SoftwareReport.DifferenceCalculator.psm1 Describe "Comparer.E2E" { It "Some tools are updated" { # Previous report $prevSoftwareReport = [SoftwareReport]::new("macOS 11") $prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)") $prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1") $prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software") $prevTools = $prevInstalledSoftware.AddHeader("Tools") $prevTools.AddToolVersion("ToolWillBeUpdated1", "1.0.0") $prevTools.AddToolVersion("ToolWillBeUpdated2", "3.0.1") $prevTools.AddToolVersionsList("ToolWillBeUpdated3", @("14.0.0", "15.5.1"), "^\d+") # Next report $nextSoftwareReport = [SoftwareReport]::new("macOS 11") $nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)") $nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1") $nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software") $nextTools = $nextInstalledSoftware.AddHeader("Tools") $nextTools.AddToolVersion("ToolWillBeUpdated1", "2.5.0") $nextTools.AddToolVersion("ToolWillBeUpdated2", "3.0.2") $nextTools.AddToolVersionsList("ToolWillBeUpdated3", @("14.2.0", "15.5.1"), "^\d+") # Compare reports $comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport) $comparer.CompareReports() $comparer.GetMarkdownReport() | Should -BeExactly @' # :desktop_computer: Actions Runner Image: macOS 11 - OS Version: macOS 11.7.1 (20G817) - Image Version: 20220922.1 ## :mega: What's changed? ### Updated
Category Tool name Previous (20220918.1) Current (20220922.1)
Tools ToolWillBeUpdated1 1.0.0 2.5.0
ToolWillBeUpdated2 3.0.1 3.0.2
ToolWillBeUpdated3 14.0.0 14.2.0
'@ } It "Some tools are updated, added and removed" { # Previous report $prevSoftwareReport = [SoftwareReport]::new("macOS 11") $prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)") $prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1") $prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software") $prevLanguagesAndRuntimes = $prevInstalledSoftware.AddHeader("Language and Runtime") $prevLanguagesAndRuntimes.AddToolVersion("ToolWillBeRemoved", "5.1.16(1)-release") $prevLanguagesAndRuntimes.AddToolVersionsListInline("ToolWithMultipleVersions3", @("1.2.100", "1.2.200", "1.3.500", "1.4.100", "1.4.200"), "^\d+\.\d+\.\d") $prevLanguagesAndRuntimes.AddToolVersion("ToolWithoutChanges", "5.34.0") $prevLanguagesAndRuntimes.AddToolVersion("ToolWillBeUpdated", "8.1.0") $prevCachedTools = $prevInstalledSoftware.AddHeader("Cached Tools") $prevCachedTools.AddToolVersionsList("ToolWithMultipleVersions1", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+") $prevCachedTools.AddToolVersionsList("ToolWithMultipleVersions2", @("14.8.0", "15.1.0", "16.4.2"), "^\d+") $prevSQLSection = $prevInstalledSoftware.AddHeader("Databases") $prevSQLSection.AddToolVersion("MineSQL", "6.1.0") $prevSQLSection.AddNote("First Note") # Next report $nextSoftwareReport = [SoftwareReport]::new("macOS 11") $nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.2 (20G922)") $nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.0") $nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software") $nextLanguagesAndRuntimes = $nextInstalledSoftware.AddHeader("Language and Runtime") $nextLanguagesAndRuntimes.AddToolVersion("ToolWillBeAdded", "16.18.0") $nextLanguagesAndRuntimes.AddToolVersionsListInline("ToolWithMultipleVersions3", @("1.2.200", "1.3.515", "1.4.100", "1.4.200", "1.5.800"), "^\d+\.\d+\.\d") $nextLanguagesAndRuntimes.AddToolVersion("ToolWithoutChanges", "5.34.0") $nextLanguagesAndRuntimes.AddToolVersion("ToolWillBeUpdated", "8.3.0") $nextCachedTools = $nextInstalledSoftware.AddHeader("Cached Tools") $nextCachedTools.AddToolVersionsList("ToolWithMultipleVersions1", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+") $nextCachedTools.AddToolVersionsList("ToolWithMultipleVersions2", @("15.1.0", "16.4.2", "17.0.1"), "^\d+") $nextSQLSection = $nextInstalledSoftware.AddHeader("Databases") $nextSQLSection.AddToolVersion("MineSQL", "6.1.1") $nextSQLSection.AddNote("Second Note") # Compare reports $comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport) $comparer.CompareReports() $comparer.GetMarkdownReport() | Should -BeExactly @' # :desktop_computer: Actions Runner Image: macOS 11 - OS Version: macOS 11.7.2 (20G922) - Image Version: 20220922.0 ## :mega: What's changed? ### Added :heavy_plus_sign:
Category Tool name Current (20220922.0)
Language and Runtime ToolWillBeAdded 16.18.0
ToolWithMultipleVersions3 1.5.800
Cached Tools ToolWithMultipleVersions2 17.0.1
### Deleted :heavy_minus_sign:
Category Tool name Previous (20220918.1)
Language and Runtime ToolWithMultipleVersions3 1.2.100
ToolWillBeRemoved 5.1.16(1)-release
Cached Tools ToolWithMultipleVersions2 14.8.0
### Updated
Category Tool name Previous (20220918.1) Current (20220922.0)
OS Version macOS 11.7.1 (20G817) macOS 11.7.2 (20G922)
Language and Runtime ToolWithMultipleVersions3 1.3.500 1.3.515
ToolWillBeUpdated 8.1.0 8.3.0
Databases MineSQL 6.1.0 6.1.1
'@ } It "Header tree changes" { # Previous report $prevSoftwareReport = [SoftwareReport]::new("macOS 11") $prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1") $prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software") $prevInstalledSoftware.AddToolVersion("ToolWithoutChanges", "5.34.0") $prevInstalledSoftware.AddHeader("HeaderWillBeRemoved").AddHeader("SubheaderWillBeRemoved").AddToolVersion("ToolWillBeRemoved", "1.0.0") $prevInstalledSoftware.AddHeader("Header1").AddToolVersion("ToolWillBeMovedToAnotherHeader", "3.0.0") # Next report $nextSoftwareReport = [SoftwareReport]::new("macOS 11") $nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.0") $nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software") $nextInstalledSoftware.AddToolVersion("ToolWithoutChanges", "5.34.0") $nextInstalledSoftware.AddHeader("HeaderWillBeAdded").AddHeader("SubheaderWillBeAdded").AddToolVersion("ToolWillBeAdded", "5.0.0") $nextInstalledSoftware.AddHeader("Header2").AddToolVersion("ToolWillBeMovedToAnotherHeader", "3.0.0") # Compare reports $comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport) $comparer.CompareReports() $comparer.GetMarkdownReport() | Should -BeExactly @' # :desktop_computer: Actions Runner Image: macOS 11 - Image Version: 20220922.0 ## :mega: What's changed? ### Added :heavy_plus_sign:
Category Tool name Current (20220922.0)
HeaderWillBeAdded >
SubheaderWillBeAdded
ToolWillBeAdded 5.0.0
Header2 ToolWillBeMovedToAnotherHeader 3.0.0
### Deleted :heavy_minus_sign:
Category Tool name Previous (20220918.1)
HeaderWillBeRemoved >
SubheaderWillBeRemoved
ToolWillBeRemoved 1.0.0
Header1 ToolWillBeMovedToAnotherHeader 3.0.0
'@ } It "Tables are added and removed" { # Previous report $prevSoftwareReport = [SoftwareReport]::new("macOS 11") $prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1") $prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software") $prevInstalledSoftware.AddHeader("HeaderWillExist").AddTable(@( [PSCustomObject]@{TableInExistingHeaderWillBeRemoved = "Q"; Value = "25"}, [PSCustomObject]@{TableInExistingHeaderWillBeRemoved = "O"; Value = "24"} )) $prevTools = $prevInstalledSoftware.AddHeader("Tools") $prevTools.AddHeader("HeaderWillBeRemoved").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "Z"; Value = "30"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "W"; Value = "29"} )) # Next report $nextSoftwareReport = [SoftwareReport]::new("macOS 11") $nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1") $nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software") $nextInstalledSoftware.AddHeader("HeaderWillExist") $nextTools = $nextInstalledSoftware.AddHeader("Tools") $nextTools.AddToolVersion("ToolWillBeAdded", "3.0.1") $nextTools.AddTable(@( [PSCustomObject]@{NewTableInExistingHeader = "A"; Value = "1"}, [PSCustomObject]@{NewTableInExistingHeader = "B"; Value = "2"} )) $nextTools.AddHeader("NewHeaderWithTable").AddTable(@( [PSCustomObject]@{NewTableInNewHeader = "C"; Value = "3"}, [PSCustomObject]@{NewTableInNewHeader = "D"; Value = "4"} )) # Compare reports $comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport) $comparer.CompareReports() $comparer.GetMarkdownReport() | Should -BeExactly @' # :desktop_computer: Actions Runner Image: macOS 11 - Image Version: 20220922.1 ## :mega: What's changed? ### Added :heavy_plus_sign:
Category Tool name Current (20220922.1)
Tools ToolWillBeAdded 3.0.1
#### Tools | NewTableInExistingHeader | Value | | ------------------------ | ----- | | A | 1 | | B | 2 | #### Tools > NewHeaderWithTable | NewTableInNewHeader | Value | | ------------------- | ----- | | C | 3 | | D | 4 | ### Deleted :heavy_minus_sign: #### HeaderWillExist | TableInExistingHeaderWillBeRemoved | Value | | ---------------------------------- | ------ | | ~~Q~~ | ~~25~~ | | ~~O~~ | ~~24~~ | #### Tools > HeaderWillBeRemoved | TableWillBeRemovedWithHeader | Value | | ---------------------------- | ------ | | ~~Z~~ | ~~30~~ | | ~~W~~ | ~~29~~ | '@ } It "Tables are changed" { # Previous report $prevSoftwareReport = [SoftwareReport]::new("macOS 11") $prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1") $prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software") $prevTools = $prevInstalledSoftware.AddHeader("Tools") $prevTools.AddHeader("TableWithAddedRows").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "AA"; Value = "10"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "AB"; Value = "11"} )) $prevTools.AddHeader("TableWithRemovedRows").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "BA"; Value = "32"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "BB"; Value = "33"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "BC"; Value = "34"} )) $prevTools.AddHeader("TableWithUpdatedRow").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "CA"; Value = "42"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "CB"; Value = "43"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "CC"; Value = "44"} )) $prevTools.AddHeader("TableWithUpdatedRows").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "DA"; Value = "50"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "DB"; Value = "51"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "DC"; Value = "52"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "DD"; Value = "53"} )) $prevTools.AddHeader("TableWithComplexChanges").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "EA"; Value = "62"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "EB"; Value = "63"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "EC"; Value = "64"} [PSCustomObject]@{TableWillBeRemovedWithHeader = "ED"; Value = "65"} )) $prevTools.AddHeader("TableWithOnlyHeaderChanged").AddTable(@( [PSCustomObject]@{TableWithOnlyHeaderChanged = "FA"; Value = "72"}, [PSCustomObject]@{TableWithOnlyHeaderChanged = "FB"; Value = "73"} )) $prevTools.AddHeader("TableWithHeaderAndRowsChanges").AddTable(@( [PSCustomObject]@{TableWithHeaderAndRowsChanges = "GA"; Value = "82"}, [PSCustomObject]@{TableWithHeaderAndRowsChanges = "GB"; Value = "83"}, [PSCustomObject]@{TableWithHeaderAndRowsChanges = "GC"; Value = "84"} )) # Next report $nextSoftwareReport = [SoftwareReport]::new("macOS 11") $nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1") $nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software") $nextTools = $nextInstalledSoftware.AddHeader("Tools") $nextTools.AddHeader("TableWithAddedRows").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "AA"; Value = "10"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "AB"; Value = "11"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "AC"; Value = "12"} )) $nextTools.AddHeader("TableWithRemovedRows").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "BB"; Value = "33"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "BC"; Value = "34"} )) $nextTools.AddHeader("TableWithUpdatedRow").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "CA"; Value = "42"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "CB"; Value = "500"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "CC"; Value = "44"} )) $nextTools.AddHeader("TableWithUpdatedRows").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "DA"; Value = "50"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "DB"; Value = "5100"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "DC"; Value = "5200"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "DD"; Value = "53"} )) $nextTools.AddHeader("TableWithComplexChanges").AddTable(@( [PSCustomObject]@{TableWillBeRemovedWithHeader = "EB"; Value = "63"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "EC"; Value = "640"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "ED"; Value = "65"}, [PSCustomObject]@{TableWillBeRemovedWithHeader = "EE"; Value = "66"} )) $nextTools.AddHeader("TableWithOnlyHeaderChanged").AddTable(@( [PSCustomObject]@{TableWithOnlyHeaderChanged2 = "FA"; Value = "72"}, [PSCustomObject]@{TableWithOnlyHeaderChanged2 = "FB"; Value = "73"} )) $nextTools.AddHeader("TableWithHeaderAndRowsChanges").AddTable(@( [PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GA"; Value = "82"}, [PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GE"; Value = "850"}, [PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GC"; Value = "840"} )) # Compare reports $comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport) $comparer.CompareReports() $comparer.GetMarkdownReport() | Should -BeExactly @' # :desktop_computer: Actions Runner Image: macOS 11 - Image Version: 20220922.1 ## :mega: What's changed? ### Added :heavy_plus_sign: #### Tools > TableWithAddedRows | TableWillBeRemovedWithHeader | Value | | ---------------------------- | ----- | | AC | 12 | #### Tools > TableWithHeaderAndRowsChanges | TableWithHeaderAndRowsChanges2 | Value | | ------------------------------ | ----- | | GA | 82 | | GE | 850 | | GC | 840 | ### Deleted :heavy_minus_sign: #### Tools > TableWithRemovedRows | TableWillBeRemovedWithHeader | Value | | ---------------------------- | ------ | | ~~BA~~ | ~~32~~ | #### Tools > TableWithHeaderAndRowsChanges | TableWithHeaderAndRowsChanges | Value | | ----------------------------- | ------ | | ~~GA~~ | ~~82~~ | | ~~GB~~ | ~~83~~ | | ~~GC~~ | ~~84~~ | ### Updated #### Tools > TableWithUpdatedRow | TableWillBeRemovedWithHeader | Value | | ---------------------------- | ------ | | ~~CB~~ | ~~43~~ | | CB | 500 | #### Tools > TableWithUpdatedRows | TableWillBeRemovedWithHeader | Value | | ---------------------------- | ------ | | ~~DB~~ | ~~51~~ | | ~~DC~~ | ~~52~~ | | DB | 5100 | | DC | 5200 | #### Tools > TableWithComplexChanges | TableWillBeRemovedWithHeader | Value | | ---------------------------- | ------ | | ~~EA~~ | ~~62~~ | | ~~EC~~ | ~~64~~ | | EC | 640 | | EE | 66 | '@ } It "Reports are identical" { # Previous report $prevSoftwareReport = [SoftwareReport]::new("macOS 11") $prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)") $prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1") $prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software") $prevTools = $prevInstalledSoftware.AddHeader("Tools") $prevTools.AddToolVersion("ToolA", "1.0.0") $prevTools.AddToolVersion("ToolB", "3.0.1") # Next report $nextSoftwareReport = [SoftwareReport]::new("macOS 11") $nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)") $nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1") $nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software") $nextTools = $nextInstalledSoftware.AddHeader("Tools") $nextTools.AddToolVersion("ToolA", "1.0.0") $nextTools.AddToolVersion("ToolB", "3.0.1") # Compare reports $comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport) $comparer.CompareReports() $comparer.GetMarkdownReport() | Should -BeExactly @' # :desktop_computer: Actions Runner Image: macOS 11 - OS Version: macOS 11.7.1 (20G817) - Image Version: 20220922.1 ## :mega: What's changed? '@ } } ================================================ FILE: helpers/software-report-base/tests/SoftwareReport.DifferenceCalculator.Unit.Tests.ps1 ================================================ using module ../SoftwareReport.Nodes.psm1 using module ../SoftwareReport.DifferenceCalculator.psm1 BeforeDiscovery { Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking } Describe "Comparer.UnitTests" { Describe "Headers Tree" { It "Add Node to existing header" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3" $comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader") } It "Add new header with Node" { $prevReport = [HeaderNode]::new("Version 1") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddHeader("MySubHeader").AddToolVersion("MyTool1", "2.1.3") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3" $comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubHeader") } It "Remove Node from existing header" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 1 $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3" $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty $comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader") } It "Remove header with Node" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3") $nextReport = [HeaderNode]::new("Version 2") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 1 $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3" $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty $comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader") } It "Node with minor changes" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.4") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 0 $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3" $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4" $comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader", "MySubHeader") } It "Node without changes" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 } It "Node is moved to different header" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddHeader("MySubheader2").AddToolVersion("MyTool1", "2.1.3") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 1 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3" $comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader2") $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3" $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty $comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader") } It "Complex structure" { $prevReport = [HeaderNode]::new("Version 1") $prevSubHeader = $prevReport.AddHeader("MyHeader").AddHeader("MySubheader") $prevSubHeader.AddToolVersion("MyTool1", "2.1.3") $prevSubHeader.AddHeader("MySubSubheader").AddToolVersion("MyTool2", "2.9.1") $prevReport.AddHeader("MyHeader2") $prevReport.AddHeader("MyHeader3").AddHeader("MySubheader3").AddToolVersion("MyTool3", "14.2.1") $nextReport = [HeaderNode]::new("Version 2") $nextSubHeader = $nextReport.AddHeader("MyHeader").AddHeader("MySubheader") $nextSubHeader.AddToolVersion("MyTool1", "2.1.4") $nextSubSubHeader = $nextSubHeader.AddHeader("MySubSubheader") $nextSubSubHeader.AddToolVersion("MyTool2", "2.9.1") $nextSubSubHeader.AddToolVersion("MyTool4", "2.7.6") $nextReport.AddHeader("MyHeader2") $nextReport.AddHeader("MyHeader3") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 1 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool4" $comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.7.6" $comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader", "MySubSubheader") $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3" $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4" $comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader") $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool3" $comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "14.2.1" $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty $comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader3", "MySubheader3") } } Describe "ToolVersionNode" { It "ToolVersionNode is updated" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.4") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 0 $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3" $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode]) $comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4" $comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader") } } Describe "ToolVersionsListNode" { It "Single version is not changed" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^.+") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^.+") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 } It "Single version is changed" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^\d+") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.4"), "^\d+") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 0 $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.1.3") $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.1.4") } It "Major version is added" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^\d+") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.1.4"), "^\d+") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.1.4") } It "Major version is removed" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.1.4"), "^\d+") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.1.4"), "^\d+") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 1 $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.1.3") $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty } It "Major version is changed" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.1.4"), "^\d+") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.2.0"), "^\d+") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 0 $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("3.1.4") $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.2.0") } It "Major version is added, removed and updated at the same time" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("1.0.0", "2.1.3", "3.1.4", "4.0.2"), "^\d+") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.2.0", "4.0.2", "5.1.0"), "^\d+") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 1 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("5.1.0") $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("3.1.4") $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.2.0") $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("1.0.0") $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty } It "Minor version is added, removed and updated at the same time" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.3.8", "2.4.9", "2.5.3", "2.6.0", "2.7.4", "2.8.0"), "^\d+\.\d+") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.5.3", "2.6.2", "2.7.5", "2.8.0", "2.9.2", "2.10.3"), "^\d+\.\d+") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 1 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.9.2", "2.10.3") $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.6.0", "2.7.4") $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.6.2", "2.7.5") $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.3.8", "2.4.9") $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty } It "Patch version is added, removed and updated at the same time" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.3.8", "2.4.9", "2.5.3", "2.6.0", "2.7.4"), "^\d+\.\d+\.\d+") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.4.9", "2.5.4", "2.6.0", "2.7.5", "2.8.2"), "^\d+\.\d+\.\d+") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 1 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1" $comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.5.4", "2.7.5", "2.8.2") $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode]) $comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1" $comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.3.8", "2.5.3", "2.7.4") $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty } } Describe "TableNode" { It "Rows are added" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2"))) $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2", "D1|D2"))) $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 $comparer.AddedItems[0].PreviousReportNode | Should -BeOfType ([TableNode]) $comparer.AddedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value" $comparer.AddedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2") $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([TableNode]) $comparer.AddedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value" $comparer.AddedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2", "D1|D2") } It "Rows are deleted" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2", "D1|D2"))) $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("C1|C2", "D1|D2"))) $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 1 $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([TableNode]) $comparer.DeletedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value" $comparer.DeletedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2", "D1|D2") $comparer.DeletedItems[0].CurrentReportNode | Should -BeOfType ([TableNode]) $comparer.DeletedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value" $comparer.DeletedItems[0].CurrentReportNode.Rows | Should -BeArray @("C1|C2", "D1|D2") } It "Rows are changed" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2"))) $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4"))) $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 0 $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode]) $comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value" $comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2") $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode]) $comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value" $comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4") } It "Rows are changed and updated at the same time" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2"))) $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4", "C1|C2"))) $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 0 $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode]) $comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value" $comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2") $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode]) $comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value" $comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4", "C1|C2") } It "Rows are changed and removed at the same time" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2"))) $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4"))) $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 1 $comparer.DeletedItems | Should -HaveCount 0 $comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode]) $comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value" $comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2") $comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode]) $comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value" $comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4") } It "Rows are not changed" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2"))) $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2"))) $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 } It "Rows are not changed but header is changed" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2"))) $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value2", @("A1|A2", "B1|B2"))) $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 } It "Rows are changed and header is changed at the same time" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2"))) $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value2", @("A1|A2", "B1|B2", "C1|C2"))) $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 1 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 1 $comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty $comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([TableNode]) $comparer.AddedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value2" $comparer.AddedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2") $comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([TableNode]) $comparer.DeletedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value" $comparer.DeletedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2") $comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty } } Describe "NoteNode" { It "NoteNode is ignored from report" { $prevReport = [HeaderNode]::new("Version 1") $prevReport.AddNote("MyFirstNote") $prevReport.AddHeader("MyFirstHeader").AddNote("MyFirstSubNote") $nextReport = [HeaderNode]::new("Version 2") $nextReport.AddNote("MySecondNote") $nextReport.AddHeader("MySecondHeader").AddNote("MySecondSubNote") $comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport) $comparer.CompareReports() $comparer.AddedItems | Should -HaveCount 0 $comparer.ChangedItems | Should -HaveCount 0 $comparer.DeletedItems | Should -HaveCount 0 } } } ================================================ FILE: helpers/software-report-base/tests/SoftwareReport.DifferenceRender.Unit.Tests.ps1 ================================================ using module ../SoftwareReport.Nodes.psm1 using module ../SoftwareReport.DifferenceRender.psm1 BeforeDiscovery { Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking } Describe "ComparerReport.UnitTests" { BeforeAll { $script:DifferenceRender = [SoftwareReportDifferenceRender]::new() } Context "CalculateHtmlTableRowSpan" { It "Without the equal cells" { $table = @( [PSCustomObject]@{ Key = "A"; Value = "1" } [PSCustomObject]@{ Key = "B"; Value = "2" } [PSCustomObject]@{ Key = "C"; Value = "3" } ) $actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key") $actual | Should -BeArray @(1, 1, 1) } It "Only equal cells" { $table = @( [PSCustomObject]@{ Key = "A"; Value = "D" } [PSCustomObject]@{ Key = "B"; Value = "D" } [PSCustomObject]@{ Key = "C"; Value = "D" } ) $actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Value") $actual | Should -BeArray @(3, 0, 0) } It "Single row" { $table = @( [PSCustomObject]@{ Key = "A"; Value = "1" } ) $actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key") $actual | Should -BeArray @(1) } It "Different cells" { $table = @( [PSCustomObject]@{ Key = "A"; Value = "1" } [PSCustomObject]@{ Key = "B"; Value = "2" } [PSCustomObject]@{ Key = "B"; Value = "3" } [PSCustomObject]@{ Key = "C"; Value = "4" } [PSCustomObject]@{ Key = "C"; Value = "5" } [PSCustomObject]@{ Key = "C"; Value = "6" } [PSCustomObject]@{ Key = "D"; Value = "7" } [PSCustomObject]@{ Key = "E"; Value = "8" } [PSCustomObject]@{ Key = "E"; Value = "9" } [PSCustomObject]@{ Key = "F"; Value = "10" } ) $actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key") $actual | Should -BeArray @(1, 2, 0, 3, 0, 0, 1, 2, 0, 1) } } Context "RenderCategory" { It "With line separator" { $actual = $DifferenceRender.RenderCategory(@("Header 1", "Header 2", "Header 3"), $true) $actual | Should -Be "Header 2 >
Header 3" } It "Without line separator" { $actual = $DifferenceRender.RenderCategory(@("Header 1", "Header 2", "Header 3"), $false) $actual | Should -Be "Header 2 > Header 3" } It "One header" { $actual = $DifferenceRender.RenderCategory(@("Header 1"), $false) $actual | Should -Be "" } It "Empty headers" { $actual = $DifferenceRender.RenderCategory(@(), $false) $actual | Should -Be "" } } Context "RenderToolName" { It "Clear tool name" { $actual = $DifferenceRender.RenderToolName("My Tool 1") $actual | Should -Be "My Tool 1" } It "Name with colon symbol" { $actual = $DifferenceRender.RenderToolName("My Tool 1:") $actual | Should -Be "My Tool 1" } } Context "StrikeTableRow" { It "Simple row" { $actual = $DifferenceRender.StrikeTableRow("Test1|Test2|Test3") $actual | Should -Be "~~Test1~~|~~Test2~~|~~Test3~~" } It "Row with spaces" { $actual = $DifferenceRender.StrikeTableRow("Test 1|Test 2|Test 3") $actual | Should -Be "~~Test 1~~|~~Test 2~~|~~Test 3~~" } } Context "RenderHtmlTable" { It "Simple table" { $table = @( [PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 1"; "Version" = "1.0" }, [PSCustomObject]@{ "Category" = "B"; "Tool name" = "My Tool 2"; "Version" = "2.0" }, [PSCustomObject]@{ "Category" = "C"; "Tool name" = "My Tool 3"; "Version" = "3.0" } ) $renderedTable = $DifferenceRender.RenderHtmlTable($table, "Category") $renderedTable | Should -Be @'
Category Tool name Version
A My Tool 1 1.0
B My Tool 2 2.0
C My Tool 3 3.0
'@ } It "Table with the same category" { $table = @( [PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 1"; "Version" = "1.0" }, [PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 2"; "Version" = "2.0" }, [PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 3"; "Version" = "3.0" }, [PSCustomObject]@{ "Category" = "B"; "Tool name" = "My Tool 4"; "Version" = "4.0" } ) $renderedTable = $DifferenceRender.RenderHtmlTable($table, "Category") $renderedTable | Should -Be @'
Category Tool name Version
A My Tool 1 1.0
My Tool 2 2.0
My Tool 3 3.0
B My Tool 4 4.0
'@ } } Context "RenderTableNodesDiff" { It "Add new table" { $previousNode = $null $currentNode = [TableNode]::new("Name|Value", @("A|1", "B|2")) $reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3")) $actual = $DifferenceRender.RenderTableNodesDiff($reportItem) $actual | Should -Be @' #### Header 2 > Header 3 | Name | Value | | ---- | ----- | | A | 1 | | B | 2 | '@ } It "Remove existing table" { $previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2")) $currentNode = $null $reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3")) $actual = $DifferenceRender.RenderTableNodesDiff($reportItem) $actual | Should -Be @' #### Header 2 > Header 3 | Name | Value | | ----- | ----- | | ~~A~~ | ~~1~~ | | ~~B~~ | ~~2~~ | '@ } It "Add new rows to existing table" { $previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2")) $currentNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4")) $reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3")) $actual = $DifferenceRender.RenderTableNodesDiff($reportItem) $actual | Should -Be @' #### Header 2 > Header 3 | Name | Value | | ---- | ----- | | C | 3 | | D | 4 | '@ } It "Remove rows from existing table" { $previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4")) $currentNode = [TableNode]::new("Name|Value", @("C|3", "D|4")) $reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3")) $actual = $DifferenceRender.RenderTableNodesDiff($reportItem) $actual | Should -Be @' #### Header 2 > Header 3 | Name | Value | | ----- | ----- | | ~~A~~ | ~~1~~ | | ~~B~~ | ~~2~~ | '@ } It "Row is changed in existing table" { $previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2")) $currentNode = [TableNode]::new("Name|Value", @("A|1", "B|3")) $reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3")) $actual = $DifferenceRender.RenderTableNodesDiff($reportItem) $actual | Should -Be @' #### Header 2 > Header 3 | Name | Value | | ----- | ----- | | ~~B~~ | ~~2~~ | | B | 3 | '@ } It "Row is changed, added and removed at the same time in existing table" { $previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4")) $currentNode = [TableNode]::new("Name|Value", @("B|2", "C|4", "D|4", "E|5")) $reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3")) $actual = $DifferenceRender.RenderTableNodesDiff($reportItem) $actual | Should -Be @' #### Header 2 > Header 3 | Name | Value | | ----- | ----- | | ~~A~~ | ~~1~~ | | ~~C~~ | ~~3~~ | | C | 4 | | E | 5 | '@ } } } ================================================ FILE: helpers/software-report-base/tests/SoftwareReport.E2E.Tests.ps1 ================================================ using module ../SoftwareReport.psm1 using module ../SoftwareReport.Nodes.psm1 Describe "SoftwareReport.E2E" { Context "Report example 1" { BeforeEach { $softwareReport = [SoftwareReport]::new("macOS 11") $softwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7 (20G817)") $softwareReport.Root.AddToolVersion("Image Version:", "20220918.1") $installedSoftware = $softwareReport.Root.AddHeader("Installed Software") $languagesAndRuntimes = $installedSoftware.AddHeader("Language and Runtime") $languagesAndRuntimes.AddToolVersion("Bash", "5.1.16(1)-release") $languagesAndRuntimes.AddToolVersionsListInline(".NET Core SDK", @("1.2.100", "1.2.200", "3.1.414"), "^\d+\.\d+\.\d") $languagesAndRuntimes.AddNode([ToolVersionNode]::new("Perl", "5.34.0")) $cachedTools = $installedSoftware.AddHeader("Cached Tools") $cachedTools.AddToolVersionsList("Ruby", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+") $cachedTools.AddToolVersionsList("Node.js", @("14.8.0", "15.1.0", "16.4.2"), "^\d+") $javaSection = $installedSoftware.AddHeader("Java") $javaSection.AddTable(@( [PSCustomObject] @{ Version = "8.0.125"; Vendor = "My Vendor"; "Environment Variable" = "JAVA_HOME_8_X64" }, [PSCustomObject] @{ Version = "11.3.103"; Vendor = "My Vendor"; "Environment Variable" = "JAVA_HOME_11_X64" } )) $sqlSection = $installedSoftware.AddHeader("MySQL") $sqlSection.AddToolVersion("MySQL", "6.1.0") $sqlSection.AddNote("MySQL service is disabled by default.`nUse the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'") $expectedMarkdown = @' # macOS 11 - OS Version: macOS 11.7 (20G817) - Image Version: 20220918.1 ## Installed Software ### Language and Runtime - Bash 5.1.16(1)-release - .NET Core SDK: 1.2.100, 1.2.200, 3.1.414 - Perl 5.34.0 ### Cached Tools #### Ruby - 2.7.3 - 2.8.1 - 3.1.2 #### Node.js - 14.8.0 - 15.1.0 - 16.4.2 ### Java | Version | Vendor | Environment Variable | | -------- | --------- | -------------------- | | 8.0.125 | My Vendor | JAVA_HOME_8_X64 | | 11.3.103 | My Vendor | JAVA_HOME_11_X64 | ### MySQL - MySQL 6.1.0 ``` MySQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service' ``` '@ } It "ToMarkdown" { $softwareReport.ToMarkdown() | Should -Be $expectedMarkdown } It "Serialization + Deserialization" { $json = $softwareReport.ToJson() $deserializedReport = [SoftwareReport]::FromJson($json) $deserializedReport.ToMarkdown() | Should -Be $expectedMarkdown } } Context "GetImageVersion" { It "Image version exists" { $softwareReport = [SoftwareReport]::new("MyReport") $softwareReport.Root.AddToolVersion("Image Version:", "123.4") $softwareReport.GetImageVersion() | Should -Be "123.4" } It "Empty report" { $softwareReport = [SoftwareReport]::new("MyReport") $softwareReport.GetImageVersion() | Should -Be "Unknown version" } } } ================================================ FILE: helpers/software-report-base/tests/SoftwareReport.Nodes.Unit.Tests.ps1 ================================================ using module ../SoftwareReport.Nodes.psm1 BeforeDiscovery { Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking } Describe "Nodes.UnitTests" { Context "ToolVersionNode" { It "ToMarkdown" { $node = [ToolVersionNode]::new("MyTool", "2.1.3") $node.ToMarkdown() | Should -Be "- MyTool 2.1.3" } It "GetValue" { $node = [ToolVersionNode]::new("MyTool", "2.1.3") $node.GetValue() | Should -Be "2.1.3" } It "Serialization" { $node = [ToolVersionNode]::new("MyTool", "2.1.3") $json = $node.ToJsonObject() $json.NodeType | Should -Be "ToolVersionNode" $json.ToolName | Should -Be "MyTool" $json.Version | Should -Be "2.1.3" } It "Deserialization" { { [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = ""; Version = "2.1.3" }) } | Should -Throw '*Exception setting "ToolName": "The argument is null or empty.*' { [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = "MyTool"; Version = "" }) } | Should -Throw 'ToolVersionNode ''MyTool'' has empty version' { [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = "MyTool"; Version = "2.1.3" }) } | Should -Not -Throw } It "Serialization + Deserialization" { $node = [ToolVersionNode]::new("MyTool", "2.1.3") $json = $node.ToJsonObject() $node2 = [ToolVersionNode]::FromJsonObject($json) $json2 = $node2.ToJsonObject() $($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json) } It "IsSimilarTo" { [ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool", "2.1.3")) | Should -BeTrue [ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -BeTrue [ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool2", "2.1.3")) | Should -BeFalse } It "IsIdenticalTo" { [ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool", "2.1.3")) | Should -BeTrue [ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -BeFalse [ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool2", "2.1.3")) | Should -BeFalse } } Context "ToolVersionsListNode" { It "ToMarkdown - List" { $node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List") $expected = @( "", "# MyTool" "- 2.7.7" "- 3.0.5" "- 3.1.3" ) -join "`n" $node.ToMarkdown() | Should -Be $expected } It "ToMarkdown - Inline" { $node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "Inline") $node.ToMarkdown() | Should -Be "- MyTool: 2.7.7, 3.0.5, 3.1.3" } It "GetValue" { $node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List") $node.GetValue() | Should -Be "2.7.7, 3.0.5, 3.1.3" } It "Serialization - List" { $node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List") $json = $node.ToJsonObject() $json.NodeType | Should -Be "ToolVersionsListNode" $json.ToolName | Should -Be "Ruby" $json.Versions | Should -BeArray @("2.7.7", "3.0.5", "3.1.3") $json.MajorVersionRegex | Should -Be "^.+" $json.ListType | Should -Be "List" } It "Serialization - Inline" { $node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "Inline") $json = $node.ToJsonObject() $json.NodeType | Should -Be "ToolVersionsListNode" $json.ToolName | Should -Be "Ruby" $json.Versions | Should -BeArray @("2.7.7", "3.0.5", "3.1.3") $json.MajorVersionRegex | Should -Be "^.+" $json.ListType | Should -Be "Inline" } It "Deserialization" { { [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = ""; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "ToolName": "The argument is null or empty.*' { [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "Versions": "The argument is null or empty.*' { [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @(); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "Versions": "The argument is null, empty,*' { [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", '2.2.4'); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw 'Multiple versions from list * return the same result from regex *' { [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = ""; ListType = "List" }) } | Should -Throw 'Version * doesn''t match regex *' { [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "Fake" }) } | Should -Throw '*Exception setting "ListType": "The argument * does not belong to the set*' { [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Not -Throw { [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "Inline" }) } | Should -Not -Throw } It "Serialization + Deserialization" { $node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List") $json = $node.ToJsonObject() $node2 = [ToolVersionsListNode]::FromJsonObject($json) $json2 = $node2.ToJsonObject() $($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json) } It "IsSimilarTo" { [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo( [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List") ) | Should -BeTrue [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo( [ToolVersionsListNode]::new("MyTool", @("2.1.5", "5.0.0"), "^.+", "List") ) | Should -BeTrue [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo( [ToolVersionsListNode]::new("MyTool2", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List") ) | Should -BeFalse } It "IsIdenticalTo" { [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo( [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List") ) | Should -BeTrue [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo( [ToolVersionsListNode]::new("MyTool", @("2.1.5", "5.0.0"), "^.+", "List") ) | Should -BeFalse [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo( [ToolVersionsListNode]::new("MyTool2", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List") ) | Should -BeFalse } It "ExtractMajorVersion" { $node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^\d+\.\d+", "List") $node.ExtractMajorVersion("2.1.3") | Should -Be "2.1" $node.ExtractMajorVersion("3.1.5") | Should -Be "3.1" $node.ExtractMajorVersion("4.0.0") | Should -Be "4.0" } Context "ValidateMajorVersionRegex" { It "Major version regex - unique versions" { $node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^\d+", "List") $node.Versions | Should -BeArray @("2.1.3", "3.1.5", "4.0.0") } It "Major version regex - non-unique versions" { { [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "3.2.0", "4.0.0"), "^\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *" } It "Minor version regex - unique versions" { $node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.4.0", "3.1.2"), "^\d+\.\d+", "List") $node.Versions | Should -BeArray @("2.1.3", "2.4.0", "3.1.2") } It "Minor version regex - non-unique versions" { { [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "3.1.2"), "^\d+\.\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *" } It "Patch version regex - unique versions" { $node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "2.1.5"), "^\d+\.\d+\.\d+", "List") $node.Versions | Should -BeArray @("2.1.3", "2.1.4", "2.1.5") } It "Patch version regex - non-unique versions" { { [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "2.1.4"), "^\d+\.\d+\.\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *" } It ".NET Core version regex - unique versions" { $node = [ToolVersionsListNode]::new("MyTool", @("2.1.100", "2.1.205", "2.1.303"), "^\d+\.\d+\.\d", "List") $node.Versions | Should -BeArray @("2.1.100", "2.1.205", "2.1.303") } It ".NET Core version regex - non-unique versions" { { [ToolVersionsListNode]::new("MyTool", @("2.1.100", "2.1.205", "2.1.230", "3.1.0"), "^\d+\.\d+\.\d", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *" } } } Context "TableNode" { It "ToMarkdown (Simple table)" { $node = [TableNode]::new("Name|Value", @("A|B", "C|D")) $node.ToMarkdown() | Should -Be @' | Name | Value | | ---- | ----- | | A | B | | C | D | '@ } It "ToMarkdown (Wide cells)" { $node = [TableNode]::new("Name|Value", @("Very long value here|B", "C|And very long value here too")) $node.ToMarkdown() | Should -Be @' | Name | Value | | -------------------- | ---------------------------- | | Very long value here | B | | C | And very long value here too | '@ } It "CalculateColumnsWidth" { [TableNode]::new("Name|Value", @("A|B", "C|D")).CalculateColumnsWidth() | Should -BeArray @(4, 5) [TableNode]::new("Name|Value", @("Very long value here|B", "C|And very long value here too")).CalculateColumnsWidth() | Should -BeArray @(20, 28) } It "Serialization" { $node = [TableNode]::new("Name|Value", @("A|B", "C|D")) $json = $node.ToJsonObject() $json.NodeType | Should -Be "TableNode" $json.Headers | Should -Be "Name|Value" $json.Rows | Should -BeArray @("A|B", "C|D") } It "Deserialization" { { [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = ""; Rows = @("A|1", "B|2") }) } | Should -Throw 'Exception setting "Headers": "The argument is null or empty. *' { [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @() }) } | Should -Throw 'Exception setting "Rows": "The argument is null, empty, *' { [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @("A|1", "B|2|T", "C|3") }) } | Should -Throw 'Table has different number of columns in different rows' { [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @("A|1", "B|2") }) } | Should -Not -Throw } It "Serialization + Deserialization" { $node = [TableNode]::new("Name|Value", @("A|B", "C|D")) $json = $node.ToJsonObject() $node2 = [TableNode]::FromJsonObject($json) $json2 = $node2.ToJsonObject() $($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json) } It "IsSimilarTo" { [TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|D"))) | Should -BeTrue [TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|D", "F|W"))) | Should -BeTrue [TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|E"))) | Should -BeTrue [TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Key", @("A|B", "C|D"))) | Should -BeTrue } It "IsIdenticalTo" { [TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|D"))) | Should -BeTrue [TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Key", @("A|B", "C|D"))) | Should -BeTrue [TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|D", "F|W"))) | Should -BeFalse [TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|E"))) | Should -BeFalse } Context "FromObjectsArray" { It "Correct table" { $table = @( [PSCustomObject]@{Name = "A"; Value = "B"} [PSCustomObject]@{Name = "C"; Value = "D"} ) $tableNode = [TableNode]::FromObjectsArray($table) $tableNode.Headers | Should -Be "Name|Value" $tableNode.Rows | Should -BeArray @("A|B", "C|D") } It "Correct table with spaces" { $table = @( [PSCustomObject]@{Name = "A B"; "My Value" = "1 2"} [PSCustomObject]@{Name = "C D"; "My Value" = "3 4"} ) $tableNode = [TableNode]::FromObjectsArray($table) $tableNode.Headers | Should -Be "Name|My Value" $tableNode.Rows | Should -BeArray @("A B|1 2", "C D|3 4") } It "Throw on empty table" { { [TableNode]::FromObjectsArray(@()) } | Should -Throw "Failed to create TableNode from empty objects array" } It "Throw on table with different columns" { $table = @( [PSCustomObject]@{Name = "A"; Value = "B"} [PSCustomObject]@{Name = "C"; Value2 = "D"} ) { [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode from objects array because objects have different properties" } It "Throw on empty row" { $table = @( [PSCustomObject]@{Name = "A"; Value = "B"}, [PSCustomObject]@{}, [PSCustomObject]@{Name = "C"; Value2 = "D"} ) { [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some objects are empty" } It "Throw on incorrect symbols in table column names" { $table = @( [PSCustomObject]@{"Name|War" = "A"; Value = "B"} [PSCustomObject]@{"Name|War" = "C"; Value = "D"} ) { [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some cells * contains forbidden symbol*" } It "Throw on incorrect symbols in table rows" { $table = @( [PSCustomObject]@{Name = "A"; Value = "B|AA"} [PSCustomObject]@{Name = "C"; Value = "D"} ) { [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some cells * contains forbidden symbol*" } } } Context "NoteNode" { It "ToMarkdown" { $node = [NoteNode]::new("Hello world`nGood Bye world") $node.ToMarkdown() | Should -Be @' ``` hello world Good Bye world ``` '@ } It "Serialization" { $node = [NoteNode]::new("MyContent`nMyContent2") $json = $node.ToJsonObject() $json.NodeType | Should -Be "NoteNode" $json.Content | Should -Be "MyContent`nMyContent2" } It "Deserialization" { { [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode" }) } | Should -Throw '*Exception setting "Content": "The argument is null or empty.*' { [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode"; Content = "" }) } | Should -Throw '*Exception setting "Content": "The argument is null or empty.*' { [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode"; Content = "MyTool" }) } | Should -Not -Throw } It "Serialization + Deserialization" { $node = [NoteNode]::new("MyContent`nMyContent2") $json = $node.ToJsonObject() $node2 = [NoteNode]::FromJsonObject($json) $json2 = $node2.ToJsonObject() $($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json) } It "IsSimilarTo" { [NoteNode]::new("MyContent").IsSimilarTo([NoteNode]::new("MyContent")) | Should -BeTrue [NoteNode]::new("MyContent").IsSimilarTo([NoteNode]::new("MyContent2")) | Should -BeFalse } It "IsIdenticalTo" { [NoteNode]::new("MyContent").IsIdenticalTo([NoteNode]::new("MyContent")) | Should -BeTrue [NoteNode]::new("MyContent").IsIdenticalTo([NoteNode]::new("MyContent2")) | Should -BeFalse } } Context "HeaderNode" { It "ToMarkdown" { $node = [HeaderNode]::new("MyHeader") $node.AddToolVersion("MyTool", "2.1.3") $node.ToMarkdown(1) | Should -Be @' # MyHeader - MyTool 2.1.3 '@ } It "ToMarkdown (level 3)" { $node = [HeaderNode]::new("MyHeader") $node.AddToolVersion("MyTool", "2.1.3") $node.ToMarkdown(3) | Should -Be @' ### MyHeader - MyTool 2.1.3 '@ } It "ToMarkdown (multiple levels)" { $node = [HeaderNode]::new("MyHeader") $node.AddHeader("MyHeader 2").AddHeader("MyHeader 3").AddHeader("MyHeader 4").AddToolVersion("MyTool", "2.1.3") $node.ToMarkdown(1) | Should -Be @' # MyHeader ## MyHeader 2 ### MyHeader 3 #### MyHeader 4 - MyTool 2.1.3 '@ } It "Serialization" { $node = [HeaderNode]::new("MyHeader") $node.AddToolVersion("MyTool", "2.1.3") $json = $node.ToJsonObject() $json.NodeType | Should -Be "HeaderNode" $json.Title | Should -Be "MyHeader" $json.Children | Should -HaveCount 1 } It "Deserialization" { { [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode" }) } | Should -Throw '*Exception setting "Title": "The argument is null or empty.*' { [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode"; Title = "" }) } | Should -Throw '*Exception setting "Title": "The argument is null or empty.*' { [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode"; Title = "MyHeader" }) } | Should -Not -Throw } It "Serialization + Deserialization" { $node = [HeaderNode]::new("MyHeader") $node.AddToolVersion("MyTool", "2.1.3") $json = $node.ToJsonObject() $node2 = [HeaderNode]::FromJsonObject($json) $json2 = $node2.ToJsonObject() $($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json) } It "IsSimilarTo" { [HeaderNode]::new("MyHeader").IsSimilarTo([HeaderNode]::new("MyHeader")) | Should -BeTrue [HeaderNode]::new("MyHeader").IsSimilarTo([HeaderNode]::new("MyHeader2")) | Should -BeFalse } It "IsIdenticalTo" { [HeaderNode]::new("MyHeader").IsIdenticalTo([HeaderNode]::new("MyHeader")) | Should -BeTrue [HeaderNode]::new("MyHeader").IsIdenticalTo([HeaderNode]::new("MyHeader2")) | Should -BeFalse } It "FindSimilarChildNode" { $node = [HeaderNode]::new("MyHeader") $node.AddToolVersion("MyTool", "2.1.3") $node.FindSimilarChildNode([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -Not -BeNullOrEmpty $node.FindSimilarChildNode([ToolVersionNode]::New("MyTool2", "1.0.0")) | Should -BeNullOrEmpty } Context "Detect node duplicates" { It "Similar HeaderNode on the same header" { $node = [HeaderNode]::new("MyHeader") $node.AddHeader("MySubHeader1") $node.AddHeader("MySubHeader2") { $node.AddHeader("MySubHeader1") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*" } It "Similar ToolVersionNode on the same header" { $node = [HeaderNode]::new("MyHeader") $node.AddToolVersion("MyTool", "2.1.3") $node.AddToolVersion("MyTool2", "2.1.3") { $node.AddToolVersion("MyTool", "2.1.3") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*" } It "Similar ToolVersionsListNode on the same header" { $node = [HeaderNode]::new("MyHeader") $node.AddToolVersionsListInline("MyTool", @("2.1.3", "3.0.0"), "^\d+") $node.AddToolVersionsListInline("MyTool2", @("2.1.3", "3.0.0"), "^\d+") { $node.AddToolVersionsList("MyTool", @("2.1.3", "3.0.0"), "^\d+") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*" } It "Similar TableNode on the same header" { $node = [HeaderNode]::new("MyHeader") $node.AddTable(@( [PSCustomObject]@{Name = "Value1"}, [PSCustomObject]@{Name = "Value2"} )) { $node.AddTable(@( [PSCustomObject]@{Name = "Value1"}, [PSCustomObject]@{Name = "Value2"} )) } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*" } It "Similar NoteNode on the same header" { $node = [HeaderNode]::new("MyHeader") $node.AddNote("MyContent") $node.AddNote("MyContent2") { $node.AddNote("MyContent") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*" } It "AddNode detects duplicates" { $node = [HeaderNode]::new("MyHeader") $node.AddNode([ToolVersionNode]::new("MyTool", "2.1.3")) { $node.AddNode([ToolVersionNode]::new("MyTool", "2.1.3")) } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*" } It "AddNodes detects duplicates" { $node = [HeaderNode]::new("MyHeader") $node.AddNodes(@( [ToolVersionNode]::new("MyTool", "2.1.3"), [ToolVersionNode]::new("MyTool2", "2.1.4") )) { $node.AddNodes(@( [ToolVersionNode]::new("MyTool3", "2.1.5"), [ToolVersionNode]::new("MyTool", "2.1.3") )) } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*" } It "Doesn't allow adding non-header nodes after header node" { $node = [HeaderNode]::new("MyHeader") { $node.AddToolVersion("MyTool", "2.1.3") } | Should -Not -Throw { $node.AddHeader("MySubHeader") } | Should -Not -Throw { $node.AddToolVersion("MyTool2", "2.1.4") } | Should -Throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node" { $node.AddHeader("MySubHeader2") } | Should -Not -Throw { $node.AddToolVersionsListInline("MyTool3", @("2.1.4", "2.1.5"), "^.+") } | Should -Throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node" { $node.AddToolVersionsList("MyTool4", @("2.1.4", "2.1.5"), "^.+") } | Should -Not -Throw } } } } ================================================ FILE: helpers/software-report-base/tests/TestHelpers.psm1 ================================================ function ShouldBeArray([Array] $ActualValue, [Array]$ExpectedValue, [Switch] $Negate, [String] $Because) { if ($Negate) { throw "Negation is not supported for Should-BeArray" } if ($ExpectedValue.Count -eq 0) { throw "Expected array cannot be empty. Use Should-BeNullOrEmpty instead." } $ExpectedValue | ForEach-Object { if ($_.GetType() -notin @([String], [Int32])) { throw "Only string or int arrays are supported in Should-BeArray" } } $actualValueJson = $ActualValue | ConvertTo-Json $expectedValueJson = $ExpectedValue | ConvertTo-Json $succeeded = ($ActualValue.Count -eq $ExpectedValue.Count) -and ($actualValueJson -eq $expectedValueJson) if (-not $succeeded) { $failureMessage = "Expected array '$actualValueJson' to be equal to '$expectedValueJson'" } return [PSCustomObject]@{ Succeeded = $succeeded FailureMessage = $failureMessage } } Add-ShouldOperator -Name BeArray ` -InternalName 'ShouldBeArray' ` -Test ${function:ShouldBeArray} ` -SupportsArrayInput ================================================ FILE: images/macos/assets/add-certificate.swift ================================================ import Foundation import Security let certInfo: CFDictionary enum SecurityError: Error { case generalError } func deleteCertificateFromKeyChain(_ certificateLabel: String) -> Bool { let delQuery: [NSString: Any] = [ kSecClass: kSecClassCertificate, kSecAttrLabel: certificateLabel, ] let delStatus: OSStatus = SecItemDelete(delQuery as CFDictionary) return delStatus == errSecSuccess } func saveCertificateToKeyChain(_ certificate: SecCertificate, certificateLabel: String) throws { SecKeychainSetPreferenceDomain(SecPreferencesDomain.system) deleteCertificateFromKeyChain(certificateLabel) let setQuery: [NSString: AnyObject] = [ kSecClass: kSecClassCertificate, kSecValueRef: certificate, kSecAttrLabel: certificateLabel as AnyObject, kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked, ] let addStatus: OSStatus = SecItemAdd(setQuery as CFDictionary, nil) guard addStatus == errSecSuccess else { throw SecurityError.generalError } var status = SecTrustSettingsSetTrustSettings(certificate, SecTrustSettingsDomain.admin, nil) } func getCertificateFromString(stringData: String) throws -> SecCertificate { if let data = NSData(base64Encoded: stringData, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) { if let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, data) { return certificate } } throw SecurityError.generalError } if CommandLine.arguments.count > 1 { let fileURL = URL(fileURLWithPath: CommandLine.arguments[1]) do { let certData = try Data(contentsOf: fileURL) let certificate = SecCertificateCreateWithData(nil, certData as CFData) if certificate != nil { print("Saving certificate") try? saveCertificateToKeyChain(certificate!, certificateLabel: "Test") } else { print("Certificate can't be read") } } catch { print("Unable to read the file \(CommandLine.arguments[1])") } } else { print("Usage: \(CommandLine.arguments[0]) [cert.file]") } ================================================ FILE: images/macos/assets/auto-software-update-arm64.exp ================================================ #! /usr/bin/expect -f set timeout -1 spawn sudo /usr/sbin/softwareupdate --restart --verbose --install "MACOSUPDATE" expect "Password*" send "[lindex $argv 0]\r" expect eof ================================================ FILE: images/macos/assets/bashprofile ================================================ [ -f $HOME/.bashrc ] && source $HOME/.bashrc ================================================ FILE: images/macos/assets/bashrc ================================================ export LC_CTYPE=en_US.UTF-8 export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 export ANDROID_HOME=${HOME}/Library/Android/sdk export ANDROID_SDK_ROOT=${HOME}/Library/Android/sdk export VM_ASSETS=/usr/local/opt/$USER/scripts export NUNIT_BASE_PATH=/Library/Developer/nunit export NUNIT3_PATH=/Library/Developer/nunit/3.6.0 export AGENT_TOOLSDIRECTORY=$HOME/hostedtoolcache export RUNNER_TOOL_CACHE=$HOME/hostedtoolcache export ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=$HOME/actionarchivecache export PATH=/Library/Frameworks/Mono.framework/Versions/Current/Commands:$PATH export PATH=/Library/Frameworks/Python.framework/Versions/Current/bin:$PATH export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH export PATH=/usr/local/bin:/usr/local/sbin:~/bin:~/.yarn/bin:$PATH export PATH="/usr/local/opt/curl/bin:$PATH" export PATH=$HOME/.cargo/bin:$PATH export RCT_NO_LAUNCH_PACKAGER=1 export DOTNET_MULTILEVEL_LOOKUP=0 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 export DOTNET_NOLOGO=1 export HOMEBREW_NO_AUTO_UPDATE=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 export HOMEBREW_CASK_OPTS="--no-quarantine" export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 export BOOTSTRAP_HASKELL_INSTALL_NO_STACK_HOOK=1 ================================================ FILE: images/macos/assets/bootstrap-provisioner/change_password ================================================ #!/bin/bash USERNAME="$1" OLD_PASSWD="$2" NEW_PASSWD="$3" UPDATE_LOGIN_KEYCHAIN="${4:-true}" sudo /usr/sbin/sysadminctl -resetPasswordFor $USERNAME -newPassword "$NEW_PASSWD" -adminUser $USERNAME -adminPassword "$OLD_PASSWD" sudo /usr/bin/python3 /Users/$USERNAME/bootstrap/kcpassword.py "$NEW_PASSWD" sudo /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser "$USERNAME" if [[ $UPDATE_LOGIN_KEYCHAIN == "true" ]]; then /usr/bin/security set-keychain-password -o "$OLD_PASSWD" -p "$NEW_PASSWD" /Users/$USERNAME/Library/Keychains/login.keychain fi ================================================ FILE: images/macos/assets/bootstrap-provisioner/installNewProvisioner.sh ================================================ #!/bin/bash -e -o pipefail BOOTSTRAP_PATH="$1" ProvisionerPackageUri="$2" ProvisionerScriptUri="$3" ScriptName="$4" ScriptParam="$5" Username="$6" arch=$(arch) if [[ $arch == "arm64" ]]; then export PATH=/usr/bin:/usr/sbin:/usr/local/bin:/bin:/sbin:/opt/homebrew/bin else export PATH=/usr/bin:/usr/sbin:/usr/local/bin:/bin:/sbin fi PROVISIONER_ROOT=/usr/local/opt/${Username} sudo mkdir -p ${PROVISIONER_ROOT} sudo chown ${Username} ${PROVISIONER_ROOT} tee -a ${PROVISIONER_ROOT}/runprovisioner.sh > /dev/null <<\EOF #!/bin/bash . ${HOME}/.bashrc /usr/local/opt/$USER/provisioner/provisioner EOF chmod +x $PROVISIONER_ROOT/runprovisioner.sh aria2c \ --enable-color=false \ --file-allocation=none \ -d ${BOOTSTRAP_PATH} "${ProvisionerPackageUri}" >> ${BOOTSTRAP_PATH}/download.log aria2c \ --enable-color=false \ --file-allocation=none \ -d ${BOOTSTRAP_PATH} "${ProvisionerScriptUri}" >> ${BOOTSTRAP_PATH}/download.log chmod +x ${BOOTSTRAP_PATH}/${ScriptName} # Install Provisioner with provided scripts eval "$BOOTSTRAP_PATH/$ScriptName $BOOTSTRAP_PATH/$ScriptParam $Username" 2>&1 | tee "$BOOTSTRAP_PATH/install.log" # State File touch $BOOTSTRAP_PATH/provisionerDone ================================================ FILE: images/macos/assets/bootstrap-provisioner/kcpassword.py ================================================ #!/usr/bin/env python3 # Port of Gavin Brock's Perl kcpassword generator to Python, by Tom Taylor # . # Perl version: https://www.brock-family.org/gavin/perl/kcpassword.html # This script was taken from https://github.com/timsutton/osx-vm-templates/blob/master/scripts/support/set_kcpassword.py # Distributed by MIT license, license can be found at the bottom of this script import sys import os def encode_data(passwd): # The magic 11 bytes - these are just repeated # 0x7D 0x89 0x52 0x23 0xD2 0xBC 0xDD 0xEA 0xA3 0xB9 0x1F key = [125,137,82,35,210,188,221,234,163,185,31] key_len = len(key) passwd = [ord(x) for x in list(passwd)] # pad passwd length out to an even multiple of key length r = len(passwd) % key_len if len(passwd) == 11: passwd += [0] elif (r > 0): passwd = passwd + [0] * (key_len - r) for n in range(0, len(passwd), len(key)): ki = 0 for j in range(n, min(n+len(key), len(passwd))): passwd[j] = passwd[j] ^ key[ki] ki += 1 return bytearray(passwd) if __name__ == "__main__": runner_pwd_encoded = encode_data(sys.argv[1]) fd = os.open('/etc/kcpassword', os.O_WRONLY | os.O_CREAT, 0o600) file = os.fdopen(fd, 'wb') file.truncate(0) file.write(runner_pwd_encoded) file.close() """ The MIT License (MIT) Copyright (c) 2013-2017 Timothy Sutton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ ================================================ FILE: images/macos/assets/bootstrap-provisioner/setAutoLogin.sh ================================================ #!/bin/bash -e -o pipefail : <<-LICENSE_BLOCK setAutoLogin (20210911) - Copyright (c) 2021 Joel Bruner (https://github.com/brunerd) Licensed under the MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. LICENSE_BLOCK USERNAME="${1}" PW="${2}" kcpasswordEncode() { #ascii string local thisString="${1}" local i #macOS cipher hex ascii representation array local cipherHex_array=( 7D 89 52 23 D2 BC DD EA A3 B9 1F ) #converted to hex representation with spaces local thisStringHex_array=( $(echo -n "${thisString}" | xxd -p -u | sed 's/../& /g') ) #get padding by subtraction if under 11 local r=$(( ${#thisStringHex_array[@]} % 11 )) local padding=0 if [ ${#thisStringHex_array[@]} -eq 11 ]; then local padding=1 elif [ $r -gt 0 ]; then local padding=$(( 11 - $r )) fi #cycle through each element of the array + padding for ((i=0; i < $(( ${#thisStringHex_array[@]} + ${padding})); i++)); do #use modulus to loop through the cipher array elements local charHex_cipher=${cipherHex_array[$(( $i % 11 ))]} #get the current hex representation element local charHex=${thisStringHex_array[$i]} #use $(( shell Aritmethic )) to ^ XOR the two 0x## values (extra padding is 0x00) #take decimal value and printf convert to two char hex value #use xxd to convert hex to actual value and append to the encodedString variable local encodedString+=$(printf "%02X" "$(( 0x${charHex_cipher} ^ 0x${charHex:-00} ))") done #return the string without a newline echo -n "${encodedString}" } #encode password and write file kcpasswordEncode "${PW}" | xxd -r -p > /etc/kcpassword #ensure ownership and permissions (600) chown root:wheel /etc/kcpassword chmod u=rw,go= /etc/kcpassword #turn on auto login /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser -string "${USERNAME}" /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUserScreenLocked -bool false echo "Auto login enabled for '${USERNAME}'" ================================================ FILE: images/macos/macos-14-Readme.md ================================================ | Announcements | |-| | [macOS 26 (Tahoe) is now generally available in GitHub Actions](https://github.com/actions/runner-images/issues/13739) | | [[macOS] The macOS 14 Sonoma based runner images will begin deprecation on July 6th and will be fully unsupported by November 2nd for GitHub Actions and Azure DevOps](https://github.com/actions/runner-images/issues/13518) | *** # macOS 14 - OS Version: macOS 14.8.4 (23J319) - Kernel Version: Darwin 23.6.0 - Image Version: 20260302.0252.1 ## Installed Software ### Language and Runtime - .NET Core SDK: 8.0.101, 8.0.204, 8.0.303, 8.0.418, 9.0.102, 9.0.203, 9.0.311, 10.0.103 - Bash 3.2.57(1)-release - Clang/LLVM 15.0.0 - Clang/LLVM (Homebrew) 15.0.7 - available on `$(brew --prefix llvm@15)/bin/clang` - GCC 13 (Homebrew GCC 13.4.0) - available by `gcc-13` alias - GCC 14 (Homebrew GCC 14.3.0) - available by `gcc-14` alias - GCC 15 (Homebrew GCC 15.2.0_1) - available by `gcc-15` alias - GNU Fortran 13 (Homebrew GCC 13.4.0) - available by `gfortran-13` alias - GNU Fortran 14 (Homebrew GCC 14.3.0) - available by `gfortran-14` alias - GNU Fortran 15 (Homebrew GCC 15.2.0_1) - available by `gfortran-15` alias - Kotlin 2.3.10-release-465 - Mono 6.12.0.188 - Node.js 20.20.0 - Perl 5.42.0 - PHP 8.5.3 - Python3 3.14.3 - Ruby 3.3.10 ### Package Management - Bundler 4.0.7 - Carthage 0.40.0 - CocoaPods 1.16.2 - Composer 2.9.5 - Homebrew 5.0.15 - NPM 10.8.2 - NuGet 6.3.1.1 - Pip3 26.0 (python 3.14) - Pipx 1.8.0 - RubyGems 4.0.7 - Vcpkg 2026 (build from commit 62159a45e1) - Yarn 1.22.22 ### Project Management - Apache Ant 1.10.15 - Apache Maven 3.9.12 - Gradle 9.3.1 ### Utilities - 7-Zip 17.05 - aria2 1.37.0 - azcopy 10.32.1 - bazel 9.0.0 - bazelisk 1.28.1 - bsdtar 3.5.3 - available by 'tar' alias - Curl 8.18.0 - Git 2.53.0 - Git LFS 3.7.1 - GitHub CLI 2.87.3 - GNU Tar 1.35 - available by 'gtar' alias - GNU Wget 1.25.0 - gpg (GnuPG) 2.4.9 - jq 1.8.1 - OpenSSL 1.1.1w 11 Sep 2023 - Packer 1.15.0 - pkgconf 2.5.1 - Unxip 3.3 - yq 4.52.4 - zstd 1.5.7 - Ninja 1.13.2 ### Tools - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.83.0 - Azure CLI (azure-devops) 1.0.2 - Bicep CLI 0.41.2 - Cmake 4.2.3 - CodeQL Action Bundle 2.24.2 - Fastlane 2.232.2 - SwiftFormat 0.59.1 - Xcbeautify 3.1.4 - Xcode Command Line Tools 16.2.0.0.1.1733547573 - Xcodes 1.6.2 ### Linters - SwiftLint 0.63.2 ### Browsers - Safari 26.3 (19623.2.7.18.1) - SafariDriver 26.3 (19623.2.7.18.1) - Google Chrome 145.0.7632.117 - Google Chrome for Testing 145.0.7632.117 - ChromeDriver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge WebDriver 145.0.3800.82 - Mozilla Firefox 148.0 - geckodriver 0.36.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | --------------- | ------------------------------------- | | CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-x64 | | EDGEWEBDRIVER | /usr/local/share/edge_driver | | GECKOWEBDRIVER | /usr/local/opt/geckodriver/bin | ### Java | Version | Environment Variable | | --------------------- | -------------------- | | 8.0.482+8 | JAVA_HOME_8_X64 | | 11.0.30+7 | JAVA_HOME_11_X64 | | 17.0.18+8 | JAVA_HOME_17_X64 | | 21.0.10+7.0 (default) | JAVA_HOME_21_X64 | | 25.0.2+10.0 | JAVA_HOME_25_X64 | ### Cached Tools #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 #### Python - 3.10.19 - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.7 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - Clippy 0.1.93 - Rustfmt 1.8.0-stable ### PowerShell Tools - PowerShell 7.4.13 #### PowerShell Modules - Az: 14.6.0 - Pester: 5.7.1 - PSScriptAnalyzer: 1.24.0 ### Xcode | Version | Build | Path | Symlinks | | -------------- | -------- | ------------------------------ | --------------------------------------------------------- | | 16.2 | 16C5032a | /Applications/Xcode_16.2.app | /Applications/Xcode_16.2.0.app | | 16.1 | 16B40 | /Applications/Xcode_16.1.app | /Applications/Xcode_16.1.0.app | | 15.4 (default) | 15F31d | /Applications/Xcode_15.4.app | /Applications/Xcode_15.4.0.app
/Applications/Xcode.app | | 15.3 | 15E204a | /Applications/Xcode_15.3.app | /Applications/Xcode_15.3.0.app | | 15.2 | 15C500b | /Applications/Xcode_15.2.app | /Applications/Xcode_15.2.0.app | | 15.1 | 15C65 | /Applications/Xcode_15.1.app | /Applications/Xcode_15.1.0.app | | 15.0.1 | 15A507 | /Applications/Xcode_15.0.1.app | /Applications/Xcode_15.0.app | #### Installed SDKs | SDK | SDK Name | Xcode Version | | ------------------------ | -------------------- | ------------- | | macOS 14.0 | macosx14.0 | 15.0.1 | | macOS 14.2 | macosx14.2 | 15.1, 15.2 | | macOS 14.4 | macosx14.4 | 15.3 | | macOS 14.5 | macosx14.5 | 15.4 | | macOS 15.1 | macosx15.1 | 16.1 | | macOS 15.2 | macosx15.2 | 16.2 | | iOS 17.0 | iphoneos17.0 | 15.0.1 | | iOS 17.2 | iphoneos17.2 | 15.1, 15.2 | | iOS 17.4 | iphoneos17.4 | 15.3 | | iOS 17.5 | iphoneos17.5 | 15.4 | | iOS 18.1 | iphoneos18.1 | 16.1 | | iOS 18.2 | iphoneos18.2 | 16.2 | | Simulator - iOS 17.0 | iphonesimulator17.0 | 15.0.1 | | Simulator - iOS 17.2 | iphonesimulator17.2 | 15.1, 15.2 | | Simulator - iOS 17.4 | iphonesimulator17.4 | 15.3 | | Simulator - iOS 17.5 | iphonesimulator17.5 | 15.4 | | Simulator - iOS 18.1 | iphonesimulator18.1 | 16.1 | | Simulator - iOS 18.2 | iphonesimulator18.2 | 16.2 | | tvOS 17.0 | appletvos17.0 | 15.0.1 | | tvOS 17.2 | appletvos17.2 | 15.1, 15.2 | | tvOS 17.4 | appletvos17.4 | 15.3 | | tvOS 17.5 | appletvos17.5 | 15.4 | | tvOS 18.1 | appletvos18.1 | 16.1 | | tvOS 18.2 | appletvos18.2 | 16.2 | | Simulator - tvOS 17.0 | appletvsimulator17.0 | 15.0.1 | | Simulator - tvOS 17.2 | appletvsimulator17.2 | 15.1, 15.2 | | Simulator - tvOS 17.4 | appletvsimulator17.4 | 15.3 | | Simulator - tvOS 17.5 | appletvsimulator17.5 | 15.4 | | Simulator - tvOS 18.1 | appletvsimulator18.1 | 16.1 | | Simulator - tvOS 18.2 | appletvsimulator18.2 | 16.2 | | watchOS 10.0 | watchos10.0 | 15.0.1 | | watchOS 10.2 | watchos10.2 | 15.1, 15.2 | | watchOS 10.4 | watchos10.4 | 15.3 | | watchOS 10.5 | watchos10.5 | 15.4 | | watchOS 11.1 | watchos11.1 | 16.1 | | watchOS 11.2 | watchos11.2 | 16.2 | | Simulator - watchOS 10.0 | watchsimulator10.0 | 15.0.1 | | Simulator - watchOS 10.2 | watchsimulator10.2 | 15.1, 15.2 | | Simulator - watchOS 10.4 | watchsimulator10.4 | 15.3 | | Simulator - watchOS 10.5 | watchsimulator10.5 | 15.4 | | Simulator - watchOS 11.1 | watchsimulator11.1 | 16.1 | | Simulator - watchOS 11.2 | watchsimulator11.2 | 16.2 | | visionOS 1.0 | xros1.0 | 15.2 | | visionOS 1.1 | xros1.1 | 15.3 | | visionOS 1.2 | xros1.2 | 15.4 | | visionOS 2.1 | xros2.1 | 16.1 | | visionOS 2.2 | xros2.2 | 16.2 | | Simulator - visionOS 1.0 | xrsimulator1.0 | 15.2 | | Simulator - visionOS 1.1 | xrsimulator1.1 | 15.3 | | Simulator - visionOS 1.2 | xrsimulator1.2 | 15.4 | | Simulator - visionOS 2.1 | xrsimulator2.1 | 16.1 | | Simulator - visionOS 2.2 | xrsimulator2.2 | 16.2 | | DriverKit 23.0 | driverkit23.0 | 15.0.1 | | DriverKit 23.2 | driverkit23.2 | 15.1, 15.2 | | DriverKit 23.4 | driverkit23.4 | 15.3 | | DriverKit 23.5 | driverkit23.5 | 15.4 | | DriverKit 24.1 | driverkit24.1 | 16.1 | | DriverKit 24.2 | driverkit24.2 | 16.2 | #### Installed Simulators | Name | OS | Simulators | | ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | iOS 17.0 | 17.0.1 | iPhone 15
iPhone 15 Plus
iPhone 15 Pro
iPhone 15 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air (5th generation)
iPad mini (6th generation)
iPad Pro (11-inch) (4th generation)
iPad Pro (12.9-inch) (6th generation) | | iOS 17.2 | 17.2 | iPhone 15
iPhone 15 Plus
iPhone 15 Pro
iPhone 15 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air (5th generation)
iPad mini (6th generation)
iPad Pro (11-inch) (4th generation)
iPad Pro (12.9-inch) (6th generation) | | iOS 17.4 | 17.4 | iPhone 15
iPhone 15 Plus
iPhone 15 Pro
iPhone 15 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air (5th generation)
iPad Air 11-inch (M2)
iPad Air 13-inch (M2)
iPad mini (6th generation)
iPad Pro (11-inch) (4th generation)
iPad Pro (12.9-inch) (6th generation)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 17.5 | 17.5 | iPhone 15
iPhone 15 Plus
iPhone 15 Pro
iPhone 15 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air 11-inch (M2)
iPad Air 13-inch (M2)
iPad mini (6th generation)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 18.1 | 18.1 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air 11-inch (M2)
iPad Air 13-inch (M2)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 18.2 | 18.2 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air 11-inch (M2)
iPad Air 13-inch (M2)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | tvOS 17.0 | 17.0 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 17.2 | 17.2 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 17.4 | 17.4 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 17.5 | 17.5 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 18.1 | 18.1 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 18.2 | 18.2 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | watchOS 10.0 | 10.0 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 5 (40mm)
Apple Watch Series 5 (44mm)
Apple Watch Series 6 (40mm)
Apple Watch Series 6 (44mm)
Apple Watch Series 7 (41mm)
Apple Watch Series 7 (45mm)
Apple Watch Series 9 (41mm)
Apple Watch Series 9 (45mm)
Apple Watch Ultra 2 (49mm) | | watchOS 10.2 | 10.2 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 5 (40mm)
Apple Watch Series 5 (44mm)
Apple Watch Series 6 (40mm)
Apple Watch Series 6 (44mm)
Apple Watch Series 7 (41mm)
Apple Watch Series 7 (45mm)
Apple Watch Series 9 (41mm)
Apple Watch Series 9 (45mm)
Apple Watch Ultra 2 (49mm) | | watchOS 10.4 | 10.4 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 5 (40mm)
Apple Watch Series 5 (44mm)
Apple Watch Series 6 (40mm)
Apple Watch Series 6 (44mm)
Apple Watch Series 7 (41mm)
Apple Watch Series 7 (45mm)
Apple Watch Series 9 (41mm)
Apple Watch Series 9 (45mm)
Apple Watch Ultra 2 (49mm) | | watchOS 10.5 | 10.5 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 5 (40mm)
Apple Watch Series 5 (44mm)
Apple Watch Series 6 (40mm)
Apple Watch Series 6 (44mm)
Apple Watch Series 7 (41mm)
Apple Watch Series 7 (45mm)
Apple Watch Series 9 (41mm)
Apple Watch Series 9 (45mm)
Apple Watch Ultra 2 (49mm) | | watchOS 11.1 | 11.1 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Ultra 2 (49mm) | | watchOS 11.2 | 11.2 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Ultra 2 (49mm) | ### Android | Package Name | Version | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 11.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1
34.0.0 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3) | | Android SDK Platform-Tools | 36.0.2 | | Android Support Repository | 47.0.0 | | CMake | 3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724 (default)
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | --------------------------------------------------- | | ANDROID_HOME | /Users/runner/Library/Android/sdk | | ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/29.0.14206865 | | ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk | ### Miscellaneous - Tcl/Tk 8.6.17 #### Environment variables | Name | Value | | ----------------- | ----------------------------------------------------------------------------------------- | | PARALLELS_DMG_URL | https://download.parallels.com/desktop/v26/26.2.2-57373/ParallelsDesktop-26.2.2-57373.dmg | ##### Notes ``` If you want to use Parallels Desktop you should download a package from URL stored in PARALLELS_DMG_URL environment variable. A system extension is allowed for this version. ``` ================================================ FILE: images/macos/macos-14-arm64-Readme.md ================================================ | Announcements | |-| | [macOS 26 (Tahoe) is now generally available in GitHub Actions](https://github.com/actions/runner-images/issues/13739) | | [[macOS] The macOS 14 Sonoma based runner images will begin deprecation on July 6th and will be fully unsupported by November 2nd for GitHub Actions and Azure DevOps](https://github.com/actions/runner-images/issues/13518) | *** # macOS 14 - OS Version: macOS 14.8.4 (23J319) - Kernel Version: Darwin 23.6.0 - Image Version: 20260302.0147.1 ## Installed Software ### Language and Runtime - .NET Core SDK: 8.0.101, 8.0.204, 8.0.303, 8.0.418, 9.0.102, 9.0.203, 9.0.311, 10.0.103 - Bash 3.2.57(1)-release - Clang/LLVM 15.0.0 - Clang/LLVM (Homebrew) 15.0.7 - available on `$(brew --prefix llvm@15)/bin/clang` - GCC 13 (Homebrew GCC 13.4.0) - available by `gcc-13` alias - GCC 14 (Homebrew GCC 14.3.0) - available by `gcc-14` alias - GCC 15 (Homebrew GCC 15.2.0_1) - available by `gcc-15` alias - GNU Fortran 13 (Homebrew GCC 13.4.0) - available by `gfortran-13` alias - GNU Fortran 14 (Homebrew GCC 14.3.0) - available by `gfortran-14` alias - GNU Fortran 15 (Homebrew GCC 15.2.0_1) - available by `gfortran-15` alias - Kotlin 2.3.10-release-465 - Mono 6.12.0.188 - Node.js 20.20.0 - Perl 5.42.0 - Python3 3.14.3 - Ruby 3.3.10 ### Package Management - Bundler 4.0.7 - Carthage 0.40.0 - CocoaPods 1.16.2 - Homebrew 5.0.15 - NPM 10.8.2 - NuGet 6.3.1.1 - Pip3 26.0 (python 3.14) - Pipx 1.8.0 - RubyGems 4.0.7 - Vcpkg 2026 (build from commit 62159a45e1) - Yarn 1.22.22 ### Project Management - Apache Ant 1.10.15 - Apache Maven 3.9.12 - Gradle 9.3.1 ### Utilities - 7-Zip 17.05 - aria2 1.37.0 - azcopy 10.32.1 - bazel 9.0.0 - bazelisk 1.28.1 - bsdtar 3.5.3 - available by 'tar' alias - Curl 8.7.1 - Git 2.53.0 - Git LFS 3.7.1 - GitHub CLI 2.87.3 - GNU Tar 1.35 - available by 'gtar' alias - GNU Wget 1.25.0 - gpg (GnuPG) 2.4.9 - jq 1.8.1 - OpenSSL 1.1.1w 11 Sep 2023 - Packer 1.15.0 - pkgconf 2.5.1 - Unxip 3.3 - yq 4.52.4 - zstd 1.5.7 - Ninja 1.13.2 ### Tools - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.83.0 - Azure CLI (azure-devops) 1.0.2 - Bicep CLI 0.41.2 - Cmake 4.2.3 - CodeQL Action Bundle 2.24.2 - Fastlane 2.232.2 - SwiftFormat 0.59.1 - Xcbeautify 3.1.4 - Xcode Command Line Tools 16.2.0.0.1.1733547573 - Xcodes 1.6.2 ### Browsers - Safari 26.3 (19623.2.7.18.1) - SafariDriver 26.3 (19623.2.7.18.1) - Google Chrome 145.0.7632.117 - Google Chrome for Testing 145.0.7632.117 - ChromeDriver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge WebDriver 145.0.3800.82 - Mozilla Firefox 148.0 - geckodriver 0.36.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | --------------- | --------------------------------------- | | CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-arm64 | | EDGEWEBDRIVER | /usr/local/share/edge_driver | | GECKOWEBDRIVER | /opt/homebrew/opt/geckodriver/bin | ### Java | Version | Environment Variable | | --------------------- | -------------------- | | 11.0.30+7 | JAVA_HOME_11_arm64 | | 17.0.18+8 | JAVA_HOME_17_arm64 | | 21.0.10+7.0 (default) | JAVA_HOME_21_arm64 | | 25.0.2+10.0 | JAVA_HOME_25_arm64 | ### Cached Tools #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 #### Python - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.7 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - Clippy 0.1.93 - Rustfmt 1.8.0-stable ### PowerShell Tools - PowerShell 7.4.13 #### PowerShell Modules - Az: 14.6.0 - Pester: 5.7.1 - PSScriptAnalyzer: 1.24.0 ### Xcode | Version | Build | Path | Symlinks | | -------------- | -------- | ------------------------------ | --------------------------------------------------------- | | 16.2 | 16C5032a | /Applications/Xcode_16.2.app | /Applications/Xcode_16.2.0.app | | 16.1 | 16B40 | /Applications/Xcode_16.1.app | /Applications/Xcode_16.1.0.app | | 15.4 (default) | 15F31d | /Applications/Xcode_15.4.app | /Applications/Xcode_15.4.0.app
/Applications/Xcode.app | | 15.3 | 15E204a | /Applications/Xcode_15.3.app | /Applications/Xcode_15.3.0.app | | 15.2 | 15C500b | /Applications/Xcode_15.2.app | /Applications/Xcode_15.2.0.app | | 15.1 | 15C65 | /Applications/Xcode_15.1.app | /Applications/Xcode_15.1.0.app | | 15.0.1 | 15A507 | /Applications/Xcode_15.0.1.app | /Applications/Xcode_15.0.app | #### Installed SDKs | SDK | SDK Name | Xcode Version | | ------------------------ | -------------------- | ------------- | | macOS 14.0 | macosx14.0 | 15.0.1 | | macOS 14.2 | macosx14.2 | 15.1, 15.2 | | macOS 14.4 | macosx14.4 | 15.3 | | macOS 14.5 | macosx14.5 | 15.4 | | macOS 15.1 | macosx15.1 | 16.1 | | macOS 15.2 | macosx15.2 | 16.2 | | iOS 17.0 | iphoneos17.0 | 15.0.1 | | iOS 17.2 | iphoneos17.2 | 15.1, 15.2 | | iOS 17.4 | iphoneos17.4 | 15.3 | | iOS 17.5 | iphoneos17.5 | 15.4 | | iOS 18.1 | iphoneos18.1 | 16.1 | | iOS 18.2 | iphoneos18.2 | 16.2 | | Simulator - iOS 17.0 | iphonesimulator17.0 | 15.0.1 | | Simulator - iOS 17.2 | iphonesimulator17.2 | 15.1, 15.2 | | Simulator - iOS 17.4 | iphonesimulator17.4 | 15.3 | | Simulator - iOS 17.5 | iphonesimulator17.5 | 15.4 | | Simulator - iOS 18.1 | iphonesimulator18.1 | 16.1 | | Simulator - iOS 18.2 | iphonesimulator18.2 | 16.2 | | tvOS 17.0 | appletvos17.0 | 15.0.1 | | tvOS 17.2 | appletvos17.2 | 15.1, 15.2 | | tvOS 17.4 | appletvos17.4 | 15.3 | | tvOS 17.5 | appletvos17.5 | 15.4 | | tvOS 18.1 | appletvos18.1 | 16.1 | | tvOS 18.2 | appletvos18.2 | 16.2 | | Simulator - tvOS 17.0 | appletvsimulator17.0 | 15.0.1 | | Simulator - tvOS 17.2 | appletvsimulator17.2 | 15.1, 15.2 | | Simulator - tvOS 17.4 | appletvsimulator17.4 | 15.3 | | Simulator - tvOS 17.5 | appletvsimulator17.5 | 15.4 | | Simulator - tvOS 18.1 | appletvsimulator18.1 | 16.1 | | Simulator - tvOS 18.2 | appletvsimulator18.2 | 16.2 | | watchOS 10.0 | watchos10.0 | 15.0.1 | | watchOS 10.2 | watchos10.2 | 15.1, 15.2 | | watchOS 10.4 | watchos10.4 | 15.3 | | watchOS 10.5 | watchos10.5 | 15.4 | | watchOS 11.1 | watchos11.1 | 16.1 | | watchOS 11.2 | watchos11.2 | 16.2 | | Simulator - watchOS 10.0 | watchsimulator10.0 | 15.0.1 | | Simulator - watchOS 10.2 | watchsimulator10.2 | 15.1, 15.2 | | Simulator - watchOS 10.4 | watchsimulator10.4 | 15.3 | | Simulator - watchOS 10.5 | watchsimulator10.5 | 15.4 | | Simulator - watchOS 11.1 | watchsimulator11.1 | 16.1 | | Simulator - watchOS 11.2 | watchsimulator11.2 | 16.2 | | visionOS 1.0 | xros1.0 | 15.2 | | visionOS 1.1 | xros1.1 | 15.3 | | visionOS 1.2 | xros1.2 | 15.4 | | visionOS 2.1 | xros2.1 | 16.1 | | visionOS 2.2 | xros2.2 | 16.2 | | Simulator - visionOS 1.0 | xrsimulator1.0 | 15.2 | | Simulator - visionOS 1.1 | xrsimulator1.1 | 15.3 | | Simulator - visionOS 1.2 | xrsimulator1.2 | 15.4 | | Simulator - visionOS 2.1 | xrsimulator2.1 | 16.1 | | Simulator - visionOS 2.2 | xrsimulator2.2 | 16.2 | | DriverKit 23.0 | driverkit23.0 | 15.0.1 | | DriverKit 23.2 | driverkit23.2 | 15.1, 15.2 | | DriverKit 23.4 | driverkit23.4 | 15.3 | | DriverKit 23.5 | driverkit23.5 | 15.4 | | DriverKit 24.1 | driverkit24.1 | 16.1 | | DriverKit 24.2 | driverkit24.2 | 16.2 | #### Installed Simulators | Name | OS | Simulators | | ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | iOS 17.0 | 17.0.1 | iPhone 15
iPhone 15 Plus
iPhone 15 Pro
iPhone 15 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air (5th generation)
iPad mini (6th generation)
iPad Pro (11-inch) (4th generation)
iPad Pro (12.9-inch) (6th generation) | | iOS 17.2 | 17.2 | iPhone 15
iPhone 15 Plus
iPhone 15 Pro
iPhone 15 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air (5th generation)
iPad mini (6th generation)
iPad Pro (11-inch) (4th generation)
iPad Pro (12.9-inch) (6th generation) | | iOS 17.4 | 17.4 | iPhone 15
iPhone 15 Plus
iPhone 15 Pro
iPhone 15 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air (5th generation)
iPad Air 11-inch (M2)
iPad Air 13-inch (M2)
iPad mini (6th generation)
iPad Pro (11-inch) (4th generation)
iPad Pro (12.9-inch) (6th generation)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 17.5 | 17.5 | iPhone 15
iPhone 15 Plus
iPhone 15 Pro
iPhone 15 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air 11-inch (M2)
iPad Air 13-inch (M2)
iPad mini (6th generation)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 18.1 | 18.1 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air 11-inch (M2)
iPad Air 13-inch (M2)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 18.2 | 18.2 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone SE (3rd generation)
iPad (10th generation)
iPad Air 11-inch (M2)
iPad Air 13-inch (M2)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | tvOS 17.0 | 17.0 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 17.2 | 17.2 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 17.4 | 17.4 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 17.5 | 17.5 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 18.1 | 18.1 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 18.2 | 18.2 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | watchOS 10.0 | 10.0 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 5 (40mm)
Apple Watch Series 5 (44mm)
Apple Watch Series 6 (40mm)
Apple Watch Series 6 (44mm)
Apple Watch Series 7 (41mm)
Apple Watch Series 7 (45mm)
Apple Watch Series 9 (41mm)
Apple Watch Series 9 (45mm)
Apple Watch Ultra 2 (49mm) | | watchOS 10.2 | 10.2 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 5 (40mm)
Apple Watch Series 5 (44mm)
Apple Watch Series 6 (40mm)
Apple Watch Series 6 (44mm)
Apple Watch Series 7 (41mm)
Apple Watch Series 7 (45mm)
Apple Watch Series 9 (41mm)
Apple Watch Series 9 (45mm)
Apple Watch Ultra 2 (49mm) | | watchOS 10.4 | 10.4 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 5 (40mm)
Apple Watch Series 5 (44mm)
Apple Watch Series 6 (40mm)
Apple Watch Series 6 (44mm)
Apple Watch Series 7 (41mm)
Apple Watch Series 7 (45mm)
Apple Watch Series 9 (41mm)
Apple Watch Series 9 (45mm)
Apple Watch Ultra 2 (49mm) | | watchOS 10.5 | 10.5 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 5 (40mm)
Apple Watch Series 5 (44mm)
Apple Watch Series 6 (40mm)
Apple Watch Series 6 (44mm)
Apple Watch Series 7 (41mm)
Apple Watch Series 7 (45mm)
Apple Watch Series 9 (41mm)
Apple Watch Series 9 (45mm)
Apple Watch Ultra 2 (49mm) | | watchOS 11.1 | 11.1 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Ultra 2 (49mm) | | watchOS 11.2 | 11.2 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Ultra 2 (49mm) | | visionOS 1.0 | 1.0 | Apple Vision Pro | | visionOS 1.1 | 1.1 | Apple Vision Pro | | visionOS 1.2 | 1.2 | Apple Vision Pro | | visionOS 2.1 | 2.1 | Apple Vision Pro | | visionOS 2.2 | 2.2 | Apple Vision Pro | ### Android | Package Name | Version | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 11.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1
34.0.0 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3) | | Android SDK Platform-Tools | 36.0.2 | | Android Support Repository | 47.0.0 | | CMake | 3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724 (default)
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | --------------------------------------------------- | | ANDROID_HOME | /Users/runner/Library/Android/sdk | | ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/29.0.14206865 | | ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk | ### Miscellaneous - Tcl/Tk 8.6.17 ================================================ FILE: images/macos/macos-15-Readme.md ================================================ | Announcements | |-| | [macOS 26 (Tahoe) is now generally available in GitHub Actions](https://github.com/actions/runner-images/issues/13739) | | [[macOS] The macOS 14 Sonoma based runner images will begin deprecation on July 6th and will be fully unsupported by November 2nd for GitHub Actions and Azure DevOps](https://github.com/actions/runner-images/issues/13518) | *** # macOS 15 - OS Version: macOS 15.7.4 (24G517) - Kernel Version: Darwin 24.6.0 - Image Version: 20260303.0227.1 ## Installed Software ### Language and Runtime - .NET Core SDK: 8.0.101, 8.0.204, 8.0.303, 8.0.418, 9.0.102, 9.0.203, 9.0.311, 10.0.103 - Bash 3.2.57(1)-release - Clang/LLVM 17.0.0 - Clang/LLVM (Homebrew) 18.1.8 - available on `$(brew --prefix llvm@18)/bin/clang` - GCC 13 (Homebrew GCC 13.4.0) - available by `gcc-13` alias - GCC 14 (Homebrew GCC 14.3.0) - available by `gcc-14` alias - GCC 15 (Homebrew GCC 15.2.0_1) - available by `gcc-15` alias - GNU Fortran 13 (Homebrew GCC 13.4.0) - available by `gfortran-13` alias - GNU Fortran 14 (Homebrew GCC 14.3.0) - available by `gfortran-14` alias - GNU Fortran 15 (Homebrew GCC 15.2.0_1) - available by `gfortran-15` alias - Kotlin 2.3.10-release-465 - Node.js 22.22.0 - Perl 5.42.0 - PHP 8.5.3 - Python3 3.14.3 - Ruby 3.3.10 ### Package Management - Bundler 4.0.7 - Carthage 0.40.0 - CocoaPods 1.16.2 - Composer 2.9.5 - Homebrew 5.0.16 - NPM 10.9.4 - Pip3 26.0 (python 3.14) - Pipx 1.8.0 - RubyGems 4.0.7 - Vcpkg 2026 (build from commit 39a6cc0e44) - Yarn 1.22.22 ### Project Management - Apache Ant 1.10.15 - Apache Maven 3.9.12 - Gradle 9.3.1 ### Utilities - 7-Zip 17.05 - aria2 1.37.0 - azcopy 10.32.1 - bazel 9.0.0 - bazelisk 1.28.1 - bsdtar 3.5.3 - available by 'tar' alias - Curl 8.18.0 - Git 2.53.0 - Git LFS 3.7.1 - GitHub CLI 2.87.3 - GNU Tar 1.35 - available by 'gtar' alias - GNU Wget 1.25.0 - gpg (GnuPG) 2.4.9 - jq 1.8.1 - OpenSSL 1.1.1w 11 Sep 2023 - Packer 1.15.0 - pkgconf 2.5.1 - Unxip 3.3 - yq 4.52.4 - zstd 1.5.7 - Ninja 1.13.2 ### Tools - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.84.0 - Azure CLI (azure-devops) 1.0.2 - Bicep CLI 0.41.2 - Cmake 4.2.3 - CodeQL Action Bundle 2.24.2 - Fastlane 2.232.2 - SwiftFormat 0.59.1 - Xcbeautify 3.1.4 - Xcode Command Line Tools 16.4.0.0.1.1747106510 - Xcodes 1.6.2 ### Linters - SwiftLint 0.63.2 ### Browsers - Safari 26.3 (20623.2.7.18.1) - SafariDriver 26.3 (20623.2.7.18.1) - Google Chrome 145.0.7632.117 - Google Chrome for Testing 145.0.7632.117 - ChromeDriver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge WebDriver 145.0.3800.82 - Mozilla Firefox 148.0 - geckodriver 0.36.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | --------------- | ------------------------------------- | | CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-x64 | | EDGEWEBDRIVER | /usr/local/share/edge_driver | | GECKOWEBDRIVER | /usr/local/opt/geckodriver/bin | ### Java | Version | Environment Variable | | --------------------- | -------------------- | | 11.0.30+7 | JAVA_HOME_11_X64 | | 17.0.18+8 | JAVA_HOME_17_X64 | | 21.0.10+7.0 (default) | JAVA_HOME_21_X64 | | 25.0.2+10.0 | JAVA_HOME_25_X64 | ### Cached Tools #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 #### Python - 3.10.19 - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.7 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - Clippy 0.1.93 - Rustfmt 1.8.0-stable ### PowerShell Tools - PowerShell 7.4.13 #### PowerShell Modules - Az: 14.6.0 - Pester: 5.7.1 - PSScriptAnalyzer: 1.24.0 ### Xcode | Version | Build | Path | Symlinks | | -------------- | -------- | ------------------------------ | -------------------------------------------------------------- | | 26.3 | 17C529 | /Applications/Xcode_26.3.app | /Applications/Xcode_26.3.0.app | | 26.2 | 17C52 | /Applications/Xcode_26.2.app | /Applications/Xcode_26.2.0.app | | 26.1.1 | 17B100 | /Applications/Xcode_26.1.1.app | /Applications/Xcode_26.1.app | | 26.0.1 | 17A400 | /Applications/Xcode_26.0.1.app | /Applications/Xcode_26.0.app | | 16.4 (default) | 16F6 | /Applications/Xcode_16.4.app | /Applications/Xcode_16.4.0.app
/Applications/Xcode.app | | 16.3 | 16E140 | /Applications/Xcode_16.3.app | /Applications/Xcode_16.3.0.app | | 16.2 | 16C5032a | /Applications/Xcode_16.2.app | /Applications/Xcode_16.2.0.app | | 16.1 | 16B40 | /Applications/Xcode_16.1.app | /Applications/Xcode_16.1.0.app | | 16.0 | 16A242d | /Applications/Xcode_16.app | /Applications/Xcode_16.0.0.app
/Applications/Xcode_16.0.app | #### Installed SDKs | SDK | SDK Name | Xcode Version | | ------------------------- | -------------------- | ------------- | | macOS 15.0 | macosx15.0 | 16.0 | | macOS 15.1 | macosx15.1 | 16.1 | | macOS 15.2 | macosx15.2 | 16.2 | | macOS 15.4 | macosx15.4 | 16.3 | | macOS 15.5 | macosx15.5 | 16.4 | | macOS 26.0 | macosx26.0 | 26.0.1 | | macOS 26.1 | macosx26.1 | 26.1.1 | | macOS 26.2 | macosx26.2 | 26.2, 26.3 | | iOS 18.0 | iphoneos18.0 | 16.0 | | iOS 18.1 | iphoneos18.1 | 16.1 | | iOS 18.2 | iphoneos18.2 | 16.2 | | iOS 18.4 | iphoneos18.4 | 16.3 | | iOS 18.5 | iphoneos18.5 | 16.4 | | iOS 26.0 | iphoneos26.0 | 26.0.1 | | iOS 26.1 | iphoneos26.1 | 26.1.1 | | iOS 26.2 | iphoneos26.2 | 26.2, 26.3 | | Simulator - iOS 18.0 | iphonesimulator18.0 | 16.0 | | Simulator - iOS 18.1 | iphonesimulator18.1 | 16.1 | | Simulator - iOS 18.2 | iphonesimulator18.2 | 16.2 | | Simulator - iOS 18.4 | iphonesimulator18.4 | 16.3 | | Simulator - iOS 18.5 | iphonesimulator18.5 | 16.4 | | Simulator - iOS 26.0 | iphonesimulator26.0 | 26.0.1 | | Simulator - iOS 26.1 | iphonesimulator26.1 | 26.1.1 | | Simulator - iOS 26.2 | iphonesimulator26.2 | 26.2, 26.3 | | tvOS 18.0 | appletvos18.0 | 16.0 | | tvOS 18.1 | appletvos18.1 | 16.1 | | tvOS 18.2 | appletvos18.2 | 16.2 | | tvOS 18.4 | appletvos18.4 | 16.3 | | tvOS 18.5 | appletvos18.5 | 16.4 | | tvOS 26.0 | appletvos26.0 | 26.0.1 | | tvOS 26.1 | appletvos26.1 | 26.1.1 | | tvOS 26.2 | appletvos26.2 | 26.2, 26.3 | | Simulator - tvOS 18.0 | appletvsimulator18.0 | 16.0 | | Simulator - tvOS 18.1 | appletvsimulator18.1 | 16.1 | | Simulator - tvOS 18.2 | appletvsimulator18.2 | 16.2 | | Simulator - tvOS 18.4 | appletvsimulator18.4 | 16.3 | | Simulator - tvOS 18.5 | appletvsimulator18.5 | 16.4 | | Simulator - tvOS 26.0 | appletvsimulator26.0 | 26.0.1 | | Simulator - tvOS 26.1 | appletvsimulator26.1 | 26.1.1 | | Simulator - tvOS 26.2 | appletvsimulator26.2 | 26.2, 26.3 | | watchOS 11.0 | watchos11.0 | 16.0 | | watchOS 11.1 | watchos11.1 | 16.1 | | watchOS 11.2 | watchos11.2 | 16.2 | | watchOS 11.4 | watchos11.4 | 16.3 | | watchOS 11.5 | watchos11.5 | 16.4 | | watchOS 26.0 | watchos26.0 | 26.0.1 | | watchOS 26.1 | watchos26.1 | 26.1.1 | | watchOS 26.2 | watchos26.2 | 26.2, 26.3 | | Simulator - watchOS 11.0 | watchsimulator11.0 | 16.0 | | Simulator - watchOS 11.1 | watchsimulator11.1 | 16.1 | | Simulator - watchOS 11.2 | watchsimulator11.2 | 16.2 | | Simulator - watchOS 11.4 | watchsimulator11.4 | 16.3 | | Simulator - watchOS 11.5 | watchsimulator11.5 | 16.4 | | Simulator - watchOS 26.0 | watchsimulator26.0 | 26.0.1 | | Simulator - watchOS 26.1 | watchsimulator26.1 | 26.1.1 | | Simulator - watchOS 26.2 | watchsimulator26.2 | 26.2, 26.3 | | visionOS 2.0 | xros2.0 | 16.0 | | visionOS 2.1 | xros2.1 | 16.1 | | visionOS 2.2 | xros2.2 | 16.2 | | visionOS 2.4 | xros2.4 | 16.3 | | visionOS 2.5 | xros2.5 | 16.4 | | visionOS 26.0 | xros26.0 | 26.0.1 | | visionOS 26.1 | xros26.1 | 26.1.1 | | visionOS 26.2 | xros26.2 | 26.2, 26.3 | | Simulator - visionOS 2.0 | xrsimulator2.0 | 16.0 | | Simulator - visionOS 2.1 | xrsimulator2.1 | 16.1 | | Simulator - visionOS 2.2 | xrsimulator2.2 | 16.2 | | Simulator - visionOS 2.4 | xrsimulator2.4 | 16.3 | | Simulator - visionOS 2.5 | xrsimulator2.5 | 16.4 | | Simulator - visionOS 26.0 | xrsimulator26.0 | 26.0.1 | | Simulator - visionOS 26.1 | xrsimulator26.1 | 26.1.1 | | Simulator - visionOS 26.2 | xrsimulator26.2 | 26.2, 26.3 | | DriverKit 24.0 | driverkit24.0 | 16.0 | | DriverKit 24.1 | driverkit24.1 | 16.1 | | DriverKit 24.2 | driverkit24.2 | 16.2 | | DriverKit 24.4 | driverkit24.4 | 16.3 | | DriverKit 24.5 | driverkit24.5 | 16.4 | | DriverKit 25.0 | driverkit25.0 | 26.0.1 | | DriverKit 25.1 | driverkit25.1 | 26.1.1 | | DriverKit 25.2 | driverkit25.2 | 26.2, 26.3 | #### Installed Simulators | Name | OS | Simulators | | ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | iOS 18.5 | 18.5 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 18.6 | 18.6 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 26.0 | 26.0.1 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M4)
iPad Pro 13-inch (M5) | | iOS 26.1 | 26.1 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M4)
iPad Pro 13-inch (M5) | | iOS 26.2 | 26.2 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M4)
iPad Pro 13-inch (M5) | | tvOS 18.5 | 18.5 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 26.1 | 26.1 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 26.2 | 26.2 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | watchOS 11.5 | 11.5 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Ultra 2 (49mm) | | watchOS 26.1 | 26.1 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 2 (49mm)
Apple Watch Ultra 3 (49mm) | | watchOS 26.2 | 26.2 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 2 (49mm)
Apple Watch Ultra 3 (49mm) | ### Android | Package Name | Version | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 16.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3) | | Android SDK Platform-Tools | 37.0.0 | | Android Support Repository | 47.0.0 | | CMake | 3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724 (default)
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | --------------------------------------------------- | | ANDROID_HOME | /Users/runner/Library/Android/sdk | | ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/29.0.14206865 | | ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk | ### Miscellaneous - Tcl/Tk 8.6.17 #### Environment variables | Name | Value | | ----------------- | ----------------------------------------------------------------------------------------- | | PARALLELS_DMG_URL | https://download.parallels.com/desktop/v26/26.2.2-57373/ParallelsDesktop-26.2.2-57373.dmg | ##### Notes ``` If you want to use Parallels Desktop you should download a package from URL stored in PARALLELS_DMG_URL environment variable. A system extension is allowed for this version. ``` ================================================ FILE: images/macos/macos-15-arm64-Readme.md ================================================ | Announcements | |-| | [macOS 26 (Tahoe) is now generally available in GitHub Actions](https://github.com/actions/runner-images/issues/13739) | | [[macOS] The macOS 14 Sonoma based runner images will begin deprecation on July 6th and will be fully unsupported by November 2nd for GitHub Actions and Azure DevOps](https://github.com/actions/runner-images/issues/13518) | *** # macOS 15 - OS Version: macOS 15.7.4 (24G517) - Kernel Version: Darwin 24.6.0 - Image Version: 20260303.0188.2 ## Installed Software ### Language and Runtime - .NET Core SDK: 8.0.101, 8.0.204, 8.0.303, 8.0.418, 9.0.102, 9.0.203, 9.0.311, 10.0.103 - Bash 3.2.57(1)-release - Clang/LLVM 17.0.0 - Clang/LLVM (Homebrew) 18.1.8 - available on `$(brew --prefix llvm@18)/bin/clang` - GCC 13 (Homebrew GCC 13.4.0) - available by `gcc-13` alias - GCC 14 (Homebrew GCC 14.3.0) - available by `gcc-14` alias - GCC 15 (Homebrew GCC 15.2.0_1) - available by `gcc-15` alias - GNU Fortran 13 (Homebrew GCC 13.4.0) - available by `gfortran-13` alias - GNU Fortran 14 (Homebrew GCC 14.3.0) - available by `gfortran-14` alias - GNU Fortran 15 (Homebrew GCC 15.2.0_1) - available by `gfortran-15` alias - Kotlin 2.3.10-release-465 - Node.js 22.22.0 - Perl 5.42.0 - Python3 3.14.3 - Ruby 3.3.10 ### Package Management - Bundler 4.0.7 - Carthage 0.40.0 - CocoaPods 1.16.2 - Homebrew 5.0.16 - NPM 10.9.4 - Pip3 26.0 (python 3.14) - Pipx 1.8.0 - RubyGems 4.0.7 - Vcpkg 2026 (build from commit 39a6cc0e44) - Yarn 1.22.22 ### Project Management - Apache Ant 1.10.15 - Apache Maven 3.9.12 - Gradle 9.3.1 ### Utilities - 7-Zip 17.05 - aria2 1.37.0 - azcopy 10.32.1 - bazel 9.0.0 - bazelisk 1.28.1 - bsdtar 3.5.3 - available by 'tar' alias - Curl 8.7.1 - Git 2.53.0 - Git LFS 3.7.1 - GitHub CLI 2.87.3 - GNU Tar 1.35 - available by 'gtar' alias - GNU Wget 1.25.0 - gpg (GnuPG) 2.4.9 - jq 1.8.1 - OpenSSL 1.1.1w 11 Sep 2023 - Packer 1.15.0 - pkgconf 2.5.1 - Unxip 3.3 - yq 4.52.4 - zstd 1.5.7 - Ninja 1.13.2 ### Tools - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.84.0 - Azure CLI (azure-devops) 1.0.2 - Bicep CLI 0.41.2 - Cmake 4.2.3 - CodeQL Action Bundle 2.24.2 - Fastlane 2.232.2 - SwiftFormat 0.59.1 - Xcbeautify 3.1.4 - Xcode Command Line Tools 16.4.0.0.1.1747106510 - Xcodes 1.6.2 ### Browsers - Safari 26.3 (20623.2.7.18.1) - SafariDriver 26.3 (20623.2.7.18.1) - Google Chrome 145.0.7632.117 - Google Chrome for Testing 145.0.7632.117 - ChromeDriver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge WebDriver 145.0.3800.82 - Mozilla Firefox 148.0 - geckodriver 0.36.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | --------------- | --------------------------------------- | | CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-arm64 | | EDGEWEBDRIVER | /usr/local/share/edge_driver | | GECKOWEBDRIVER | /opt/homebrew/opt/geckodriver/bin | ### Java | Version | Environment Variable | | --------------------- | -------------------- | | 11.0.30+7 | JAVA_HOME_11_arm64 | | 17.0.18+8 | JAVA_HOME_17_arm64 | | 21.0.10+7.0 (default) | JAVA_HOME_21_arm64 | | 25.0.2+10.0 | JAVA_HOME_25_arm64 | ### Cached Tools #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 #### Python - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.7 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - Clippy 0.1.93 - Rustfmt 1.8.0-stable ### PowerShell Tools - PowerShell 7.4.13 #### PowerShell Modules - Az: 14.6.0 - Pester: 5.7.1 - PSScriptAnalyzer: 1.24.0 ### Xcode | Version | Build | Path | Symlinks | | -------------- | -------- | ------------------------------ | -------------------------------------------------------------- | | 26.3 | 17C529 | /Applications/Xcode_26.3.app | /Applications/Xcode_26.3.0.app | | 26.2 | 17C52 | /Applications/Xcode_26.2.app | /Applications/Xcode_26.2.0.app | | 26.1.1 | 17B100 | /Applications/Xcode_26.1.1.app | /Applications/Xcode_26.1.app | | 26.0.1 | 17A400 | /Applications/Xcode_26.0.1.app | /Applications/Xcode_26.0.app | | 16.4 (default) | 16F6 | /Applications/Xcode_16.4.app | /Applications/Xcode_16.4.0.app
/Applications/Xcode.app | | 16.3 | 16E140 | /Applications/Xcode_16.3.app | /Applications/Xcode_16.3.0.app | | 16.2 | 16C5032a | /Applications/Xcode_16.2.app | /Applications/Xcode_16.2.0.app | | 16.1 | 16B40 | /Applications/Xcode_16.1.app | /Applications/Xcode_16.1.0.app | | 16.0 | 16A242d | /Applications/Xcode_16.app | /Applications/Xcode_16.0.0.app
/Applications/Xcode_16.0.app | #### Installed SDKs | SDK | SDK Name | Xcode Version | | ------------------------- | -------------------- | ------------- | | macOS 15.0 | macosx15.0 | 16.0 | | macOS 15.1 | macosx15.1 | 16.1 | | macOS 15.2 | macosx15.2 | 16.2 | | macOS 15.4 | macosx15.4 | 16.3 | | macOS 15.5 | macosx15.5 | 16.4 | | macOS 26.0 | macosx26.0 | 26.0.1 | | macOS 26.1 | macosx26.1 | 26.1.1 | | macOS 26.2 | macosx26.2 | 26.2, 26.3 | | iOS 18.0 | iphoneos18.0 | 16.0 | | iOS 18.1 | iphoneos18.1 | 16.1 | | iOS 18.2 | iphoneos18.2 | 16.2 | | iOS 18.4 | iphoneos18.4 | 16.3 | | iOS 18.5 | iphoneos18.5 | 16.4 | | iOS 26.0 | iphoneos26.0 | 26.0.1 | | iOS 26.1 | iphoneos26.1 | 26.1.1 | | iOS 26.2 | iphoneos26.2 | 26.2, 26.3 | | Simulator - iOS 18.0 | iphonesimulator18.0 | 16.0 | | Simulator - iOS 18.1 | iphonesimulator18.1 | 16.1 | | Simulator - iOS 18.2 | iphonesimulator18.2 | 16.2 | | Simulator - iOS 18.4 | iphonesimulator18.4 | 16.3 | | Simulator - iOS 18.5 | iphonesimulator18.5 | 16.4 | | Simulator - iOS 26.0 | iphonesimulator26.0 | 26.0.1 | | Simulator - iOS 26.1 | iphonesimulator26.1 | 26.1.1 | | Simulator - iOS 26.2 | iphonesimulator26.2 | 26.2, 26.3 | | tvOS 18.0 | appletvos18.0 | 16.0 | | tvOS 18.1 | appletvos18.1 | 16.1 | | tvOS 18.2 | appletvos18.2 | 16.2 | | tvOS 18.4 | appletvos18.4 | 16.3 | | tvOS 18.5 | appletvos18.5 | 16.4 | | tvOS 26.0 | appletvos26.0 | 26.0.1 | | tvOS 26.1 | appletvos26.1 | 26.1.1 | | tvOS 26.2 | appletvos26.2 | 26.2, 26.3 | | Simulator - tvOS 18.0 | appletvsimulator18.0 | 16.0 | | Simulator - tvOS 18.1 | appletvsimulator18.1 | 16.1 | | Simulator - tvOS 18.2 | appletvsimulator18.2 | 16.2 | | Simulator - tvOS 18.4 | appletvsimulator18.4 | 16.3 | | Simulator - tvOS 18.5 | appletvsimulator18.5 | 16.4 | | Simulator - tvOS 26.0 | appletvsimulator26.0 | 26.0.1 | | Simulator - tvOS 26.1 | appletvsimulator26.1 | 26.1.1 | | Simulator - tvOS 26.2 | appletvsimulator26.2 | 26.2, 26.3 | | watchOS 11.0 | watchos11.0 | 16.0 | | watchOS 11.1 | watchos11.1 | 16.1 | | watchOS 11.2 | watchos11.2 | 16.2 | | watchOS 11.4 | watchos11.4 | 16.3 | | watchOS 11.5 | watchos11.5 | 16.4 | | watchOS 26.0 | watchos26.0 | 26.0.1 | | watchOS 26.1 | watchos26.1 | 26.1.1 | | watchOS 26.2 | watchos26.2 | 26.2, 26.3 | | Simulator - watchOS 11.0 | watchsimulator11.0 | 16.0 | | Simulator - watchOS 11.1 | watchsimulator11.1 | 16.1 | | Simulator - watchOS 11.2 | watchsimulator11.2 | 16.2 | | Simulator - watchOS 11.4 | watchsimulator11.4 | 16.3 | | Simulator - watchOS 11.5 | watchsimulator11.5 | 16.4 | | Simulator - watchOS 26.0 | watchsimulator26.0 | 26.0.1 | | Simulator - watchOS 26.1 | watchsimulator26.1 | 26.1.1 | | Simulator - watchOS 26.2 | watchsimulator26.2 | 26.2, 26.3 | | visionOS 2.0 | xros2.0 | 16.0 | | visionOS 2.1 | xros2.1 | 16.1 | | visionOS 2.2 | xros2.2 | 16.2 | | visionOS 2.4 | xros2.4 | 16.3 | | visionOS 2.5 | xros2.5 | 16.4 | | visionOS 26.0 | xros26.0 | 26.0.1 | | visionOS 26.1 | xros26.1 | 26.1.1 | | visionOS 26.2 | xros26.2 | 26.2, 26.3 | | Simulator - visionOS 2.0 | xrsimulator2.0 | 16.0 | | Simulator - visionOS 2.1 | xrsimulator2.1 | 16.1 | | Simulator - visionOS 2.2 | xrsimulator2.2 | 16.2 | | Simulator - visionOS 2.4 | xrsimulator2.4 | 16.3 | | Simulator - visionOS 2.5 | xrsimulator2.5 | 16.4 | | Simulator - visionOS 26.0 | xrsimulator26.0 | 26.0.1 | | Simulator - visionOS 26.1 | xrsimulator26.1 | 26.1.1 | | Simulator - visionOS 26.2 | xrsimulator26.2 | 26.2, 26.3 | | DriverKit 24.0 | driverkit24.0 | 16.0 | | DriverKit 24.1 | driverkit24.1 | 16.1 | | DriverKit 24.2 | driverkit24.2 | 16.2 | | DriverKit 24.4 | driverkit24.4 | 16.3 | | DriverKit 24.5 | driverkit24.5 | 16.4 | | DriverKit 25.0 | driverkit25.0 | 26.0.1 | | DriverKit 25.1 | driverkit25.1 | 26.1.1 | | DriverKit 25.2 | driverkit25.2 | 26.2, 26.3 | #### Installed Simulators | Name | OS | Simulators | | ------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | iOS 18.5 | 18.5 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 18.6 | 18.6 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 13-inch (M4) | | iOS 26.0 | 26.0.1 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M4)
iPad Pro 13-inch (M5) | | iOS 26.1 | 26.1 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M4)
iPad Pro 13-inch (M5) | | iOS 26.2 | 26.2 | iPhone 16
iPhone 16 Plus
iPhone 16 Pro
iPhone 16 Pro Max
iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPhone SE (3rd generation)
iPad (10th generation)
iPad (A16)
iPad Air 11-inch (M2)
iPad Air 11-inch (M3)
iPad Air 13-inch (M2)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M4)
iPad Pro 13-inch (M5) | | tvOS 18.5 | 18.5 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 26.1 | 26.1 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 26.2 | 26.2 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | watchOS 11.5 | 11.5 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Ultra 2 (49mm) | | watchOS 26.1 | 26.1 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 2 (49mm)
Apple Watch Ultra 3 (49mm) | | watchOS 26.2 | 26.2 | Apple Watch SE (40mm) (2nd generation)
Apple Watch SE (44mm) (2nd generation)
Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 10 (42mm)
Apple Watch Series 10 (46mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 2 (49mm)
Apple Watch Ultra 3 (49mm) | | visionOS 2.3 | 2.3 | Apple Vision Pro | | visionOS 2.4 | 2.4 | Apple Vision Pro | | visionOS 2.5 | 2.5 | Apple Vision Pro | | visionOS 26.1 | 26.1 | Apple Vision Pro | | visionOS 26.2 | 26.2 | Apple Vision Pro | ### Android | Package Name | Version | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 16.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3) | | Android SDK Platform-Tools | 37.0.0 | | Android Support Repository | 47.0.0 | | CMake | 3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724 (default)
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | --------------------------------------------------- | | ANDROID_HOME | /Users/runner/Library/Android/sdk | | ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/29.0.14206865 | | ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk | ### Miscellaneous - Tcl/Tk 8.6.17 ================================================ FILE: images/macos/macos-26-Readme.md ================================================ | Announcements | |-| | [macOS 26 (Tahoe) is now generally available in GitHub Actions](https://github.com/actions/runner-images/issues/13739) | | [[macOS] The macOS 14 Sonoma based runner images will begin deprecation on July 6th and will be fully unsupported by November 2nd for GitHub Actions and Azure DevOps](https://github.com/actions/runner-images/issues/13518) | *** # macOS 26 - OS Version: macOS 26.3 (25D125) - Kernel Version: Darwin 25.3.0 - Image Version: 20260303.0134.1 ## Installed Software ### Language and Runtime - .NET Core SDK: 8.0.101, 8.0.204, 8.0.303, 8.0.418, 9.0.102, 9.0.203, 9.0.311, 10.0.103 - Bash 3.2.57(1)-release - Clang/LLVM 17.0.0 - Clang/LLVM (Homebrew) 20.1.8 - available on `$(brew --prefix llvm@20)/bin/clang` - GCC 13 (Homebrew GCC 13.4.0) - available by `gcc-13` alias - GCC 14 (Homebrew GCC 14.3.0) - available by `gcc-14` alias - GCC 15 (Homebrew GCC 15.2.0_1) - available by `gcc-15` alias - GNU Fortran 13 (Homebrew GCC 13.4.0) - available by `gfortran-13` alias - GNU Fortran 14 (Homebrew GCC 14.3.0) - available by `gfortran-14` alias - GNU Fortran 15 (Homebrew GCC 15.2.0_1) - available by `gfortran-15` alias - Kotlin 2.3.10-release-465 - Node.js 24.14.0 - Perl 5.42.0 - PHP 8.5.3 - Python3 3.14.3 - Ruby 3.4.8 ### Package Management - Bundler 4.0.7 - Carthage 0.40.0 - CocoaPods 1.16.2 - Composer 2.9.5 - Homebrew 5.0.16 - NPM 11.9.0 - Pip3 26.0 (python 3.14) - Pipx 1.8.0 - RubyGems 4.0.7 - Vcpkg 2026 (build from commit 39a6cc0e44) - Yarn 1.22.22 ### Project Management - Apache Ant 1.10.15 - Apache Maven 3.9.12 - Gradle 9.3.1 ### Utilities - 7-Zip 17.05 - aria2 1.37.0 - azcopy 10.32.1 - bazel 9.0.0 - bazelisk 1.28.1 - bsdtar 3.5.3 - available by 'tar' alias - Curl 8.18.0 - Git 2.53.0 - Git LFS 3.7.1 - GitHub CLI 2.87.3 - GNU Tar 1.35 - available by 'gtar' alias - GNU Wget 1.25.0 - gpg (GnuPG) 2.4.9 - jq 1.8.1 - OpenSSL 3.6.1 27 Jan 2026 (Library: OpenSSL 3.6.1 27 Jan 2026) - Packer 1.15.0 - pkgconf 2.5.1 - Unxip 3.3 - yq 4.52.4 - zstd 1.5.7 - Ninja 1.13.2 ### Tools - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.84.0 - Azure CLI (azure-devops) 1.0.2 - Bicep CLI 0.41.2 - Cmake 4.2.3 - CodeQL Action Bundle 2.24.2 - Fastlane 2.232.2 - SwiftFormat 0.59.1 - Xcbeautify 3.1.4 - Xcode Command Line Tools 26.3.0.0.1.1771626560 - Xcodes 1.6.2 ### Linters - SwiftLint 0.63.2 ### Browsers - Safari 26.3 (21623.2.7.11.6) - SafariDriver 26.3 (21623.2.7.11.6) - Google Chrome 145.0.7632.117 - Google Chrome for Testing 145.0.7632.117 - ChromeDriver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge WebDriver 145.0.3800.82 - Mozilla Firefox 148.0 - geckodriver 0.36.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | --------------- | ------------------------------------- | | CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-x64 | | EDGEWEBDRIVER | /usr/local/share/edge_driver | | GECKOWEBDRIVER | /usr/local/opt/geckodriver/bin | ### Java | Version | Environment Variable | | --------------------- | -------------------- | | 11.0.30+7 | JAVA_HOME_11_X64 | | 17.0.18+8 | JAVA_HOME_17_X64 | | 21.0.10+7.0 (default) | JAVA_HOME_21_X64 | | 25.0.2+10.0 | JAVA_HOME_25_X64 | ### Cached Tools #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 #### Python - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Go - 1.23.12 - 1.24.13 - 1.25.7 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - Clippy 0.1.93 - Rustfmt 1.8.0-stable ### PowerShell Tools - PowerShell 7.4.13 #### PowerShell Modules - Az: 14.6.0 - Pester: 5.7.1 - PSScriptAnalyzer: 1.24.0 ### Xcode | Version | Build | Path | Symlinks | | -------------- | -------- | ----------------------------------- | -------------------------------------------------------------- | | 26.4 (beta) | 17E5170d | /Applications/Xcode_26.4_beta_2.app | /Applications/Xcode_26.4.0.app
/Applications/Xcode_26.4.app | | 26.3 | 17C529 | /Applications/Xcode_26.3.app | /Applications/Xcode_26.3.0.app | | 26.2 (default) | 17C52 | /Applications/Xcode_26.2.app | /Applications/Xcode_26.2.0.app
/Applications/Xcode.app | | 26.1.1 | 17B100 | /Applications/Xcode_26.1.1.app | /Applications/Xcode_26.1.app | | 26.0.1 | 17A400 | /Applications/Xcode_26.0.1.app | /Applications/Xcode_26.0.app | #### Installed SDKs | SDK | SDK Name | Xcode Version | | ------------------------- | -------------------- | ------------- | | macOS 26.0 | macosx26.0 | 26.0.1 | | macOS 26.1 | macosx26.1 | 26.1.1 | | macOS 26.2 | macosx26.2 | 26.2, 26.3 | | macOS 26.4 | macosx26.4 | 26.4 | | iOS 26.0 | iphoneos26.0 | 26.0.1 | | iOS 26.1 | iphoneos26.1 | 26.1.1 | | iOS 26.2 | iphoneos26.2 | 26.2, 26.3 | | iOS 26.4 | iphoneos26.4 | 26.4 | | Simulator - iOS 26.0 | iphonesimulator26.0 | 26.0.1 | | Simulator - iOS 26.1 | iphonesimulator26.1 | 26.1.1 | | Simulator - iOS 26.2 | iphonesimulator26.2 | 26.2, 26.3 | | Simulator - iOS 26.4 | iphonesimulator26.4 | 26.4 | | tvOS 26.0 | appletvos26.0 | 26.0.1 | | tvOS 26.1 | appletvos26.1 | 26.1.1 | | tvOS 26.2 | appletvos26.2 | 26.2, 26.3 | | tvOS 26.4 | appletvos26.4 | 26.4 | | Simulator - tvOS 26.0 | appletvsimulator26.0 | 26.0.1 | | Simulator - tvOS 26.1 | appletvsimulator26.1 | 26.1.1 | | Simulator - tvOS 26.2 | appletvsimulator26.2 | 26.2, 26.3 | | Simulator - tvOS 26.4 | appletvsimulator26.4 | 26.4 | | watchOS 26.0 | watchos26.0 | 26.0.1 | | watchOS 26.1 | watchos26.1 | 26.1.1 | | watchOS 26.2 | watchos26.2 | 26.2, 26.3 | | watchOS 26.4 | watchos26.4 | 26.4 | | Simulator - watchOS 26.0 | watchsimulator26.0 | 26.0.1 | | Simulator - watchOS 26.1 | watchsimulator26.1 | 26.1.1 | | Simulator - watchOS 26.2 | watchsimulator26.2 | 26.2, 26.3 | | Simulator - watchOS 26.4 | watchsimulator26.4 | 26.4 | | visionOS 26.0 | xros26.0 | 26.0.1 | | visionOS 26.1 | xros26.1 | 26.1.1 | | visionOS 26.2 | xros26.2 | 26.2, 26.3 | | visionOS 26.4 | xros26.4 | 26.4 | | Simulator - visionOS 26.0 | xrsimulator26.0 | 26.0.1 | | Simulator - visionOS 26.1 | xrsimulator26.1 | 26.1.1 | | Simulator - visionOS 26.2 | xrsimulator26.2 | 26.2, 26.3 | | Simulator - visionOS 26.4 | xrsimulator26.4 | 26.4 | | DriverKit 25.0 | driverkit25.0 | 26.0.1 | | DriverKit 25.1 | driverkit25.1 | 26.1.1 | | DriverKit 25.2 | driverkit25.2 | 26.2, 26.3 | | DriverKit 25.4 | driverkit25.4 | 26.4 | #### Installed Simulators | Name | OS | Simulators | | ------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | iOS 26.0 | 26.0.1 | iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPad (A16)
iPad Air 11-inch (M3)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M4)
iPad Pro 13-inch (M5) | | iOS 26.1 | 26.1 | iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPad (A16)
iPad Air 11-inch (M3)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M5) | | iOS 26.2 | 26.2 | iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPad (A16)
iPad Air 11-inch (M3)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M5) | | tvOS 26.0 | 26.0 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 26.1 | 26.1 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 26.2 | 26.2 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | watchOS 26.0 | 26.0 | Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 3 (49mm) | | watchOS 26.1 | 26.1 | Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 3 (49mm) | | watchOS 26.2 | 26.2 | Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 3 (49mm) | ### Android | Package Name | Version | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 16.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2) | | Android SDK Platform-Tools | 37.0.0 | | Android Support Repository | 47.0.0 | | CMake | 3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724 (default)
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | --------------------------------------------------- | | ANDROID_HOME | /Users/runner/Library/Android/sdk | | ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/29.0.14206865 | | ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk | ### Miscellaneous - Tcl/Tk 8.6.17 ================================================ FILE: images/macos/macos-26-arm64-Readme.md ================================================ | Announcements | |-| | [macOS 26 (Tahoe) is now generally available in GitHub Actions](https://github.com/actions/runner-images/issues/13739) | | [[macOS] The macOS 14 Sonoma based runner images will begin deprecation on July 6th and will be fully unsupported by November 2nd for GitHub Actions and Azure DevOps](https://github.com/actions/runner-images/issues/13518) | *** # macOS 26 - OS Version: macOS 26.3 (25D125) - Kernel Version: Darwin 25.3.0 - Image Version: 20260303.0251.1 ## Installed Software ### Language and Runtime - .NET Core SDK: 8.0.101, 8.0.204, 8.0.303, 8.0.418, 9.0.102, 9.0.203, 9.0.311, 10.0.103 - Bash 3.2.57(1)-release - Clang/LLVM 17.0.0 - Clang/LLVM (Homebrew) 20.1.8 - available on `$(brew --prefix llvm@20)/bin/clang` - GCC 13 (Homebrew GCC 13.4.0) - available by `gcc-13` alias - GCC 14 (Homebrew GCC 14.3.0) - available by `gcc-14` alias - GCC 15 (Homebrew GCC 15.2.0_1) - available by `gcc-15` alias - GNU Fortran 13 (Homebrew GCC 13.4.0) - available by `gfortran-13` alias - GNU Fortran 14 (Homebrew GCC 14.3.0) - available by `gfortran-14` alias - GNU Fortran 15 (Homebrew GCC 15.2.0_1) - available by `gfortran-15` alias - Kotlin 2.3.10-release-465 - Node.js 24.14.0 - Perl 5.42.0 - Python3 3.14.3 - Ruby 3.4.8 ### Package Management - Bundler 4.0.7 - Carthage 0.40.0 - CocoaPods 1.16.2 - Homebrew 5.0.16 - NPM 11.9.0 - Pip3 26.0 (python 3.14) - Pipx 1.8.0 - RubyGems 4.0.7 - Vcpkg 2026 (build from commit 39a6cc0e44) - Yarn 1.22.22 ### Project Management - Apache Ant 1.10.15 - Apache Maven 3.9.12 - Gradle 9.3.1 ### Utilities - 7-Zip 17.05 - aria2 1.37.0 - azcopy 10.32.1 - bazel 9.0.0 - bazelisk 1.28.1 - bsdtar 3.5.3 - available by 'tar' alias - Curl 8.7.1 - Git 2.53.0 - Git LFS 3.7.1 - GitHub CLI 2.87.3 - GNU Tar 1.35 - available by 'gtar' alias - GNU Wget 1.25.0 - gpg (GnuPG) 2.4.9 - jq 1.8.1 - OpenSSL 3.6.1 27 Jan 2026 (Library: OpenSSL 3.6.1 27 Jan 2026) - Packer 1.15.0 - pkgconf 2.5.1 - Unxip 3.3 - yq 4.52.4 - zstd 1.5.7 - Ninja 1.13.2 ### Tools - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.84.0 - Azure CLI (azure-devops) 1.0.2 - Bicep CLI 0.41.2 - Cmake 4.2.3 - CodeQL Action Bundle 2.24.2 - Fastlane 2.232.2 - SwiftFormat 0.59.1 - Xcbeautify 3.1.4 - Xcode Command Line Tools 26.3.0.0.1.1771626560 - Xcodes 1.6.2 ### Browsers - Safari 26.3 (21623.2.7.11.6) - SafariDriver 26.3 (21623.2.7.11.6) - Google Chrome 145.0.7632.117 - Google Chrome for Testing 145.0.7632.117 - ChromeDriver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge WebDriver 145.0.3800.82 - Mozilla Firefox 148.0 - geckodriver 0.36.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | --------------- | --------------------------------------- | | CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-arm64 | | EDGEWEBDRIVER | /usr/local/share/edge_driver | | GECKOWEBDRIVER | /opt/homebrew/opt/geckodriver/bin | ### Java | Version | Environment Variable | | --------------------- | -------------------- | | 11.0.30+7 | JAVA_HOME_11_arm64 | | 17.0.18+8 | JAVA_HOME_17_arm64 | | 21.0.10+7.0 (default) | JAVA_HOME_21_arm64 | | 25.0.2+10.0 | JAVA_HOME_25_arm64 | ### Cached Tools #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 #### Python - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Go - 1.23.12 - 1.24.13 - 1.25.7 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - Clippy 0.1.93 - Rustfmt 1.8.0-stable ### PowerShell Tools - PowerShell 7.4.13 #### PowerShell Modules - Az: 14.6.0 - Pester: 5.7.1 - PSScriptAnalyzer: 1.24.0 ### Xcode | Version | Build | Path | Symlinks | | -------------- | -------- | ----------------------------------- | -------------------------------------------------------------- | | 26.4 (beta) | 17E5170d | /Applications/Xcode_26.4_beta_2.app | /Applications/Xcode_26.4.0.app
/Applications/Xcode_26.4.app | | 26.3 | 17C529 | /Applications/Xcode_26.3.app | /Applications/Xcode_26.3.0.app | | 26.2 (default) | 17C52 | /Applications/Xcode_26.2.app | /Applications/Xcode_26.2.0.app
/Applications/Xcode.app | | 26.1.1 | 17B100 | /Applications/Xcode_26.1.1.app | /Applications/Xcode_26.1.app | | 26.0.1 | 17A400 | /Applications/Xcode_26.0.1.app | /Applications/Xcode_26.0.app | #### Installed SDKs | SDK | SDK Name | Xcode Version | | ------------------------- | -------------------- | ------------- | | macOS 26.0 | macosx26.0 | 26.0.1 | | macOS 26.1 | macosx26.1 | 26.1.1 | | macOS 26.2 | macosx26.2 | 26.2, 26.3 | | macOS 26.4 | macosx26.4 | 26.4 | | iOS 26.0 | iphoneos26.0 | 26.0.1 | | iOS 26.1 | iphoneos26.1 | 26.1.1 | | iOS 26.2 | iphoneos26.2 | 26.2, 26.3 | | iOS 26.4 | iphoneos26.4 | 26.4 | | Simulator - iOS 26.0 | iphonesimulator26.0 | 26.0.1 | | Simulator - iOS 26.1 | iphonesimulator26.1 | 26.1.1 | | Simulator - iOS 26.2 | iphonesimulator26.2 | 26.2, 26.3 | | Simulator - iOS 26.4 | iphonesimulator26.4 | 26.4 | | tvOS 26.0 | appletvos26.0 | 26.0.1 | | tvOS 26.1 | appletvos26.1 | 26.1.1 | | tvOS 26.2 | appletvos26.2 | 26.2, 26.3 | | tvOS 26.4 | appletvos26.4 | 26.4 | | Simulator - tvOS 26.0 | appletvsimulator26.0 | 26.0.1 | | Simulator - tvOS 26.1 | appletvsimulator26.1 | 26.1.1 | | Simulator - tvOS 26.2 | appletvsimulator26.2 | 26.2, 26.3 | | Simulator - tvOS 26.4 | appletvsimulator26.4 | 26.4 | | watchOS 26.0 | watchos26.0 | 26.0.1 | | watchOS 26.1 | watchos26.1 | 26.1.1 | | watchOS 26.2 | watchos26.2 | 26.2, 26.3 | | watchOS 26.4 | watchos26.4 | 26.4 | | Simulator - watchOS 26.0 | watchsimulator26.0 | 26.0.1 | | Simulator - watchOS 26.1 | watchsimulator26.1 | 26.1.1 | | Simulator - watchOS 26.2 | watchsimulator26.2 | 26.2, 26.3 | | Simulator - watchOS 26.4 | watchsimulator26.4 | 26.4 | | visionOS 26.0 | xros26.0 | 26.0.1 | | visionOS 26.1 | xros26.1 | 26.1.1 | | visionOS 26.2 | xros26.2 | 26.2, 26.3 | | visionOS 26.4 | xros26.4 | 26.4 | | Simulator - visionOS 26.0 | xrsimulator26.0 | 26.0.1 | | Simulator - visionOS 26.1 | xrsimulator26.1 | 26.1.1 | | Simulator - visionOS 26.2 | xrsimulator26.2 | 26.2, 26.3 | | Simulator - visionOS 26.4 | xrsimulator26.4 | 26.4 | | DriverKit 25.0 | driverkit25.0 | 26.0.1 | | DriverKit 25.1 | driverkit25.1 | 26.1.1 | | DriverKit 25.2 | driverkit25.2 | 26.2, 26.3 | | DriverKit 25.4 | driverkit25.4 | 26.4 | #### Installed Simulators | Name | OS | Simulators | | ------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | iOS 26.0 | 26.0.1 | iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPad (A16)
iPad Air 11-inch (M3)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M4)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M4)
iPad Pro 13-inch (M5) | | iOS 26.1 | 26.1 | iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPad (A16)
iPad Air 11-inch (M3)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M5) | | iOS 26.2 | 26.2 | iPhone 16e
iPhone 17
iPhone 17 Pro
iPhone 17 Pro Max
iPhone Air
iPad (A16)
iPad Air 11-inch (M3)
iPad Air 13-inch (M3)
iPad mini (A17 Pro)
iPad Pro 11-inch (M5)
iPad Pro 13-inch (M5) | | tvOS 26.0 | 26.0 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 26.1 | 26.1 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | tvOS 26.2 | 26.2 | Apple TV
Apple TV 4K (3rd generation)
Apple TV 4K (3rd generation) (at 1080p) | | watchOS 26.0 | 26.0 | Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 3 (49mm) | | watchOS 26.1 | 26.1 | Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 3 (49mm) | | watchOS 26.2 | 26.2 | Apple Watch SE 3 (40mm)
Apple Watch SE 3 (44mm)
Apple Watch Series 11 (42mm)
Apple Watch Series 11 (46mm)
Apple Watch Ultra 3 (49mm) | | visionOS 26.0 | 26.0 | Apple Vision Pro | | visionOS 26.1 | 26.1 | Apple Vision Pro | | visionOS 26.2 | 26.2 | Apple Vision Pro | ### Android | Package Name | Version | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 16.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2) | | Android SDK Platform-Tools | 37.0.0 | | Android Support Repository | 47.0.0 | | CMake | 3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724 (default)
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | --------------------------------------------------- | | ANDROID_HOME | /Users/runner/Library/Android/sdk | | ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/29.0.14206865 | | ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/27.3.13750724 | | ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk | ### Miscellaneous - Tcl/Tk 8.6.17 ================================================ FILE: images/macos/scripts/build/Configure-Toolset.ps1 ================================================ ################################################################################ ## File: Configure-Toolset.ps1 ## Team: CI-Build ## Desc: Configure toolset ################################################################################ Import-Module "~/image-generation/helpers/Common.Helpers.psm1" function Get-ToolsetToolFullPath { param ( [Parameter(Mandatory)] [string] $ToolName, [Parameter(Mandatory)] [string] $ToolVersion, [Parameter(Mandatory)] [string] $ToolArchitecture ) $toolPath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath $toolName $toolPathVersion = Join-Path -Path $toolPath -ChildPath $toolVersion $foundVersion = Get-Item $toolPathVersion | Sort-Object -Property {[version]$_.name} -Descending | Select-Object -First 1 $installationDir = Join-Path -Path $foundVersion -ChildPath $toolArchitecture return $installationDir } $arch = Get-Architecture $toolcache = (Get-ToolsetContent).toolcache foreach ($tool in $toolcache) { $toolName = $tool.name $toolEnvironment = $tool.arch.$arch.variable_template if (-not $toolEnvironment) { continue } foreach ($toolVersion in $tool.arch.$arch.versions) { Write-Host "Set $toolName $toolVersion environment variable..." $toolPath = Get-ToolsetToolFullPath -ToolName $toolName -ToolVersion $toolVersion -ToolArchitecture $arch $envName = $toolEnvironment -f $toolVersion.split(".") # Add environment variable name=value $envVar = "export {0}={1}" -f $envName, $toolPath Add-Content -Path "${env:HOME}/.bashrc" -Value $envVar } } ================================================ FILE: images/macos/scripts/build/Configure-Xcode-Simulators.ps1 ================================================ ################################################################################ ## File: Configure-Xcode-Simulators.ps1 ## Team: CI-Build ## Desc: CHeck and remove duplicate simulators ################################################################################ Import-Module "~/image-generation/helpers/Common.Helpers.psm1" Import-Module "~/image-generation/helpers/Xcode.Helpers.psm1" $arch = Get-Architecture $xcodeVersions = (Get-ToolsetContent).xcode.${arch}.versions $defaultXcode = (Get-ToolsetContent).xcode.default # Switch to each Xcode version foreach ($xcodeVersion in $xcodeVersions.link) { Write-Host "Switching to Xcode $xcodeVersion" Switch-Xcode -Version $XcodeVersion # Make object of all simulators $devicesList = $(xcrun simctl list -j devices | ConvertFrom-Json) $devicesObject = [System.Collections.ArrayList]@() foreach ($runtime in $devicesList.devices.psobject.Properties.name) { foreach ($device in $devicesList.devices.$runtime) { $devicesObject += [PSCustomObject]@{ runtime = $runtime DeviceName = $($device.name) DeviceId = $($device.udid) DeviceCreationTime = (Get-Item $HOME/Library/Developer/CoreSimulator/Devices/$($device.udid)).CreationTime } } } # Remove duplicates foreach ($simRuntume in $devicesObject.runtime | Sort-Object -Unique) { [System.Collections.ArrayList]$sameRuntimeDevices = [array]$($devicesObject | Where-Object {$_.runtime -eq $simRuntume} | Sort-Object -Property DeviceName) Write-Host "///////////////////////////////////////////////////////////////////" Write-Host "// Checking for duplicates in $simRuntume " $devicesAsHashTable = $sameRuntimeDevices | Group-Object -Property DeviceName -AsHashTable -AsString foreach ($key in $devicesAsHashTable.Keys) { if ( $devicesAsHashTable[$key].count -gt 1) { Write-Host "// Duplicates for $key - $($devicesAsHashTable[$key].count)" } } Write-Host "///////////////////////////////////////////////////////////////////" for ($i = 0; $i -lt $sameRuntimeDevices.Count; $i++) { if ( [string]::IsNullOrEmpty($($sameRuntimeDevices[$i+1].DeviceName)) ){ Write-Host "No more devices to compare in $simRuntume" Write-Host "-------------------------------------------------------------------" continue } Write-Host "$($sameRuntimeDevices[$i].DeviceName) - DeviceId $($sameRuntimeDevices[$i].DeviceId) comparing with" Write-Host "$($sameRuntimeDevices[$i+1].DeviceName) - DeviceId $($sameRuntimeDevices[$i+1].DeviceId)" Write-Host "-------------------------------------------------------------------" if ($sameRuntimeDevices[$i].DeviceName -eq $sameRuntimeDevices[$i+1].DeviceName) { Write-Host "*******************************************************************" Write-Host "** Duplicate found" if ($sameRuntimeDevices[$i].DeviceCreationTime -lt $sameRuntimeDevices[$i+1].DeviceCreationTime) { Write-Host "** will be removed $($sameRuntimeDevices[$i+1].DeviceName) with id $($sameRuntimeDevices[$i+1].DeviceId)" xcrun simctl delete $sameRuntimeDevices[$i+1].DeviceId $sameRuntimeDevices.RemoveAt($i+1) } else { Write-Host "** will be removed $($sameRuntimeDevices[$i].DeviceName) with id $($sameRuntimeDevices[$i].DeviceId)" xcrun simctl delete $sameRuntimeDevices[$i].DeviceId $sameRuntimeDevices.RemoveAt($i) } Write-Host "*******************************************************************" } } } } # Restore default Xcode Write-Host "Restoring default Xcode to $defaultXcode" Switch-Xcode -Version $defaultXcode ================================================ FILE: images/macos/scripts/build/Install-Toolset.ps1 ================================================ ################################################################################ ## File: Install-Toolset.ps1 ## Team: CI-Build ## Desc: Install toolset ################################################################################ Import-Module "~/image-generation/tests/Helpers.psm1" Import-Module "~/image-generation/helpers/Common.Helpers.psm1" Function Install-Asset { param( [Parameter(Mandatory=$true)] [object] $ReleaseAsset ) Write-Host "Download $($ReleaseAsset.filename) archive..." $assetArchivePath = Invoke-DownloadWithRetry $ReleaseAsset.download_url Write-Host "Extract $($ReleaseAsset.filename) content..." $assetFolderPath = Join-Path "/tmp" "$($ReleaseAsset.filename)-temp-dir" New-Item -ItemType Directory -Path $assetFolderPath | Out-Null tar -xzf $assetArchivePath -C $assetFolderPath Write-Host "Invoke installation script..." Push-Location -Path $assetFolderPath Invoke-Expression "bash ./setup.sh" Pop-Location } $arch = Get-Architecture # Get toolcache content from toolset $toolsToInstall = @("Python", "Node", "Go") $tools = (Get-ToolsetContent).toolcache | Where-Object {$toolsToInstall -contains $_.Name} foreach ($tool in $tools) { # Get versions manifest for current tool $assets = Invoke-RestMethod $tool.url -MaximumRetryCount 10 -RetryIntervalSec 30 # Get github release asset for each version foreach ($version in $tool.arch.$arch.versions) { $asset = $assets | Where-Object version -like $version ` | Select-Object -ExpandProperty files ` | Where-Object { ($_.platform -eq $tool.platform) -and ($_.arch -eq $arch)} ` | Select-Object -First 1 Write-Host "Installing $($tool.name) $version..." if ($null -ne $asset) { Install-Asset -ReleaseAsset $asset } else { Write-Host "Asset was not found in versions manifest" exit 1 } } } # Ensure python3 and pip3 point to the latest installed Python version # Fix for ./setup.sh script behavior for python3 and pip3 symlinks # Only Intel images are affected since /usr/local/bin is used for Intel # ARM images use /opt/homebrew/bin which is managed by Homebrew Write-Host "Ensuring python3 and pip3 point to the latest installed Python version from Homebrew" brew unlink python@3.14 && brew link python@3.14 --force --overwrite Split-Path (readlink (which python3)) Invoke-PesterTests "Toolcache" ================================================ FILE: images/macos/scripts/build/Install-Xcode.ps1 ================================================ ################################################################################ ## File: Install-Xcode.ps1 ## Desc: Install Xcode ################################################################################ $ErrorActionPreference = "Stop" Import-Module "$env:HOME/image-generation/helpers/Common.Helpers.psm1" Import-Module "$env:HOME/image-generation/helpers/Xcode.Installer.psm1" -DisableNameChecking $os = Get-OSVersion $arch = Get-Architecture [Array]$xcodeVersions = (Get-ToolsetContent).xcode.$arch.versions Write-Host $xcodeVersions $defaultXcode = (Get-ToolsetContent).xcode.default [Array]::Reverse($xcodeVersions) $threadCount = "5" Write-Host "Installing Xcode versions..." $xcodeVersions | ForEach-Object -ThrottleLimit $threadCount -Parallel { $ErrorActionPreference = "Stop" Import-Module "$env:HOME/image-generation/helpers/Common.Helpers.psm1" Import-Module "$env:HOME/image-generation/helpers/Xcode.Installer.psm1" -DisableNameChecking Install-XcodeVersion -Version $_.version -LinkTo $_.link -Sha256Sum $_.sha256 Confirm-XcodeIntegrity -Version $_.link } $xcodeVersions | ForEach-Object { Approve-XcodeLicense -Version $_.link } Write-Host "Configuring Xcode versions..." $xcodeVersions | ForEach-Object { Write-Host "Configuring Xcode $($_.link) ..." Invoke-XcodeRunFirstLaunch -Version $_.link Install-XcodeAdditionalSimulatorRuntimes -Version $_.link -Arch $arch -Runtimes $_.install_runtimes if ($_.link -match '^(\d+)\.(\d+)(?:\.(\d+))?$' -and [int]$matches[1] -ge 26) { Install-XcodeAdditionalComponents -Version $_.link Update-DyldCache -Version $_.link } } Invoke-XcodeRunFirstLaunch -Version $defaultXcode Write-Host "Configuring Xcode symlinks..." $xcodeVersions | ForEach-Object { Build-XcodeSymlinks -Version $_.link -Symlinks $_.symlinks # Skip creating symlink to install multiple releases of the same Xcode version side-by-side if ($_."skip-symlink" -ne "true") { Build-ProvisionatorSymlink -Version $_.link } } Write-Host "Rebuilding Launch Services database ..." $xcodeVersions | ForEach-Object { Initialize-XcodeLaunchServicesDb -Version $_.link } Write-Host "Setting default Xcode to $defaultXcode" Switch-Xcode -Version $defaultXcode New-Item -Path "/Applications/Xcode.app" -ItemType SymbolicLink -Value (Get-XcodeRootPath -Version $defaultXcode) | Out-Null Write-Host "Setting environment variables 'XCODE__DEVELOPER_DIR'" Set-XcodeDeveloperDirEnvironmentVariables -XcodeList $xcodeVersions.link ================================================ FILE: images/macos/scripts/build/Update-XcodeSimulators.ps1 ================================================ ################################################################################ ## File: Update-XcodeSimulators.ps1 ## Desc: Check available Xcode simulators and create missing ones ################################################################################ $ErrorActionPreference = "Stop" Import-Module "$env:HOME/image-generation/helpers/Xcode.Helpers.psm1" -DisableNameChecking Import-Module "$env:HOME/image-generation/software-report/SoftwareReport.Xcode.psm1" -DisableNameChecking function Test-SimulatorInstalled { param( [Parameter(Mandatory)] [string] $RuntimeId, [Parameter(Mandatory)] [string] $DeviceId, [Parameter(Mandatory)] [string] $SimulatorName, [Parameter(Mandatory)] [string] $XcodeVersion ) $simctlPath = Get-XcodeToolPath -Version $XcodeVersion -ToolName "simctl" if (-not (Test-Path $simctlPath)) { Write-Host "Skip validating simulator '$SimulatorName [$RuntimeId]' because Xcode $XcodeVersion is not installed" return } $simulatorFullNameDebug = "$SimulatorName [$RuntimeId]" Write-Host "Checking Xcode simulator '$simulatorFullNameDebug' (Xcode $XcodeVersion)..." # Get all available devices [string]$rawDevicesInfo = Invoke-Expression "$simctlPath list devices --json" $jsonDevicesInfo = ($rawDevicesInfo | ConvertFrom-Json).devices # Checking if simulator already exists $existingSimulator = $jsonDevicesInfo.$RuntimeId | Where-Object { $_.deviceTypeIdentifier -eq $DeviceId } | Select-Object -First 1 if ($null -eq $existingSimulator) { Write-Host "Simulator '$simulatorFullNameDebug' is missed. Creating it..." Invoke-Expression "$simctlPath create '$SimulatorName' '$DeviceId' '$RuntimeId'" } elseif ($existingSimulator.name -ne $SimulatorName) { Write-Host "Simulator '$simulatorFullNameDebug' is named incorrectly. Renaming it from '$($existingSimulator.name)' to '$SimulatorName'..." Invoke-Expression "$simctlPath rename '$($existingSimulator.udid)' '$SimulatorName'" } else { Write-Host "Simulator '$simulatorFullNameDebug' is installed correctly." } } # First run doesn't provide full data about devices Get-XcodeInfoList | Out-Null ================================================ FILE: images/macos/scripts/build/configure-auto-updates.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-auto-updates.sh ## Desc: Disabling automatic updates ################################################################################ sudo softwareupdate --schedule off defaults write com.apple.SoftwareUpdate AutomaticDownload -int 0 defaults write com.apple.SoftwareUpdate CriticalUpdateInstall -int 0 defaults write com.apple.commerce AutoUpdate -bool false defaults write com.apple.SoftwareUpdate AutomaticCheckEnabled -bool false ================================================ FILE: images/macos/scripts/build/configure-autologin.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-autologin.sh ## Desc: add a Daemon to re-detect the attached network interfaces after vm is booted. ## Maintainer: @timsutton ## script was taken from https://github.com/timsutton/osx-vm-templates/blob/master/scripts/autologin.sh ################################################################################ echo "Enabling automatic GUI login for the '$USERNAME' user.." python3 $HOME/bootstrap/kcpassword.py "$PASSWORD" /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser "$USERNAME" /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUserScreenLocked -bool false : ' The MIT License (MIT) Copyright (c) 2013-2017 Timothy Sutton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ' ================================================ FILE: images/macos/scripts/build/configure-hostname.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-hostname.sh ## Desc: Change the hostname at startup to prevent duplicates ## Hostname and Computername should contain .local in name to avoid name resolution issues ################################################################################ tee -a /usr/local/bin/change_hostname.sh > /dev/null <<\EOF #!/bin/bash -e -o pipefail name="Mac-$(python3 -c 'from time import time; print(int(round(time() * 1000)))')" scutil --set HostName "${name}.local" scutil --set LocalHostName $name scutil --set ComputerName "${name}.local" EOF chmod +x /usr/local/bin/change_hostname.sh sudo tee -a /Library/LaunchDaemons/change_hostname.plist > /dev/null <<\EOF Label change-hostname Program /usr/local/bin/change_hostname.sh RunAtLoad EOF ================================================ FILE: images/macos/scripts/build/configure-machine.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-machine.sh ## Desc: Configure guest OS settings ################################################################################ source ~/utils/utils.sh echo "Enabling developer mode..." sudo /usr/sbin/DevToolsSecurity --enable # Turn off hibernation and get rid of the sleepimage sudo pmset -a hibernatemode 0 sudo rm -f /var/vm/sleepimage # Set computer, disk, and display sleep to never sudo pmset -a sleep 0 disksleep 0 displaysleep 0 # Disable App Nap System Wide defaults write NSGlobalDomain NSAppSleepDisabled -bool YES # Disable Keyboard Setup Assistant window sudo defaults write /Library/Preferences/com.apple.keyboardtype "keyboardtype" -dict-add "3-7582-0" -int 40 # Update VoiceOver Utility to allow VoiceOver to be controlled with AppleScript # by creating a special Accessibility DB file (SIP must be disabled) and # updating the user defaults system to reflect this change. if csrutil status | grep -Eq "System Integrity Protection status: (disabled|unknown)"; then sudo bash -c 'echo -n "a" > /private/var/db/Accessibility/.VoiceOverAppleScriptEnabled' fi defaults write com.apple.VoiceOver4/default SCREnableAppleScript -bool YES # https://developer.apple.com/support/expiration/ # Enterprise iOS Distribution Certificates generated between February 7 and September 1st, 2020 will expire on February 7, 2023. # Rotate the certificate before expiration to ensure your apps are installed and signed with an active certificate. # Confirm that the correct intermediate certificate is installed by verifying the expiration date is set to 2030. # sudo security delete-certificate -Z FF6797793A3CD798DC5B2ABEF56F73EDC9F83A64 /Library/Keychains/System.keychain swiftc -suppress-warnings "${HOME}/image-generation/add-certificate.swift" certs=( AppleWWDRCAG3.cer DeveloperIDG2CA.cer ) for cert in ${certs[@]}; do echo "Adding ${cert} certificate" cert_path="${HOME}/${cert}" curl -fsSL "https://www.apple.com/certificateauthority/${cert}" --output ${cert_path} sudo ./add-certificate ${cert_path} rm ${cert_path} done rm -f ./add-certificate # enable-automationmode-without-authentication brew install expect retry=10 while [[ $retry -gt 0 ]]; do { /usr/bin/expect < /etc/ntp.conf << EOF server 0.pool.ntp.org server 1.pool.ntp.org server 2.pool.ntp.org server 3.pool.ntp.org server time.apple.com server time.windows.com EOF # Set the timezone to UTC. echo "The Timezone setting to UTC..." ln -sf /usr/share/zoneinfo/UTC /etc/localtime ================================================ FILE: images/macos/scripts/build/configure-preimagedata.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-preimagedata.sh ## Desc: Configure data used in the image ################################################################################ source ~/utils/utils.sh arch=$(get_arch) imagedata_file="$HOME/imagedata.json" image_version=$(echo $IMAGE_VERSION | cut -d _ -f 2) image_version_major=${image_version/.*/} image_version_minor=$(echo $image_version | cut -d "." -f 2) os_name=$(sw_vers -productName) os_version=$(sw_vers -productVersion) os_build=$(sw_vers -buildVersion) label_version=$(echo $os_version | cut -d. -f1) if [[ $arch == "arm64" ]]; then image_label="macos-${label_version}-arm64" else image_label="macos-${label_version}" fi software_url="https://github.com/actions/runner-images/blob/${image_label}/${image_version_major}.${image_version_minor}/images/macos/${image_label}-Readme.md" releaseUrl="https://github.com/actions/runner-images/releases/tag/${image_label}%2F${image_version_major}.${image_version_minor}" cat < $imagedata_file [ { "group": "Operating System", "detail": "${os_name}\n${os_version}\n${os_build}" }, { "group": "Runner Image", "detail": "Image: ${image_label}\nVersion: ${image_version}\nIncluded Software: ${software_url}\nImage Release: ${releaseUrl}" } ] EOF echo "export ImageVersion=$image_version" >> $HOME/.bashrc echo "export ImageOS=$IMAGE_OS" >> $HOME/.bashrc ================================================ FILE: images/macos/scripts/build/configure-shell.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-shell.sh ## Desc: Configure shell to use bash ################################################################################ source ~/utils/utils.sh arch=$(get_arch) echo "Changing shell to bash" sudo chsh -s /bin/bash $USERNAME sudo chsh -s /bin/bash root # Check MacOS architecture and add HOMEBREW PATH to bashrc if [[ $arch == "arm64" ]]; then echo "Adding Homebrew environment to bash" # Discussed here: https://github.com/Homebrew/brew/pull/18366 echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.bashrc fi ================================================ FILE: images/macos/scripts/build/configure-ssh.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-ssh.sh ## Desc: Configure ssh ################################################################################ [[ ! -d ~/.ssh ]] && mkdir ~/.ssh 2>/dev/null chmod 777 ~/.ssh ssh-keyscan -t rsa,ecdsa,ed25519 github.com >> ~/.ssh/known_hosts ssh-keyscan -t rsa ssh.dev.azure.com >> ~/.ssh/known_hosts ================================================ FILE: images/macos/scripts/build/configure-system.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-system.sh ## Desc: Post deployment system configuration actions ################################################################################ source ~/utils/utils.sh echo "Set solid color wallpaper" osascript -e 'tell application "Finder" to set desktop picture to POSIX file "/System/Library/Desktop Pictures/Solid Colors/Black.png"' echo "Close all finder windows because they can interfere with UI tests" close_finder_window echo "Disable Handoff and Continuity" defaults write com.apple.coreservices.useractivityd ActivityReceivingEnabled -bool false defaults write com.apple.coreservices.useractivityd ActivityAdvertisingAllowed -bool false echo "Disable graphic effects in System" defaults write com.apple.universalaccess reduceMotion -bool true defaults write com.apple.universalaccess reduceTransparency -bool true echo "Disable analytics daemon (requires SIP to be disabled)" sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.SubmitDiagInfo.plist echo "Disable notification center agent" launchctl unload -w /System/Library/LaunchAgents/com.apple.notificationcenterui.plist echo "Disable Time Machine and it's daemon" sudo tmutil disable sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.backupd.plist echo "Disable Apple Push Notification Service daemon" sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.apsd.plist echo "Set SMC monitoring cadence to 0 to reduce CPU usage" sudo defaults -currentHost write /Library/Preferences/com.apple.powerlogd SMCMonitorCadence 0 echo "Disable Performance and Power Management daemon if possible" sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.PerfPowerServices.plist # Remove Parallels Desktop # https://github.com/actions/runner-images/issues/6105 # https://github.com/actions/runner-images/issues/10143 if is_SonomaX64 || is_SequoiaX64; then brew uninstall parallels fi # Simple warmup of the default Xcode echo "Warm up the default Xcode" xcodebuild -version > /dev/null xcrun simctl list > /dev/null xcrun simctl list devices > /dev/null echo "Put documentation to $HOME root" cp $HOME/image-generation/output/software-report.* $HOME/ echo "Remove fastlane cached cookie" rm -rf ~/.fastlane # Clean up npm cache which collected during image-generation # we have to do that here because `npm install` is run in a few different places during image-generation echo "Clean up npm cache" npm cache clean --force # Clean yarn cache yarn cache clean echo "Clean up temporary directories" sudo rm -rf ~/utils /tmp/* # Erase all indexes and wait until the rebuilding process ends, # for now there is no clear way to get status of indexing process on macOS, it takes around 3-6 minutes to accomplish echo "Erase all MDS indexes and wait until the rebuilding process ends" sudo mdutil -E / > /dev/null echo "Wait for 6 minutes or until the indexing process end signal is found in logs" for _ in {1..12}; do sleep 30 result=$(sudo log show --last 1m | grep -E 'mds.*Released.*BackgroundTask' || true) if [[ -n "$result" ]]; then echo "Sign of indexing completion found:" echo "$result" break fi done echo "Check if the indexing process or other CPU intensive process (5% and more) is still running" cool=0 while true; do usage=$(top -l 2 | grep "CPU usage" | awk '{print int($3)}' | tr -d '%' | tail -n 1) echo "Current CPU usage: ${usage}%" if [ $usage -lt 5 ]; then cool=$((cool + 1)) else ps -arcwwwxo ppid,pid,%cpu,%mem,time,command | head -n 2 || true cool=0 fi echo "Feeling cool for $cool intervals" if [ $cool -gt 5 ]; then echo "Cooled down, exiting..." break fi sleep 1 done echo "Delete symlink for tests running" sudo rm -f /usr/local/bin/invoke_tests echo "Clean Homebrew downloads" sudo rm -rf /Users/$USER/Library/Caches/Homebrew/downloads/* # Uninstall expect used in configure-machine.sh brew uninstall expect ================================================ FILE: images/macos/scripts/build/configure-tccdb-macos.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-tccdb-macos.sh ## Desc: Configure permissions to the TCC.db ################################################################################ source ~/utils/utils.sh # /Library/Application\ Support/com.apple.TCC/TCC.db systemValuesArray=( "'kTCCServiceAccessibility','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceAccessibility','/opt/hca/hosted-compute-agent',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,NULL,1592919552" "'kTCCServiceAccessibility','/opt/hca/start_hca.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321319" "'kTCCServiceAccessibility','/usr/bin/osascript',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321319" "'kTCCServiceAccessibility','/usr/libexec/sshd-keygen-wrapper',1,2,4,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'UNUSED',NULL,0,1644564233" "'kTCCServiceAccessibility','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,NULL,1592919552" "'kTCCServiceAccessibility','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321319" "'kTCCServiceAccessibility','com.apple.Terminal',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1591180502" "'kTCCServiceAccessibility','com.apple.dt.Xcode-Helper',0,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1551941368" "'kTCCServiceAppleEvents','/bin/bash',1,2,0,1,NULL,NULL,0,'com.apple.systemevents',NULL,NULL,1591532620" "'kTCCServiceAppleEvents','/opt/hca/hosted-compute-agent',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552" "'kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552" "'kTCCServiceAppleEvents','com.apple.Terminal',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1591180502" "'kTCCServiceAppleEvents','/usr/bin/osascript',1,2,0,1,NULL,NULL,0,'com.apple.systemevents',NULL,NULL,1591532620" "'kTCCServiceAppleEvents','/usr/bin/osascript',1,2,0,1,NULL,NULL,0,'com.apple.Safari',NULL,NULL,1755087312" "'kTCCServiceAppleEvents','/bin/bash',1,2,0,1,NULL,NULL,0,'com.apple.Safari',NULL,NULL,1755087312" "'kTCCServiceAppleEvents','/opt/hca/hosted-compute-agent',1,2,0,1,NULL,NULL,0,'com.apple.Safari',NULL,NULL,1755087312" "'kTCCServiceBluetoothAlways','/opt/hca/hosted-compute-agent',1,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1736467200" "'kTCCServiceBluetoothAlways','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1736467200" "'kTCCServiceMicrophone','/opt/hca/hosted-compute-agent',1,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1736467200" "'kTCCServiceMicrophone','/opt/hca/start_hca.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1576661342" "'kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1736467200" "'kTCCServiceMicrophone','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1576661342" "'kTCCServicePostEvent','/Library/Application Support/Veertu/Anka/addons/ankarund',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1644565949" "'kTCCServicePostEvent','/opt/hca/start_hca.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321326" "'kTCCServicePostEvent','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321326" "'kTCCServiceScreenCapture','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1599831148" "'kTCCServiceScreenCapture','/opt/hca/hosted-compute-agent',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159" "'kTCCServiceScreenCapture','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159" "'kTCCServiceSystemPolicyAllFiles','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceSystemPolicyAllFiles','/opt/hca/start_hca.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceSystemPolicyAllFiles','/usr/libexec/sshd-keygen-wrapper',1,0,4,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'UNUSED',NULL,0,1639660695" "'kTCCServiceSystemPolicyAllFiles','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceSystemPolicyAllFiles','com.apple.Terminal',0,2,4,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'UNUSED',NULL,0,1678990068" "'kTCCServiceSystemPolicyAllFiles','com.microsoft.wdav',0,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,0,1643970979" "'kTCCServiceSystemPolicyAllFiles','com.microsoft.wdav.epsext',0,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,0,1643970979" "'kTCCServiceSystemPolicyNetworkVolumes','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceSystemPolicyNetworkVolumes','com.apple.Terminal',0,2,4,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'UNUSED',NULL,0,1678990068" ) for values in "${systemValuesArray[@]}"; do configure_system_tccdb "$values,NULL,NULL,'UNUSED',${values##*,}" done # $HOME/Library/Application\ Support/com.apple.TCC/TCC.db userValuesArray=( "'kTCCServiceAccessibility','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceAccessibility','/usr/bin/osascript',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321319" "'kTCCServiceAccessibility','com.apple.Terminal',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1591180502" "'kTCCServiceAppleEvents','/Library/Application Support/Veertu/Anka/addons/ankarund',1,2,3,1,NULL,NULL,0,'com.apple.Terminal',X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,1655808179" "'kTCCServiceAppleEvents','/Library/Application Support/Veertu/Anka/addons/ankarund',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1629294900" "'kTCCServiceAppleEvents','/Library/Application Support/Veertu/Anka/addons/ankarund',1,2,3,1,NULL,NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,164456761" "'kTCCServiceAppleEvents','/bin/bash',1,2,0,1,NULL,NULL,0,'com.apple.finder',NULL,NULL,1592919552" "'kTCCServiceAppleEvents','/bin/bash',1,2,0,1,NULL,NULL,0,'com.apple.systemevents',NULL,NULL,1591532620" "'kTCCServiceAppleEvents','/usr/bin/osascript',1,2,0,1,NULL,NULL,0,'com.apple.finder',NULL,NULL,1592919552" "'kTCCServiceAppleEvents','/usr/bin/osascript',1,2,0,1,NULL,NULL,0,'com.apple.systemevents',NULL,NULL,1591532620" "'kTCCServiceAppleEvents','/opt/hca/hosted-compute-agent',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552" "'kTCCServiceAppleEvents','/opt/hca/hosted-compute-agent',1,2,3,1,NULL,NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,1592919552" "'kTCCServiceAppleEvents','/opt/hca/start_hca.sh',1,2,0,1,NULL,NULL,0,'com.apple.systemevents',NULL,NULL,1574241374" "'kTCCServiceAppleEvents','/usr/libexec/sshd-keygen-wrapper',1,2,0,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1591357685" "'kTCCServiceAppleEvents','/usr/libexec/sshd-keygen-wrapper',1,2,3,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,1644564201" "'kTCCServiceAppleEvents','/usr/libexec/sshd-keygen-wrapper',1,2,3,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'com.apple.Terminal',X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,1650386089" "'kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552" "'kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,1592919552" "'kTCCServiceAppleEvents','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,0,'com.apple.systemevents',NULL,NULL,1574241374" "'kTCCServiceAppleEvents','com.apple.Terminal',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,1591180478" "'kTCCServiceBluetoothAlways','/opt/hca/hosted-compute-agent',1,2,3,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1736467200" "'kTCCServiceBluetoothAlways','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1736467200" "'kTCCServiceMicrophone','/opt/hca/hosted-compute-agent',1,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1736467200" "'kTCCServiceMicrophone','/opt/hca/start_hca.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1576661342" "'kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1736467200" "'kTCCServiceMicrophone','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1576661342" "'kTCCServiceMicrophone','com.apple.CoreSimulator.SimulatorTrampoline',0,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1576347152" "'kTCCServicePostEvent','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceScreenCapture','/opt/hca/hosted-compute-agent',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159" "'kTCCServiceScreenCapture','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159" "'kTCCServiceScreenCapture','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceScreenCapture','/usr/bin/osascript',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321319" "'kTCCServiceScreenCapture','com.apple.Terminal',0,2,4,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'UNUSED',NULL,0,1678990068" "'kTCCServiceSystemPolicyAllFiles','/opt/hca/start_hca.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceSystemPolicyAllFiles','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993" "'kTCCServiceUbiquity','/System/Library/PrivateFrameworks/PhotoLibraryServices.framework/Versions/A/Support/photolibraryd',1,2,5,1,NULL,NULL,NULL,'UNUSED',NULL,0,1619461750" "'kTCCServiceUbiquity','com.apple.CloudDocs.MobileDocumentsFileProvider',0,2,0,1,X'fade0c000000004c0000000100000006000000020000002f636f6d2e6170706c652e436c6f7564446f63732e4d6f62696c65446f63756d656e747346696c6550726f76696465720000000003',NULL,NULL,'UNUSED',NULL,0,1570793290" "'kTCCServiceUbiquity','com.apple.PassKitCore',0,2,5,1,NULL,NULL,NULL,'UNUSED',NULL,0,1619516250" "'kTCCServiceUbiquity','com.apple.TextEdit',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465787445646974000000000003',NULL,NULL,'UNUSED',NULL,0,1566368356" "'kTCCServiceUbiquity','com.apple.mail',0,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1551941469" ) for values in "${userValuesArray[@]}"; do configure_user_tccdb "$values,NULL,NULL,'UNUSED',${values##*,}" done ================================================ FILE: images/macos/scripts/build/configure-windows.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-windows.sh ## Desc: Close open windows ################################################################################ source ~/utils/utils.sh # Close System Preferences window because since Ventura arm64 it is opened by default on Apperance tab if is_Arm64; then echo "Close System Preferences window" osascript -e 'tell application "System Preferences" to quit' fi retry=10 while [[ $retry -gt 0 ]]; do openwindows=$(osascript -e 'tell application "System Events" to get every window of (every process whose class of windows contains window)') && break retry=$((retry-1)) if [[ $retry -eq 0 ]]; then echo "No retry attempts left" exit 1 fi sleep 30 done IFS=',' read -r -a windowslist <<< "$openwindows" if [[ -n ${openwindows} ]]; then echo "Found opened window:" fi for key in ${!windowslist[@]}; do if [[ ${windowslist[$key]} =~ "NotificationCenter" ]]; then echo "[Warning] ${windowslist[$key]}" else echo " - ${windowslist[$key]}" | xargs scripterror=true fi done if [[ ${scripterror} = true ]]; then exit 1 fi ================================================ FILE: images/macos/scripts/build/configure-xcode.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: configure-xcode.sh ## Desc: Configure Xcode after installation ################################################################################ source ~/utils/utils.sh XCODE_LIST=($(get_toolset_value '.xcode.versions | reverse | .[].link')) DEFAULT_XCODE_VERSION=$(get_toolset_value '.xcode.default') # https://github.com/microsoft/appcenter/issues/847 # Assets.xcassets : error : CoreData: error: (6922) I/O error for database # at $HOME/Library/Developer/Xcode/UserData/IB Support/Simulator Devices/{GUID} echo "Erase a device's contents and settings:" for XCODE_VERSION in ${XCODE_LIST[@]}; do echo " Xcode Version: ${XCODE_VERSION}" launchctl remove com.apple.CoreSimulator.CoreSimulatorService || true #add sleep to let CoreSimulatorService to exit sleep 3 # Select xcode version by default sudo xcode-select -s /Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer # Erase a device's contents and settings xcrun simctl erase all #add sleep due to sometimes "xcrun simctl list" takes more than a few moments and script fails when trying to remove CoreSimulatorSerivce sleep 10 done # Select xcode version by default echo "Setting Xcode ${DEFAULT_XCODE_VERSION} as default" sudo xcode-select -s /Applications/Xcode_${DEFAULT_XCODE_VERSION}.app/Contents/Developer ================================================ FILE: images/macos/scripts/build/install-actions-cache.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-actions-cache.sh ## Desc: Download latest release from https://github.com/actions/action-versions ## Maintainer: #actions-runtime and @TingluoHuang ################################################################################ source ~/utils/utils.sh echo "Check if ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE folder exist..." if [[ ! -d $ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE ]]; then mkdir -p $ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE fi download_url=$(resolve_github_release_asset_url "actions/action-versions" "contains(\"action-versions.tar.gz\")" "latest" "$API_PAT") echo "Downloading action-versions $download_url" archive_path=$(download_with_retry $download_url) tar -xzf $archive_path -C $ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE invoke_tests "ActionArchiveCache" ================================================ FILE: images/macos/scripts/build/install-android-sdk.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-android-sdk.sh ## Desc: Install Android SDK, NDK and tools ################################################################################ source ~/utils/utils.sh add_filtered_installation_components() { local minimum_version=$1 shift local tools_array=("$@") for item in ${tools_array[@]}; do # Take the last version number that appears after the last '-' or ';' item_version=$(echo "$item" | grep -oE '[-;][0-9.]+' | grep -oE '[0-9.]+') if [[ "$(printf "${minimum_version}\n${item_version}\n" | sort -V | head -n1)" == "$minimum_version" ]]; then components+=($item) fi done } get_full_ndk_version() { local majorVersion=$1 ndkVersion=$(${SDKMANAGER} --list | grep "ndk;${majorVersion}.*" | awk '{gsub("ndk;", ""); print $1}' | sort -V | tail -n1) echo "$ndkVersion" } components=() android_platform=$(get_toolset_value '.android.platform_min_version') android_build_tool=$(get_toolset_value '.android.build_tools_min_version') android_extra_list=($(get_toolset_value '.android."extras"[]')) android_addon_list=($(get_toolset_value '.android."addons"[]')) android_additional_tools=($(get_toolset_value '.android."additional_tools"[]')) android_ndk_major_versions=($(get_toolset_value '.android.ndk."versions"[]')) android_ndk_major_default=$(get_toolset_value '.android.ndk.default') android_ndk_major_latest=$(get_toolset_value '.android.ndk."versions"[-1]') # Get the latest command line tools from https://developer.android.com/studio#cmdline-tools # Newer version(s) require Java 11 by default # See https://github.com/actions/runner-images/issues/6960 ANDROID_HOME=$HOME/Library/Android/sdk # Download the latest command line tools so that we can accept all of the licenses. # See https://developer.android.com/studio/#command-tools cmdlineToolsVersion=$(get_toolset_value '.android."cmdline-tools"') if [[ $cmdlineToolsVersion == "latest" ]]; then repository_xml_url="https://dl.google.com/android/repository/repository2-1.xml" repository_xml_path=$(download_with_retry $repository_xml_url) cmdlineToolsVersion=$( yq -p=xml \ '.sdk-repository.remotePackage[] | select(."+@path" == "cmdline-tools;latest" and .channelRef."+@ref" == "channel-0").archives.archive[].complete.url | select(contains("commandlinetools-mac"))' \ "$repository_xml_path" ) if [[ -z $cmdlineToolsVersion ]]; then echo "Failed to parse latest command-line tools version" exit 1 fi fi echo "Downloading android command line tools..." archive_path=$(download_with_retry "https://dl.google.com/android/repository/${cmdlineToolsVersion}") echo "Uncompressing android command line tools..." mkdir -p "$ANDROID_HOME" unzip -q "$archive_path" -d "$ANDROID_HOME/cmdline-tools" # Command line tools need to be placed in $ANDROID_HOME/cmdline-tools/latest to function properly mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" echo ANDROID_HOME is "$ANDROID_HOME" export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest:$ANDROID_HOME/cmdline-tools/latest/bin SDKMANAGER=$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager echo "Installing latest tools & platform tools..." echo y | $SDKMANAGER "tools" "platform-tools" echo "Installing latest ndk..." for ndk_version in "${android_ndk_major_versions[@]}" do ndk_full_version=$(get_full_ndk_version $ndk_version) echo y | $SDKMANAGER "ndk;$ndk_full_version" done ndkDefault=$(get_full_ndk_version $android_ndk_major_default) ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$ndkDefault ndkLatest=$(get_full_ndk_version $android_ndk_major_latest) ANDROID_NDK_LATEST_HOME=$ANDROID_HOME/ndk/$ndkLatest # ANDROID_NDK, ANDROID_NDK_HOME, and ANDROID_NDK_ROOT variables should be set as many customer builds depend on them https://github.com/actions/runner-images/issues/5879 echo "export ANDROID_NDK=$ANDROID_NDK_HOME" >> "${HOME}/.bashrc" echo "export ANDROID_NDK_HOME=$ANDROID_NDK_HOME" >> "${HOME}/.bashrc" echo "export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME" >> "${HOME}/.bashrc" echo "export ANDROID_NDK_LATEST_HOME=$ANDROID_NDK_LATEST_HOME" >> "${HOME}/.bashrc" availablePlatforms=($($SDKMANAGER --list | grep "platforms;android-[0-9]" | cut -d"|" -f 1 | sort -u)) add_filtered_installation_components $android_platform "${availablePlatforms[@]}" allBuildTools=($($SDKMANAGER --list --include_obsolete | grep "build-tools;" | cut -d"|" -f 1 | sort -u)) availableBuildTools=$(echo ${allBuildTools[@]//*rc[0-9]/}) add_filtered_installation_components $android_build_tool "${availableBuildTools[@]}" echo "y" | $SDKMANAGER ${components[@]} for extra_name in "${android_extra_list[@]}" do echo "Installing extra $extra_name ..." echo y | $SDKMANAGER "extras;$extra_name" done for addon_name in "${android_addon_list[@]}" do echo "Installing add-on $addon_name ..." echo y | $SDKMANAGER "add-ons;$addon_name" done for tool_name in "${android_additional_tools[@]}" do echo "Installing additional tool $tool_name ..." echo y | $SDKMANAGER "$tool_name" done # Download SDK tools to preserve backward compatibility sdk_tools_version=$(get_toolset_value '.android."sdk-tools"') if [ "$sdk_tools_version" != "null" ]; then sdk_tools_archive_path=$(download_with_retry "https://dl.google.com/android/repository/${sdk_tools_version}") unzip -o -qq "$sdk_tools_archive_path" -d "${ANDROID_SDK_ROOT}" fi invoke_tests "Android" ================================================ FILE: images/macos/scripts/build/install-audiodevice.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-audiodevice.sh ## Desc: Install audio device ################################################################################ source ~/utils/utils.sh echo "install switchaudio-osx" brew_smart_install "switchaudio-osx" echo "install sox" brew_smart_install "sox" invoke_tests "System" "Audio Device" ================================================ FILE: images/macos/scripts/build/install-aws-tools.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-aws-tools.sh ## Desc: Install the AWS CLI, Session Manager plugin for the AWS CLI, and AWS SAM CLI ################################################################################ source ~/utils/utils.sh echo "Installing aws..." awscliv2_pkg_path=$(download_with_retry "https://awscli.amazonaws.com/AWSCLIV2.pkg") sudo installer -pkg "$awscliv2_pkg_path" -target / echo "Installing aws sam cli..." brew tap aws/tap brew_smart_install aws-sam-cli echo "Install aws cli session manager" brew install --cask session-manager-plugin invoke_tests "Common" "AWS" ================================================ FILE: images/macos/scripts/build/install-azcopy.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-azcopy.sh ## Desc: Install AzCopy ################################################################################ source ~/utils/utils.sh if is_Arm64; then url="https://aka.ms/downloadazcopy-v10-mac-arm64" else url="https://aka.ms/downloadazcopy-v10-mac" fi # Install AzCopy archive_path=$(download_with_retry ${url}) unzip -qq $archive_path -d /tmp/azcopy extract_path=$(echo /tmp/azcopy/azcopy*) cp $extract_path/azcopy /usr/local/bin/azcopy chmod +x /usr/local/bin/azcopy invoke_tests "Common" "AzCopy" ================================================ FILE: images/macos/scripts/build/install-bicep.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-bicep.sh ## Desc: Install bicep cli ################################################################################ source ~/utils/utils.sh echo "Installing bicep cli..." brew tap azure/bicep brew_smart_install bicep invoke_tests "Common" "Bicep" ================================================ FILE: images/macos/scripts/build/install-chrome.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-chrome.sh ## Desc: Install chrome and chrome for testing browsers ################################################################################ source ~/utils/utils.sh arch=$(get_arch) echo "Installing Google Chrome..." brew install --cask google-chrome # Parse Google Chrome version full_chrome_version=$("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version) full_chrome_version=${full_chrome_version#Google Chrome } chrome_version=${full_chrome_version%.*} echo "Google Chrome version is $full_chrome_version" # Get Google Chrome versions information chrome_platform="mac-$arch" CHROME_VERSIONS_URL="https://googlechromelabs.github.io/chrome-for-testing/latest-patch-versions-per-build-with-downloads.json" chrome_versions_json=$(download_with_retry "$CHROME_VERSIONS_URL") # Download and unpack the latest release of Chrome Driver chromedriver_version=$(cat $chrome_versions_json | jq -r '.builds["'"$chrome_version"'"].version') echo "Installing Chrome Driver version $chromedriver_version" chromedriver_url=$(cat $chrome_versions_json | jq -r '.builds["'"$chrome_version"'"].downloads.chromedriver[] | select(.platform=="'"${chrome_platform}"'").url') chromedriver_dir="/usr/local/share/chromedriver-$chrome_platform" chromedriver_bin="$chromedriver_dir/chromedriver" chromedriver_archive_path=$(download_with_retry $chromedriver_url) unzip -qq $chromedriver_archive_path -d /tmp/ sudo mv /tmp/chromedriver-$chrome_platform $chromedriver_dir ln -s $chromedriver_bin /usr/local/bin/chromedriver echo "export CHROMEWEBDRIVER=$chromedriver_dir" >> ${HOME}/.bashrc # Download and unpack the latest release of Google Chrome for Testing chrome_for_testing_version=$(cat $chrome_versions_json | jq -r '.builds["'"$chrome_version"'"].version') echo "Installing Google Chrome for Testing version $chrome_for_testing_version" chrome_for_testing_url=$(cat $chrome_versions_json | jq -r '.builds["'"$chrome_version"'"].downloads.chrome[] | select(.platform=="'"${chrome_platform}"'").url') chrome_for_testing_app="Google Chrome for Testing.app" chrome_for_testing_archive_path=$(download_with_retry $chrome_for_testing_url) unzip -qq $chrome_for_testing_archive_path -d /tmp/ mv "/tmp/chrome-$chrome_platform/$chrome_for_testing_app" "/Applications/$chrome_for_testing_app" echo "Installing Selenium" brew_smart_install "selenium-server" invoke_tests "Browsers" "Chrome" ================================================ FILE: images/macos/scripts/build/install-cocoapods.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-cocoapods.sh ## Desc: Install Cocoapods ################################################################################ # Setup the Cocoapods echo "Installing Cocoapods..." pod setup # Create a symlink to /usr/local/bin since it was removed due to Homebrew change. ln -sf $(which pod) /usr/local/bin/pod invoke_tests "Common" "CocoaPods" ================================================ FILE: images/macos/scripts/build/install-codeql-bundle.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-codeql-bundle.sh ## Desc: Install CodeQL bundle ################################################################################ source ~/utils/utils.sh # Retrieve the latest major version of the CodeQL Action to use in the base URL for downloading the bundle. [ -n "$API_PAT" ] && authString=(-H "Authorization: token ${API_PAT}") releases=$(curl "${authString[@]}" -s "https://api.github.com/repos/github/codeql-action/releases") # Get the release tags starting with v[0-9] and sort them in descending order, then parse the first one to get the major version. codeql_action_latest_major_version=$(echo "$releases" | jq -r '.[].tag_name' | grep -E '^v[0-9]' | sort -nr | head -n 1 | sed -E 's/^v([0-9]+).*/\1/') if [ -z "$codeql_action_latest_major_version" ]; then echo "Error: Unable to find the latest major version of the CodeQL Action." exit 1 fi # Retrieve the CLI version of the latest CodeQL bundle. defaults_json_path=$(download_with_retry "https://raw.githubusercontent.com/github/codeql-action/v$codeql_action_latest_major_version/src/defaults.json") bundle_version=$(jq -r '.cliVersion' "$defaults_json_path") bundle_tag_name="codeql-bundle-v$bundle_version" echo "Downloading CodeQL bundle $bundle_version..." # Note that this is the all-platforms CodeQL bundle, to support scenarios where customers run # different operating systems within containers. archive_path=$(download_with_retry "https://github.com/github/codeql-action/releases/download/$bundle_tag_name/codeql-bundle-osx64.tar.gz") codeql_toolcache_path=$AGENT_TOOLSDIRECTORY/CodeQL/$bundle_version/x64 mkdir -p "$codeql_toolcache_path" echo "Unpacking the downloaded CodeQL bundle archive..." tar -xzf "$archive_path" -C "$codeql_toolcache_path" # Touch a file to indicate to the CodeQL Action that this bundle shipped with the toolcache. This is # to support overriding the CodeQL version specified in defaults.json on GitHub Enterprise. touch "$codeql_toolcache_path/pinned-version" # Touch a file to indicate to the toolcache that setting up CodeQL is complete. touch "$codeql_toolcache_path.complete" invoke_tests "Common" "CodeQL Bundle" ================================================ FILE: images/macos/scripts/build/install-common-utils.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-common-utils.sh ## Desc: Install utils listed in toolset file ################################################################################ source ~/utils/utils.sh common_packages=$(get_toolset_value '.brew.common_packages[]') for package in $common_packages; do echo "Installing $package..." case "$package" in packer) # Packer has been deprecated in Homebrew. Use tap to install Packer. brew install hashicorp/tap/packer ;; tcl-tk@8) brew_smart_install "$package" if is_SonomaX64 || is_SequoiaX64 || is_TahoeX64; then # Fix for https://github.com/actions/runner-images/issues/11074 ln -sf "$(brew --prefix tcl-tk@8)/lib/libtcl8.6.dylib" /usr/local/lib/libtcl8.6.dylib ln -sf "$(brew --prefix tcl-tk@8)/lib/libtk8.6.dylib" /usr/local/lib/libtk8.6.dylib fi ;; # Default behaviour for all other packages *) brew_smart_install "$package" ;; esac done cask_packages=$(get_toolset_value '.brew.cask_packages[]') for package in $cask_packages; do echo "Installing $package..." if is_Arm64 && [[ $package == "parallels" ]]; then echo "Parallels installation is skipped for arm64 architecture" else brew install --cask $package fi done # Load "Parallels International GmbH" if is_SonomaX64 || is_SequoiaX64; then sudo kextload /Applications/Parallels\ Desktop.app/Contents/Library/Extensions/10.9/prl_hypervisor.kext || true fi # Execute AppleScript to change security preferences for macOS12, macOS13, macOS14 and macOS15 # System Preferences -> Security & Privacy -> General -> Unlock -> Allow -> Not now if is_SonomaX64 || is_SequoiaX64; then for retry in {4..0}; do echo "Executing AppleScript to change security preferences. Retries left: $retry" { set -e osascript -e 'tell application "System Events" to get application processes where visible is true' if is_SonomaX64; then osascript $HOME/utils/confirm-identified-developers-macos14.scpt $USER_PASSWORD fi if is_SequoiaX64; then osascript $HOME/utils/confirm-identified-developers-macos15.scpt $USER_PASSWORD fi } && break if [[ $retry -eq 0 ]]; then echo "Executing AppleScript failed. No retries left" exit 1 fi echo "Executing AppleScript failed. Sleeping for 10 seconds and retrying" sleep 10 done fi # Validate "Parallels International GmbH" kext if is_SonomaX64 || is_SequoiaX64; then echo "Closing System Settings window if it is still opened" killall "System Settings" || true echo "Checking parallels kexts" dbName="/var/db/SystemPolicyConfiguration/KextPolicy" dbQuery="SELECT * FROM kext_policy WHERE bundle_id LIKE 'com.parallels.kext.%';" kext=$(sudo sqlite3 $dbName "$dbQuery") if [[ -z $kext ]]; then echo "Parallels International GmbH not found" exit 1 fi # Create env variable url=$(brew info --json=v2 --installed | jq -r '.casks[] | select(.name[] == "Parallels Desktop").url') if [[ -z $url ]]; then echo "Unable to parse url for Parallels Desktop cask" exit 1 fi echo "export PARALLELS_DMG_URL=$url" >> ${HOME}/.bashrc fi # Install Azure DevOps extension for Azure Command Line Interface az extension add -n azure-devops # Invoke tests for all basic tools invoke_tests "BasicTools" ================================================ FILE: images/macos/scripts/build/install-dotnet.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-dotnet.sh ## Desc: Install dotnet ################################################################################ source ~/utils/utils.sh export DOTNET_CLI_TELEMETRY_OPTOUT=1 arch=$(get_arch) # Download installer from dot.net and keep it locally DOTNET_INSTALL_SCRIPT="https://dot.net/v1/dotnet-install.sh" install_script_path=$(download_with_retry $DOTNET_INSTALL_SCRIPT) chmod +x $install_script_path args_list=() echo "Parsing dotnet SDK (except rc and preview versions) from .json..." dotnet_versions=($(get_toolset_value ".dotnet.arch[\"$arch\"].versions | .[]")) for dotnet_version in ${dotnet_versions[@]}; do release_url="https://raw.githubusercontent.com/dotnet/core/main/release-notes/${dotnet_version}/releases.json" releases_json_file=$(download_with_retry "$release_url") args_list+=( $(cat $releases_json_file | \ jq -r '.releases[].sdk."version"' | \ grep -v -E '\-(preview|rc)\d*' | \ sort -r | rev | uniq -s 2 | rev) ) done for ARGS in ${args_list[@]}; do $install_script_path --version $ARGS -NoPath --arch $arch done # dotnet installer doesn't create symlink to executable in /user/local/bin # Moreover at that moment /user/local/bin doesn't exist (though already added to $PATH) ln -s ~/.dotnet/dotnet /usr/local/bin/dotnet # Validate installation if [[ $(dotnet --list-sdks | wc -l) -lt "1" ]]; then echo "The .NET Core SDK is not installed" exit 1 fi echo 'export PATH="$PATH:$HOME/.dotnet/tools"' >> $HOME/.bashrc echo "Dotnet operations have been completed successfully..." invoke_tests "Common" ".NET" ================================================ FILE: images/macos/scripts/build/install-edge.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-edge.sh ## Desc: Install edge browser ################################################################################ source ~/utils/utils.sh echo "Installing Microsoft Edge..." brew install --cask microsoft-edge EDGE_INSTALLATION_PATH="/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" edge_version=$("$EDGE_INSTALLATION_PATH" --version | cut -d' ' -f 3) edge_version_major=$(echo $edge_version | cut -d'.' -f 1) echo "Version of Microsoft Edge: ${edge_version}" echo "Installing Microsoft Edge WebDriver..." edge_driver_version_file_path=$(download_with_retry "https://msedgedriver.microsoft.com/LATEST_RELEASE_${edge_version_major}_MACOS") edge_driver_latest_version=$(iconv -f utf-16 -t utf-8 "$edge_driver_version_file_path" | tr -d '\r') if is_Arm64; then edge_driver_url="https://msedgedriver.microsoft.com/${edge_driver_latest_version}/edgedriver_mac64_m1.zip" else edge_driver_url="https://msedgedriver.microsoft.com/${edge_driver_latest_version}/edgedriver_mac64.zip" fi echo "Compatible version of WebDriver: ${edge_driver_latest_version}" edge_driver_archive_path=$(download_with_retry "$edge_driver_url") # Move webdriver to the separate directory to be consistent with the docs # https://docs.microsoft.com/en-us/azure/devops/pipelines/test/continuous-test-selenium?view=azure-devops#decide-how-you-will-deploy-and-test-your-app EDGE_DRIVER_DIR="/usr/local/share/edge_driver" mkdir -p $EDGE_DRIVER_DIR unzip -qq $edge_driver_archive_path -d $EDGE_DRIVER_DIR ln -s $EDGE_DRIVER_DIR/msedgedriver /usr/local/bin/msedgedriver echo "export EDGEWEBDRIVER=${EDGE_DRIVER_DIR}" >> ${HOME}/.bashrc # Configure Edge Updater to prevent auto update # https://learn.microsoft.com/en-us/deployedge/edge-learnmore-edgeupdater-for-macos sudo mkdir "/Library/Managed Preferences" cat < /dev/null updatePolicies global UpdateDefault 3 EOF sudo chown root:wheel "/Library/Managed Preferences/com.microsoft.EdgeUpdater.plist" invoke_tests "Browsers" "Edge" ================================================ FILE: images/macos/scripts/build/install-firefox.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-firefox.sh ## Desc: Install firefox browser ################################################################################ source ~/utils/utils.sh echo "Installing Firefox..." brew install --cask firefox echo "Installing Geckodriver..." brew_smart_install "geckodriver" geckoPath="$(brew --prefix geckodriver)/bin" echo "Add GECKOWEBDRIVER to bashrc..." echo "export GECKOWEBDRIVER=${geckoPath}" >> ${HOME}/.bashrc invoke_tests "Browsers" "Firefox" ================================================ FILE: images/macos/scripts/build/install-gcc.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-gcc.sh ## Desc: Install GCC ################################################################################ source ~/utils/utils.sh gccVersions=$(get_toolset_value '.gcc.versions | .[]') for gccVersion in $gccVersions; do brew_smart_install "gcc@${gccVersion}" done # Delete default gfortran link if it exists https://github.com/actions/runner-images/issues/1280 gfortranPath=$(which gfortran) || true if [[ $gfortranPath ]]; then rm $gfortranPath fi invoke_tests "Common" "GCC" ================================================ FILE: images/macos/scripts/build/install-git.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-git.sh ## Desc: Install Git and Git LFS ################################################################################ source ~/utils/utils.sh echo "Installing Git..." brew_smart_install "git" git config --global --add safe.directory "*" echo "Installing Git LFS" brew_smart_install "git-lfs" # Update global git config git lfs install # Update system git config sudo git lfs install --system echo "Disable all the Git help messages..." git config --global advice.pushUpdateRejected false git config --global advice.pushNonFFCurrent false git config --global advice.pushNonFFMatching false git config --global advice.pushAlreadyExists false git config --global advice.pushFetchFirst false git config --global advice.pushNeedsForce false git config --global advice.statusHints false git config --global advice.statusUoption false git config --global advice.commitBeforeMerge false git config --global advice.resolveConflict false git config --global advice.implicitIdentity false git config --global advice.detachedHead false git config --global advice.amWorkDir false git config --global advice.rmHints false invoke_tests "Git" ================================================ FILE: images/macos/scripts/build/install-homebrew.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-homebrew.sh ## Desc: Install Homebrew ################################################################################ source ~/utils/utils.sh arch=$(get_arch) echo "Installing Homebrew..." homebrew_installer_path=$(download_with_retry "https://raw.githubusercontent.com/Homebrew/install/master/install.sh") /bin/bash $homebrew_installer_path if [[ $arch == "arm64" ]]; then /opt/homebrew/bin/brew update /opt/homebrew/bin/brew upgrade /opt/homebrew/bin/brew upgrade --cask /opt/homebrew/bin/brew cleanup eval "$(/opt/homebrew/bin/brew shellenv)" fi git clone https://github.com/Homebrew/homebrew-cask $(brew --repository)/Library/Taps/homebrew/homebrew-cask --origin=origin --template= --config core.fsmonitor=false --depth 1 git clone https://github.com/Homebrew/homebrew-core $(brew --repository)/Library/Taps/homebrew/homebrew-core --origin=origin --template= --config core.fsmonitor=false --depth 1 brew tap homebrew/cask brew tap homebrew/core echo "Disabling Homebrew analytics..." brew analytics off # jq is required for further installation scripts echo "Installing jq..." brew_smart_install jq echo "Installing curl..." brew_smart_install curl echo "Installing wget..." brew_smart_install "wget" ================================================ FILE: images/macos/scripts/build/install-llvm.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-llvm.sh ## Desc: Install LLVM ################################################################################ source ~/utils/utils.sh llvmVersion=$(get_toolset_value '.llvm.version') brew_smart_install "llvm@${llvmVersion}" invoke_tests "LLVM" ================================================ FILE: images/macos/scripts/build/install-mono.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-mono.sh ## Desc: Install Mono Framework ################################################################################ source ~/utils/utils.sh # Install Mono Framework mono_version_full=$(get_toolset_value '.mono.framework.version') mono_pkg_sha256=$(get_toolset_value '.mono.framework.sha256') mono_version=$(echo $mono_version_full | cut -d. -f 1,2,3) mono_version_short=$(echo $mono_version_full | cut -d. -f 1,2) mono_pkg_url="https://download.mono-project.com/archive/${mono_version}/macos-10-universal/MonoFramework-MDK-${mono_version_full}.macos10.xamarin.universal.pkg" MONO_VERSIONS_PATH='/Library/Frameworks/Mono.framework/Versions' mono_pkg_path=$(download_with_retry $mono_pkg_url) use_checksum_comparison $mono_pkg_path $mono_pkg_sha256 echo "Installing Mono Framework ${mono_version_full}..." sudo installer -pkg $mono_pkg_path -target / # Download and install NUnit console nunit_version=$(get_toolset_value '.mono.nunit.version') nunit_archive_url="https://github.com/nunit/nunit-console/releases/download/${nunit_version}/NUnit.Console-${nunit_version}.zip" nunit_archive_sha256=$(get_toolset_value '.mono.nunit.sha256') NUNIT_PATH="/Library/Developer/nunit" nunit_version_path=$NUNIT_PATH/$nunit_version nunit_archive_path=$(download_with_retry $nunit_archive_url) use_checksum_comparison $nunit_archive_path $nunit_archive_sha256 echo "Installing NUnit ${nunit_version}..." sudo mkdir -p $nunit_version_path sudo unzip -q $nunit_archive_path -d $nunit_version_path # Create a wrapper script for nunit3-console echo "Creating nunit3-console wrapper..." nunit3_console_wrapper=$(mktemp) cat < "$nunit3_console_wrapper" #!/bin/bash -e -o pipefail exec ${MONO_VERSIONS_PATH}/${mono_version}/bin/mono --debug \$MONO_OPTIONS $nunit_version_path/nunit3-console.exe "\$@" EOF cat $nunit3_console_wrapper sudo chmod +x $nunit3_console_wrapper sudo mv $nunit3_console_wrapper "${MONO_VERSIONS_PATH}/${mono_version}/Commands/nunit3-console" # Create a symlink for the short version of Mono (e.g., 6.12) echo "Creating short symlink '${mono_version_short}'..." sudo ln -s ${MONO_VERSIONS_PATH}/${mono_version} ${MONO_VERSIONS_PATH}/${mono_version_short} # Invoke tests and Mono invoke_tests "Mono" ================================================ FILE: images/macos/scripts/build/install-nginx.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-nginx.sh ## Desc: Install Nginx ################################################################################ source ~/utils/utils.sh brew_smart_install nginx sudo sed -Ei '' 's/listen.*/listen 80;/' $(brew --prefix)/etc/nginx/nginx.conf invoke_tests "WebServers" "Nginx" ================================================ FILE: images/macos/scripts/build/install-node.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-node.sh ## Desc: Install Node.js ################################################################################ source ~/utils/utils.sh defaultVersion=$(get_toolset_value '.node.default') echo "Installing Node.js $defaultVersion" brew_smart_install "node@$defaultVersion" brew link node@$defaultVersion --force --overwrite echo Installing yarn... yarn_installer_path=$(download_with_retry "https://yarnpkg.com/install.sh") bash $yarn_installer_path invoke_tests "Node" "Node.js" ================================================ FILE: images/macos/scripts/build/install-openjdk.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-openjdk.sh ## Desc: Install openjdk ################################################################################ source ~/utils/utils.sh createEnvironmentVariable() { local JAVA_VERSION=$1 local DEFAULT=$2 if [[ $arch == "arm64" ]]; then INSTALL_PATH_PATTERN=$(echo ${AGENT_TOOLSDIRECTORY}/Java_Temurin-Hotspot_jdk/${JAVA_VERSION}*/arm64/Contents/Home/) else INSTALL_PATH_PATTERN=$(echo ${AGENT_TOOLSDIRECTORY}/Java_Temurin-Hotspot_jdk/${JAVA_VERSION}*/x64/Contents/Home/) fi if [[ ${DEFAULT} == "True" ]]; then echo "Setting up JAVA_HOME variable to ${INSTALL_PATH_PATTERN}" echo "export JAVA_HOME=${INSTALL_PATH_PATTERN}" >> ${HOME}/.bashrc fi if [[ $arch == "arm64" ]]; then echo "Setting up JAVA_HOME_${JAVA_VERSION}_arm64 variable to ${INSTALL_PATH_PATTERN}" echo "export JAVA_HOME_${JAVA_VERSION}_arm64=${INSTALL_PATH_PATTERN}" >> ${HOME}/.bashrc else echo "Setting up JAVA_HOME_${JAVA_VERSION}_X64 variable to ${INSTALL_PATH_PATTERN}" echo "export JAVA_HOME_${JAVA_VERSION}_X64=${INSTALL_PATH_PATTERN}" >> ${HOME}/.bashrc fi } installOpenJDK() { local JAVA_VERSION=$1 # Get link for Java binaries and Java version hotspot_json_path=$(download_with_retry "https://api.adoptium.net/v3/assets/latest/${JAVA_VERSION}/hotspot") if [[ $arch == "arm64" ]]; then asset=$(jq -r '.[] | select(.binary.os=="mac" and .binary.image_type=="jdk" and .binary.architecture=="aarch64")' "$hotspot_json_path") else asset=$(jq -r '.[] | select(.binary.os=="mac" and .binary.image_type=="jdk" and .binary.architecture=="x64")' "$hotspot_json_path") fi archive_url=$(echo "$asset" | jq -r '.binary.package.link') fullVersion=$(echo "$asset" | jq -r '.version.semver' | tr '+' '-') # Remove 'LTS' suffix from the version if present fullVersion="${fullVersion//.LTS/}" JAVA_TOOLCACHE_PATH=${AGENT_TOOLSDIRECTORY}/Java_Temurin-Hotspot_jdk javaToolcacheVersionPath=$JAVA_TOOLCACHE_PATH/${fullVersion} if [[ $arch == "arm64" ]]; then javaToolcacheVersionArchPath=${javaToolcacheVersionPath}/arm64 else javaToolcacheVersionArchPath=${javaToolcacheVersionPath}/x64 fi # Download and extract Java binaries archive_path=$(download_with_retry $archive_url) echo "Creating ${javaToolcacheVersionArchPath} directory" mkdir -p ${javaToolcacheVersionArchPath} tar -xf $archive_path -C ${javaToolcacheVersionArchPath} --strip-components=1 # Create complete file if [[ $arch == "arm64" ]]; then touch ${javaToolcacheVersionPath}/arm64.complete else touch ${javaToolcacheVersionPath}/x64.complete fi # Create a symlink to '/Library/Java/JavaVirtualMachines' # so '/usr/libexec/java_home' will be able to find Java sudo ln -sf ${javaToolcacheVersionArchPath} /Library/Java/JavaVirtualMachines/Temurin-Hotspot-${JAVA_VERSION}.jdk } arch=$(get_arch) defaultVersion=$(get_toolset_value '.java.'$arch'.default') jdkVersionsToInstall=($(get_toolset_value ".java.${arch}.versions[]")) for jdkVersionToInstall in ${jdkVersionsToInstall[@]}; do installOpenJDK ${jdkVersionToInstall} if [[ ${jdkVersionToInstall} == ${defaultVersion} ]] then createEnvironmentVariable ${jdkVersionToInstall} True else createEnvironmentVariable ${jdkVersionToInstall} False fi done echo Installing Maven... brew_smart_install "maven" echo Installing Gradle ... brew_smart_install "gradle" invoke_tests "Java" ================================================ FILE: images/macos/scripts/build/install-openssl.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-openssl.sh ## Desc: Install openssl ################################################################################ source ~/utils/utils.sh echo "Install openssl@1.1" COMMIT=d91dabd087cb0b906c92a825df9e5e5e1a4f59f8 FORMULA_URL="https://raw.githubusercontent.com/Homebrew/homebrew-core/$COMMIT/Formula/o/openssl@1.1.rb" FORMULA_PATH="$(brew --repository)/Library/Taps/homebrew/homebrew-core/Formula/o/openssl@1.1.rb" mkdir -p "$(dirname $FORMULA_PATH)" curl -fsSL $FORMULA_URL -o $FORMULA_PATH HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_FROM_API=1 brew install openssl@1.1 if ! is_Arm64; then # Symlink brew openssl@1.1 to `/usr/local/bin` as Homebrew refuses ln -sf $(brew --prefix openssl@1.1)/bin/openssl /usr/local/bin/openssl else # arm64 has a different installation prefix for brew ln -sf $(brew --prefix openssl@1.1)/bin/openssl /opt/homebrew/bin/openssl fi if ! is_Arm64; then # Most of build systems and scripts look up ssl here ln -sf $(brew --cellar openssl@1.1)/1.1* /usr/local/opt/openssl fi invoke_tests "OpenSSL" ================================================ FILE: images/macos/scripts/build/install-php.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-php.sh ## Desc: Install PHP ################################################################################ source ~/utils/utils.sh echo Installing PHP phpVersionToolset=$(get_toolset_value '.php.version') brew_smart_install "php@${phpVersionToolset}" echo Installing composer brew_smart_install "composer" invoke_tests "PHP" ================================================ FILE: images/macos/scripts/build/install-postgresql.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-postgresql.sh ## Desc: Install PostgreSQL ################################################################################ source ~/utils/utils.sh # Fetch PostgreSQL version to install from the toolset toolsetVersion=$(get_toolset_value '.postgresql.version') # Install latest version of PostgreSQL brew_smart_install postgresql@$toolsetVersion # Service PostgreSQL should be started before use postgreService=$(brew services list | grep -oe "postgresql\S*") brew services start $postgreService # Verify PostgreSQL is ready for accept incoming connections echo "Check PostgreSQL service is running" i=10 COMMAND='pg_isready' while [[ $i -gt 0 ]]; do echo "Check PostgreSQL service status" eval $COMMAND && break ((i--)) if [[ $i == 0 ]]; then echo "PostgreSQL service not ready, all attempts exhausted" exit 1 fi echo "PostgreSQL service not ready, wait 10 more sec, attempts left: $i" sleep 10 done # Stop PostgreSQL brew services stop $postgreService invoke_tests "Databases" "PostgreSQL" ================================================ FILE: images/macos/scripts/build/install-powershell.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-powershell.sh ## Desc: Install PowerShell ################################################################################ source ~/utils/utils.sh echo Installing PowerShell... arch=$(get_arch) metadata_json_path=$(download_with_retry "https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/metadata.json") pwshVersionToolset=$(get_toolset_value '.pwsh.version') pwshVersions=$(jq -r '.LTSReleaseTag[]' $metadata_json_path) for version in ${pwshVersions[@]}; do if [[ "$version" =~ "$pwshVersionToolset" ]]; then download_url=$(resolve_github_release_asset_url "PowerShell/PowerShell" "contains(\"osx-$arch.pkg\")" "$version" "$API_PAT") break fi done pkg_path=$(download_with_retry $download_url) # Work around the issue on macOS Big Sur 11.5 or higher for possible error message ("can't be opened because Apple cannot check it for malicious software") when installing the package sudo xattr -rd com.apple.quarantine $pkg_path sudo installer -pkg $pkg_path -target / # Install PowerShell modules psModules=$(get_toolset_value '.powershellModules[].name') for module in ${psModules[@]}; do echo "Installing $module module" moduleVersions="$(get_toolset_value ".powershellModules[] | select(.name==\"$module\") | .versions[]?")" if [[ -z $moduleVersions ]];then # Check MacOS architecture and sudo on Arm64 if [[ $arch == "arm64" ]]; then sudo pwsh -command "& {Install-Module $module -Force -Scope AllUsers}" else pwsh -command "& {Install-Module $module -Force -Scope AllUsers}" fi else for version in ${moduleVersions[@]}; do # Check MacOS architecture and sudo on Arm64 if [[ $arch == "arm64" ]]; then echo " - $version" sudo pwsh -command "& {Install-Module $module -RequiredVersion $version -Force -Scope AllUsers}" else echo " - $version" pwsh -command "& {Install-Module $module -RequiredVersion $version -Force -Scope AllUsers}" fi done fi done # Fix permission root => runner after installing powershell for arm64 arch if [[ $arch == "arm64" ]]; then sudo chown -R $USER ~/.local ~/.cache ~/.config fi # A dummy call to initialize .IdentityService directory pwsh -command "& {Import-Module Az}" # powershell link was removed in powershell-6.0.0-beta9 sudo ln -s /usr/local/bin/pwsh /usr/local/bin/powershell invoke_tests "Powershell" ================================================ FILE: images/macos/scripts/build/install-python.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-python.sh ## Desc: Install Python ################################################################################ source ~/utils/utils.sh echo "Installing Python Tooling" # Close Finder window close_finder_window # Installing latest Homebrew Python 3 to handle python3 and pip3 symlinks echo "Brew Installing default Python 3" brew_smart_install "python3" # Pipx has its own Python dependency echo "Installing pipx" if is_Arm64; then export PIPX_BIN_DIR="$HOME/.local/bin" export PIPX_HOME="$HOME/.local/pipx" else export PIPX_BIN_DIR=/usr/local/opt/pipx_bin export PIPX_HOME=/usr/local/opt/pipx fi brew_smart_install "pipx" echo "export PIPX_BIN_DIR=${PIPX_BIN_DIR}" >> ${HOME}/.bashrc echo "export PIPX_HOME=${PIPX_HOME}" >> ${HOME}/.bashrc echo 'export PATH="$PIPX_BIN_DIR:$PATH"' >> ${HOME}/.bashrc invoke_tests "Python" ================================================ FILE: images/macos/scripts/build/install-rosetta.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-rosetta.sh ## Desc: Install Rosetta ################################################################################ echo "Installing Rosetta" /usr/sbin/softwareupdate --install-rosetta --agree-to-license ================================================ FILE: images/macos/scripts/build/install-ruby.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-ruby.sh ## Desc: Install Ruby ################################################################################ source ~/utils/utils.sh arch=$(get_arch) DEFAULT_RUBY_VERSION=$(get_toolset_value '.ruby.default') RUBY_PATH=$AGENT_TOOLSDIRECTORY/Ruby TOOLSET_VERSIONS=$(get_toolset_value '.toolcache[] | select(.name | contains("Ruby")) | .arch.'$arch'.versions[]') echo "Installing Ruby..." brew_smart_install "ruby@${DEFAULT_RUBY_VERSION}" echo "Set Ruby ${DEFAULT_RUBY_VERSION} as default" if [[ $arch == "arm64" ]]; then export PATH=/opt/homebrew/opt/ruby@${DEFAULT_RUBY_VERSION}/bin:$PATH else export PATH=/usr/local/opt/ruby@${DEFAULT_RUBY_VERSION}/bin:$PATH fi echo "Setting up gem environment" GEM_PATH=$(gem env|awk '/EXECUTABLE DIRECTORY/ {print $4}') echo "GEM_PATH=$GEM_PATH" >> $HOME/.bashrc if [[ $arch == "arm64" ]]; then echo 'export PATH="$GEM_PATH:/opt/homebrew/opt/ruby@'${DEFAULT_RUBY_VERSION}'/bin:$PATH"' >> $HOME/.bashrc else echo 'export PATH="$GEM_PATH:/usr/local/opt/ruby@'${DEFAULT_RUBY_VERSION}'/bin:$PATH"' >> $HOME/.bashrc fi echo "Check if Ruby hostedtoolcache folder exists" if [[ ! -d $RUBY_PATH ]]; then mkdir -p "$RUBY_PATH" fi for toolset_version in ${TOOLSET_VERSIONS[@]}; do echo "Installing Ruby version: $toolset_version..." download_url=$(resolve_github_release_asset_url "ruby/ruby-builder" "contains(\"darwin-$arch.tar.gz\")" "ruby-$toolset_version" "$API_PAT") package_tar_name="${download_url##*/}" ruby_version=$(echo "$package_tar_name" | cut -d'-' -f 2) ruby_version_path="$RUBY_PATH/$ruby_version" echo "Create Ruby $ruby_version directory" mkdir -p "$ruby_version_path" echo "Downloading tar archive $package_tar_name" archive_path=$(download_with_retry "$download_url") echo "Expand $package_tar_name to the $ruby_version_path folder" tar xf "$archive_path" -C "$ruby_version_path" complete_file_path=$ruby_version_path/$arch.complete if [[ ! -f $complete_file_path ]]; then echo "Create complete file" touch $complete_file_path fi done invoke_tests "Ruby" ================================================ FILE: images/macos/scripts/build/install-rubygems.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-rubygems.sh ## Desc: Install RubyGems ################################################################################ source ~/utils/utils.sh echo "Updating RubyGems..." gem update --system # Temporarily install activesupport 7.2.2.1 due to compatibility issues with cocoapods https://github.com/CocoaPods/CocoaPods/issues/12081 gem install activesupport -v 7.2.2.1 gemsToInstall=$(get_toolset_value '.ruby.rubygems | .[]') if [[ -n $gemsToInstall ]]; then for gem in $gemsToInstall; do echo "Installing gem $gem" gem install $gem done fi invoke_tests "RubyGem" ================================================ FILE: images/macos/scripts/build/install-rust.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-rust.sh ## Desc: Install Rust ################################################################################ source ~/utils/utils.sh echo "Installing Rustup..." brew_smart_install "rustup-init" echo "Installing Rust language..." rustup-init -y --no-modify-path --default-toolchain=stable --profile=minimal echo "Initialize environment variables..." CARGO_HOME=$HOME/.cargo echo "Install common tools..." rustup component add rustfmt clippy echo "Cleanup Cargo registry cached data..." rm -rf $CARGO_HOME/registry/* invoke_tests "Rust" ================================================ FILE: images/macos/scripts/build/install-safari.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-safari.sh ## Desc: Install Safari browser ################################################################################ echo "Enabling safari driver..." # https://developer.apple.com/documentation/webkit/testing_with_webdriver_in_safari # Safari’s executable is located at /usr/bin/safaridriver # Configure Safari to Enable WebDriver Support sudo safaridriver --enable echo "Enabling the 'Allow Remote Automation' option in Safari's Develop menu" mkdir -p $HOME/Library/WebDriver safari_plist="$HOME/Library/WebDriver/com.apple.Safari.plist" # "|| true" is needed to suppress exit code 1 in case if property or file doesn't exist /usr/libexec/PlistBuddy -c 'delete AllowRemoteAutomation' $safari_plist || true /usr/libexec/PlistBuddy -c 'add AllowRemoteAutomation bool true' $safari_plist invoke_tests "Browsers" "Safari" ================================================ FILE: images/macos/scripts/build/install-swiftlint.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-swiftlint.sh ## Desc: Install SwiftLint ################################################################################ source ~/utils/utils.sh echo "Installing Swiftlint..." brew_smart_install "swiftlint" invoke_tests "Linters" "SwiftLint" ================================================ FILE: images/macos/scripts/build/install-unxip.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-unxip.sh ## Desc: Install unxip ################################################################################ source ~/utils/utils.sh echo "Installing unxip..." unxip_pkg=$(download_with_retry "https://github.com/saagarjha/unxip/releases/download/v3.1/unxip") unxip_pkg_sha256="926ecd7bffa201c7b2b8a729fc70fbf228cf624a0e6856c13f935a97fa4fc71a" use_checksum_comparison $unxip_pkg $unxip_pkg_sha256 install "$unxip_pkg" /usr/local/bin/unxip invoke_tests "Common" "Unxip" ================================================ FILE: images/macos/scripts/build/install-vcpkg.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-vcpkg.sh ## Desc: Install vcpkg ################################################################################ source ~/utils/utils.sh # Set env variable for vcpkg VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg echo "export VCPKG_INSTALLATION_ROOT=${VCPKG_INSTALLATION_ROOT}" | tee -a ~/.bashrc # Install vcpkg sudo git clone https://github.com/Microsoft/vcpkg $VCPKG_INSTALLATION_ROOT sudo $VCPKG_INSTALLATION_ROOT/bootstrap-vcpkg.sh $VCPKG_INSTALLATION_ROOT/vcpkg integrate install sudo chmod -R 0777 $VCPKG_INSTALLATION_ROOT ln -sf $VCPKG_INSTALLATION_ROOT/vcpkg /usr/local/bin invoke_tests "Common" "vcpkg" ================================================ FILE: images/macos/scripts/build/install-xcode-clt.sh ================================================ #!/bin/bash -e -o pipefail ################################################################################ ## File: install-xcode-clt.sh ## Desc: Install Xcode Command Line Tools ################################################################################ source ~/utils/utils.sh is_clt_installed() { clt_path=$(xcode-select -p 2>&1) [[ -d $clt_path ]] } install_clt() { echo "Searching online for the Command Line Tools" # This temporary file prompts the 'softwareupdate' utility to list the Command Line Tools clt_placeholder="/tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress" sudo touch $clt_placeholder cltPattern="Command Line Tools" clt_label_command="/usr/sbin/softwareupdate -l | grep -B 1 -E '${cltPattern}' | awk -F'*' '/^ *\\*/ {print \$2}' | sed -e 's/^ *Label: //' -e 's/^ *//' | sort -V | tail -n1" clt_label=$(eval $clt_label_command) || true if [[ -n "$clt_label" ]]; then echo "Installing $clt_label" sudo "/usr/sbin/softwareupdate" "-i" "$clt_label" fi sudo "/bin/rm" "-f" "$clt_placeholder" } echo "Installing Command Line Tools..." install_clt # Retry the installation if tools are not installed from the first attempt retries=30 sleepInterval=60 while ! is_clt_installed; do if [[ $retries -eq 0 ]]; then echo "Unable to find the Command Line Tools, all the attempts exhausted" exit 1 fi echo "Command Line Tools not found, trying to install them via software updates, $retries attempts left" install_clt ((retries--)) echo "Wait $sleepInterval seconds before the next check for installed Command Line Tools" sleep $sleepInterval done ================================================ FILE: images/macos/scripts/docs-gen/Generate-SoftwareReport.ps1 ================================================ using module ./software-report-base/SoftwareReport.psm1 using module ./software-report-base/SoftwareReport.Nodes.psm1 param ( [Parameter(Mandatory)][string] $OutputDirectory, $ImageName ) $ErrorActionPreference = "Stop" Import-Module "$PSScriptRoot/SoftwareReport.Common.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/SoftwareReport.Xcode.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/SoftwareReport.Android.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/SoftwareReport.Java.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/SoftwareReport.Toolcache.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/SoftwareReport.Browsers.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/SoftwareReport.Helpers.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/../helpers/Xcode.Helpers.psm1" # Operating System info $os = Get-OSVersion # OS info $osInfo = Build-OSInfoSection $ImageName # Software report $softwareReport = [SoftwareReport]::new($osInfo) $installedSoftware = $softwareReport.Root.AddHeader("Installed Software") # Language and Runtime $languageAndRuntime = $installedSoftware.AddHeader("Language and Runtime") $languageAndRuntime.AddToolVersionsListInline(".NET Core SDK", $(Get-DotnetVersionList), '^\d+\.\d+\.\d') $languageAndRuntime.AddToolVersion("Bash", $(Get-BashVersion)) $languageAndRuntime.AddNodes($(Get-ClangLLVMVersions)) $languageAndRuntime.AddNodes($(Get-GccVersions)) $languageAndRuntime.AddNodes($(Get-FortranVersions)) $languageAndRuntime.AddToolVersion("Kotlin", $(Get-KotlinVersion)) if (($os.IsSonoma)) { $languageAndRuntime.AddToolVersion("Mono", $(Get-MonoVersion)) } $languageAndRuntime.AddToolVersion("Node.js", $(Get-NodeVersion)) $languageAndRuntime.AddToolVersion("Perl", $(Get-PerlVersion)) if ((-not $os.IsArm64)) { $languageAndRuntime.AddToolVersion("PHP", $(Get-PHPVersion)) } $languageAndRuntime.AddToolVersion("Python3", $(Get-Python3Version)) $languageAndRuntime.AddToolVersion("Ruby", $(Get-RubyVersion)) # Package Management $packageManagement = $installedSoftware.AddHeader("Package Management") $packageManagement.AddToolVersion("Bundler", $(Get-BundlerVersion)) $packageManagement.AddToolVersion("Carthage", $(Get-CarthageVersion)) $packageManagement.AddToolVersion("CocoaPods", $(Get-CocoaPodsVersion)) if ((-not $os.IsArm64)) { $packageManagement.AddToolVersion("Composer", $(Get-ComposerVersion)) } $packageManagement.AddToolVersion("Homebrew", $(Get-HomebrewVersion)) $packageManagement.AddToolVersion("NPM", $(Get-NPMVersion)) if (($os.IsSonoma)) { $packageManagement.AddToolVersion("NuGet", $(Get-NuGetVersion)) } $packageManagement.AddToolVersion("Pip3", $(Get-Pip3Version)) $packageManagement.AddToolVersion("Pipx", $(Get-PipxVersion)) $packageManagement.AddToolVersion("RubyGems", $(Get-RubyGemsVersion)) $packageManagement.AddToolVersion("Vcpkg", $(Get-VcpkgVersion)) $packageManagement.AddToolVersion("Yarn", $(Get-YarnVersion)) # Project Management $projectManagement = $installedSoftware.AddHeader("Project Management") $projectManagement.AddToolVersion("Apache Ant", $(Get-ApacheAntVersion)) $projectManagement.AddToolVersion("Apache Maven", $(Get-MavenVersion)) $projectManagement.AddToolVersion("Gradle", $(Get-GradleVersion)) # Utilities $utilities = $installedSoftware.AddHeader("Utilities") $utilities.AddToolVersion("7-Zip", $(Get-7zipVersion)) $utilities.AddToolVersion("aria2", $(Get-Aria2Version)) $utilities.AddToolVersion("azcopy", $(Get-AzcopyVersion)) $utilities.AddToolVersion("bazel", $(Get-BazelVersion)) $utilities.AddToolVersion("bazelisk", $(Get-BazeliskVersion)) $utilities.AddToolVersion("bsdtar", $(Get-BsdtarVersion)) $utilities.AddToolVersion("Curl", $(Get-CurlVersion)) $utilities.AddToolVersion("Git", $(Get-GitVersion)) $utilities.AddToolVersion("Git LFS", $(Get-GitLFSVersion)) $utilities.AddToolVersion("GitHub CLI", $(Get-GitHubCLIVersion)) $utilities.AddToolVersion("GNU Tar", $(Get-GnuTarVersion)) $utilities.AddToolVersion("GNU Wget", $(Get-WgetVersion)) $utilities.AddToolVersion("gpg (GnuPG)", $(Get-GPGVersion)) $utilities.AddToolVersion("jq", $(Get-JqVersion)) $utilities.AddToolVersion("OpenSSL", $(Get-OpenSSLVersion)) $utilities.AddToolVersion("Packer", $(Get-PackerVersion)) $utilities.AddToolVersion("pkgconf", $(Get-PKGConfVersion)) $utilities.AddToolVersion("Unxip", $(Get-UnxipVersion)) $utilities.AddToolVersion("yq", $(Get-YqVersion)) $utilities.AddToolVersion("zstd", $(Get-ZstdVersion)) $utilities.AddToolVersion("Ninja", $(Get-NinjaVersion)) # Tools $tools = $installedSoftware.AddHeader("Tools") $tools.AddToolVersion("AWS CLI", $(Get-AWSCLIVersion)) $tools.AddToolVersion("AWS SAM CLI", $(Get-AWSSAMCLIVersion)) $tools.AddToolVersion("AWS Session Manager CLI", $(Get-AWSSessionManagerCLIVersion)) $tools.AddToolVersion("Azure CLI", $(Get-AzureCLIVersion)) $tools.AddToolVersion("Azure CLI (azure-devops)", $(Get-AzureDevopsVersion)) $tools.AddToolVersion("Bicep CLI", $(Get-BicepVersion)) $tools.AddToolVersion("Cmake", $(Get-CmakeVersion)) $tools.AddToolVersion("CodeQL Action Bundle", $(Get-CodeQLBundleVersion)) $tools.AddToolVersion("Fastlane", $(Get-FastlaneVersion)) $tools.AddToolVersion("SwiftFormat", $(Get-SwiftFormatVersion)) $tools.AddToolVersion("Xcbeautify", $(Get-XcbeautifyVersion)) $tools.AddToolVersion("Xcode Command Line Tools", $(Get-XcodeCommandLineToolsVersion)) $tools.AddToolVersion("Xcodes", $(Get-XcodesVersion)) # Linters if ((-not $os.IsArm64)) { $linters = $installedSoftware.AddHeader("Linters") $linters.AddToolVersion("SwiftLint", $(Get-SwiftLintVersion)) } # Browsers $browsers = $installedSoftware.AddHeader("Browsers") $browsers.AddNodes($(Build-BrowserSection)) $browsers.AddNode($(Build-BrowserWebdriversEnvironmentTable)) # Java $java = $installedSoftware.AddHeader("Java") $java.AddTable($(Get-JavaVersions)) # Toolcache $toolcache = $installedSoftware.AddHeader("Cached Tools") $toolcache.AddNodes($(Build-ToolcacheSection)) # Rust $rust = $installedSoftware.AddHeader("Rust Tools") $rust.AddToolVersion("Cargo", $(Get-RustCargoVersion)) $rust.AddToolVersion("Rust", $(Get-RustVersion)) $rust.AddToolVersion("Rustdoc", $(Get-RustdocVersion)) $rust.AddToolVersion("Rustup", $(Get-RustupVersion)) $rustPackages = $rust.AddHeader("Packages") $rustPackages.AddToolVersion("Clippy", $(Get-RustClippyVersion)) $rustPackages.AddToolVersion("Rustfmt", $(Get-RustfmtVersion)) # PowerShell $powerShell = $installedSoftware.AddHeader("PowerShell Tools") $powerShell.AddToolVersion("PowerShell", $(Get-PowershellVersion)) $powerShellModules = $powerShell.AddHeader("PowerShell Modules") $powerShellModules.AddNodes($(Get-PowerShellModules)) # Xcode section $xcode = $installedSoftware.AddHeader("Xcode") # First run doesn't provide full data about devices and runtimes Get-XcodeInfoList | Out-Null $xcodeInfo = Get-XcodeInfoList $xcode.AddTable($(Build-XcodeTable $xcodeInfo)) $installedSdks = $xcode.AddHeader("Installed SDKs") $installedSdks.AddTable($(Build-XcodeSDKTable $xcodeInfo)) $installedSimulators = $xcode.AddHeader("Installed Simulators") $installedSimulators.AddTable($(Build-XcodeSimulatorsTable $xcodeInfo)) # Android section $android = $installedSoftware.AddHeader("Android") $androidTable = Build-AndroidTable $android.AddTable($androidTable) $androidEnv = $android.AddHeader("Environment variables") $androidEnv.AddTable($(Build-AndroidEnvironmentTable)) if (($os.IsSonoma -or $os.IsSequoia -or $os.IsTahoe)) { $miscellaneous = $installedSoftware.AddHeader("Miscellaneous") $miscellaneous.AddToolVersion("Tcl/Tk", $(Get-TclTkVersion)) } if (($os.IsSonomaX64 -or $os.IsSequoiaX64)) { Write-Host "Adding environment variables for parallels" $miscellaneousEnv = $miscellaneous.AddHeader("Environment variables") $miscellaneousEnv.AddTable($(Build-MiscellaneousEnvironmentTable)) $notes = @' If you want to use Parallels Desktop you should download a package from URL stored in PARALLELS_DMG_URL environment variable. A system extension is allowed for this version. '@ $miscellaneousEnvNotes = $miscellaneousEnv.AddHeader("Notes") $miscellaneousEnvNotes.AddNote($notes) } if (-not (Test-Path $OutputDirectory)) { New-Item -Path $OutputDirectory -ItemType Directory | Out-Null } # # Write final reports # Write-Host $markdownExtended $softwareReport.ToJson() | Out-File -FilePath "${OutputDirectory}/software-report.json" -Encoding UTF8NoBOM $softwareReport.ToMarkdown() | Out-File -FilePath "${OutputDirectory}/software-report.md" -Encoding UTF8NoBOM ================================================ FILE: images/macos/scripts/docs-gen/SoftwareReport.Android.psm1 ================================================ Import-Module "$PSScriptRoot/SoftwareReport.Helpers.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" function Split-TableRowByColumns { param ( [string] $Row ) return $Row.Split("|") | ForEach-Object { $_.trim() } } function Get-AndroidSDKRoot { return Join-Path $env:HOME "Library" "Android" "sdk" } function Get-AndroidSDKManagerPath { $androidSDKDir = Get-AndroidSDKRoot return Join-Path $androidSDKDir "cmdline-tools" "latest" "bin" "sdkmanager" } function Get-AndroidInstalledPackages { $androidSDKManagerPath = Get-AndroidSDKManagerPath $androidSDKManagerList = Invoke-Expression "$androidSDKManagerPath --list_installed" return $androidSDKManagerList } function Get-AndroidPackages { $androidSDKDir = Get-AndroidSDKRoot $androidSDKManagerPath = Get-AndroidSDKManagerPath $packagesListFile = Join-Path $androidSDKDir "packages-list.txt" if (-Not (Test-Path -Path $packagesListFile -PathType Leaf)) { (& $androidSDKManagerPath --list --verbose) | Where-Object { $_ -Match "^[^\s]" } | Where-Object { $_ -NotMatch "^(Loading |Info: Parsing |---|\[=+|Installed |Available )" } | Where-Object { $_ -NotMatch "^[^;]*$" } | Out-File -FilePath $packagesListFile Write-Host Android packages list: Get-Content $packagesListFile } return Get-Content $packagesListFile } function Build-AndroidTable { Write-Host "Build-AndroidTable" $packageInfo = Get-AndroidInstalledPackages return @( @{ "Package" = "Android Command Line Tools" "Version" = Get-AndroidCommandLineToolsVersion }, @{ "Package" = "Android Emulator" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android Emulator" }, @{ "Package" = "Android SDK Build-tools" "Version" = Get-AndroidBuildToolVersions -PackageInfo $packageInfo }, @{ "Package" = "Android SDK Platforms" "Version" = Get-AndroidPlatformVersions -PackageInfo $packageInfo }, @{ "Package" = "Android SDK Platform-Tools" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android SDK Platform-Tools" }, @{ "Package" = "Android SDK Tools" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android SDK Tools" }, @{ "Package" = "Android Support Repository" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android Support Repository" }, @{ "Package" = "CMake" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "cmake" }, @{ "Package" = "Google APIs" "Version" = Get-AndroidGoogleAPIsVersions -PackageInfo $packageInfo }, @{ "Package" = "Google Play services" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Google Play services" }, @{ "Package" = "Google Repository" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Google Repository" }, @{ "Package" = "NDK" "Version" = Get-AndroidNDKVersions }, @{ "Package" = "SDK Patch Applier v4" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "SDK Patch Applier v4" } ) | Where-Object { $_.Version } | ForEach-Object { [PSCustomObject] @{ "Package Name" = $_.Package "Version" = $_.Version } } } function Build-AndroidEnvironmentTable { $androidVersions = Get-Item env:ANDROID_* $shoulddResolveLink = 'ANDROID_NDK', 'ANDROID_NDK_HOME', 'ANDROID_NDK_ROOT', 'ANDROID_NDK_LATEST_HOME' return $androidVersions | Sort-Object -Property Name | ForEach-Object { [PSCustomObject] @{ "Name" = $_.Name "Value" = if ($shoulddResolveLink.Contains($_.Name )) { Get-PathWithLink($_.Value) } else { $_.Value } } } } function Get-AndroidPackageVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo, [Parameter(Mandatory)] [object] $MatchedString ) $versions = $packageInfo | Where-Object { $_ -Match $MatchedString } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[1] } return ($versions -Join "
") } function Get-AndroidPlatformVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $packageInfo | Where-Object { $_ -Match "Android SDK Platform " } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ $revision = $packageInfoParts[1] $version = $packageInfoParts[0].split(";")[1] return "$version (rev $revision)" } [array]::Reverse($versions) return ($versions -Join "
") } function Get-AndroidCommandLineToolsVersion { $commandLineTools = Get-AndroidSDKManagerPath (& $commandLineTools --version | Out-String).Trim() -match "(?^(\d+\.){1,}\d+$)" | Out-Null $commandLineToolsVersion = $Matches.Version return $commandLineToolsVersion } function Get-AndroidBuildToolVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $packageInfo | Where-Object { $_ -Match "Android SDK Build-Tools" } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[1] } $groupVersions = @() $versions | ForEach-Object { $majorVersion = $_.Split(".")[0] $groupVersions += $versions | Where-Object { $_.StartsWith($majorVersion) } | Join-String -Separator " " } return ($groupVersions | Sort-Object -Descending -Unique | Join-String -Separator "
") } function Get-AndroidGoogleAPIsVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $packageInfo | Where-Object { $_ -Match "Google APIs" } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[0].split(";")[1] } return ($versions -Join "
") } function Get-AndroidNDKVersions { $ndkFolderPath = Join-Path (Get-AndroidSDKRoot) "ndk" $versions += Get-ChildItem -Path $ndkFolderPath -Name $ndkDefaultVersion = (Get-ToolsetContent).android.ndk.default $ndkDefaultFullVersion = Get-ChildItem "$env:ANDROID_HOME/ndk/$ndkDefaultVersion.*" -Name | Select-Object -Last 1 return ($versions | ForEach-Object { $defaultPostfix = ( $_ -eq $ndkDefaultFullVersion ) ? " (default)" : "" $_ + $defaultPostfix } | Join-String -Separator "
") } ================================================ FILE: images/macos/scripts/docs-gen/SoftwareReport.Browsers.psm1 ================================================ function Build-BrowserSection { $nodes = @() $os = Get-OSVersion $nodes += @( [ToolVersionNode]::new("Safari", $(Get-SafariVersion)) [ToolVersionNode]::new("SafariDriver", $(Get-SafariDriverVersion)) [ToolVersionNode]::new("Google Chrome", $(Get-ChromeVersion)) [ToolVersionNode]::new("Google Chrome for Testing", $(Get-ChromeForTestingVersion)) [ToolVersionNode]::new("ChromeDriver", $(Get-ChromeDriverVersion)) [ToolVersionNode]::new("Microsoft Edge", $(Get-EdgeVersion)) [ToolVersionNode]::new("Microsoft Edge WebDriver", $(Get-EdgeDriverVersion)) ) $nodes += @( [ToolVersionNode]::new("Mozilla Firefox", $(Get-FirefoxVersion)) [ToolVersionNode]::new("geckodriver", $(Get-GeckodriverVersion)) [ToolVersionNode]::new("Selenium server", $(Get-SeleniumVersion)) ) return $nodes } function Get-SafariVersion { $version = Run-Command "defaults read /Applications/Safari.app/Contents/Info CFBundleShortVersionString" $build = Run-Command "defaults read /Applications/Safari.app/Contents/Info CFBundleVersion" return "$version ($build)" } function Get-SafariDriverVersion { $version = Run-Command "safaridriver --version" | Take-Part -Part 3, 4 return $version } function Get-ChromeVersion { $chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" $version = Run-Command "'${chromePath}' --version" return ($version -replace ("^Google Chrome")).Trim() } function Get-ChromeForTestingVersion { $chromePath = "/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" $version = Run-Command "'${chromePath}' --version" return ($version -replace ("^Google Chrome for Testing")).Trim() } function Get-ChromeDriverVersion { $rawOutput = Run-Command "chromedriver --version" $version = $rawOutput | Take-Part -Part 1 return $version } function Get-EdgeVersion { $edgePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" $version = Run-Command "'${edgePath}' --version" return ($version -replace ("^Microsoft Edge")).Trim() } function Get-EdgeDriverVersion { return Run-Command "msedgedriver --version" | Take-Part -Part 3 } function Get-FirefoxVersion { $firefoxPath = "/Applications/Firefox.app/Contents/MacOS/firefox" $version = Run-Command "'${firefoxPath}' --version" return ($version -replace "^Mozilla Firefox").Trim() } function Get-GeckodriverVersion { $version = Run-Command "geckodriver --version" | Select-Object -First 1 return ($version -replace "^geckodriver").Trim() } function Get-SeleniumVersion { $os = Get-OSVersion if ($os.IsArm64) { $cellarPath = "/opt/homebrew/Cellar" } else { $cellarPath = "/usr/local/Cellar" } $seleniumVersion = (Get-ChildItem -Path "$cellarPath/selenium-server*/*").Name return $seleniumVersion } function Build-BrowserWebdriversEnvironmentTable { $node = [HeaderNode]::new("Environment variables") $table = @( @{ "Name" = "CHROMEWEBDRIVER" "Value" = $env:CHROMEWEBDRIVER }, @{ "Name" = "EDGEWEBDRIVER" "Value" = $env:EDGEWEBDRIVER }, @{ "Name" = "GECKOWEBDRIVER" "Value" = $env:GECKOWEBDRIVER } ) | ForEach-Object { [PSCustomObject] @{ "Name" = $_.Name "Value" = $_.Value } } $node.AddTable($table) return $node } ================================================ FILE: images/macos/scripts/docs-gen/SoftwareReport.Common.psm1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" function Get-BashVersion { $version = bash -c 'echo ${BASH_VERSION}' return $version } function Get-DotnetVersionList { $sdkRawList = Run-Command "dotnet --list-sdks" return $sdkRawList | ForEach-Object { Take-Part $_ -Part 0 } } function Get-RustVersion { $rustVersion = Run-Command "rustc --version" | Take-Part -Part 1 return $rustVersion } function Get-RustfmtVersion { $version = Run-Command "rustfmt --version" | Take-Part -Part 1 return $version } function Get-RustdocVersion { $version = Run-Command "rustdoc --version" | Take-Part -Part 1 return $version } function Get-RustCargoVersion { $version = Run-Command "cargo --version" | Take-Part -Part 1 return $version } function Get-RustClippyVersion { $version = Run-Command "cargo clippy --version" | Take-Part -Part 1 return $version } function Get-RustupVersion { $rustupVersion = Run-Command "rustup --version" | Select-Object -First 1 | Take-Part -Part 1 return $rustupVersion } function Get-VcpkgVersion { $vcpkgVersion = Run-Command "vcpkg version" | Select-Object -First 1 | Take-Part -Part 5 | Take-Part -Part 0 -Delimiter "-" $commitId = git -C "/usr/local/share/vcpkg" rev-parse --short HEAD return "$vcpkgVersion (build from commit $commitId)" } function Get-GccVersions { $versionList = (Get-ToolsetContent).gcc.versions $versionList | Foreach-Object { $nameVersion = Run-Command "gcc-${_} --version" | Select-Object -First 1 $version = ($nameVersion -replace "^gcc-${_}").Trim() -replace '\).*$', ')' return [ToolVersionNode]::new("GCC ${_}", "$version - available by ``gcc-${_}`` alias") } } function Get-FortranVersions { $versionList = (Get-ToolsetContent).gcc.versions $versionList | Foreach-Object { $nameVersion = Run-Command "gfortran-${_} --version" | Select-Object -First 1 $version = ($nameVersion -replace "^GNU Fortran").Trim() -replace '\).*$', ')' return [ToolVersionNode]::new("GNU Fortran ${_}", "$version - available by ``gfortran-${_}`` alias") } } function Get-ClangLLVMVersions { $clangVersionRegex = [Regex]::new("(?\d+\.\d+\.\d+)") $defaultClangOutput = Run-Command "clang --version" | Out-String $defaultClangVersion = $clangVersionRegex.Match($defaultClangOutput).Groups['version'].Value $homebrewClangPath = '$(brew --prefix llvm@{0})/bin/clang' -f ((Get-ToolsetContent).llvm.version) $homebrewClangOutput = Run-Command "$homebrewClangPath --version" | Out-String $homebrewClangVersion = $clangVersionRegex.Match($homebrewClangOutput).Groups['version'].Value return @( [ToolVersionNode]::new("Clang/LLVM", $defaultClangVersion) [ToolVersionNode]::new("Clang/LLVM (Homebrew)", "$homebrewClangVersion - available on ``$homebrewClangPath``") ) } function Get-Pip3Version { $command = "pip3 --version" $commandOutput = Run-Command $command $versionPart1 = $commandOutput | Take-Part -Part 1 $versionPart2 = $commandOutput | Take-Part -Part 4 $versionPart3 = $commandOutput | Take-Part -Part 5 return "${versionPart1} ${versionPart2} ${versionPart3}" } function Get-PipxVersion { $pipxVersion = Run-Command "pipx --version" -SuppressStderr return $pipxVersion } function Build-OSInfoSection { param ( [string] $ImageName ) $fieldsToInclude = @("System Version:", "Kernel Version:") $rawSystemInfo = Run-Command "system_profiler SPSoftwareDataType" $parsedSystemInfo = $rawSystemInfo | Where-Object { -not ($_ | Select-String -NotMatch $fieldsToInclude) } | ForEach-Object { $_.Trim() } $parsedSystemInfo[0] -match "System Version: macOS (?\d+)" | Out-Null $version = $Matches.Version $systemVersion = $parsedSystemInfo[0].Replace($fieldsToInclude[0],"").Trim() $kernelVersion = $parsedSystemInfo[1].Replace($fieldsToInclude[1],"").Trim() $osInfoNode = [HeaderNode]::new("macOS $version") $osInfoNode.AddToolVersion("OS Version:", $systemVersion) $osInfoNode.AddToolVersion("Kernel Version:", $kernelVersion) $osInfoNode.AddToolVersion("Image Version:", $ImageName.Split('_')[1]) return $osInfoNode } function Get-MonoVersion { $monoVersion = Run-Command "mono --version" | Out-String | Take-Part -Part 4 return $monoVersion } function Get-NodeVersion { $nodeVersion = Run-Command "node --version" return $nodeVersion.TrimStart("v") } function Get-PerlVersion { $version = Run-Command "perl -e 'print substr(`$^V,1)'" return $version } function Get-Python3Version { $python3Version = Run-Command "python3 --version" return ($python3Version -replace "^Python").Trim() } function Get-RubyVersion { $rubyVersion = Run-Command "ruby --version" | Take-Part -Part 1 return $rubyVersion } function Get-PHPVersion { $PHPVersion = Run-Command "php --version" | Select-Object -First 1 | Take-Part -Part 0,1 return ($PHPVersion -replace "^PHP").Trim() } function Get-BundlerVersion { $bundlerVersion = Run-Command "bundle --version" return ($bundlerVersion -replace "^Bundler version").Trim() } function Get-CarthageVersion { $carthageVersion = Run-Command "carthage version" -SuppressStderr return $carthageVersion } function Get-CocoaPodsVersion { $cocoaPodsVersion = Run-Command "pod --version" return $cocoaPodsVersion } function Get-HomebrewVersion { $homebrewVersion = Run-Command "brew --version" | Select-Object -First 1 return ($homebrewVersion -replace "^Homebrew").Trim() } function Get-NPMVersion { $NPMVersion = Run-Command "npm --version" return $NPMVersion } function Get-YarnVersion { $yarmVersion = Run-Command "yarn --version" return $yarmVersion } function Get-NuGetVersion { $nugetVersion = Run-Command "nuget help" | Select-Object -First 1 | Take-Part -Part 2 return $nugetVersion } function Get-RubyGemsVersion { $rubyGemsVersion = Run-Command "gem --version" return $rubyGemsVersion } function Get-ComposerVersion { $composerVersion = Run-Command "composer --version" | Select-Object -First 1 | Take-Part -Part 2 return $composerVersion } function Get-MavenVersion { $mavenVersion = Run-Command "mvn -version" | Select-Object -First 1 | Take-Part -Part 2 return $mavenVersion } #gradle output differs on the first launch – a welcome message, that we don't need is rendered. The solution is to take the last "Gradle" occurrence from the output function Get-GradleVersion { $gradleVersion = (Run-Command "gradle --version" | Select-String "Gradle")[-1] return ($gradleVersion.Line -replace "^Gradle").Trim() } function Get-ApacheAntVersion { $apacheAntVersion = Run-Command "ant -version" | Take-Part -Part 0,1,3 return ($apacheAntVersion -replace "^Apache Ant\(TM\)").Trim() } function Get-CurlVersion { $curlVersion = Run-Command "curl --version" | Select-Object -First 1 | Take-Part -Part 1 return $curlVersion } function Get-GitVersion { $gitVersion = Run-Command "git --version" | Take-Part -Part -1 return $gitVersion } function Get-GitLFSVersion { $gitLFSVersion = Run-Command "git-lfs version" | Take-Part -Part 0 | Take-Part -Part 1 -Delimiter "/" return $gitLFSVersion } function Get-GitHubCLIVersion { $ghVersion = Run-Command "gh --version" | Select-String "gh version" | Select-Object -First 1 | Take-Part -Part 2 return $ghVersion } function Get-WgetVersion { $wgetVersion = Run-Command "wget --version" | Select-String "GNU Wget" | Take-Part -Part 2 return $wgetVersion } function Get-PackerVersion { $packerVersion = Run-Command "packer --version" | Select-String "Packer" | Select-Object -First 1 | Take-Part -Part 1 return ($packerVersion.Trim("v")) } function Get-OpenSSLVersion { $opensslVersion = Run-Command "openssl version" return ($opensslVersion -replace "^OpenSSL").Trim() } function Get-JqVersion { $jqVersion = Run-Command "jq --version" | Take-Part -Part 1 -Delimiter "-" return $jqVersion } function Get-GPGVersion { $gpgVersion = Run-Command "gpg --version" | Select-String 'gpg (GnuPG)' -SimpleMatch return ($gpgVersion.Line -replace "^gpg \(GnuPG\)").Trim() } function Get-Aria2Version { $aria2Version = Run-Command "aria2c --version" | Select-Object -First 1 | Take-Part -Part 2 return $aria2Version } function Get-AzcopyVersion { $azcopyVersion = [string]$(Run-Command "azcopy --version") | Take-Part -Part 2 return $azcopyVersion } function Get-ZstdVersion { $zstdVersion = Run-Command "zstd --version" | Take-Part -Part 1 -Delimiter "v" | Take-Part -Part 0 -Delimiter "," return $zstdVersion } function Get-BazelVersion { $bazelVersion = Run-Command "bazel --version" | Take-Part -Part 0 -Delimiter "-" return ($bazelVersion -replace "^bazel").Trim() } function Get-BazeliskVersion { $bazeliskVersion = Run-Command "brew list bazelisk --versions" return ($bazeliskVersion -replace "^bazelisk").Trim() } function Get-7zipVersion { $7zip = Run-Command "7z i" | Select-String "7-Zip" | Take-Part -Part 0,2 return ($7zip -replace "^7-Zip").Trim() } function Get-GnuTarVersion { $gnuTar = Run-Command "gtar --version" | Select-String "tar" | Take-Part -Part 3 return "$gnuTar - available by 'gtar' alias" } function Get-BsdtarVersion { $bsdtar = Run-Command "tar --version" | Take-Part -Part 1 return "$bsdtar - available by 'tar' alias" } function Get-ParallelVersion { $parallelVersion = Run-Command "parallel --version" | Select-String "GNU parallel" | Select-Object -First 1 return ($parallelVersion -replace "^GNU parallel").Trim() } function Get-FastlaneVersion { $fastlaneVersion = Run-Command "fastlane --version" | Select-String "^fastlane [0-9]" | Take-Part -Part 1 return $fastlaneVersion } function Get-CmakeVersion { $cmakeVersion = Run-Command "cmake --version" | Select-Object -First 1 | Take-Part -Part 2 return $cmakeVersion } function Get-AzureCLIVersion { $azureCLIVersion = (az version | ConvertFrom-Json).'azure-cli' return $azureCLIVersion } function Get-AzureDevopsVersion { $azdevopsVersion = (az version | ConvertFrom-Json).extensions.'azure-devops' return $azdevopsVersion } function Get-AWSCLIVersion { $awsVersion = Run-Command "aws --version" | Take-Part -Part 0 | Take-Part -Delimiter "/" -Part 1 return $awsVersion } function Get-AWSSAMCLIVersion { $awsSamVersion = Run-Command "sam --version" | Take-Part -Part 3 return $awsSamVersion } function Get-AWSSessionManagerCLIVersion { $awsSessionManagerVersion = Run-Command "session-manager-plugin --version" return $awsSessionManagerVersion } function Get-SwiftFormatVersion { $swiftFormatVersion = Run-Command "swiftformat --version" return $swiftFormatVersion } function Get-SwiftLintVersion { $swiftlintVersion = Run-Command "swiftlint version" return $swiftlintVersion } function Get-PowershellVersion { $powershellVersion = Run-Command "powershell --version" return ($powershellVersion -replace "^PowerShell").Trim() } function Get-BicepVersion { $bicepVersion = Run-Command "bicep --version" | Take-Part -Part 3 return $bicepVersion } function Get-KotlinVersion { $kotlinVersion = Run-Command "kotlin -version" | Take-Part -Part 2 return $kotlinVersion } function Get-TclTkVersion { $tcltkVersion = (Run-Command "brew info --json tcl-tk@8" | ConvertFrom-Json).installed.version return $tcltkVersion } function Get-YqVersion { $yqVersion = Run-Command "yq --version" $yqVersion -match "\d{1,2}\.\d{1,2}\.\d{1,2}" | Out-Null return ($Matches[0]) } function Build-MiscellaneousEnvironmentTable { return @( @{ "Name" = "PARALLELS_DMG_URL" "Value" = $env:PARALLELS_DMG_URL } ) | ForEach-Object { [PSCustomObject] @{ "Name" = $_.Name "Value" = $_.Value } } } function Get-CodeQLBundleVersion { $CodeQLVersionWildcard = Join-Path $Env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath "*" $CodeQLVersionPath = Get-ChildItem $CodeQLVersionWildcard | Select-Object -First 1 -Expand FullName $CodeQLPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "codeql" $CodeQLVersion = & $CodeQLPath version --quiet return $CodeQLVersion } function Get-PKGConfVersion { $pkgconfVersion = Run-Command "pkgconf --version" return $pkgconfVersion } function Get-XcbeautifyVersion { $XcbeautifyVersion = Run-Command "xcbeautify --version" return $XcbeautifyVersion } function Get-XcodesVersion { $XcodesVersion = Run-Command "xcodes version" return $XcodesVersion } function Get-UnxipVersion { $unxipVersion = Run-Command "unxip --version" | Take-Part -Part 1 return $unxipVersion } function Get-NinjaVersion { return $(ninja --version) } ================================================ FILE: images/macos/scripts/docs-gen/SoftwareReport.Helpers.psm1 ================================================ function Run-Command { param ( [Parameter(Mandatory=$true)] [string] $Command, [switch] $SuppressStderr ) # Bash trick to suppress and show error output because some commands write to stderr (for example, "python --version") $redirectOutputArguments = If ($SuppressStderr) { "2> /dev/null" } Else { "2>&1" } $stdout = & bash -c "${Command} ${redirectOutputArguments}" return $stdout } function Take-Part { param ( [Parameter(ValueFromPipeline)] [string] $toolOutput, [string] $Delimiter = " ", [int[]] $Part ) $parts = $toolOutput.Split($Delimiter, [System.StringSplitOptions]::RemoveEmptyEntries) $selectedParts = $parts[$Part] return [string]::Join($Delimiter, $selectedParts) } function Get-LinkTarget { param ( [string] $inputPath ) $link = Get-Item $inputPath | Select-Object -ExpandProperty Target if ($link) { return " -> $link" } return "" } function Get-PathWithLink { param ( [string] $inputPath ) $link = Get-LinkTarget($inputPath) return "${inputPath}${link}" } function Get-BrewPackageVersion { param ( [string] $CommandName ) (Get-LinkTarget (Get-Command $CommandName).Source | Out-String) -match "(?(\d+.){2}\d+)" | Out-Null $packageVersion = $Matches.Version return $packageVersion } ================================================ FILE: images/macos/scripts/docs-gen/SoftwareReport.Java.psm1 ================================================ function Get-JavaVersions { $defaultJavaPath = (Get-Item env:JAVA_HOME).value $os = Get-OSVersion if ($os.IsArm64) { $javaVersions = Get-Item env:JAVA_HOME_*_arm64 } else { $javaVersions = Get-Item env:JAVA_HOME_*_X64 } $sortRules = @{ Expression = { [Int32]$_.Name.Split("_")[2] } Descending = $false } return $javaVersions | Sort-Object $sortRules | ForEach-Object { $javaPath = $_.Value # Take semver from the java path $version = $javaPath.split('/')[5] $fullVersion = $version.Replace('-', '+') $defaultPostfix = ($javaPath -eq $defaultJavaPath) ? " (default)" : "" [PSCustomObject] @{ "Version" = $fullVersion + $defaultPostfix "Environment Variable" = $_.Name } } } ================================================ FILE: images/macos/scripts/docs-gen/SoftwareReport.Toolcache.psm1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" function Get-ToolcacheRubyVersions { $toolcachePath = Join-Path $env:HOME "hostedtoolcache" "Ruby" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ } } function Get-ToolcachePythonVersions { $toolcachePath = Join-Path $env:HOME "hostedtoolcache" "Python" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ } } function Get-ToolcacheNodeVersions { $toolcachePath = Join-Path $env:HOME "hostedtoolcache" "Node" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ } } function Get-ToolcacheGoVersions { $toolcachePath = Join-Path $env:HOME "hostedtoolcache" "Go" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ } } function Build-ToolcacheSection { $nodes = @() $nodes += @( [ToolVersionsListNode]::new("Ruby", $(Get-ToolcacheRubyVersions), '^\d+\.\d+', "List") [ToolVersionsListNode]::new("Python", $(Get-ToolcachePythonVersions), '^\d+\.\d+', "List"), [ToolVersionsListNode]::new("Node.js", $(Get-ToolcacheNodeVersions), '^\d+', "List"), [ToolVersionsListNode]::new("Go", $(Get-ToolcacheGoVersions), '^\d+\.\d+', "List") ) return $nodes } function Get-PowerShellModules { $modules = ((Get-ToolsetContent).powershellModules).name $modules | ForEach-Object { $moduleName = $_ $moduleVersions = Get-Module -Name $moduleName -ListAvailable | Select-Object -ExpandProperty Version | Sort-Object -Unique return [ToolVersionsListNode]::new($moduleName, $moduleVersions, '^\d+', "Inline") } } ================================================ FILE: images/macos/scripts/docs-gen/SoftwareReport.Xcode.psm1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/../helpers/Xcode.Helpers.psm1" function Get-XcodePaths { $xcodePaths = Get-ChildItem -Path "/Applications" -Filter "Xcode_*.app" | Where-Object { !$_.LinkType } return $xcodePaths | Select-Object -ExpandProperty Fullname } function Get-XcodeSDKList { param( [Parameter(Mandatory)] [string]$XcodeRootPath ) $versionInfo = Get-XcodeVersionInfo -XcodeRootPath $XcodeRootPath $xcodebuildPath = Get-XcodeToolPath -XcodeRootPath $XcodeRootPath -ToolName "xcodebuild" if ($versionInfo.Version -le [System.Version]::Parse("9.4.1")) { $output = Invoke-Expression "$xcodebuildPath -showsdks" $sdkList = $output | Where-Object { $_ -Match "-sdk" } return $sdkList | ForEach-Object { $displayName, $canonicalName = $_.Split("-sdk") return @{ canonicalName = $canonicalName.Trim() displayName = $displayName.Trim() } } } [string]$output = Invoke-Expression "$xcodebuildPath -showsdks -json" return $output | ConvertFrom-Json } function Get-XcodeInfoList { $defaultXcodeRootPath = Get-DefaultXcodeRootPath $xcodeInfo = @{} Get-XcodePaths | ForEach-Object { $xcodeRootPath = $_ Switch-Xcode -XcodeRootPath $xcodeRootPath $versionInfo = Get-XcodeVersionInfo -XcodeRootPath $xcodeRootPath $versionInfo.Path = $xcodeRootPath $versionInfo.IsDefault = ($xcodeRootPath -eq $defaultXcodeRootPath) $versionInfo.IsStable = Test-XcodeStableRelease -XcodeRootPath $xcodeRootPath $xcodeInfo.Add($xcodeRootPath, [PSCustomObject] @{ VersionInfo = $versionInfo SDKInfo = Get-XcodeSDKList -XcodeRootPath $xcodeRootPath SimulatorsInfo = Get-XcodeSimulatorsInfo }) } Switch-Xcode -XcodeRootPath $defaultXcodeRootPath return $xcodeInfo } function Get-XcodePlatformOrder { param ( [Parameter(Mandatory)] [string] $PlatformName ) Switch ($PlatformName) { "macOS" { 1 } "iOS" { 2 } "Simulator - iOS" { 3 } "tvOS" { 4 } "Simulator - tvOS" { 5 } "watchOS" { 6 } "Simulator - watchOS" { 7 } "visionOS" { 8 } "Simulator - visionOS" { 9 } Default { 100 } } } function Get-XcodeCommandLineToolsVersion { $xcodeCommandLineToolsVersion = Run-Command "pkgutil --pkg-info com.apple.pkg.CLTools_Executables" | Select -Index 1 | Take-Part -Part 1 return $xcodeCommandLineToolsVersion } function Build-XcodeTable { param ( [Parameter(Mandatory)] [hashtable] $xcodeInfo ) $sortRules = @{ Expression = { $_.Version } Descending = $true } $xcodeList = $xcodeInfo.Values | ForEach-Object { $_.VersionInfo } | Sort-Object $sortRules return $xcodeList | ForEach-Object { $defaultPostfix = if ($_.IsDefault) { " (default)" } else { "" } $betaPostfix = if ($_.IsStable) { "" } else { " (beta)" } $targetPath = $_.Path $symlinks = @() Get-ChildItem -Path "/Applications" | ForEach-Object { if ($_.LinkType -eq 'SymbolicLink') { $linkTarget = & readlink $_.FullName if ($linkTarget -eq $targetPath) { $symlinks += $_.FullName } } } if ($null -eq $symlinks) { $symlinks = @("N/A") } return [PSCustomObject] @{ "Version" = $_.Version.ToString() + $betaPostfix + $defaultPostfix "Build" = $_.Build "Path" = $_.Path "Symlinks" = [String]::Join("
", $symlinks) } } } function Build-XcodeDevicesList { param ( [Parameter(Mandatory)][object] $XcodeInfo, [Parameter(Mandatory)][object] $Runtime ) $runtimeId = $Runtime.identifier $runtimeName = $Runtime.name $output = $XcodeInfo.SimulatorsInfo.devices.$runtimeId if ($null -eq $output) { $output = $XcodeInfo.SimulatorsInfo.devices.$runtimeName } return $output } function Build-XcodeSDKTable { param ( [Parameter(Mandatory)] [hashtable] $xcodeInfo ) $sdkNames = @() $xcodeInfo.Values | ForEach-Object { $_.SDKInfo | ForEach-Object { $sdkNames += $_.canonicalName } } $sdkNames = $sdkNames | Select-Object -Unique return $sdkNames | ForEach-Object { $sdkName = $_ $sdkDisplayName = "" $xcodeList = @() $xcodeInfo.Values | ForEach-Object { $sdk = $_.SDKInfo | Where-Object { $_.canonicalName -eq $sdkName } | Select-Object -First 1 if ($sdk) { $sdkDisplayName = $sdk.displayName $xcodeList += $_.VersionInfo.Version } } $xcodeList = $xcodeList | Sort-Object return [PSCustomObject] @{ "SDK" = $sdkDisplayName "SDK Name" = $sdkName "Xcode Version" = [String]::Join(", ", $xcodeList) } } | Sort-Object { # Sort rule 1 $sdkNameParts = $_."SDK".Split(" ") $platformName = [String]::Join(" ", $sdkNameParts[0..($sdkNameParts.Length - 2)]) return Get-XcodePlatformOrder $platformName }, { # Sort rule 2 $sdkNameParts = $_."SDK".Split(" ") return [System.Version]::Parse($sdkNameParts[-1]) } } function Format-XcodeSimulatorName { param( [Parameter(Mandatory)][string] $Device ) $formattedDeviceName = $Device.Replace("ʀ", "R") return $formattedDeviceName } function Build-XcodeSimulatorsTable { param ( [Parameter(Mandatory)] [hashtable] $xcodeInfo ) $runtimes = @() $xcodeInfo.Values | ForEach-Object { $_.SimulatorsInfo.runtimes | ForEach-Object { $runtimes += $_ } } $runtimes = $runtimes | Sort-Object @{ Expression = { $_.identifier } } -Unique return $runtimes | ForEach-Object { $runtime = $_ $runtimeDevices = @() $xcodeInfo.Values | ForEach-Object { $runtimeFound = $_.SimulatorsInfo.runtimes | Where-Object { $_.identifier -eq $runtime.identifier } | Select-Object -First 1 if ($runtimeFound) { $devicesToAdd = Build-XcodeDevicesList -XcodeInfo $_ -Runtime $runtimeFound $runtimeDevices += $devicesToAdd | Select-Object -ExpandProperty name } } $runtimeDevices = $runtimeDevices | ForEach-Object { Format-XcodeSimulatorName $_ } | Select-Object -Unique If (($runtimeDevices | Where-Object { -not ([string]::IsNullOrWhitespace($_)) }).Count -eq 0) { $sortedRuntimeDevices = @("N/A") } else { $sortedRuntimeDevices = $runtimeDevices | Sort-Object @{ Expression = { $_.Split(" ")[0] }; Descending = $true; }, { $_.Split(" ") | Select-Object -Skip 1 | Join-String -Separator " " } } return [PSCustomObject] @{ "Name" = $runtime.name "OS" = $runtime.version "Simulators" = [String]::Join("
", $sortedRuntimeDevices) } } | Sort-Object { # Sort rule 1 $sdkNameParts = $_."Name".Split(" ") $platformName = [String]::Join(" ", $sdkNameParts[0..($sdkNameParts.Length - 2)]) return Get-XcodePlatformOrder $platformName }, { # Sort rule 2 $sdkNameParts = $_."Name".Split(" ") return [System.Version]::Parse($sdkNameParts[-1]) } } ================================================ FILE: images/macos/scripts/helpers/Common.Helpers.psm1 ================================================ function Get-CommandResult { param ( [Parameter(Mandatory=$true)] [string] $Command, [switch] $Multiline ) # Bash trick to suppress and show error output because some commands write to stderr (for example, "python --version") $stdout = & bash -c "$Command 2>&1" $exitCode = $LASTEXITCODE return @{ Output = If ($Multiline -eq $true) { $stdout } else { [string]$stdout } ExitCode = $exitCode } } # Gets path to the tool, analogue of 'which tool' function Get-ToolPath($tool) { return (Get-Command $tool).Path } # Returns the object with information about current OS # It can be used for OS-specific tests function Get-OSVersion { $osVersion = [Environment]::OSVersion $processorArchitecture = arch return [PSCustomObject]@{ Version = $osVersion.Version Platform = $osVersion.Platform IsArm64 = $processorArchitecture -eq "arm64" IsSonoma = $($osVersion.Version.Major -eq "14") IsSonomaArm64 = $($osVersion.Version.Major -eq "14" -and $processorArchitecture -eq "arm64") IsSonomaX64 = $($osVersion.Version.Major -eq "14" -and $processorArchitecture -ne "arm64") IsSequoia = $($osVersion.Version.Major -eq "15") IsSequoiaArm64 = $($osVersion.Version.Major -eq "15" -and $processorArchitecture -eq "arm64") IsSequoiaX64 = $($osVersion.Version.Major -eq "15" -and $processorArchitecture -ne "arm64") IsTahoe = $($osVersion.Version.Major -eq "26") IsTahoeArm64 = $($osVersion.Version.Major -eq "26" -and $processorArchitecture -eq "arm64") IsTahoeX64 = $($osVersion.Version.Major -eq "26" -and $processorArchitecture -ne "arm64") } } function Get-ToolsetContent { <# .SYNOPSIS Retrieves the content of the toolset.json file. .DESCRIPTION This function reads the toolset.json in path provided by IMAGE_FOLDER environment variable and returns the content as a PowerShell object. #> $toolsetPath = Join-Path $env:IMAGE_FOLDER "toolset.json" $toolsetJson = Get-Content -Path $toolsetPath -Raw ConvertFrom-Json -InputObject $toolsetJson } function Invoke-DownloadWithRetry { Param ( [Parameter(Mandatory)] [string] $Url, [Alias("Destination")] [string] $Path ) if (-not $Path) { $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join '' $re = "[{0}]" -f [RegEx]::Escape($invalidChars) $fileName = [IO.Path]::GetFileName($Url) -replace $re if ([String]::IsNullOrEmpty($fileName)) { $fileName = [System.IO.Path]::GetRandomFileName() } $Path = Join-Path -Path "/tmp" -ChildPath $fileName } Write-Host "Downloading package from $Url to $Path..." $interval = 30 $downloadStartTime = Get-Date for ($retries = 20; $retries -gt 0; $retries--) { try { $attemptStartTime = Get-Date (New-Object System.Net.WebClient).DownloadFile($Url, $Path) $attemptSeconds = [math]::Round(($(Get-Date) - $attemptStartTime).TotalSeconds, 2) Write-Host "Package downloaded in $attemptSeconds seconds" break } catch { $attemptSeconds = [math]::Round(($(Get-Date) - $attemptStartTime).TotalSeconds, 2) Write-Warning "Package download failed in $attemptSeconds seconds" Write-Warning $_.Exception.Message } if ($retries -eq 0) { $totalSeconds = [math]::Round(($(Get-Date) - $downloadStartTime).TotalSeconds, 2) throw "Package download failed after $totalSeconds seconds" } Write-Warning "Waiting $interval seconds before retrying (retries left: $retries)..." Start-Sleep -Seconds $interval } return $Path } function Get-Architecture { $arch = arch if ($arch -ne "arm64") { $arch = "x64" } return $arch } ================================================ FILE: images/macos/scripts/helpers/Xcode.Helpers.psm1 ================================================ function Get-XcodeRootPath { Param ( [Parameter(Mandatory)] [string] $Version ) return "/Applications/Xcode_$Version.app" } function Get-DefaultXcodeRootPath { return (Get-Item -Path "/Applications/Xcode.app").Target } function Get-XcodeToolPath { param ( [Parameter(ParameterSetName = 'Version')] [string] $Version, [Parameter(ParameterSetName = 'Path')] [string] $XcodeRootPath, [string] $ToolName ) if ($PSCmdlet.ParameterSetName -eq "Version") { $XcodeRootPath = Get-XcodeRootPath $Version } return Join-Path $XcodeRootPath "Contents/Developer/usr/bin" $ToolName } function Get-XcodeVersionInfo { param ( [Parameter(Mandatory)] [string] $XcodeRootPath ) $xcodebuildPath = Get-XcodeToolPath -XcodeRootPath $XcodeRootPath -ToolName "xcodebuild" [string]$output = Invoke-Expression "$xcodebuildPath -version" $versionOutputParts = $output.Split(" ") return @{ Version = [System.Version]::Parse($versionOutputParts[1]) Build = $versionOutputParts[4] } } function Switch-Xcode { param ( [Parameter(ParameterSetName = 'Version')] [string] $Version, [Parameter(ParameterSetName = 'Path')] [string] $XcodeRootPath ) if ($PSCmdlet.ParameterSetName -eq "Version") { $XcodeRootPath = Get-XcodeRootPath $Version } Write-Verbose "Switching Xcode to '${XcodeRootPath}'" Invoke-Expression "sudo xcode-select --switch ${XcodeRootPath}" } function Test-XcodeStableRelease { param ( [Parameter(ParameterSetName = 'Version')] [string] $Version, [Parameter(ParameterSetName = 'Path')] [string] $XcodeRootPath ) if ($PSCmdlet.ParameterSetName -eq "Version") { $XcodeRootPath = Get-XcodeRootPath $Version } $licenseInfoPlistPath = Join-Path $XcodeRootPath "Contents" "Resources" "LicenseInfo.plist" $releaseType = & defaults read $licenseInfoPlistPath "licenseType" return -not ($releaseType -match "beta") } function Get-XcodeSimulatorsInfo { param ( [string] $Filter ) [string]$rawSimulatorsInfo = Invoke-Expression "xcrun simctl list --json" $jsonSimulatorsInfo = $rawSimulatorsInfo | ConvertFrom-Json if ($Filter) { return $jsonSimulatorsInfo | Select-Object -ExpandProperty $Filter } return $jsonSimulatorsInfo } function Get-XcodeDevicesList { $result = @() $runtimes = Get-XcodeSimulatorsInfo -Filter "devices" $runtimes.PSObject.Properties | ForEach-Object { $runtimeName = $_.Name $devices = $_.Value $devices | Where-Object { $availability = $_.availability $isAvailable = $_.isAvailable return (($availability -eq "(available)") -or ($isAvailable -eq "YES") -or ($isAvailable -eq $true)) } | ForEach-Object { $deviceName = $_.name $result += "$runtimeName $deviceName" } } return $result } function Get-XcodePairsList { $result = @() $runtimes = Get-XcodeSimulatorsInfo -Filter "pairs" $runtimes.PSObject.Properties | Where-Object { return $_.Value.state -match "active" } | ForEach-Object { $watchName = $_.Value.watch.name $phoneName = $_.Value.phone.name $result += "$watchName $phoneName" } return $result } #Helper function for execution of xcversion due to: https://github.com/fastlane/fastlane/issues/18161 function Invoke-XCVersion { param ( [Parameter(Mandatory)] [string] $Arguments, [Parameter()] [int] $RetryAttempts = 7, [Parameter()] [int] $PauseDurationSecs = 1 ) $Command = "xcversion $Arguments" Write-Host "Execute [$Command]" for ($Attempt=1; $Attempt -le $RetryAttempts; $Attempt++) { Write-Host "Current attempt: [$Attempt]" $result = Get-CommandResult -Command $Command -Multiline Write-Host "Exit code: [$($result.ExitCode)]" Write-Host "Output message: " $result.Output | ForEach-Object { Write-Host $_ } if ($result.ExitCode -ne 0) { Start-Sleep -Seconds $PauseDurationSecs } else { return $result.Output } } if ($result.ExitCode -ne 0) { throw "Command [$Command] has finished with non-zero exit code." } } ================================================ FILE: images/macos/scripts/helpers/Xcode.Installer.psm1 ================================================ Import-Module "$PSScriptRoot/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Xcode.Helpers.psm1" function Install-XcodeVersion { param ( [Parameter(Mandatory)] [string] $Version, [Parameter(Mandatory)] [string] $LinkTo, [Parameter(Mandatory)] [string] $Sha256Sum ) $xcodeDownloadDirectory = "$env:HOME/Library/Caches/XcodeInstall" $xcodeTargetPath = Get-XcodeRootPath -Version $LinkTo $xcodeXipDirectory = Invoke-DownloadXcodeArchive -DownloadDirectory $xcodeDownloadDirectory -Version $Version Expand-XcodeXipArchive -DownloadDirectory $xcodeXipDirectory.FullName -TargetPath $xcodeTargetPath Remove-Item -Path $xcodeXipDirectory -Force -Recurse } function Invoke-DownloadXcodeArchive { param ( [Parameter(Mandatory)] [string] $DownloadDirectory, [Parameter(Mandatory)] [string] $Version ) Write-Host "Downloading Xcode $Version" $tempXipDirectory = New-Item -Path $DownloadDirectory -Name "Xcode$Version" -ItemType "Directory" $xcodeFileName = 'Xcode-{0}.xip' -f $Version $xcodeUri = '{0}{1}?{2}'-f ${env:XCODE_INSTALL_STORAGE_URL}, $xcodeFileName, ${env:XCODE_INSTALL_SAS} $xcodeFullPath = Join-Path $tempXipDirectory.FullName $xcodeFileName Invoke-DownloadWithRetry -Url $xcodeUri -Path $xcodeFullPath | Out-Null # Validating checksum $xcodeSha256 = Get-FileHash -Path $xcodeFullPath -Algorithm SHA256 | Select-Object -ExpandProperty Hash if ($xcodeSha256 -ne $Sha256Sum) { throw "Xcode $Version checksum mismatch. Expected: $Sha256Sum, Actual: $xcodeSha256" } return $tempXipDirectory } function Expand-XcodeXipArchive { param ( [Parameter(Mandatory)] [string] $DownloadDirectory, [Parameter(Mandatory)] [string] $TargetPath ) $xcodeXipPath = Get-ChildItem -Path $DownloadDirectory -Filter "Xcode-*.xip" | Select-Object -First 1 Write-Host "Extracting Xcode from '$xcodeXipPath'" Push-Location $DownloadDirectory if ([boolean] (Get-Command 'unxip' -ErrorAction 'SilentlyContinue')) { Invoke-ValidateCommand "unxip $xcodeXipPath" } else { Invoke-ValidateCommand "xip -x $xcodeXipPath" } Pop-Location if (Test-Path "$DownloadDirectory/Xcode-beta.app") { Write-Host "Renaming Xcode-beta.app to Xcode.app" Rename-Item -Path "$DownloadDirectory/Xcode-beta.app" -NewName "Xcode.app" } if (-not (Test-Path "$DownloadDirectory/Xcode.app")) { throw "XIP archive '$xcodeXipPath' doesn't contain 'Xcode.app'" } Write-Host "Moving '$DownloadDirectory/Xcode.app' to '$TargetPath'" Move-Item -Path "$DownloadDirectory/Xcode.app" -Destination $TargetPath } function Confirm-XcodeIntegrity { param ( [Parameter(Mandatory)] [string] $Version ) $XcodeRootPath = Get-XcodeRootPath -Version $Version if (Test-XcodeStableRelease -XcodeRootPath $XcodeRootPath) { Write-Host "Validating Xcode integrity for '$XcodeRootPath'..." Invoke-ValidateCommand "spctl --assess --raw $XcodeRootPath" } } function Approve-XcodeLicense { param ( [Parameter(Mandatory)] [string] $Version ) $os = Get-OSVersion $XcodeRootPath = Get-XcodeRootPath -Version $Version Write-Host "Approving Xcode license for '$XcodeRootPath'..." $xcodeBuildPath = Get-XcodeToolPath -XcodeRootPath $XcodeRootPath -ToolName "xcodebuild" if ($os.IsSonoma) { Invoke-ValidateCommand -Command "sudo $xcodeBuildPath -license accept" -Timeout 15 } else { Invoke-ValidateCommand -Command "sudo $xcodeBuildPath -license accept" } } function Install-XcodeAdditionalComponents { param ( [Parameter(Mandatory)] [string] $Version ) Write-Host "Installing additional MetalToolchain component for Xcode $Version..." $xcodeRootPath = Get-XcodeRootPath -Version $Version $xcodeBuildPath = Get-XcodeToolPath -XcodeRootPath $xcodeRootPath -ToolName "xcodebuild" Invoke-ValidateCommand "$xcodeBuildPath -downloadComponent MetalToolchain" | Out-Null } function Invoke-XcodeRunFirstLaunch { param ( [Parameter(Mandatory)] [string] $Version ) Write-Host "Running 'runFirstLaunch' for Xcode $Version..." $xcodeRootPath = Get-XcodeToolPath -Version $Version -ToolName "xcodebuild" Invoke-ValidateCommand "sudo $xcodeRootPath -runFirstLaunch" } function Install-XcodeAdditionalSimulatorRuntimes { param ( [Parameter(Mandatory)] [string] $Version, [Parameter(Mandatory)] [string] $Arch, [Parameter(Mandatory)] [object] $Runtimes ) Write-Host "Installing Simulator Runtimes for Xcode $Version ..." $xcodebuildPath = Get-XcodeToolPath -Version $Version -ToolName 'xcodebuild' $validRuntimes = @("iOS", "watchOS", "tvOS") # Determine architecture variant suffix for Xcode 26+ $archSuffix = "" if ($Version -match '^(\d+)\.' -and [int]$matches[1] -ge 26) { $archSuffix = "-architectureVariant universal" } # visionOS is only available on arm64 if ($Arch -eq "arm64") { $validRuntimes += "visionOS" } # Install all runtimes / skip runtimes if ($Runtimes -eq "default") { Write-Host "Installing all runtimes for Xcode $Version ..." Invoke-ValidateCommand "$xcodebuildPath -downloadAllPlatforms $archSuffix" | Out-Null return } elseif ($Runtimes -eq "none") { Write-Host "Skipping runtimes installation for Xcode $Version ..." return } # Convert $Runtimes to hashtable if ($Runtimes -is [System.Object[]]) { $convertedRuntimes = @{} foreach ($entry in $Runtimes) { if ($entry -is [PSCustomObject]) { $entry = $entry | ConvertTo-Json -Compress | ConvertFrom-Json -AsHashtable } # Copy all keys and values from the entry to the converted runtimes foreach ($key in $entry.Keys) { if ($convertedRuntimes.ContainsKey($key)) { $convertedRuntimes[$key] += $entry[$key] } else { $convertedRuntimes[$key] = $entry[$key] } } } $Runtimes = $convertedRuntimes } # Validate runtimes format if ($Runtimes -isnot [System.Collections.Hashtable]) { throw "Invalid runtime format for Xcode $(Version): Expected hashtable, got [$($Runtimes.GetType())]" } # Install runtimes for specified platforms foreach ($platform in $validRuntimes) { if (-not $Runtimes.ContainsKey($platform)) { Write-Host "No runtimes specified for $platform in the toolset for Xcode $Version, please check the toolset." return } foreach ($platformVersion in $Runtimes[$platform]) { switch ($platformVersion) { "skip" { Write-Host "Skipping $platform runtimes installation for Xcode $Version ..." continue } "default" { Write-Host "Installing default $platform runtime for Xcode $Version ..." Invoke-ValidateCommand "$xcodebuildPath -downloadPlatform $platform $archSuffix" | Out-Null continue } default { # Version might be a semver or a build number if (($platformVersion -match "^\d{1,2}\.\d(\.\d)?$") -or ($platformVersion -match "^[a-zA-Z0-9]{6,8}$")) { Write-Host "Installing $platform $platformVersion runtime for Xcode $Version ..." Invoke-ValidateCommand "$xcodebuildPath -downloadPlatform $platform -buildVersion $platformVersion $archSuffix" | Out-Null continue } throw "$platformVersion is not a valid value for $platform version. Valid values are 'default', or 'skip', or a semver from 0.0 to 99.9.(9), or a build number." } } } } } function Build-XcodeSymlinks { param ( [Parameter(Mandatory)] [string] $Version, [string[]] $Symlinks ) $sourcePath = Get-XcodeRootPath -Version $Version $Symlinks | Where-Object { $_ } | ForEach-Object { $targetPath = Get-XcodeRootPath -Version $_ Write-Host "Creating symlink: '$targetPath' -> '$sourcePath'" New-Item -Path $targetPath -ItemType SymbolicLink -Value $sourcePath | Out-Null } } function Initialize-XcodeLaunchServicesDb { param ( [Parameter(Mandatory)] [string] $Version ) $xcodePath = Get-XcodeRootPath -Version $Version $lsregister = '/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister' Get-ChildItem -Recurse -Filter "*.app" $xcodePath | Foreach-Object { & $lsregister -f $_.FullName} } function Build-ProvisionatorSymlink { param ( [Parameter(Mandatory)] [string] $Version ) $sourcePath = Get-XcodeRootPath -Version $Version $versionInfo = Get-XcodeVersionInfo -XcodeRootPath $sourcePath $targetVersion = [SemVer]::Parse($versionInfo.Version).ToString() $targetPath = Get-XcodeRootPath -Version $targetVersion if ($sourcePath -ne $targetPath) { Write-Host "Creating symlink: '$targetPath' -> '$sourcePath'" New-Item -Path $targetPath -ItemType SymbolicLink -Value $sourcePath | Out-Null } } function Set-XcodeDeveloperDirEnvironmentVariables { param ( [Parameter(Mandatory)] [string[]] $XcodeList ) $exactVersionsList = $XcodeList | Where-Object { Test-XcodeStableRelease -Version $_ } | ForEach-Object { $xcodeRootPath = Get-XcodeRootPath -Version $_ $xcodeVersionInfo = Get-XcodeVersionInfo -XcodeRootPath $xcodeRootPath return @{ RootPath = $xcodeRootPath Version = [SemVer]::Parse($xcodeVersionInfo.Version) } } | Sort-Object -Property Version -Descending $majorVersions = $exactVersionsList.Version.Major | Select-Object -Unique $majorVersions | ForEach-Object { $majorVersion = $_ $latestXcodeVersion = $exactVersionsList | Where-Object { $_.Version.Major -eq $majorVersion } | Select-Object -First 1 $variableName = "XCODE_${_}_DEVELOPER_DIR" $variableValue = "$($latestXcodeVersion.RootPath)/Contents/Developer" Write-Host "Set ${variableName}=${variableValue}" "export ${variableName}=${variableValue}" | Out-File "$env:HOME/.bashrc" -Append } } function Invoke-ValidateCommand { param ( [Parameter(Mandatory)] [string] $Command, [Uint] $Timeout = 0 ) if ($Timeout -eq 0) { $output = Invoke-Expression -Command $Command if ($LASTEXITCODE -ne 0) { throw "Command '$Command' has finished with exit code $LASTEXITCODE" } return $output } else { $job = $command | Start-Job -ScriptBlock { $output = Invoke-Expression -Command $input if ($LASTEXITCODE -ne 0) { throw 'Command failed' } return $output } $waitObject = $job | Wait-Job -Timeout $Timeout if (-not $waitObject) { throw "Command '$Command' has timed out" } if ($waitObject.State -eq 'Failed') { throw "Command '$Command' has failed" } Receive-Job -Job $job } } function Update-DyldCache { param ( [Parameter(Mandatory)] [string] $Version ) Write-Host "Updating dyld shared cache for Xcode $Version ..." Switch-Xcode -Version $Version Invoke-ValidateCommand "xcrun simctl runtime dyld_shared_cache update --all" } ================================================ FILE: images/macos/scripts/helpers/confirm-identified-developers-macos14.scpt ================================================ # This AppleScript clicks "Allow" for "System Software from developer "Parallels International GmbH" # Steps: # - Open System Settings -> Privacy & Security # - Click 'Allow' for 'System Software from developer "Parallels International GmbH' # - Enter password for runner on run argv set userpassword to item 1 of argv tell application "System Settings" activate delay 5 end tell tell application "System Events" tell process "System Settings" set frontmost to true repeat until exists window 1 delay 2 end repeat tell splitter group 1 of group 1 of window 1 select row 18 of outline 1 of scroll area 1 of group 1 delay 5 click UI Element 2 of group 5 of scroll area 1 of group 1 of group 2 delay 5 keystroke userpassword delay 5 keystroke return delay 5 end tell end tell end tell end run ================================================ FILE: images/macos/scripts/helpers/confirm-identified-developers-macos15.scpt ================================================ # This AppleScript clicks "Allow" for "System Software from developer "Parallels International GmbH" # Steps: # - Open System Settings -> Privacy & Security # - Click 'Allow' for 'System Software from developer "Parallels International GmbH' # - Enter password for runner on run argv set userpassword to item 1 of argv tell application "System Settings" activate delay 5 end tell tell application "System Events" tell process "System Settings" set frontmost to true repeat until exists window 1 delay 2 end repeat tell splitter group 1 of group 1 of window 1 select row 27 of outline 1 of scroll area 1 of group 1 delay 5 click UI element 1 of row 27 of outline 1 of scroll area 1 of group 1 delay 5 keystroke userpassword delay 5 keystroke return delay 5 end tell end tell end tell end run ================================================ FILE: images/macos/scripts/helpers/invoke-tests.sh ================================================ #!/bin/bash -e -o pipefail source $HOME/.bashrc pwsh -Command "Import-Module '$HOME/image-generation/tests/Helpers.psm1' -DisableNameChecking Invoke-PesterTests -TestFile \"$1\" -TestName \"$2\"" ================================================ FILE: images/macos/scripts/helpers/utils.sh ================================================ #!/bin/bash -e -o pipefail download_with_retry() { url=$1 download_path=$2 if [ -z "$download_path" ]; then download_path="/tmp/$(basename "$url")" fi echo "Downloading package from $url to $download_path..." >&2 interval=30 download_start_time=$(date +%s) for ((retries=20; retries>0; retries--)); do attempt_start_time=$(date +%s) if http_code=$(curl -4sSLo "$download_path" "$url" -w '%{http_code}'); then attempt_seconds=$(($(date +%s) - attempt_start_time)) if [ "$http_code" -eq 200 ]; then echo "Package downloaded in $attempt_seconds seconds" >&2 break else echo "Received HTTP status code $http_code after $attempt_seconds seconds" >&2 fi else attempt_seconds=$(($(date +%s) - attempt_start_time)) echo "Package download failed in $attempt_seconds seconds" >&2 fi if [ $retries -eq 0 ]; then total_seconds=$(($(date +%s) - download_start_time)) echo "Package download failed after $total_seconds seconds" >&2 exit 1 fi echo "Waiting $interval seconds before retrying (retries left: $retries)..." >&2 sleep $interval done echo "$download_path" } is_Arm64() { [ "$(arch)" = "arm64" ] } is_Tahoe() { [ "$OSTYPE" = "darwin25" ] } is_TahoeArm64() { is_Tahoe && is_Arm64 } is_TahoeX64() { is_Tahoe && ! is_Arm64 } is_Sequoia() { [ "$OSTYPE" = "darwin24" ] } is_SequoiaArm64() { is_Sequoia && is_Arm64 } is_SequoiaX64() { is_Sequoia && ! is_Arm64 } is_Sonoma() { [ "$OSTYPE" = "darwin23" ] } is_SonomaArm64() { is_Sonoma && is_Arm64 } is_SonomaX64() { is_Sonoma && ! is_Arm64 } get_toolset_value() { local toolset_path=$(echo "$IMAGE_FOLDER/toolset.json") local query=$1 echo "$(jq -r "$query" $toolset_path)" } # brew provides package bottles for different macOS versions # The 'brew install' command will fail if a package bottle does not exist # Use the '--build-from-source' option to build from source in this case brew_smart_install() { local tool_name=$1 echo "Downloading $tool_name..." # get deps & cache em failed=true for i in {1..10}; do brew deps $tool_name > /tmp/$tool_name && failed=false || sleep 60 [ "$failed" = false ] && break done if [ "$failed" = true ]; then echo "Failed: brew deps $tool_name" exit 1; fi for dep in $(cat /tmp/$tool_name) $tool_name; do failed=true for i in {1..10}; do brew --cache $dep >/dev/null && failed=false || sleep 60 [ "$failed" = false ] && break done if [ "$failed" = true ]; then echo "Failed: brew --cache $dep" exit 1; fi done failed=true for i in {1..10}; do brew install $tool_name && failed=false || sleep 60 [ "$failed" = false ] && break done if [ "$failed" = true ]; then echo "Failed: brew install $tool_name" exit 1; fi } configure_system_tccdb () { local values=$1 local dbPath="/Library/Application Support/com.apple.TCC/TCC.db" local sqlQuery="INSERT OR IGNORE INTO access VALUES($values);" sudo sqlite3 "$dbPath" "$sqlQuery" } configure_user_tccdb () { local values=$1 local dbPath="$HOME/Library/Application Support/com.apple.TCC/TCC.db" local sqlQuery="INSERT OR IGNORE INTO access VALUES($values);" sqlite3 "$dbPath" "$sqlQuery" } resolve_github_release_asset_url() { local repo=$1 local filter=$2 local version=${3:-"*"} local api_pat=$4 page_size="100" [ -n "$api_pat" ] && authString=(-H "Authorization: token ${api_pat}") json=$(curl "${authString[@]}" -fsSL "https://api.github.com/repos/${repo}/releases?per_page=${page_size}") if [[ $version == "latest" ]]; then tag_name=$(echo $json | jq -r '.[].tag_name' | sort --unique --version-sort | egrep -v ".*-[a-z]|beta" | tail -n 1) elif [[ $version == *"*"* ]]; then tag_name=$(echo $json | jq -r '.[].tag_name' | sort --unique --version-sort | egrep -v ".*-[a-z]|beta" | egrep "${version}" | tail -n 1) else tag_name=$(echo $json | jq -r '.[].tag_name' | sort --unique --version-sort | egrep -v ".*-[a-z]|beta" | egrep "${version}") fi download_url=$(echo $json | jq -r ".[] | select(.tag_name==\"${tag_name}\").assets[].browser_download_url | select(${filter})" | head -n 1) if [ -z "$download_url" ]; then echo "Failed to parse a download url for the '${tag_name}' tag using '${filter}' filter" exit 1 fi echo $download_url } # Close all finder windows because they can interfere with UI tests close_finder_window() { osascript -e 'tell application "Finder" to close windows' } get_arch() { arch=$(arch) if [[ $arch == "arm64" ]]; then echo "arm64" else echo "x64" fi } use_checksum_comparison() { local file_path=$1 local checksum=$2 local sha_type=${3:-"256"} echo "Performing checksum verification" if [[ ! -f "$file_path" ]]; then echo "File not found: $file_path" exit 1 fi local_file_hash=$(shasum --algorithm "$sha_type" "$file_path" | awk '{print $1}') if [[ "$local_file_hash" != "$checksum" ]]; then echo "Checksum verification failed. Expected hash: $checksum; Actual hash: $local_file_hash." exit 1 else echo "Checksum verification passed" fi } ================================================ FILE: images/macos/scripts/tests/ActionArchiveCache.Tests.ps1 ================================================ Describe "ActionArchiveCache" { Context "Action archive cache directory not empty" { It "$HOME/actionarchivecache not empty" { (Get-ChildItem -Path "$env:HOME/actionarchivecache/*.tar.gz" -Recurse).Count | Should -BeGreaterThan 0 } } Context "Action tarball not empty" { $testCases = Get-ChildItem -Path "$env:HOME/actionarchivecache/*.tar.gz" -Recurse | ForEach-Object { @{ ActionTarball = $_.FullName } } It "" -TestCases $testCases { param ([string] $ActionTarball) (Get-Item "$ActionTarball").Length | Should -BeGreaterThan 0 } } } ================================================ FILE: images/macos/scripts/tests/Android.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/../software-report/SoftwareReport.Android.psm1" -DisableNameChecking $os = Get-OSVersion Describe "Android" { $androidSdkManagerPackages = Get-AndroidPackages [int]$platformMinVersion = (Get-ToolsetContent).android.platform_min_version [version]$buildToolsMinVersion = (Get-ToolsetContent).android.build_tools_min_version [array]$ndkVersions = (Get-ToolsetContent).android.ndk.versions $ndkFullVersions = $ndkVersions | ForEach-Object { Get-ChildItem "$env:ANDROID_HOME/ndk/${_}.*" -Name | Select-Object -Last 1 } | ForEach-Object { "ndk/${_}" } # Platforms starting with a letter are the preview versions, which is not installed on the image $platformVersionsList = ($androidSdkManagerPackages | Where-Object { "$_".StartsWith("platforms;") }) -replace 'platforms;android-', '' | Where-Object { $_ -match "^\d" } | Sort-Object -Unique $platformsInstalled = $platformVersionsList | Where-Object { [int]($_.Split("-")[0]) -ge $platformMinVersion } | ForEach-Object { "platforms/android-${_}" } $buildToolsList = ($androidSdkManagerPackages | Where-Object { "$_".StartsWith("build-tools;") }) -replace 'build-tools;', '' $buildTools = $buildToolsList | Where-Object { $_ -match "\d+(\.\d+){2,}$" } | Where-Object { [version]$_ -ge $buildToolsMinVersion } | Sort-Object -Unique | ForEach-Object { "build-tools/${_}" } $androidPackages = @( "tools", "platform-tools", "cmake", $platformsInstalled, $buildTools, $ndkFullVersions, ((Get-ToolsetContent).android.extras | ForEach-Object { "extras/${_}" }), ((Get-ToolsetContent).android.addons | ForEach-Object { "add-ons/${_}" }), ((Get-ToolsetContent).android.additional_tools) ) | ForEach-Object { $_ } # Remove empty strings from array to avoid possible issues $androidPackages = $androidPackages | Where-Object { $_ } BeforeAll { $ANDROID_SDK_DIR = Join-Path $env:HOME "Library" "Android" "sdk" function Confirm-AndroidPackage { param ( [Parameter(Mandatory = $true)] [string] $PackageName ) # Convert 'm2repository;com;android;support;constraint;constraint-layout-solver;1.0.0-beta1' -> # 'm2repository/com/android/support/constraint/constraint-layout-solver/1.0.0-beta1' $PackageName = $PackageName.Replace(";", "/") $targetPath = Join-Path $ANDROID_SDK_DIR $PackageName $targetPath | Should -Exist } } Context "SDKManagers" { $testCases = @( @{ PackageName = "Command-line tools" Sdkmanager = "$env:ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" } ) It "Sdkmanager from is available" -TestCases $testCases { "$Sdkmanager --version" | Should -ReturnZeroExitCode } } Context "Packages" { $testCases = $androidPackages | ForEach-Object { @{ PackageName = $_ } } It "" -TestCases $testCases { param ([string] $PackageName) Confirm-AndroidPackage $PackageName } } } ================================================ FILE: images/macos/scripts/tests/BasicTools.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "Azure CLI" { It "Azure CLI" { "az -v" | Should -ReturnZeroExitCode } } Describe "Azure DevOps CLI" { It "az devops" { "az devops -h" | Should -ReturnZeroExitCode } } Describe "Carthage" { It "Carthage" { "carthage version" | Should -ReturnZeroExitCode } } Describe "cmake" { It "cmake" { "cmake --version" | Should -ReturnZeroExitCode } } Describe "SwiftFormat" { It "SwiftFormat" { "swiftformat --version" | Should -ReturnZeroExitCode } } Describe "GnuPG" { It "GnuPG" { "gpg --version" | Should -ReturnZeroExitCode } } Describe "zstd" { It "zstd" { "zstd --version" | Should -ReturnZeroExitCode } } Describe "Packer" { It "Packer" { "packer --version" | Should -ReturnZeroExitCode } } Describe "Perl" { It "Perl" { "perl -e 'print substr($^V,1)'" | Should -ReturnZeroExitCode } } Describe "Tcl/Tk" -Skip:($os.IsArm64) { It "libtcl" { Test-Path "/usr/local/lib/libtcl8.6.dylib" | Should -BeTrue Test-Path "/usr/local/lib/libtk8.6.dylib" | Should -BeTrue } } Describe "bazelisk" { It "bazelisk" { "bazelisk version" | Should -ReturnZeroExitCode } } Describe "GitHub CLI" { It "GitHub CLI" { "gh --version" | Should -ReturnZeroExitCode } } Describe "7-Zip" { It "7-Zip" { "7z i" | Should -ReturnZeroExitCode } } Describe "Apache Ant" { It "Apache Ant" { "ant -version" | Should -ReturnZeroExitCode } } Describe "Aria2" { It "Aria2" { "aria2c --version" | Should -ReturnZeroExitCode } } Describe "GNU Tar" { It "GNU Tar" { "gtar --version" | Should -ReturnZeroExitCode } } Describe "bazel" { It "bazel" { "bazel --version" | Should -ReturnZeroExitCode } } Describe "jq" { It "jq" { "jq --version" | Should -ReturnZeroExitCode } } Describe "curl" { It "curl" { "curl --version" | Should -ReturnZeroExitCode } } Describe "wget" { It "wget" { "wget --version" | Should -ReturnZeroExitCode } } Describe "Homebrew" { It "Homebrew" { "brew --version" | Should -ReturnZeroExitCode } } Describe "Kotlin" { $kotlinPackages = @("kapt", "kotlin", "kotlinc", "kotlinc-jvm", "kotlinc-js") It " is available" -TestCases ($kotlinPackages | ForEach-Object { @{ toolName = $_ } }) { "$toolName -help" | Should -ReturnZeroExitCode } } Describe "yq" { It "yq" { "yq --version" | Should -ReturnZeroExitCode } } Describe "pkgconf" { It "pkgconf" { "pkgconf --version" | Should -ReturnZeroExitCode } } Describe "Ninja" { New-item -Path "/tmp/ninjaproject" -ItemType Directory -Force Set-Location '/tmp/ninjaproject' @' cmake_minimum_required(VERSION 3.10) project(NinjaTest NONE) '@ | Out-File -FilePath "./CMakeLists.txt" It "Make a simple ninja project" { "cmake -GNinja /tmp/ninjaproject" | Should -ReturnZeroExitCode } It "build.ninja file should exist" { $buildFilePath = Join-Path "/tmp/ninjaproject" "build.ninja" $buildFilePath | Should -Exist } It "Ninja" { "ninja --version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/macos/scripts/tests/Browsers.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "Chrome" { BeforeAll { $chromeLocation = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" $chromeForTestingLocation = "/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" } It "Chrome" { $chromeLocation | Should -Exist "'$chromeLocation' --version" | Should -ReturnZeroExitCode } It "Chrome for Testing" { $chromeForTestingLocation | Should -Exist "'$chromeForTestingLocation' --version" | Should -ReturnZeroExitCode } It "Chrome Driver" { "chromedriver --version" | Should -ReturnZeroExitCode } It "Chrome for Testing and Chrome Driver major versions are the same" { $chromeMajor = (& $chromeForTestingLocation --version).Trim("Google Chrome for Testing ").Split(".")[0] $chromeDriverMajor = (chromedriver --version).Trim("ChromeDriver ").Split(".")[0] $chromeMajor | Should -BeExactly $chromeDriverMajor } } Describe "Selenium server" { It "Selenium server" { $os = Get-OSVersion if ($os.IsArm64) { $cellarPath = "/opt/homebrew/Cellar" } else { $cellarPath = "/usr/local/Cellar" } (Get-ChildItem -Path "$cellarPath/selenium-server*/*").Name | Should -BeLike "4.*" } } Describe "Edge" { It "Microsoft Edge" { $edgeLocation = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" $edgeLocation | Should -Exist "'$edgeLocation' --version" | Should -ReturnZeroExitCode } It "Microsoft Edge Driver" { "msedgedriver --version" | Should -ReturnZeroExitCode } } Describe "Firefox" { It "Firefox" { $firefoxLocation = "/Applications/Firefox.app/Contents/MacOS/firefox" $firefoxLocation | Should -Exist "'$firefoxLocation' --version" | Should -ReturnZeroExitCode } It "Geckodriver" { "geckodriver --version" | Should -ReturnZeroExitCode } } Describe "Safari" { It "'Allow Remote Automation' option is activated" { $plistPath = "$env:HOME/Library/WebDriver/com.apple.Safari.plist" $command = "/usr/libexec/PlistBuddy -c 'print AllowRemoteAutomation' $plistPath" $plistPath | Should -Exist $commandResult = Get-CommandResult $command $commandResult.ExitCode | Should -Be 0 $commandResult.Output | Should -Be "true" } } ================================================ FILE: images/macos/scripts/tests/Common.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking $os = Get-OSVersion Describe ".NET" { It ".NET" { "dotnet --version" | Should -ReturnZeroExitCode } } Describe "GCC" { $testCases = (Get-ToolsetContent).gcc.versions | ForEach-Object { @{Version = $_ } } It "GCC " -TestCases $testCases { param ( [string] $Version ) "gcc-$Version --version" | Should -ReturnZeroExitCode } It "Gfortran " -TestCases $testCases { param ( [string] $Version ) "gfortran-$Version --version" | Should -ReturnZeroExitCode } It "Gfortran is not found in the default path" { "$(which gfortran)" | Should -BeNullOrEmpty } } Describe "vcpkg" { It "vcpkg" { "vcpkg version" | Should -ReturnZeroExitCode } } Describe "AWS" { It "AWS CLI" { "aws --version" | Should -ReturnZeroExitCode } It "AWS SAM CLI" { "sam --version" | Should -ReturnZeroExitCode } It "AWS Session Manager CLI" { "session-manager-plugin --version" | Should -ReturnZeroExitCode } } Describe "AzCopy" { It "AzCopy" { "azcopy --version" | Should -ReturnZeroExitCode } } Describe "CocoaPods" { It "CocoaPods" { "pod --version" | Should -ReturnZeroExitCode } } Describe "Bicep" { It "Bicep" { "bicep --version" | Should -ReturnZeroExitCode } } Describe "CodeQL Bundle" { It "Is installed" { $CodeQLVersionWildcard = Join-Path $Env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath "*" $CodeQLVersionPath = Get-ChildItem $CodeQLVersionWildcard | Select-Object -First 1 -Expand FullName $CodeQLPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "codeql" "$CodeQLPath version --quiet" | Should -ReturnZeroExitCode $CodeQLPacksPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "qlpacks" $CodeQLPacksPath | Should -Exist } } Describe "Unxip" { It "Unxip" { "unxip --version" | Should -ReturnZeroExitCode } } Describe "Sudoers" { It "Sudo Cache" { "sudo -v" | Should -ReturnZeroExitCode } It "Sudoers files" { "sudo visudo -c" | Should -ReturnZeroExitCode } } ================================================ FILE: images/macos/scripts/tests/Git.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "Git" { It "git is installed" { "git --version" | Should -ReturnZeroExitCode } It "git lfs is installed" { "git lfs version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/macos/scripts/tests/Helpers.psm1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" function Confirm-ArrayWithoutDuplicates { param ( [AllowEmptyCollection()] [Parameter(Mandatory = $true)] [array] $Items, [string] $Because ) $uniqueList = @() $Items | ForEach-Object { $uniqueList | Should -Not -Contain $_ -Because $Because $uniqueList += $_ } } function Confirm-UrlAvailability { param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Url ) $result = $true try { $requestResult = Invoke-WebRequest $Url -DisableKeepAlive -UseBasicParsing -Method Head $result = ($requestResult.StatusCode -eq 200) } catch { $result = $false } $result | Should -BeTrue -Because "'$Url' should be available, but it is not" } function Confirm-IdenticalFileContent { param ( [Parameter(Mandatory)] [string] $File1, [Parameter(Mandatory)] [string] $File2 ) $File1 | Should -Exist -Because "The content of '$File1' should be identical with content of '$File2' but '$File1' doesn't exist" $File2 | Should -Exist -Because "The content of '$File1' should be identical with content of '$File2' but '$File2' doesn't exist" $content1 = Get-Content -Raw $File1 $content2 = Get-Content -Raw $File2 $content1 | Should -Be $content2 -Because "The content of '$File1' should be identical with content of '$File2' but they are different" } function ShouldReturnZeroExitCode { Param ( [String] $ActualValue, [switch] $Negate, [string] $Because # This parameter is unused by we need it to match Pester asserts signature ) $result = Get-CommandResult $ActualValue [bool]$succeeded = $result.ExitCode -eq 0 if ($Negate) { $succeeded = -not $succeeded } if (-not $succeeded) { $commandOutputIndent = " " * 4 $commandOutput = ($result.Output | ForEach-Object { "${commandOutputIndent}${_}" }) -join "`n" $failureMessage = "Command '${ActualValue}' has finished with exit code ${actualExitCode}`n${commandOutput}" } return [PSCustomObject] @{ Succeeded = $succeeded FailureMessage = $failureMessage } } function ShouldMatchCommandOutput { Param ( [String] $ActualValue, [String] $RegularExpression, [switch] $Negate ) $output = (Get-CommandResult $ActualValue).Output | Out-String [bool] $succeeded = $output -cmatch $RegularExpression if ($Negate) { $succeeded = -not $succeeded } $failureMessage = '' if (-not $succeeded) { if ($Negate) { $failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to not match '$output', but it did match." } else { $failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to match '$output', but it did not match." } } return [PSCustomObject] @{ Succeeded = $succeeded FailureMessage = $failureMessage } } If (Get-Command -Name Add-ShouldOperator -ErrorAction SilentlyContinue) { Add-ShouldOperator -Name ReturnZeroExitCode -InternalName ShouldReturnZeroExitCode -Test ${function:ShouldReturnZeroExitCode} Add-ShouldOperator -Name MatchCommandOutput -InternalName ShouldMatchCommandOutput -Test ${function:ShouldMatchCommandOutput} } function Invoke-PesterTests { Param ( [Parameter(Mandatory)][string] $TestFile, [string] $TestName ) $testPath = "$HOME/image-generation/tests/${TestFile}.Tests.ps1" if (-not (Test-Path $testPath)) { throw "Unable to find test file '$TestFile' on '$testPath'." } # Check that Pester module is imported if (!(Get-Module "Pester")) { Import-Module Pester } $configuration = [PesterConfiguration] @{ Run = @{ Path = $testPath; PassThru = $true } Output = @{ Verbosity = "Detailed"; RenderMode = "Plaintext" } } if ($TestName) { $configuration.Filter.FullName = $TestName } # Switch ErrorActionPreference to make sure that tests will fail on silent errors too $backupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = "Stop" $results = Invoke-Pester -Configuration $configuration $ErrorActionPreference = $backupErrorActionPreference # Fail in case if no tests are run if (-not ($results -and ($results.FailedCount -eq 0) -and ($results.PassedCount -gt 0))) { $results throw "Test run has failed" } } ================================================ FILE: images/macos/scripts/tests/Java.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking $os = Get-OSVersion $arch = Get-Architecture function Get-NativeVersionFormat { param($Version) if ($Version -in "8") { return "1.${Version}" } return $Version } Describe "Java" { BeforeAll { function Confirm-JavaVersion { param($JavaCommand, $ExpectedVersion) $commandResult = Get-CommandResult $JavaCommand $commandResult.ExitCode | Should -Be 0 $matchResult = $commandResult.Output | Select-String '^openjdk version \"([\d\._]+)\"' $matchResult.Matches.Success | Should -BeTrue $version = $matchResult.Matches.Groups[1].Value $version | Should -BeLike "${ExpectedVersion}*" } } $toolsetJava = (Get-ToolsetContent).java $defaultVersion = $toolsetJava.$arch.default $jdkVersions = $toolsetJava.$arch.versions if ($os.IsArm64) { $testCases = $jdkVersions | ForEach-Object { @{ Title = $_; Version = (Get-NativeVersionFormat $_); EnvVariable = "JAVA_HOME_${_}_arm64" } } } else { $testCases = $jdkVersions | ForEach-Object { @{ Title = $_; Version = (Get-NativeVersionFormat $_); EnvVariable = "JAVA_HOME_${_}_X64" } } } $testCases += @{ Title = "Default"; Version = (Get-NativeVersionFormat $defaultVersion); EnvVariable = "JAVA_HOME" } $testCases | ForEach-Object { Context $_.Title { It "Version is found by 'java_home'" -TestCases $_ { "/usr/libexec/java_home -v${Version}" | Should -ReturnZeroExitCode } It "Java " -TestCases $_ { $envVariablePath = [System.Environment]::GetEnvironmentVariable($EnvVariable) $javaBinPath = Join-Path $envVariablePath "/bin/java" Confirm-JavaVersion -JavaCommand "$javaBinPath -version" -ExpectedVersion $Version } if ($_.Title -eq "Default") { It "Version is default" -TestCases $_ { Confirm-JavaVersion -JavaCommand "java -version" -ExpectedVersion $Version } } } } Context "Maven" { Describe "Maven" { It "Maven" { "mvn --version" | Should -ReturnZeroExitCode } } } Context "Gradle" { Describe "Gradle" { It "Gradle is installed" { "gradle --version" | Should -ReturnZeroExitCode } It "Gradle is installed to /usr/local/bin" -Skip:($os.IsArm64) { (Get-Command "gradle").Path | Should -BeExactly "/usr/local/bin/gradle" } It "Gradle is installed to /opt/homebrew/bin/gradle" -Skip:(-not $os.IsArm64) { (Get-Command "gradle").Path | Should -BeExactly "/opt/homebrew/bin/gradle" } } } } ================================================ FILE: images/macos/scripts/tests/LLVM.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Describe "Clang/LLVM" { BeforeAll { $toolsetVersion = (Get-ToolsetContent).llvm.version } It "Clang/LLVM is installed and version is correct" { $clangVersion = & "$(brew --prefix llvm@$toolsetVersion)/bin/clang" --version $clangVersion[0] | Should -BeLike "*${toolsetVersion}*" } } ================================================ FILE: images/macos/scripts/tests/Linters.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking $os = Get-OSVersion Describe "SwiftLint" -Skip:($os.IsArm64) { It "SwiftLint" { "swiftlint version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/macos/scripts/tests/Mono.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking $os = Get-OSVersion BeforeAll { function Get-ShortSymlink { param ( [string] $Version ) $versionParts = $Version.Split(".") return [String]::Join(".", $versionParts[0..1]) } } if ($os.IsSonoma) { Describe "Mono" { $MONO_VERSIONS_PATH = "/Library/Frameworks/Mono.framework/Versions" $monoToolsetVersion = @((Get-ToolsetContent).mono.framework.version) $versionFolderPath = Join-Path $MONO_VERSIONS_PATH ([System.Version]::Parse($monoToolsetVersion).ToString(3)) $testCase = @{ MonoVersion = $monoToolsetVersion; VersionFolderPath = $versionFolderPath; MonoVersionsPath = $MONO_VERSIONS_PATH } It "is installed" -TestCases $testCase { param ( [string] $VersionFolderPath ) $monoBinPath = Join-Path $VersionFolderPath "bin" "mono" $VersionFolderPath | Should -Exist $monoBinPath | Should -Exist } It "is available via short link" -TestCases $testCase { param ( [string] $MonoVersion, [string] $MonoVersionsPath, [string] $VersionFolderPath ) $shortSymlink = Get-ShortSymlink $MonoVersion # only 'major.minor' $shortSymlinkFolderPath = Join-Path $MonoVersionsPath $shortSymlink if ($shortSymlink -eq "4.8") { return } # Skip this test for Mono 4.8 because it doesn't contain VERSION file $shortVersionPath = Join-Path $shortSymlinkFolderPath "VERSION" $fullVersionPath = Join-Path $VersionFolderPath "VERSION" Confirm-IdenticalFileContent -File1 $shortVersionPath -File2 $fullVersionPath } It "NUnit console is installed" -TestCases $testCase { param ( [string] $VersionFolderPath ) $nunitPath = Join-Path $VersionFolderPath "Commands" "nunit3-console" $nunitPath | Should -Exist } It "Nuget is installed" -TestCases $testCase { param ( [string] $VersionFolderPath ) $nugetBinaryPath = Join-Path $VersionFolderPath "lib" "mono" "nuget" "nuget.exe" $nugetBinaryWrapperPath = Join-Path $VersionFolderPath "bin" "nuget" $nugetCommandPath = Join-Path $VersionFolderPath "Commands" "nuget" $nugetBinaryPath | Should -Exist $nugetCommandPath | Should -Exist $nugetBinaryWrapperPath | Should -Exist } It "Nuget is valid" -TestCases $testCase { param ( [string] $VersionFolderPath ) $nugetBinaryWrapperPath = Join-Path $VersionFolderPath "bin" "nuget" "$nugetBinaryWrapperPath" | Should -ReturnZeroExitCode } It "MSBuild is available" { "msbuild -version" | Should -ReturnZeroExitCode } } } ================================================ FILE: images/macos/scripts/tests/Node.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking $os = Get-OSVersion Describe "Node.js" { It "Node.js is installed" { "node --version" | Should -ReturnZeroExitCode } It "Node.js version should correspond to the version in the toolset" { node --version | Should -BeLike "v$((Get-ToolsetContent).node.default)*" } It "NPM is installed" { "npm --version" | Should -ReturnZeroExitCode } It "Yarn is installed" { "yarn --version" | Should -ReturnZeroExitCode } } Describe "Global NPM Packages" { $globalNpmPackages = (Get-ToolsetContent).npm.global_packages $globalNpmPackagesWithTests = $globalNpmPackages | Where-Object { $_.test } | ForEach-Object { @{ Name = $_.name; Test = $_.test } } It "" -TestCases $globalNpmPackagesWithTests { $Test | Should -ReturnZeroExitCode } } ================================================ FILE: images/macos/scripts/tests/OpenSSL.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "OpenSSL" { Context "OpenSSL Version" { It "OpenSSL is available" { "openssl version" | Should -ReturnZeroExitCode } } Context "OpenSSL 1.1 Path Check" -Skip:($os.IsTahoe) { It "OpenSSL 1.1 path exists" { $openSSLpath = brew --prefix openssl@1.1 $openSSLpath | Should -Exist } } Context "OpenSSL 1.1 is default" -Skip:($os.IsTahoe) { It "Default OpenSSL version is 1.1" { $commandResult = Get-CommandResult "openssl version" $commandResult.Output | Should -Match "OpenSSL 1.1" } } Context "OpenSSL 3 Path Check" -Skip:(-not $os.IsTahoe) { It "OpenSSL 3 path exists" { $openSSLpath = brew --prefix openssl@3 $openSSLpath | Should -Exist } } Context "OpenSSL 3 is default" -Skip:(-not $os.IsTahoe) { It "Default OpenSSL version is 3" { $commandResult = Get-CommandResult "openssl version" $commandResult.Output | Should -Match "OpenSSL 3" } } } ================================================ FILE: images/macos/scripts/tests/PHP.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "PHP" { Context "PHP" -Skip:($os.IsArm64) { It "PHP Path" { Get-ToolPath "php" | Should -Not -BeLike "/usr/bin/php*" } It "PHP version" { $phpVersionToolset = (Get-ToolsetContent).php.version $phpInstalledVersion = php --version | Out-String | Select-String "${phpVersionToolset}" $phpInstalledVersion | Should -BeLike "PHP ${phpVersionToolset}*" } } Context "Composer" -Skip:($os.IsArm64) { It "Composer" { "composer --version" | Should -ReturnZeroExitCode } } } ================================================ FILE: images/macos/scripts/tests/Powershell.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking Describe "Powershell" { Context "Powershell is installed" { It "Powershell is installed" { "pwsh -v" | Should -ReturnZeroExitCode } } Context "Powershell Modules" { $modules = (Get-ToolsetContent).powershellModules $withoutVersionsModules = $modules | Where-Object {-not $_.versions} | ForEach-Object { @{moduleName = $_.name} } $withVersionsModules = $modules | Where-Object {$_.versions} | ForEach-Object { $moduleName = $_.name $_.versions | ForEach-Object { @{moduleName = $moduleName; expectedVersion = $_} } } It " is installed" -TestCases $withoutVersionsModules { param ( [string] $moduleName ) Get-Module -Name $moduleName -ListAvailable | Should -BeTrue } if ($withVersionsModules) { It " with is installed" -TestCases $withVersionsModules { param ( [string] $moduleName, [string] $expectedVersion ) (Get-Module -Name $moduleName -ListAvailable).Version -contains $expectedVersion | Should -BeTrue } } } } ================================================ FILE: images/macos/scripts/tests/Python.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking $os = Get-OSVersion Describe "Python3" { It "Python 3 is available" { "python3 --version" | Should -ReturnZeroExitCode } if ($os.IsArm64) { It "Python 3 is installed under /opt/homebrew/bin/" { Get-ToolPath "python3" | Should -BeLike "/opt/homebrew/bin/*" } } else { It "Python 3 is installed under /usr/local/bin" { Get-ToolPath "python3" | Should -BeLike "/usr/local/bin/*" } } It "Pip 3 is available" { "pip3 --version" | Should -ReturnZeroExitCode } It "Pipx is available" { "pipx --version" | Should -ReturnZeroExitCode } It "Pip 3 and Python 3 came from the same path prefix" { $pip3Path = Split-Path (readlink (which pip3)) $python3Path = Split-Path (readlink (which python3)) $pip3Path | Should -BeExactly $python3Path } It "Pip 3 and Python 3 came from brew formula" { Split-Path (readlink (which pip3)) | Should -BeLike "*/Cellar/*" Split-Path (readlink (which python3)) | Should -BeLike "*/Cellar/*" } } ================================================ FILE: images/macos/scripts/tests/Rosetta.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "Rosetta" -Skip:(-not $os.IsArm64) { It "Rosetta is available" { $commandResult = Get-CommandResult "/usr/bin/pgrep oahd" $commandResult.Output | Should -Match "\d+" $commandResult.ExitCode | Should -Be 0 } } ================================================ FILE: images/macos/scripts/tests/Ruby.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking Describe "Ruby" { It "Ruby is available" { "ruby --version" | Should -ReturnZeroExitCode } It "Ruby is installed via HomeBrew" { Get-ToolPath "ruby" | Should -Not -BeLike "/usr/bin/ruby*" } It "Ruby tools are consistent" { $os = Get-OSVersion $expectedPrefix = if ($os.IsArm64) { "/opt/homebrew" } else { "/usr/local" } Get-ToolPath "ruby" | Should -Match "^$expectedPrefix.*" Get-ToolPath "gem" | Should -Match "^$expectedPrefix.*" Get-ToolPath "bundler" | Should -Match "^$expectedPrefix.*" } It "Ruby gems permissions are valid" { "gem install bundle" | Should -ReturnZeroExitCode "gem uninstall bundle" | Should -ReturnZeroExitCode } } ================================================ FILE: images/macos/scripts/tests/RubyGem.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "RubyGems" { $gemTestCases = (Get-ToolsetContent).ruby.rubygems | ForEach-Object { @{gemName = $_} } if ($gemTestCases) { It "Gem is installed" -TestCases $gemTestCases { "gem list -i '^$gemName$'" | Should -MatchCommandOutput "true" } } } Describe "Bundler" { It "Bundler" { "bundler --version" | Should -ReturnZeroExitCode } } Describe "Fastlane" { It "Fastlane" { "fastlane --version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/macos/scripts/tests/RunAll-Tests.ps1 ================================================ Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking Invoke-PesterTests "*" ================================================ FILE: images/macos/scripts/tests/Rust.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "Rust" { Context "Rust" { It "Rustup is installed" { "rustup --version" | Should -ReturnZeroExitCode } It "Rustc is installed" { "rustc --version" | Should -ReturnZeroExitCode } } Context "Cargo" { It "Cargo is installed" { "cargo --version" | Should -ReturnZeroExitCode } } } ================================================ FILE: images/macos/scripts/tests/System.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" $os = Get-OSVersion Describe "Disk free space" { It "Image has more than 30GB free space" { # we should have at least 30 GB of free space on macOS images # 10GB here: https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops#capabilities-and-limitations # 14GB here: https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories # 30GB due to: https://github.com/actions/runner-images/issues/10511 $diskInfo = Get-PSDrive "/" $totalSpaceGB = [math]::Floor(($diskInfo.Used + $diskInfo.Free) / 1GB) $freeSpaceGB = [math]::Floor($diskInfo.Free / 1GB) Write-Host " [i] Disk size: ${totalSpaceGB} GB; Free space: ${freeSpaceGB} GB" $freeSpaceGB | Should -BeGreaterOrEqual 30 } } Describe "Certificate" { It "Apple Worldwide Developer Relations Certification Authority[expired: 2030-02] is installed" { $sha1Hash = "06EC06599F4ED0027CC58956B4D3AC1255114F35" $certs = security find-certificate -a -c Worldwide -p -Z | Out-String $certs | Should -Match $sha1Hash } It "Developer ID Certification Authority[expired: 2031-09] is installed" { $sha1Hash = "5B45F61068B29FCC8FFFF1A7E99B78DA9E9C4635" $certs = security find-certificate -a -c "Developer ID" -p -Z | Out-String $certs | Should -Match $sha1Hash } } Describe "AutomationModeTool" { It "Does not require user authentication" { automationmodetool | Out-String | Should -Match "DOES NOT REQUIRE" } } ================================================ FILE: images/macos/scripts/tests/Toolcache.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking $arch = Get-Architecture $os = Get-OSVersion Describe "Toolcache" { $toolcacheDirectory = Join-Path $env:HOME "hostedtoolcache" [array]$packages += (Get-ToolsetContent).toolcache | ForEach-Object { return [PSCustomObject] @{ ToolName = ($_.name).ToLower() Arch = $arch Versions = $_.arch.$arch | Where-Object{ $_ } | ForEach-Object { $_.versions.Replace(".*", "") } } } Context "Python" { $pythonDirectory = Join-Path $toolcacheDirectory "Python" $pythonPackage = $packages | Where-Object { $_.ToolName -eq "python" } | Select-Object -First 1 $testCase = @{ PythonDirectory = $pythonDirectory } It "Toolcache directory exists" -TestCases $testCase { param ( [string] $PythonDirectory ) $PythonDirectory | Should -Exist } It "Toolcache directory contains at least one version of Python" -TestCases $testCase { param ( [string] $PythonDirectory ) (Get-ChildItem -Path $PythonDirectory -Directory).Count | Should -BeGreaterThan 0 } $pythonPackage.Versions | Where-Object { $_ } | ForEach-Object { Context "$_" { $versionDirectory = Get-ChildItem -Path $pythonDirectory -Directory -Filter "$_*" | Select-Object -First 1 $pythonBinPath = Join-Path $versionDirectory.FullName $pythonPackage.Arch "python" $testCase = @{ PythonVersion = $_; PythonBinPath = $pythonBinPath } It "Version" -TestCases $testCase { param ( [string] $PythonVersion, [string] $PythonBinPath ) $result = Get-CommandResult "$PythonBinPath --version" $result.Output | Should -BeLike "*$PythonVersion*" $result.ExitCode | Should -Be 0 } It "Run test script" -TestCases $testCase { param ( [string] $PythonBinPath ) "$PythonBinPath -c 'import sys;print(sys.version)'" | Should -ReturnZeroExitCode } } } } Context "Ruby" { $rubyDirectory = Join-Path $toolcacheDirectory "Ruby" $rubyPackage = $packages | Where-Object { $_.ToolName -eq "Ruby" } | Select-Object -First 1 $testCase = @{ RubyDirectory = $rubyDirectory } It "Toolcache directory exists" -TestCases $testCase { param ( [string] $RubyDirectory ) $RubyDirectory | Should -Exist } It "Toolcache directory contains at least one version of Ruby" -TestCases $testCase { param ( [string] $RubyDirectory ) (Get-ChildItem -Path $RubyDirectory -Directory).Count | Should -BeGreaterThan 0 } $rubyPackage.Versions | Where-Object { $_ } | ForEach-Object { Context "$_" { $versionDirectory = Get-ChildItem -Path $rubyDirectory -Directory -Filter "$_*" | Select-Object -First 1 $rubyBinPath = Join-Path $versionDirectory.FullName $rubyPackage.Arch "bin" "ruby" $testCase = @{ RubyVersion = $_; RubyBinPath = $rubyBinPath } It "Version" -TestCases $testCase { param ( [string] $RubyVersion, [string] $RubyBinPath ) $result = Get-CommandResult "$RubyBinPath --version" $result.Output | Should -BeLike "*$RubyVersion*" $result.ExitCode | Should -Be 0 } It "Run test script" -TestCases $testCase { param ( [string] $RubyBinPath ) "$RubyBinPath -e 'puts RUBY_VERSION'" | Should -ReturnZeroExitCode } } } } Context "Node" { $nodeDirectory = Join-Path $toolcacheDirectory "node" $nodePackage = $packages | Where-Object { $_.ToolName -eq "node" } | Select-Object -First 1 $testCase = @{ NodeDirectory = $nodeDirectory } It "Toolcache directory exists" -TestCases $testCase { param ( [string] $NodeDirectory ) $NodeDirectory | Should -Exist } It "Toolcache directory contains at least one version of Node" -TestCases $testCase { param ( [string] $NodeDirectory ) (Get-ChildItem -Path $NodeDirectory -Directory).Count | Should -BeGreaterThan 0 } $nodePackage.Versions | Where-Object { $_ } | ForEach-Object { Context "$_" { $versionDirectory = Get-ChildItem -Path $nodeDirectory -Directory -Filter "$_*" | Select-Object -First 1 $nodeBinPath = Join-Path $versionDirectory.FullName $nodePackage.Arch "bin" "node" $npmBinPath = Join-Path $versionDirectory.FullName $nodePackage.Arch "bin" "npm" $testCase = @{ NodeVersion = $_; NodeBinPath = $nodeBinPath; NpmBinPath = $npmBinPath } It "Version Node" -TestCases $testCase { param ( [string] $NodeVersion, [string] $NodeBinPath ) $result = Get-CommandResult "$NodeBinPath --version" $result.Output | Should -BeLike "*$NodeVersion*" $result.ExitCode | Should -Be 0 } It "Version Npm" -TestCases $testCase { param ( [string] $NpmBinPath ) "$NpmBinPath --version" | Should -ReturnZeroExitCode } It "Run test script" -TestCases $testCase { param ( [string] $NodeBinPath ) "$NodeBinPath -e 'console.log(process.version)'" | Should -ReturnZeroExitCode } } } } Context "Go" { $goDirectory = Join-Path $toolcacheDirectory "go" $goPackage = $packages | Where-Object { $_.ToolName -eq "go" } | Select-Object -First 1 $testCase = @{ GoDirectory = $goDirectory } It "Toolcache directory exists" -TestCases $testCase { param ( [string] $GoDirectory ) $GoDirectory | Should -Exist } It "Toolcache directory contains at least one version of Go" -TestCases $testCase { param ( [string] $GoDirectory ) (Get-ChildItem -Path $GoDirectory -Directory).Count | Should -BeGreaterThan 0 } $goPackage.Versions | Where-Object { $_ } | ForEach-Object { Context "$_" { $versionDirectory = Get-ChildItem -Path $goDirectory -Directory -Filter "$_*" | Select-Object -First 1 $goBinPath = Join-Path $versionDirectory.FullName $goPackage.Arch "bin" "go" $testCase = @{ GoVersion = $_; GoBinPath = $goBinPath } It "Version Go" -TestCases $testCase { param ( [string] $GoVersion, [string] $GoBinPath ) $result = Get-CommandResult "$GoBinPath version" $result.Output | Should -BeLike "*$GoVersion*" $result.ExitCode | Should -Be 0 } } } } } ================================================ FILE: images/macos/scripts/tests/Toolset.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/Helpers.psm1" $toolsets = Get-ChildItem -Path $PSScriptRoot -Filter "toolset-*.json" function Get-ShortVersion([System.Version] $Version) { return [System.Version]::Parse($Version).ToString(2) } Describe "Toolset JSON validation" { $toolsets | ForEach-Object { It "$($_.Name) is valid" { $jsonContent = Get-Content -Raw $_.Fullname $jsonContent | Test-Json | Should -BeTrue } } } $toolsets | ForEach-Object { Describe "$($_.Name)" { $toolset = Get-Content -Raw $_.Fullname | ConvertFrom-Json Context "Xcode" { It "Default Xcode should be defined" { $toolset.xcode.default | Should -BeTrue } It "Default Xcode is listed in Xcode list" { $toolset.xcode.versions | Should -Contain $toolset.xcode.default } } } } ================================================ FILE: images/macos/scripts/tests/Xcode.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Import-Module "$PSScriptRoot/../helpers/Xcode.Helpers.psm1" Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking $arch = Get-Architecture $xcodeVersions = (Get-ToolsetContent).xcode.${arch}.versions $defaultXcode = (Get-ToolsetContent).xcode.default $latestXcodeVersion = $xcodeVersions | Select-Object -First 1 $os = Get-OSVersion Describe "Xcode" { $testCases = $xcodeVersions | ForEach-Object { @{ XcodeVersion = $_.link; LatestXcodeVersion = $xcodeVersions[0].link; Symlinks = $_.symlinks } } Context "Versions" { It "" -TestCases $testCases { $xcodebuildPath = Get-XcodeToolPath -Version $XcodeVersion -ToolName "xcodebuild" "$xcodebuildPath -version" | Should -ReturnZeroExitCode } } Context "Default" { $defaultXcodeTestCase = @{ DefaultXcode = $defaultXcode } It "Default Xcode is " -TestCases $defaultXcodeTestCase { "xcodebuild -version" | Should -ReturnZeroExitCode If ($DefaultXcode -ilike "*_*") { Write-Host "Composite version detected (beta/RC/preview)" $DefaultXcode = $DefaultXcode.split("_")[0] If ($DefaultXcode -notlike "*.*") { $DefaultXcode = "${DefaultXcode}.0" } } (Get-CommandResult "xcodebuild -version").Output | Should -BeLike "Xcode ${DefaultXcode}*" } It "Xcode.app points to default Xcode" -TestCases $defaultXcodeTestCase { $xcodeApp = "/Applications/Xcode.app" $expectedTarget = Get-XcodeRootPath -Version $DefaultXcode $xcodeApp | Should -Exist $expectedTarget | Should -Exist (Get-Item $xcodeApp).Target | Should -Be $expectedTarget } } Context "Additional tools" { It "Xcode tools are installed" -TestCases $testCases { $TOOLS_NOT_INSTALLED_EXIT_CODE = 69 $xcodebuildPath = Get-XcodeToolPath -Version $XcodeVersion -ToolName "xcodebuild" $result = Get-CommandResult "$xcodebuildPath -checkFirstLaunchStatus" if ($XcodeVersion -ne $LatestXcodeVersion) { $result.ExitCode | Should -Not -Be $TOOLS_NOT_INSTALLED_EXIT_CODE } else { $result.ExitCode | Should -BeIn (0, $TOOLS_NOT_INSTALLED_EXIT_CODE) } } } Context "Symlinks" { It "Xcode has correct symlinks" -TestCases $testCases { $sourcePath = Get-XcodeRootPath -Version $XcodeVersion $Symlinks | Where-Object { $_ } | ForEach-Object { $targetPath = Get-XcodeRootPath -Version $_ $targetPath | Should -Exist (Get-Item $targetPath).Target | Should -Be $sourcePath } } } It "/Applications/Xcode* symlinks are valid" { $symlinks = Get-ChildItem "/Applications" -Filter "Xcode*" | Where-Object { $_.LinkType } $symlinks.Target | ForEach-Object { $_ | Should -Exist } } } Describe "XCODE_DEVELOPER_DIR variables" { $exactVersionsList = $xcodeVersions.link | Where-Object { Test-XcodeStableRelease -Version $_ } | ForEach-Object { $xcodeRootPath = Get-XcodeRootPath -Version $_ $xcodeVersionInfo = Get-XcodeVersionInfo -XcodeRootPath $xcodeRootPath return @{ RootPath = $xcodeRootPath Version = [SemVer]::Parse($xcodeVersionInfo.Version) } } | Sort-Object -Property Version -Descending $majorVersions = $exactVersionsList.Version.Major | Select-Object -Unique $testCases = $majorVersions | ForEach-Object { @{ MajorVersion = $_; VersionsList = $exactVersionsList } } It "XCODE__DEVELOPER_DIR" -TestCases $testCases { $variableName = "XCODE_${MajorVersion}_DEVELOPER_DIR" $actualPath = [System.Environment]::GetEnvironmentVariable($variableName) $expectedVersion = $VersionsList | Where-Object { $_.Version.Major -eq $MajorVersion } | Select-Object -First 1 $expectedPath = "$($expectedVersion.RootPath)/Contents/Developer" $actualPath | Should -Exist $actualPath | Should -Be $expectedPath } } Describe "Xcode simulators" { $xcodeVersions.link | Where-Object { Test-XcodeStableRelease -Version $_ } | ForEach-Object { Context "$_" { $testCase = @{ XcodeVersion = $_ } It "No duplicates in devices" -TestCases $testCase { Switch-Xcode -Version $XcodeVersion [array]$devicesList = @(Get-XcodeDevicesList | Where-Object { $_ }) Write-Host "Devices for $XcodeVersion" Write-Host ($devicesList -join "`n") Confirm-ArrayWithoutDuplicates $devicesList -Because "Found duplicate device simulators" } } } AfterEach { $defaultXcode = (Get-ToolsetContent).xcode.default Switch-Xcode -Version $defaultXcode } } ================================================ FILE: images/macos/templates/macOS-14.anka.pkr.hcl ================================================ packer { required_plugins { veertu-anka = { version = ">= v3.2.0" source = "github.com/veertuinc/veertu-anka" } } } locals { image_folder = "/Users/${var.vm_username}/image-generation" } variable "builder_type" { type = string default = "veertu-anka-vm-clone" validation { condition = contains(["veertu-anka-vm-clone", "null"], var.builder_type) error_message = "The builder_type value must be one of [veertu-anka-vm-clone, null]." } } variable "source_vm_name" { type = string } variable "source_vm_port" { type = number default = 22 } variable "source_vm_tag" { type = string default = "" } variable "socks_proxy" { type = string default = "" } variable "build_id" { type = string } variable "vm_username" { type = string sensitive = true } variable "vm_password" { type = string sensitive = true } variable "github_api_pat" { type = string sensitive = true default = "" } variable "xcode_install_storage_url" { type = string sensitive = true } variable "xcode_install_sas" { type = string sensitive = true } variable "vcpu_count" { type = string default = "6" } variable "ram_size" { type = string default = "24G" } variable "image_os" { type = string default = "macos14" } source "veertu-anka-vm-clone" "template" { vm_name = "${var.build_id}" source_vm_name = "${var.source_vm_name}" source_vm_tag = "${var.source_vm_tag}" vcpu_count = "${var.vcpu_count}" ram_size = "${var.ram_size}" stop_vm = "true" } source "null" "template" { ssh_host = "${var.source_vm_name}" ssh_port = "${var.source_vm_port}" ssh_username = "${var.vm_username}" ssh_password = "${var.vm_password}" ssh_proxy_host = "${var.socks_proxy}" } build { sources = ["source.${var.builder_type}.template"] provisioner "shell" { inline = ["mkdir ${local.image_folder}"] } provisioner "file" { destination = "${local.image_folder}/" sources = [ "${path.root}/../scripts/tests", "${path.root}/../scripts/docs-gen", "${path.root}/../scripts/helpers" ] } provisioner "file" { destination = "${local.image_folder}/docs-gen/" source = "${path.root}/../../../helpers/software-report-base" } provisioner "file" { destination = "${local.image_folder}/add-certificate.swift" source = "${path.root}/../assets/add-certificate.swift" } provisioner "file" { destination = ".bashrc" source = "${path.root}/../assets/bashrc" } provisioner "file" { destination = ".bash_profile" source = "${path.root}/../assets/bashprofile" } provisioner "shell" { inline = ["mkdir ~/bootstrap"] } provisioner "file" { destination = "bootstrap" source = "${path.root}/../assets/bootstrap-provisioner/" } provisioner "file" { destination = "${local.image_folder}/toolset.json" source = "${path.root}/../toolsets/toolset-14.json" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = [ "mv ${local.image_folder}/docs-gen ${local.image_folder}/software-report", "mkdir ~/utils", "mv ${local.image_folder}/helpers/confirm-identified-developers-macos14.scpt ~/utils", "mv ${local.image_folder}/helpers/invoke-tests.sh ~/utils", "mv ${local.image_folder}/helpers/utils.sh ~/utils", ] } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-xcode-clt.sh", "${path.root}/../scripts/build/install-homebrew.sh" ] } provisioner "shell" { environment_vars = ["PASSWORD=${var.vm_password}", "USERNAME=${var.vm_username}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-tccdb-macos.sh", "${path.root}/../scripts/build/configure-autologin.sh", "${path.root}/../scripts/build/configure-auto-updates.sh", "${path.root}/../scripts/build/configure-ntpconf.sh", "${path.root}/../scripts/build/configure-shell.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.build_id}", "IMAGE_OS=${var.image_os}", "PASSWORD=${var.vm_password}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-preimagedata.sh", "${path.root}/../scripts/build/configure-ssh.sh", "${path.root}/../scripts/build/configure-machine.sh" ] } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "USER_PASSWORD=${var.vm_password}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" pause_before = "30s" scripts = [ "${path.root}/../scripts/build/configure-windows.sh", "${path.root}/../scripts/build/install-powershell.sh", "${path.root}/../scripts/build/install-mono.sh", "${path.root}/../scripts/build/install-dotnet.sh", "${path.root}/../scripts/build/install-python.sh", "${path.root}/../scripts/build/install-azcopy.sh", "${path.root}/../scripts/build/install-openssl.sh", "${path.root}/../scripts/build/install-ruby.sh", "${path.root}/../scripts/build/install-rubygems.sh", "${path.root}/../scripts/build/install-git.sh", "${path.root}/../scripts/build/install-node.sh", "${path.root}/../scripts/build/install-common-utils.sh" ] } provisioner "shell" { environment_vars = ["XCODE_INSTALL_STORAGE_URL=${var.xcode_install_storage_url}", "XCODE_INSTALL_SAS=${var.xcode_install_sas}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Install-Xcode.ps1" } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-actions-cache.sh", "${path.root}/../scripts/build/install-llvm.sh", "${path.root}/../scripts/build/install-swiftlint.sh", "${path.root}/../scripts/build/install-openjdk.sh", "${path.root}/../scripts/build/install-php.sh", "${path.root}/../scripts/build/install-aws-tools.sh", "${path.root}/../scripts/build/install-rust.sh", "${path.root}/../scripts/build/install-gcc.sh", "${path.root}/../scripts/build/install-cocoapods.sh", "${path.root}/../scripts/build/install-android-sdk.sh", "${path.root}/../scripts/build/install-vcpkg.sh", "${path.root}/../scripts/build/install-safari.sh", "${path.root}/../scripts/build/install-chrome.sh", "${path.root}/../scripts/build/install-edge.sh", "${path.root}/../scripts/build/install-firefox.sh", "${path.root}/../scripts/build/install-bicep.sh", "${path.root}/../scripts/build/install-codeql-bundle.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" scripts = [ "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Configure-Xcode-Simulators.ps1" } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" inline = [ "pwsh -File \"${local.image_folder}/software-report/Generate-SoftwareReport.ps1\" -OutputDirectory \"${local.image_folder}/output\" -ImageName ${var.build_id}", "pwsh -File \"${local.image_folder}/tests/RunAll-Tests.ps1\"" ] } provisioner "file" { destination = "${path.root}/../../image-output/macos-14-Readme.md" direction = "download" source = "${local.image_folder}/output/software-report.md" } provisioner "file" { destination = "${path.root}/../../image-output/software-report.json" direction = "download" source = "${local.image_folder}/output/software-report.json" } provisioner "shell" { inline = ["rm -rf \"$(brew --cache)\""] } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-hostname.sh", "${path.root}/../scripts/build/configure-system.sh" ] } } ================================================ FILE: images/macos/templates/macOS-14.arm64.anka.pkr.hcl ================================================ packer { required_plugins { veertu-anka = { version = ">= v3.2.0" source = "github.com/veertuinc/veertu-anka" } } } locals { image_folder = "/Users/${var.vm_username}/image-generation" } variable "builder_type" { type = string default = "veertu-anka-vm-clone" validation { condition = contains(["veertu-anka-vm-clone", "null"], var.builder_type) error_message = "The builder_type value must be one of [veertu-anka-vm-clone, null]." } } variable "source_vm_name" { type = string } variable "source_vm_port" { type = number default = 22 } variable "source_vm_tag" { type = string default = "" } variable "socks_proxy" { type = string default = "" } variable "build_id" { type = string } variable "vm_username" { type = string sensitive = true } variable "vm_password" { type = string sensitive = true } variable "github_api_pat" { type = string sensitive = true default = "" } variable "xcode_install_storage_url" { type = string sensitive = true } variable "xcode_install_sas" { type = string sensitive = true } variable "vcpu_count" { type = string default = "6" } variable "ram_size" { type = string default = "8G" } variable "image_os" { type = string default = "macos14" } source "veertu-anka-vm-clone" "template" { vm_name = "${var.build_id}" source_vm_name = "${var.source_vm_name}" source_vm_tag = "${var.source_vm_tag}" vcpu_count = "${var.vcpu_count}" ram_size = "${var.ram_size}" stop_vm = "true" log_level = "debug" } source "null" "template" { ssh_host = "${var.source_vm_name}" ssh_port = "${var.source_vm_port}" ssh_username = "${var.vm_username}" ssh_password = "${var.vm_password}" ssh_proxy_host = "${var.socks_proxy}" } build { sources = ["source.${var.builder_type}.template"] provisioner "shell" { inline = ["mkdir ${local.image_folder}"] } provisioner "file" { destination = "${local.image_folder}/" sources = [ "${path.root}/../scripts/tests", "${path.root}/../scripts/docs-gen", "${path.root}/../scripts/helpers" ] } provisioner "file" { destination = "${local.image_folder}/docs-gen/" source = "${path.root}/../../../helpers/software-report-base" } provisioner "file" { destination = "${local.image_folder}/add-certificate.swift" source = "${path.root}/../assets/add-certificate.swift" } provisioner "file" { destination = ".bashrc" source = "${path.root}/../assets/bashrc" } provisioner "file" { destination = ".bash_profile" source = "${path.root}/../assets/bashprofile" } provisioner "shell" { inline = ["mkdir ~/bootstrap"] } provisioner "file" { destination = "bootstrap" source = "${path.root}/../assets/bootstrap-provisioner/" } provisioner "file" { destination = "${local.image_folder}/toolset.json" source = "${path.root}/../toolsets/toolset-14.json" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = [ "mv ${local.image_folder}/docs-gen ${local.image_folder}/software-report", "mkdir ~/utils", "mv ${local.image_folder}/helpers/invoke-tests.sh ~/utils", "mv ${local.image_folder}/helpers/utils.sh ~/utils", ] } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-xcode-clt.sh", "${path.root}/../scripts/build/install-homebrew.sh", "${path.root}/../scripts/build/install-rosetta.sh" ] } provisioner "shell" { environment_vars = ["PASSWORD=${var.vm_password}", "USERNAME=${var.vm_username}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-tccdb-macos.sh", "${path.root}/../scripts/build/configure-autologin.sh", "${path.root}/../scripts/build/configure-auto-updates.sh", "${path.root}/../scripts/build/configure-ntpconf.sh", "${path.root}/../scripts/build/configure-shell.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.build_id}", "IMAGE_OS=${var.image_os}", "PASSWORD=${var.vm_password}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-preimagedata.sh", "${path.root}/../scripts/build/configure-ssh.sh", "${path.root}/../scripts/build/configure-machine.sh" ] } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "USER_PASSWORD=${var.vm_password}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" pause_before = "30s" scripts = [ "${path.root}/../scripts/build/configure-windows.sh", "${path.root}/../scripts/build/install-powershell.sh", "${path.root}/../scripts/build/install-mono.sh", "${path.root}/../scripts/build/install-dotnet.sh", "${path.root}/../scripts/build/install-python.sh", "${path.root}/../scripts/build/install-azcopy.sh", "${path.root}/../scripts/build/install-openssl.sh", "${path.root}/../scripts/build/install-ruby.sh", "${path.root}/../scripts/build/install-rubygems.sh", "${path.root}/../scripts/build/install-git.sh", "${path.root}/../scripts/build/install-node.sh", "${path.root}/../scripts/build/install-common-utils.sh" ] } provisioner "shell" { environment_vars = ["XCODE_INSTALL_STORAGE_URL=${var.xcode_install_storage_url}", "XCODE_INSTALL_SAS=${var.xcode_install_sas}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Install-Xcode.ps1" } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-actions-cache.sh", "${path.root}/../scripts/build/install-llvm.sh", "${path.root}/../scripts/build/install-openjdk.sh", "${path.root}/../scripts/build/install-aws-tools.sh", "${path.root}/../scripts/build/install-rust.sh", "${path.root}/../scripts/build/install-gcc.sh", "${path.root}/../scripts/build/install-cocoapods.sh", "${path.root}/../scripts/build/install-android-sdk.sh", "${path.root}/../scripts/build/install-vcpkg.sh", "${path.root}/../scripts/build/install-safari.sh", "${path.root}/../scripts/build/install-chrome.sh", "${path.root}/../scripts/build/install-firefox.sh", "${path.root}/../scripts/build/install-bicep.sh", "${path.root}/../scripts/build/install-codeql-bundle.sh", "${path.root}/../scripts/build/install-edge.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" scripts = [ "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Configure-Xcode-Simulators.ps1" } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" inline = [ "pwsh -File \"${local.image_folder}/software-report/Generate-SoftwareReport.ps1\" -OutputDirectory \"${local.image_folder}/output\" -ImageName ${var.build_id}", "pwsh -File \"${local.image_folder}/tests/RunAll-Tests.ps1\"" ] } provisioner "file" { destination = "${path.root}/../../image-output/macos-14-arm64-Readme.md" direction = "download" source = "${local.image_folder}/output/software-report.md" } provisioner "file" { destination = "${path.root}/../../image-output/software-report.json" direction = "download" source = "${local.image_folder}/output/software-report.json" } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-hostname.sh", "${path.root}/../scripts/build/configure-system.sh" ] } } ================================================ FILE: images/macos/templates/macOS-15.anka.pkr.hcl ================================================ packer { required_plugins { veertu-anka = { version = ">= v3.2.0" source = "github.com/veertuinc/veertu-anka" } } } locals { image_folder = "/Users/${var.vm_username}/image-generation" } variable "builder_type" { type = string default = "veertu-anka-vm-clone" validation { condition = contains(["veertu-anka-vm-clone", "null"], var.builder_type) error_message = "The builder_type value must be one of [veertu-anka-vm-clone, null]." } } variable "source_vm_name" { type = string } variable "source_vm_port" { type = number default = 22 } variable "source_vm_tag" { type = string default = "" } variable "socks_proxy" { type = string default = "" } variable "build_id" { type = string } variable "vm_username" { type = string sensitive = true } variable "vm_password" { type = string sensitive = true } variable "github_api_pat" { type = string sensitive = true default = "" } variable "xcode_install_storage_url" { type = string sensitive = true } variable "xcode_install_sas" { type = string sensitive = true } variable "vcpu_count" { type = string default = "6" } variable "ram_size" { type = string default = "8G" } variable "image_os" { type = string default = "macos15" } source "veertu-anka-vm-clone" "template" { vm_name = "${var.build_id}" source_vm_name = "${var.source_vm_name}" source_vm_tag = "${var.source_vm_tag}" vcpu_count = "${var.vcpu_count}" ram_size = "${var.ram_size}" stop_vm = "true" } source "null" "template" { ssh_host = "${var.source_vm_name}" ssh_port = "${var.source_vm_port}" ssh_username = "${var.vm_username}" ssh_password = "${var.vm_password}" ssh_proxy_host = "${var.socks_proxy}" } build { sources = ["source.${var.builder_type}.template"] provisioner "shell" { inline = ["mkdir ${local.image_folder}"] } provisioner "file" { destination = "${local.image_folder}/" sources = [ "${path.root}/../scripts/tests", "${path.root}/../scripts/docs-gen", "${path.root}/../scripts/helpers" ] } provisioner "file" { destination = "${local.image_folder}/docs-gen/" source = "${path.root}/../../../helpers/software-report-base" } provisioner "file" { destination = "${local.image_folder}/add-certificate.swift" source = "${path.root}/../assets/add-certificate.swift" } provisioner "file" { destination = ".bashrc" source = "${path.root}/../assets/bashrc" } provisioner "file" { destination = ".bash_profile" source = "${path.root}/../assets/bashprofile" } provisioner "shell" { inline = ["mkdir ~/bootstrap"] } provisioner "file" { destination = "bootstrap" source = "${path.root}/../assets/bootstrap-provisioner/" } provisioner "file" { destination = "${local.image_folder}/toolset.json" source = "${path.root}/../toolsets/toolset-15.json" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = [ "mv ${local.image_folder}/docs-gen ${local.image_folder}/software-report", "mkdir ~/utils", "mv ${local.image_folder}/helpers/confirm-identified-developers-macos15.scpt ~/utils", "mv ${local.image_folder}/helpers/invoke-tests.sh ~/utils", "mv ${local.image_folder}/helpers/utils.sh ~/utils" ] } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-xcode-clt.sh", "${path.root}/../scripts/build/install-homebrew.sh" ] } provisioner "shell" { environment_vars = ["PASSWORD=${var.vm_password}", "USERNAME=${var.vm_username}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-tccdb-macos.sh", "${path.root}/../scripts/build/configure-autologin.sh", "${path.root}/../scripts/build/configure-auto-updates.sh", "${path.root}/../scripts/build/configure-ntpconf.sh", "${path.root}/../scripts/build/configure-shell.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.build_id}", "IMAGE_OS=${var.image_os}", "PASSWORD=${var.vm_password}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-preimagedata.sh", "${path.root}/../scripts/build/configure-ssh.sh", "${path.root}/../scripts/build/configure-machine.sh" ] } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "USER_PASSWORD=${var.vm_password}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" pause_before = "30s" scripts = [ "${path.root}/../scripts/build/configure-windows.sh", "${path.root}/../scripts/build/install-powershell.sh", "${path.root}/../scripts/build/install-dotnet.sh", "${path.root}/../scripts/build/install-python.sh", "${path.root}/../scripts/build/install-azcopy.sh", "${path.root}/../scripts/build/install-openssl.sh", "${path.root}/../scripts/build/install-ruby.sh", "${path.root}/../scripts/build/install-rubygems.sh", "${path.root}/../scripts/build/install-git.sh", "${path.root}/../scripts/build/install-node.sh", "${path.root}/../scripts/build/install-common-utils.sh" ] } provisioner "shell" { environment_vars = ["XCODE_INSTALL_STORAGE_URL=${var.xcode_install_storage_url}", "XCODE_INSTALL_SAS=${var.xcode_install_sas}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Install-Xcode.ps1" } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-actions-cache.sh", "${path.root}/../scripts/build/install-llvm.sh", "${path.root}/../scripts/build/install-swiftlint.sh", "${path.root}/../scripts/build/install-openjdk.sh", "${path.root}/../scripts/build/install-php.sh", "${path.root}/../scripts/build/install-aws-tools.sh", "${path.root}/../scripts/build/install-rust.sh", "${path.root}/../scripts/build/install-gcc.sh", "${path.root}/../scripts/build/install-cocoapods.sh", "${path.root}/../scripts/build/install-android-sdk.sh", "${path.root}/../scripts/build/install-vcpkg.sh", "${path.root}/../scripts/build/install-safari.sh", "${path.root}/../scripts/build/install-chrome.sh", "${path.root}/../scripts/build/install-edge.sh", "${path.root}/../scripts/build/install-firefox.sh", "${path.root}/../scripts/build/install-bicep.sh", "${path.root}/../scripts/build/install-codeql-bundle.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" scripts = [ "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Configure-Xcode-Simulators.ps1" } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" inline = [ "pwsh -File \"${local.image_folder}/software-report/Generate-SoftwareReport.ps1\" -OutputDirectory \"${local.image_folder}/output\" -ImageName ${var.build_id}", "pwsh -File \"${local.image_folder}/tests/RunAll-Tests.ps1\"" ] } provisioner "file" { destination = "${path.root}/../../image-output/macos-15-Readme.md" direction = "download" source = "${local.image_folder}/output/software-report.md" } provisioner "file" { destination = "${path.root}/../../image-output/software-report.json" direction = "download" source = "${local.image_folder}/output/software-report.json" } provisioner "shell" { inline = ["rm -rf \"$(brew --cache)\""] } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-hostname.sh", "${path.root}/../scripts/build/configure-system.sh" ] } } ================================================ FILE: images/macos/templates/macOS-15.arm64.anka.pkr.hcl ================================================ packer { required_plugins { veertu-anka = { version = ">= v3.2.0" source = "github.com/veertuinc/veertu-anka" } } } locals { image_folder = "/Users/${var.vm_username}/image-generation" } variable "builder_type" { type = string default = "veertu-anka-vm-clone" validation { condition = contains(["veertu-anka-vm-clone", "null"], var.builder_type) error_message = "The builder_type value must be one of [veertu-anka-vm-clone, null]." } } variable "source_vm_name" { type = string } variable "source_vm_port" { type = number default = 22 } variable "source_vm_tag" { type = string default = "" } variable "socks_proxy" { type = string default = "" } variable "build_id" { type = string } variable "vm_username" { type = string sensitive = true } variable "vm_password" { type = string sensitive = true } variable "github_api_pat" { type = string sensitive = true default = "" } variable "xcode_install_storage_url" { type = string sensitive = true } variable "xcode_install_sas" { type = string sensitive = true } variable "vcpu_count" { type = string default = "6" } variable "ram_size" { type = string default = "8G" } variable "image_os" { type = string default = "macos15" } source "veertu-anka-vm-clone" "template" { vm_name = "${var.build_id}" source_vm_name = "${var.source_vm_name}" source_vm_tag = "${var.source_vm_tag}" vcpu_count = "${var.vcpu_count}" ram_size = "${var.ram_size}" stop_vm = "true" log_level = "debug" } source "null" "template" { ssh_host = "${var.source_vm_name}" ssh_port = "${var.source_vm_port}" ssh_username = "${var.vm_username}" ssh_password = "${var.vm_password}" ssh_proxy_host = "${var.socks_proxy}" } build { sources = ["source.${var.builder_type}.template"] provisioner "shell" { inline = ["mkdir ${local.image_folder}"] } provisioner "file" { destination = "${local.image_folder}/" sources = [ "${path.root}/../scripts/tests", "${path.root}/../scripts/docs-gen", "${path.root}/../scripts/helpers" ] } provisioner "file" { destination = "${local.image_folder}/docs-gen/" source = "${path.root}/../../../helpers/software-report-base" } provisioner "file" { destination = "${local.image_folder}/add-certificate.swift" source = "${path.root}/../assets/add-certificate.swift" } provisioner "file" { destination = ".bashrc" source = "${path.root}/../assets/bashrc" } provisioner "file" { destination = ".bash_profile" source = "${path.root}/../assets/bashprofile" } provisioner "shell" { inline = ["mkdir ~/bootstrap"] } provisioner "file" { destination = "bootstrap" source = "${path.root}/../assets/bootstrap-provisioner/" } provisioner "file" { destination = "${local.image_folder}/toolset.json" source = "${path.root}/../toolsets/toolset-15.json" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = [ "mv ${local.image_folder}/docs-gen ${local.image_folder}/software-report", "mkdir ~/utils", "mv ${local.image_folder}/helpers/invoke-tests.sh ~/utils", "mv ${local.image_folder}/helpers/utils.sh ~/utils" ] } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-xcode-clt.sh", "${path.root}/../scripts/build/install-homebrew.sh", "${path.root}/../scripts/build/install-rosetta.sh" ] } provisioner "shell" { environment_vars = ["PASSWORD=${var.vm_password}", "USERNAME=${var.vm_username}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-tccdb-macos.sh", "${path.root}/../scripts/build/configure-autologin.sh", "${path.root}/../scripts/build/configure-auto-updates.sh", "${path.root}/../scripts/build/configure-ntpconf.sh", "${path.root}/../scripts/build/configure-shell.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.build_id}", "IMAGE_OS=${var.image_os}", "PASSWORD=${var.vm_password}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-preimagedata.sh", "${path.root}/../scripts/build/configure-ssh.sh", "${path.root}/../scripts/build/configure-machine.sh" ] } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "USER_PASSWORD=${var.vm_password}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" pause_before = "30s" scripts = [ "${path.root}/../scripts/build/configure-windows.sh", "${path.root}/../scripts/build/install-powershell.sh", "${path.root}/../scripts/build/install-dotnet.sh", "${path.root}/../scripts/build/install-python.sh", "${path.root}/../scripts/build/install-azcopy.sh", "${path.root}/../scripts/build/install-openssl.sh", "${path.root}/../scripts/build/install-ruby.sh", "${path.root}/../scripts/build/install-rubygems.sh", "${path.root}/../scripts/build/install-git.sh", "${path.root}/../scripts/build/install-node.sh", "${path.root}/../scripts/build/install-common-utils.sh" ] } provisioner "shell" { environment_vars = ["XCODE_INSTALL_STORAGE_URL=${var.xcode_install_storage_url}", "XCODE_INSTALL_SAS=${var.xcode_install_sas}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Install-Xcode.ps1" } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-actions-cache.sh", "${path.root}/../scripts/build/install-llvm.sh", "${path.root}/../scripts/build/install-openjdk.sh", "${path.root}/../scripts/build/install-aws-tools.sh", "${path.root}/../scripts/build/install-rust.sh", "${path.root}/../scripts/build/install-gcc.sh", "${path.root}/../scripts/build/install-cocoapods.sh", "${path.root}/../scripts/build/install-android-sdk.sh", "${path.root}/../scripts/build/install-vcpkg.sh", "${path.root}/../scripts/build/install-safari.sh", "${path.root}/../scripts/build/install-chrome.sh", "${path.root}/../scripts/build/install-firefox.sh", "${path.root}/../scripts/build/install-bicep.sh", "${path.root}/../scripts/build/install-codeql-bundle.sh", "${path.root}/../scripts/build/install-edge.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" scripts = [ "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Configure-Xcode-Simulators.ps1" } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" inline = [ "pwsh -File \"${local.image_folder}/software-report/Generate-SoftwareReport.ps1\" -OutputDirectory \"${local.image_folder}/output\" -ImageName ${var.build_id}", "pwsh -File \"${local.image_folder}/tests/RunAll-Tests.ps1\"" ] } provisioner "file" { destination = "${path.root}/../../image-output/macos-15-arm64-Readme.md" direction = "download" source = "${local.image_folder}/output/software-report.md" } provisioner "file" { destination = "${path.root}/../../image-output/software-report.json" direction = "download" source = "${local.image_folder}/output/software-report.json" } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-hostname.sh", "${path.root}/../scripts/build/configure-system.sh" ] } } ================================================ FILE: images/macos/templates/macOS-26.anka.pkr.hcl ================================================ packer { required_plugins { veertu-anka = { version = ">= v3.2.0" source = "github.com/veertuinc/veertu-anka" } } } locals { image_folder = "/Users/${var.vm_username}/image-generation" } variable "builder_type" { type = string default = "veertu-anka-vm-clone" validation { condition = contains(["veertu-anka-vm-clone", "null"], var.builder_type) error_message = "The builder_type value must be one of [veertu-anka-vm-clone, null]." } } variable "source_vm_name" { type = string } variable "source_vm_port" { type = number default = 22 } variable "source_vm_tag" { type = string default = "" } variable "socks_proxy" { type = string default = "" } variable "build_id" { type = string } variable "vm_username" { type = string sensitive = true } variable "vm_password" { type = string sensitive = true } variable "github_api_pat" { type = string sensitive = true default = "" } variable "xcode_install_storage_url" { type = string sensitive = true } variable "xcode_install_sas" { type = string sensitive = true } variable "vcpu_count" { type = string default = "6" } variable "ram_size" { type = string default = "8G" } variable "image_os" { type = string default = "macos26" } source "veertu-anka-vm-clone" "template" { vm_name = "${var.build_id}" source_vm_name = "${var.source_vm_name}" source_vm_tag = "${var.source_vm_tag}" vcpu_count = "${var.vcpu_count}" ram_size = "${var.ram_size}" stop_vm = "true" log_level = "debug" } source "null" "template" { ssh_host = "${var.source_vm_name}" ssh_port = "${var.source_vm_port}" ssh_username = "${var.vm_username}" ssh_password = "${var.vm_password}" ssh_proxy_host = "${var.socks_proxy}" } build { sources = ["source.${var.builder_type}.template"] provisioner "shell" { inline = ["mkdir ${local.image_folder}"] } provisioner "file" { destination = "${local.image_folder}/" sources = [ "${path.root}/../scripts/tests", "${path.root}/../scripts/docs-gen", "${path.root}/../scripts/helpers" ] } provisioner "file" { destination = "${local.image_folder}/docs-gen/" source = "${path.root}/../../../helpers/software-report-base" } provisioner "file" { destination = "${local.image_folder}/add-certificate.swift" source = "${path.root}/../assets/add-certificate.swift" } provisioner "file" { destination = ".bashrc" source = "${path.root}/../assets/bashrc" } provisioner "file" { destination = ".bash_profile" source = "${path.root}/../assets/bashprofile" } provisioner "shell" { inline = ["mkdir ~/bootstrap"] } provisioner "file" { destination = "bootstrap" source = "${path.root}/../assets/bootstrap-provisioner/" } provisioner "file" { destination = "${local.image_folder}/toolset.json" source = "${path.root}/../toolsets/toolset-26.json" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = [ "mv ${local.image_folder}/docs-gen ${local.image_folder}/software-report", "mkdir ~/utils", "mv ${local.image_folder}/helpers/invoke-tests.sh ~/utils", "mv ${local.image_folder}/helpers/utils.sh ~/utils" ] } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-xcode-clt.sh", "${path.root}/../scripts/build/install-homebrew.sh" ] } provisioner "shell" { environment_vars = ["PASSWORD=${var.vm_password}", "USERNAME=${var.vm_username}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-tccdb-macos.sh", "${path.root}/../scripts/build/configure-autologin.sh", "${path.root}/../scripts/build/configure-auto-updates.sh", "${path.root}/../scripts/build/configure-ntpconf.sh", "${path.root}/../scripts/build/configure-shell.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.build_id}", "IMAGE_OS=${var.image_os}", "PASSWORD=${var.vm_password}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-preimagedata.sh", "${path.root}/../scripts/build/configure-ssh.sh", "${path.root}/../scripts/build/configure-machine.sh" ] } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "USER_PASSWORD=${var.vm_password}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" pause_before = "30s" scripts = [ "${path.root}/../scripts/build/configure-windows.sh", "${path.root}/../scripts/build/install-powershell.sh", "${path.root}/../scripts/build/install-dotnet.sh", "${path.root}/../scripts/build/install-python.sh", "${path.root}/../scripts/build/install-azcopy.sh", "${path.root}/../scripts/build/install-ruby.sh", "${path.root}/../scripts/build/install-rubygems.sh", "${path.root}/../scripts/build/install-git.sh", "${path.root}/../scripts/build/install-node.sh", "${path.root}/../scripts/build/install-common-utils.sh" ] } provisioner "shell" { environment_vars = ["XCODE_INSTALL_STORAGE_URL=${var.xcode_install_storage_url}", "XCODE_INSTALL_SAS=${var.xcode_install_sas}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Install-Xcode.ps1" } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-actions-cache.sh", "${path.root}/../scripts/build/install-llvm.sh", "${path.root}/../scripts/build/install-swiftlint.sh", "${path.root}/../scripts/build/install-openjdk.sh", "${path.root}/../scripts/build/install-php.sh", "${path.root}/../scripts/build/install-aws-tools.sh", "${path.root}/../scripts/build/install-rust.sh", "${path.root}/../scripts/build/install-gcc.sh", "${path.root}/../scripts/build/install-cocoapods.sh", "${path.root}/../scripts/build/install-android-sdk.sh", "${path.root}/../scripts/build/install-vcpkg.sh", "${path.root}/../scripts/build/install-safari.sh", "${path.root}/../scripts/build/install-chrome.sh", "${path.root}/../scripts/build/install-firefox.sh", "${path.root}/../scripts/build/install-bicep.sh", "${path.root}/../scripts/build/install-codeql-bundle.sh", "${path.root}/../scripts/build/install-edge.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" scripts = [ "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Configure-Xcode-Simulators.ps1" } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" inline = [ "pwsh -File \"${local.image_folder}/software-report/Generate-SoftwareReport.ps1\" -OutputDirectory \"${local.image_folder}/output\" -ImageName ${var.build_id}", "pwsh -File \"${local.image_folder}/tests/RunAll-Tests.ps1\"" ] } provisioner "file" { destination = "${path.root}/../../image-output/macos-26-Readme.md" direction = "download" source = "${local.image_folder}/output/software-report.md" } provisioner "file" { destination = "${path.root}/../../image-output/software-report.json" direction = "download" source = "${local.image_folder}/output/software-report.json" } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-hostname.sh", "${path.root}/../scripts/build/configure-system.sh" ] } } ================================================ FILE: images/macos/templates/macOS-26.arm64.anka.pkr.hcl ================================================ packer { required_plugins { veertu-anka = { version = ">= v3.2.0" source = "github.com/veertuinc/veertu-anka" } } } locals { image_folder = "/Users/${var.vm_username}/image-generation" } variable "builder_type" { type = string default = "veertu-anka-vm-clone" validation { condition = contains(["veertu-anka-vm-clone", "null"], var.builder_type) error_message = "The builder_type value must be one of [veertu-anka-vm-clone, null]." } } variable "source_vm_name" { type = string } variable "source_vm_port" { type = number default = 22 } variable "source_vm_tag" { type = string default = "" } variable "socks_proxy" { type = string default = "" } variable "build_id" { type = string } variable "vm_username" { type = string sensitive = true } variable "vm_password" { type = string sensitive = true } variable "github_api_pat" { type = string sensitive = true default = "" } variable "xcode_install_storage_url" { type = string sensitive = true } variable "xcode_install_sas" { type = string sensitive = true } variable "vcpu_count" { type = string default = "6" } variable "ram_size" { type = string default = "8G" } variable "image_os" { type = string default = "macos26" } source "veertu-anka-vm-clone" "template" { vm_name = "${var.build_id}" source_vm_name = "${var.source_vm_name}" source_vm_tag = "${var.source_vm_tag}" vcpu_count = "${var.vcpu_count}" ram_size = "${var.ram_size}" stop_vm = "true" log_level = "debug" } source "null" "template" { ssh_host = "${var.source_vm_name}" ssh_port = "${var.source_vm_port}" ssh_username = "${var.vm_username}" ssh_password = "${var.vm_password}" ssh_proxy_host = "${var.socks_proxy}" } build { sources = ["source.${var.builder_type}.template"] provisioner "shell" { inline = ["mkdir ${local.image_folder}"] } provisioner "file" { destination = "${local.image_folder}/" sources = [ "${path.root}/../scripts/tests", "${path.root}/../scripts/docs-gen", "${path.root}/../scripts/helpers" ] } provisioner "file" { destination = "${local.image_folder}/docs-gen/" source = "${path.root}/../../../helpers/software-report-base" } provisioner "file" { destination = "${local.image_folder}/add-certificate.swift" source = "${path.root}/../assets/add-certificate.swift" } provisioner "file" { destination = ".bashrc" source = "${path.root}/../assets/bashrc" } provisioner "file" { destination = ".bash_profile" source = "${path.root}/../assets/bashprofile" } provisioner "shell" { inline = ["mkdir ~/bootstrap"] } provisioner "file" { destination = "bootstrap" source = "${path.root}/../assets/bootstrap-provisioner/" } provisioner "file" { destination = "${local.image_folder}/toolset.json" source = "${path.root}/../toolsets/toolset-26.json" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = [ "mv ${local.image_folder}/docs-gen ${local.image_folder}/software-report", "mkdir ~/utils", "mv ${local.image_folder}/helpers/invoke-tests.sh ~/utils", "mv ${local.image_folder}/helpers/utils.sh ~/utils" ] } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-xcode-clt.sh", "${path.root}/../scripts/build/install-homebrew.sh", "${path.root}/../scripts/build/install-rosetta.sh" ] } provisioner "shell" { environment_vars = ["PASSWORD=${var.vm_password}", "USERNAME=${var.vm_username}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-tccdb-macos.sh", "${path.root}/../scripts/build/configure-autologin.sh", "${path.root}/../scripts/build/configure-auto-updates.sh", "${path.root}/../scripts/build/configure-ntpconf.sh", "${path.root}/../scripts/build/configure-shell.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.build_id}", "IMAGE_OS=${var.image_os}", "PASSWORD=${var.vm_password}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-preimagedata.sh", "${path.root}/../scripts/build/configure-ssh.sh", "${path.root}/../scripts/build/configure-machine.sh" ] } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "USER_PASSWORD=${var.vm_password}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" pause_before = "30s" scripts = [ "${path.root}/../scripts/build/configure-windows.sh", "${path.root}/../scripts/build/install-powershell.sh", "${path.root}/../scripts/build/install-dotnet.sh", "${path.root}/../scripts/build/install-python.sh", "${path.root}/../scripts/build/install-azcopy.sh", "${path.root}/../scripts/build/install-ruby.sh", "${path.root}/../scripts/build/install-rubygems.sh", "${path.root}/../scripts/build/install-git.sh", "${path.root}/../scripts/build/install-node.sh", "${path.root}/../scripts/build/install-common-utils.sh" ] } provisioner "shell" { environment_vars = ["XCODE_INSTALL_STORAGE_URL=${var.xcode_install_storage_url}", "XCODE_INSTALL_SAS=${var.xcode_install_sas}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Install-Xcode.ps1" } provisioner "shell" { execute_command = "source $HOME/.bash_profile; sudo {{ .Vars }} {{ .Path }}" expect_disconnect = true inline = ["echo 'Reboot VM'", "shutdown -r now"] } provisioner "shell" { environment_vars = ["API_PAT=${var.github_api_pat}", "IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/install-actions-cache.sh", "${path.root}/../scripts/build/install-llvm.sh", "${path.root}/../scripts/build/install-openjdk.sh", "${path.root}/../scripts/build/install-aws-tools.sh", "${path.root}/../scripts/build/install-rust.sh", "${path.root}/../scripts/build/install-gcc.sh", "${path.root}/../scripts/build/install-cocoapods.sh", "${path.root}/../scripts/build/install-android-sdk.sh", "${path.root}/../scripts/build/install-vcpkg.sh", "${path.root}/../scripts/build/install-safari.sh", "${path.root}/../scripts/build/install-chrome.sh", "${path.root}/../scripts/build/install-firefox.sh", "${path.root}/../scripts/build/install-bicep.sh", "${path.root}/../scripts/build/install-codeql-bundle.sh", "${path.root}/../scripts/build/install-edge.sh" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" scripts = [ "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1" ] } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} pwsh -f {{ .Path }}" script = "${path.root}/../scripts/build/Configure-Xcode-Simulators.ps1" } provisioner "shell" { environment_vars = ["IMAGE_FOLDER=${local.image_folder}"] execute_command = "source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" inline = [ "pwsh -File \"${local.image_folder}/software-report/Generate-SoftwareReport.ps1\" -OutputDirectory \"${local.image_folder}/output\" -ImageName ${var.build_id}", "pwsh -File \"${local.image_folder}/tests/RunAll-Tests.ps1\"" ] } provisioner "file" { destination = "${path.root}/../../image-output/macos-26-arm64-Readme.md" direction = "download" source = "${local.image_folder}/output/software-report.md" } provisioner "file" { destination = "${path.root}/../../image-output/software-report.json" direction = "download" source = "${local.image_folder}/output/software-report.json" } provisioner "shell" { execute_command = "chmod +x {{ .Path }}; source $HOME/.bash_profile; {{ .Vars }} {{ .Path }}" scripts = [ "${path.root}/../scripts/build/configure-hostname.sh", "${path.root}/../scripts/build/configure-system.sh" ] } } ================================================ FILE: images/macos/toolsets/Readme.md ================================================ # Toolset JSON structure ## Xcode - `versions` - the array of objects that will present installed Xcode versions - `link` property points to the place where Xcode will be located on image. `/Applications/Xcode_.app` - `version` points to Xcode version that will be downloaded and installed - `symlinks` describes the list of aliases where symlinks will be created to - `sha256` used to check integrity of the Xcode installer file - `install_runtimes` – controls the installation of simulator runtimes: - `default` – installs all default runtimes. - `none` – skips runtime installation. - **Hashtable** – allows manual selection: - Mandatory keys: `[ "iOS", "watchOS", "tvOS" ]`, plus `visionOS` for arm64 images. - Values [array of string]: - `"default"` – installs the default runtime. - `"skip"` – skips installation. - Specific version numbers, e.g., `"18.2"`, `"2.2"`, `"18.3.1"`. - Apple release version, e.g., `"22E5216h"`, `"17A577"`. - `default` - version of Xcode to set as default (should be matched with any `link` in `versions` property) **Example:** `"11.2"` **Note:** - `version` is specified with `+` or `-`, exact Xcode name should be like `16.2.0-Beta.3+16C5023f` or `16.2_Release_Candidate+16C5031c` and will be matching `.xip` file name. - `link` is specified with `_` and doesn't contain spaces, example: `16.2_Release_Candidate` or `16.1`; pattern description: - DOWNLOAD_URL="https://download.developer.apple.com/Developer_Tools/$SOURCE_FILE_LOCATION/$SOURCE_FILE_NAME.$FILE_EXTENSION" - SOURCE_FILE_NAME: "Xcode_$link" - SOURCE_FILE_LOCATION: "Xcode_$link" - FILE_EXTENSION: xip **Example:** String format: ```json "versions": [ { "link": "16_beta_4", "version": "16.0.0-Beta.4+16A5211f", "symlinks": ["16.0"], "install_runtimes": "none", "sha256": "4270cd8021b2f7f512ce91bfc4423b25bccab36cdab21834709d798c8daade72"}, { "link": "15.4", "version": "15.4.0+15F31d", "install_runtimes": "default", "sha256": "82d3d61804ff3f4c7c82085e91dc701037ddaa770e542848b2477e22f4e8aa7a"} ] ``` Block format: ```json "versions": [ { "link": "16.2", "version": "16.2+16C5032a", "sha256": "0e367d06eb7c334ea143bada5e4422f56688aabff571bedf0d2ad9434b7290de", "install_runtimes": [ { "iOS": ["18.0", "18.1", "18.2"] }, { "watchOS": "default" }, { "tvOS": "default" }, { "visionOS": "2.2" } ] }, { "link": "16.1", "version": "16.1+16B40", "sha256": "8ca961d55981f983d21b99a95a6b0ac04905b837f6e11346ee86d28f12afe720", "install_runtimes": "default" } ] ``` ## Android - `platform-list` - the array of android platforms to install. **Example:** `[ "android-29", "android-28", "android-27" ]` - `build-tools` - the array of android build tools to install. **Example:** `[ "29.0.2", "29.0.1", "29.0.0", "28.0.3" ]` - `extras` - the array of android extra items to install. **Example:** `[ "google;google_play_services", "intel;Hardware_Accelerated_Execution_Manager" ]` - `addons` - the array of android add-ons to install. **Example:** `[ "addon-google_apis-google-24", "addon-google_apis-google-23" ]` ## Toolset JSON validation File `Toolset.Tests.ps1` contains PowerShell [Pester](https://github.com/Pester/Pester) tests that validate JSON toolset files. Type `Invoke-Pester` in the current folder in PowerShell to run tests. ================================================ FILE: images/macos/toolsets/toolset-14.json ================================================ { "xcode": { "default": "15.4", "x64": { "versions": [ { "link": "16.2", "filename": "Xcode_16.2", "version": "16.2+16C5032a", "sha256": "0e367d06eb7c334ea143bada5e4422f56688aabff571bedf0d2ad9434b7290de", "install_runtimes": [ { "iOS": "18.2" }, { "watchOS": "default" }, { "tvOS": "default" } ] }, { "link": "16.1", "filename": "Xcode_16.1", "version": "16.1+16B40", "sha256": "8ca961d55981f983d21b99a95a6b0ac04905b837f6e11346ee86d28f12afe720", "install_runtimes": "default" }, { "link": "15.4", "filename": "Xcode_15.4", "version": "15.4.0+15F31d", "sha256": "82d3d61804ff3f4c7c82085e91dc701037ddaa770e542848b2477e22f4e8aa7a", "install_runtimes": "default" }, { "link": "15.3", "filename": "Xcode_15.3", "version": "15.3.0+15E204a", "sha256": "f13f6a2e2df432c3008e394640b8549a18c285acd7fd148d6c4bac8c3a5af234", "install_runtimes": "default" }, { "link": "15.2", "filename": "Xcode_15.2", "version": "15.2.0+15C500b", "sha256": "04E93680C6DDBEC84666531BE412DE778AFC8EAC6AB2037F4C2BE7290818B59B", "install_runtimes": "default" }, { "link": "15.1", "filename": "Xcode_15.1", "version": "15.1.0+15C65", "sha256": "857D8DB537BAC82BF99DE0E1D3895D214D4D02101C1340CEF3DAF6E821BA1D05", "install_runtimes": "default" }, { "link": "15.0.1", "filename": "Xcode_15.0.1", "version": "15.0.1+15A507", "sha256": "5AC17AE6060CAFC3C7112C6DA0B153450BE21F1DE6632777FBA9FBC9D999C9E8", "symlinks": ["15.0"], "install_runtimes": "default" } ] }, "arm64":{ "versions": [ { "link": "16.2", "filename": "Xcode_16.2", "version": "16.2+16C5032a", "sha256": "0e367d06eb7c334ea143bada5e4422f56688aabff571bedf0d2ad9434b7290de", "install_runtimes": [ { "iOS": "18.2" }, { "watchOS": "default" }, { "tvOS": "default" }, { "visionOS": "2.2" } ] }, { "link": "16.1", "filename": "Xcode_16.1", "version": "16.1+16B40", "sha256": "8ca961d55981f983d21b99a95a6b0ac04905b837f6e11346ee86d28f12afe720", "install_runtimes": "default" }, { "link": "15.4", "filename": "Xcode_15.4", "version": "15.4.0+15F31d", "sha256": "82d3d61804ff3f4c7c82085e91dc701037ddaa770e542848b2477e22f4e8aa7a", "install_runtimes": "default" }, { "link": "15.3", "filename": "Xcode_15.3", "version": "15.3.0+15E204a", "sha256": "f13f6a2e2df432c3008e394640b8549a18c285acd7fd148d6c4bac8c3a5af234", "install_runtimes": "default" }, { "link": "15.2", "filename": "Xcode_15.2", "version": "15.2.0+15C500b", "sha256": "04E93680C6DDBEC84666531BE412DE778AFC8EAC6AB2037F4C2BE7290818B59B", "install_runtimes": "default" }, { "link": "15.1", "filename": "Xcode_15.1", "version": "15.1.0+15C65", "sha256": "857D8DB537BAC82BF99DE0E1D3895D214D4D02101C1340CEF3DAF6E821BA1D05", "install_runtimes": "default" }, { "link": "15.0.1", "filename": "Xcode_15.0.1", "version": "15.0.1+15A507", "sha256": "5AC17AE6060CAFC3C7112C6DA0B153450BE21F1DE6632777FBA9FBC9D999C9E8", "symlinks": ["15.0"], "install_runtimes": "default" } ] } }, "java": { "x64": { "default": "21", "versions": [ "8", "11", "17", "21", "25" ] }, "arm64": { "default": "21", "versions": [ "11", "17", "21", "25" ] } }, "android": { "cmdline-tools": "commandlinetools-mac-10406996_latest.zip", "sdk-tools": "sdk-tools-darwin-4333796.zip", "platform_min_version": "34", "build_tools_min_version": "34.0.0", "extras": [ "android;m2repository", "google;m2repository", "google;google_play_services" ], "addons": [], "additional_tools": [ "cmake;3.31.5", "cmake;4.1.2" ], "ndk": { "default": "27", "versions": [ "27", "28", "29" ] } }, "powershellModules": [ { "name": "Az", "versions": [ "14.6.0" ] }, { "name": "Pester" }, { "name": "PSScriptAnalyzer" } ], "brew": { "common_packages": [ "ant", "aria2", "azure-cli", "bazelisk", "carthage", "cmake", "gh", "gnupg", "gnu-tar", "kotlin", "libpq", "libsodium", "p7zip", "packer", "perl", "pkgconf", "swiftformat", "tcl-tk@8", "zstd", "ninja", "gmp", "yq", "unxip", "xcbeautify", "xcodes" ], "cask_packages": [ "parallels" ] }, "gcc": { "versions": [ "13", "14", "15" ] }, "dotnet": { "arch":{ "x64": { "versions": [ "8.0", "9.0", "10.0" ] }, "arm64": { "versions": [ "8.0", "9.0", "10.0" ] } } }, "ruby": { "default": "3.3", "rubygems": [ "cocoapods", "bundler", "fastlane" ] }, "toolcache": [ { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "versions": [ "3.10.*", "3.11.*", "3.12.*", "3.13.*", "3.14.*" ] }, "arm64": { "versions": [ "3.11.*", "3.12.*", "3.13.*", "3.14.*" ] } } }, { "name": "Node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "versions": [ "20.*", "22.*", "24.*" ] }, "arm64": { "versions": [ "20.*", "22.*", "24.*" ] } } }, { "name": "Go", "url" : "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "variable_template" : "GOROOT_{0}_{1}_X64", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ] }, "arm64": { "variable_template" : "GOROOT_{0}_{1}_ARM64", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ] } } }, { "name": "Ruby", "arch": { "x64": { "versions": [ "3.2.*", "3.3.*", "3.4.*", "4.0.*" ] }, "arm64": { "versions": [ "3.2.*", "3.3.*", "3.4.*", "4.0.*" ] } } } ], "node": { "default": "20" }, "llvm": { "version": "15" }, "php": { "version": "8.5" }, "pwsh": { "version": "7.4" }, "mono": { "framework":{ "version": "6.12.0.188", "sha256": "07cdd4e5e72b562892960b7fc73af470db7a4ffc2f68bb834eb3d0a874bbd12c" }, "nunit": { "version": "3.15.4", "sha256": "356dab61433b5be76b76fd0e2e979bda56d164f6d85a900e55c3a4a5fffa28de" } } } ================================================ FILE: images/macos/toolsets/toolset-15.json ================================================ { "xcode": { "default": "16.4", "x64": { "versions": [ { "link": "26.3", "filename": "Xcode_26.3_Universal", "version": "26.3+17C529", "sha256": "cf87232e0419785170edcfa070b750f28808ec00b489ab540c08b7d197c79ae4", "install_runtimes": "none" }, { "link": "26.2", "filename": "Xcode_26.2_Universal", "version": "26.2+17C52", "sha256": "8f29ab6a9ac6670d3cf53545ffdb1c317d11607fa8db38fc56d3391df7783fbd", "install_runtimes": [ { "iOS": ["26.2"] }, { "watchOS": ["default"] }, { "tvOS": ["default"] } ] }, { "link": "26.1.1", "filename": "Xcode_26.1.1_Universal", "version": "26.1.1+17B100", "symlinks": ["26.1"], "sha256": "ed55d55fa28455c11a65e0809ba8fdf7d83fdeb268aabf9af7fcc1ee911543eb", "install_runtimes": "default" }, { "link": "26.0.1", "filename": "Xcode_26.0.1_Universal", "version": "26.0.1+17A400", "symlinks": ["26.0"], "sha256": "9881c457068c86ac91e94cca2d7116dfd01cb7179c22b0863b63c7f3bb7e7695", "install_runtimes": [ { "iOS": ["default"] }, { "watchOS": ["skip"] }, { "tvOS": ["skip"] } ] }, { "link": "16.4", "filename": "Xcode_16.4", "version": "16.4.0+16F6", "sha256": "2dbf65ba28fb85b34e72c14c529a42d5c3189ab0f11fb29fdebd5f4ee6c87900", "install_runtimes": [ { "iOS": ["18.5", "18.6"] }, { "watchOS": ["11.5"] }, { "tvOS": ["18.5"] } ] }, { "link": "16.3", "filename": "Xcode_16.3", "version": "16.3+16E140", "sha256": "c593177b73e45f31e1cf7ced131760d8aa8e1532f5bbf8ba11a4ded01da14fbb", "install_runtimes": "none" }, { "link": "16.2", "filename": "Xcode_16.2", "version": "16.2+16C5032a", "sha256": "0e367d06eb7c334ea143bada5e4422f56688aabff571bedf0d2ad9434b7290de", "install_runtimes": "none" }, { "link": "16.1", "filename": "Xcode_16.1", "version": "16.1+16B40", "sha256": "8ca961d55981f983d21b99a95a6b0ac04905b837f6e11346ee86d28f12afe720", "install_runtimes": "none" }, { "link": "16", "filename": "Xcode_16", "version": "16.0.0+16A242d", "sha256": "4a26c3d102a55c7222fb145e0ee1503249c9c26c6e02dc64d783c8810b37b1e3", "symlinks": ["16.0"], "install_runtimes": "none" } ] }, "arm64":{ "versions": [ { "link": "26.3", "filename": "Xcode_26.3_Universal", "version": "26.3+17C529", "sha256": "cf87232e0419785170edcfa070b750f28808ec00b489ab540c08b7d197c79ae4", "install_runtimes": "none" }, { "link": "26.2", "filename": "Xcode_26.2_Universal", "version": "26.2+17C52", "sha256": "8f29ab6a9ac6670d3cf53545ffdb1c317d11607fa8db38fc56d3391df7783fbd", "install_runtimes": [ { "iOS": ["26.2"] }, { "watchOS": ["default"] }, { "tvOS": ["default"] }, { "visionOS": ["default"] } ] }, { "link": "26.1.1", "filename": "Xcode_26.1.1_Universal", "version": "26.1.1+17B100", "symlinks": ["26.1"], "sha256": "ed55d55fa28455c11a65e0809ba8fdf7d83fdeb268aabf9af7fcc1ee911543eb", "install_runtimes": "default" }, { "link": "26.0.1", "filename": "Xcode_26.0.1_Universal", "version": "26.0.1+17A400", "symlinks": ["26.0"], "sha256": "9881c457068c86ac91e94cca2d7116dfd01cb7179c22b0863b63c7f3bb7e7695", "install_runtimes": [ { "iOS": ["default"] }, { "watchOS": ["skip"] }, { "tvOS": ["skip"] }, { "visionOS": ["skip"] } ] }, { "link": "16.4", "filename": "Xcode_16.4", "version": "16.4.0+16F6", "sha256": "2dbf65ba28fb85b34e72c14c529a42d5c3189ab0f11fb29fdebd5f4ee6c87900", "install_runtimes": [ { "iOS": ["18.5", "18.6"] }, { "watchOS": ["11.5"] }, { "tvOS": ["18.5"] }, { "visionOS": ["2.3", "2.4", "2.5"] } ] }, { "link": "16.3", "filename": "Xcode_16.3", "version": "16.3+16E140", "sha256": "c593177b73e45f31e1cf7ced131760d8aa8e1532f5bbf8ba11a4ded01da14fbb", "install_runtimes": "none" }, { "link": "16.2", "filename": "Xcode_16.2", "version": "16.2+16C5032a", "sha256": "0e367d06eb7c334ea143bada5e4422f56688aabff571bedf0d2ad9434b7290de", "install_runtimes": "none" }, { "link": "16.1", "filename": "Xcode_16.1", "version": "16.1+16B40", "sha256": "8ca961d55981f983d21b99a95a6b0ac04905b837f6e11346ee86d28f12afe720", "install_runtimes": "none" }, { "link": "16", "filename": "Xcode_16", "version": "16.0.0+16A242d", "sha256": "4a26c3d102a55c7222fb145e0ee1503249c9c26c6e02dc64d783c8810b37b1e3", "symlinks": ["16.0"], "install_runtimes": "none" } ] } }, "java": { "x64": { "default": "21", "versions": [ "11", "17", "21", "25" ] }, "arm64": { "default": "21", "versions": [ "11", "17", "21", "25" ] } }, "android": { "cmdline-tools": "commandlinetools-mac-12266719_latest.zip", "sdk-tools": "sdk-tools-darwin-4333796.zip", "platform_min_version": "34", "build_tools_min_version": "35.0.0", "extras": [ "android;m2repository", "google;m2repository", "google;google_play_services" ], "addons": [], "additional_tools": [ "cmake;3.31.5", "cmake;4.1.2" ], "ndk": { "default": "27", "versions": [ "27", "28", "29" ] } }, "powershellModules": [ { "name": "Az", "versions": [ "14.6.0" ] }, { "name": "Pester" }, { "name": "PSScriptAnalyzer" } ], "brew": { "common_packages": [ "ant", "aria2", "azure-cli", "bazelisk", "carthage", "cmake", "gh", "gnupg", "gnu-tar", "kotlin", "libpq", "libsodium", "p7zip", "packer", "perl", "pkgconf", "swiftformat", "tcl-tk@8", "zstd", "ninja", "gmp", "yq", "unxip", "xcbeautify", "xcodes" ], "cask_packages": [ "parallels" ] }, "gcc": { "versions": [ "13", "14", "15" ] }, "dotnet": { "arch":{ "x64": { "versions": [ "8.0", "9.0", "10.0" ] }, "arm64": { "versions": [ "8.0", "9.0", "10.0" ] } } }, "ruby": { "default": "3.3", "rubygems": [ "cocoapods", "bundler", "fastlane" ] }, "toolcache": [ { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "versions": [ "3.10.*", "3.11.*", "3.12.*", "3.13.*", "3.14.*" ] }, "arm64": { "versions": [ "3.11.*", "3.12.*", "3.13.*", "3.14.*" ] } } }, { "name": "Node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "versions": [ "20.*", "22.*", "24.*" ] }, "arm64": { "versions": [ "20.*", "22.*", "24.*" ] } } }, { "name": "Go", "url" : "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "variable_template" : "GOROOT_{0}_{1}_X64", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ] }, "arm64": { "variable_template" : "GOROOT_{0}_{1}_ARM64", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ] } } }, { "name": "Ruby", "arch": { "x64": { "versions": [ "3.2.*", "3.3.*", "3.4.*", "4.0.*" ] }, "arm64": { "versions": [ "3.2.*", "3.3.*", "3.4.*", "4.0.*" ] } } } ], "node": { "default": "22" }, "llvm": { "version": "18" }, "php": { "version": "8.5" }, "pwsh": { "version": "7.4" } } ================================================ FILE: images/macos/toolsets/toolset-26.json ================================================ { "xcode": { "default": "26.2", "x64": { "versions": [ { "link": "26.4_beta_3", "filename": "Xcode_26.4_beta_3_Universal", "version": "26.4+17E5179g", "symlinks": ["26.4"], "sha256": "dc55afeb7cdbfed3547996ce273058b46a8922c78005ab371da7f4bcdddfa53a", "install_runtimes": "none" }, { "link": "26.3", "filename": "Xcode_26.3_Universal", "version": "26.3+17C529", "sha256": "cf87232e0419785170edcfa070b750f28808ec00b489ab540c08b7d197c79ae4", "install_runtimes": "none" }, { "link": "26.2", "filename": "Xcode_26.2_Universal", "version": "26.2+17C52", "sha256": "8f29ab6a9ac6670d3cf53545ffdb1c317d11607fa8db38fc56d3391df7783fbd", "install_runtimes": [ { "iOS": ["26.2"] }, { "watchOS": ["default"] }, { "tvOS": ["default"] } ] }, { "link": "26.1.1", "filename": "Xcode_26.1.1_Universal", "version": "26.1.1+17B100", "symlinks": ["26.1"], "sha256": "ed55d55fa28455c11a65e0809ba8fdf7d83fdeb268aabf9af7fcc1ee911543eb", "install_runtimes": "default" }, { "link": "26.0.1", "filename": "Xcode_26.0.1_Universal", "version": "26.0.1+17A400", "symlinks": ["26.0"], "sha256": "9881c457068c86ac91e94cca2d7116dfd01cb7179c22b0863b63c7f3bb7e7695", "install_runtimes": "default" } ] }, "arm64": { "versions": [ { "link": "26.4_beta_3", "filename": "Xcode_26.4_beta_3_Universal", "version": "26.4+17E5179g", "symlinks": ["26.4"], "sha256": "dc55afeb7cdbfed3547996ce273058b46a8922c78005ab371da7f4bcdddfa53a", "install_runtimes": "none" }, { "link": "26.3", "filename": "Xcode_26.3_Universal", "version": "26.3+17C529", "sha256": "cf87232e0419785170edcfa070b750f28808ec00b489ab540c08b7d197c79ae4", "install_runtimes": "none" }, { "link": "26.2", "filename": "Xcode_26.2_Universal", "version": "26.2+17C52", "sha256": "8f29ab6a9ac6670d3cf53545ffdb1c317d11607fa8db38fc56d3391df7783fbd", "install_runtimes": [ { "iOS": ["26.2"] }, { "watchOS": ["default"] }, { "tvOS": ["default"] }, { "visionOS": ["default"] } ] }, { "link": "26.1.1", "filename": "Xcode_26.1.1_Universal", "version": "26.1.1+17B100", "symlinks": ["26.1"], "sha256": "ed55d55fa28455c11a65e0809ba8fdf7d83fdeb268aabf9af7fcc1ee911543eb", "install_runtimes": "default" }, { "link": "26.0.1", "filename": "Xcode_26.0.1_Universal", "version": "26.0.1+17A400", "symlinks": ["26.0"], "sha256": "9881c457068c86ac91e94cca2d7116dfd01cb7179c22b0863b63c7f3bb7e7695", "install_runtimes": "default" } ] } }, "java": { "x64": { "default": "21", "versions": [ "11", "17", "21", "25" ] }, "arm64": { "default": "21", "versions": [ "11", "17", "21", "25" ] } }, "android": { "cmdline-tools": "commandlinetools-mac-12266719_latest.zip", "sdk-tools": "sdk-tools-darwin-4333796.zip", "platform_min_version": "35", "build_tools_min_version": "35.0.0", "extras": [ "android;m2repository", "google;m2repository", "google;google_play_services" ], "addons": [], "additional_tools": [ "cmake;3.31.5", "cmake;4.1.2" ], "ndk": { "default": "27", "versions": [ "27","28", "29" ] } }, "powershellModules": [ { "name": "Az", "versions": [ "14.6.0" ] }, { "name": "Pester" }, { "name": "PSScriptAnalyzer" } ], "brew": { "common_packages": [ "ant", "aria2", "azure-cli", "bazelisk", "carthage", "cmake", "gh", "gnupg", "gnu-tar", "kotlin", "libpq", "libsodium", "openssl", "p7zip", "packer", "perl", "pkgconf", "swiftformat", "tcl-tk@8", "zstd", "ninja", "gmp", "yq", "unxip", "xcbeautify", "xcodes" ], "cask_packages": [] }, "gcc": { "versions": [ "13", "14", "15" ] }, "dotnet": { "arch":{ "x64": { "versions": [ "8.0", "9.0", "10.0" ] }, "arm64": { "versions": [ "8.0", "9.0", "10.0" ] } } }, "ruby": { "default": "3.4", "rubygems": [ "cocoapods", "bundler", "fastlane" ] }, "toolcache": [ { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "versions": [ "3.11.*", "3.12.*", "3.13.*", "3.14.*" ] }, "arm64": { "versions": [ "3.11.*", "3.12.*", "3.13.*", "3.14.*" ] } } }, { "name": "Node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "versions": [ "20.*", "22.*", "24.*" ] }, "arm64": { "versions": [ "20.*", "22.*", "24.*" ] } } }, { "name": "Go", "url" : "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json", "platform" : "darwin", "arch": { "x64": { "variable_template" : "GOROOT_{0}_{1}_X64", "versions": [ "1.23.*", "1.24.*", "1.25.*" ] }, "arm64": { "variable_template" : "GOROOT_{0}_{1}_ARM64", "versions": [ "1.23.*", "1.24.*", "1.25.*" ] } } }, { "name": "Ruby", "arch": { "x64": { "versions": [ "3.2.*", "3.3.*", "3.4.*", "4.0.*" ] }, "arm64": { "versions": [ "3.2.*", "3.3.*", "3.4.*", "4.0.*" ] } } } ], "node": { "default": "24" }, "llvm": { "version": "20" }, "php": { "version": "8.5" }, "pwsh": { "version": "7.4" } } ================================================ FILE: images/ubuntu/Ubuntu2204-Readme.md ================================================ | Announcements | |-| | [[Windows/Ubuntu] Docker Server and Client will be updated to version 29.1.*, Docker Compose will be updated to version 2.40.3 on February 9th, 2026](https://github.com/actions/runner-images/issues/13474) | *** # Ubuntu 22.04 - OS Version: 22.04.5 LTS - Kernel Version: 6.8.0-1044-azure - Image Version: 20260309.57.1 - Systemd version: 249.11-0ubuntu3.17 ## Installed Software ### Language and Runtime - Bash 5.1.16(1)-release - Clang: 13.0.1, 14.0.0, 15.0.7 - Clang-format: 13.0.1, 14.0.0, 15.0.7 - Clang-tidy: 13.0.1, 14.0.0, 15.0.7 - Dash 0.5.11+git20210903+057cd650a4ed-3build1 - GNU C++: 10.5.0, 11.4.0, 12.3.0 - GNU Fortran: 10.5.0, 11.4.0, 12.3.0 - Julia 1.12.5 - Kotlin 2.3.10-release-465 - Mono 6.12.0.200 - MSBuild 16.10.1.31701 (Mono 6.12.0.200) - Node.js 20.20.1 - Perl 5.34.0 - Python 3.10.12 - Ruby 3.0.2p107 - Swift 6.2.4 ### Package Management - cpan 1.64 - Helm 3.20.0 - Homebrew 5.0.16 - Miniconda 26.1.1 - Npm 10.8.2 - NuGet 6.6.1.2 - Pip 22.0.2 - Pip3 22.0.2 - Pipx 1.8.0 - RubyGems 3.3.5 - Vcpkg (build from commit 751fdf7bbc) - Yarn 1.22.22 #### Environment variables | Name | Value | | ----------------------- | ---------------------- | | CONDA | /usr/share/miniconda | | VCPKG_INSTALLATION_ROOT | /usr/local/share/vcpkg | #### Homebrew note ``` Location: /home/linuxbrew Note: Homebrew is pre-installed on image but not added to PATH. run the eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" command to accomplish this. ``` ### Project Management - Ant 1.10.12 - Gradle 9.4.0 - Lerna 9.0.5 - Maven 3.9.13 - Sbt 1.12.5 ### Tools - Ansible 2.17.14 - apt-fast 1.10.0 - AzCopy 10.32.1 - available by `azcopy` and `azcopy10` aliases - Bazel 9.0.0 - Bazelisk 1.28.1 - Bicep 0.41.2 - Buildah 1.23.1 - CMake 3.31.6 - CodeQL Action Bundle 2.24.3 - Docker Amazon ECR Credential Helper 0.12.0 - Docker Compose v2 2.38.2 - Docker-Buildx 0.32.1 - Docker Client 28.0.4 - Docker Server 28.0.4 - Fastlane 2.232.2 - Git 2.53.0 - Git LFS 3.7.1 - Git-ftp 1.6.0 - Haveged 1.9.14 - Heroku 10.17.0 - jq 1.6 - Kind 0.31.0 - Kubectl 1.35.2 - Kustomize 5.8.1 - Leiningen 2.12.0 - MediaInfo 21.09 - Mercurial 6.1.1 - Minikube 1.38.1 - n 10.2.0 - Newman 6.2.2 - nvm 0.40.4 - OpenSSL 3.0.2-0ubuntu1.21 - Packer 1.15.0 - Parcel 2.16.4 - Podman 3.4.4 - Pulumi 3.225.1 - R 4.5.2 - Skopeo 1.4.1 - Sphinx Open Source Search Server 2.2.11 - SVN 1.14.1 - Terraform 1.14.6 - yamllint 1.38.0 - yq 4.52.4 - zstd 1.5.7 - Ninja 1.13.2 ### CLI Tools - Alibaba Cloud CLI 3.2.12 - AWS CLI 2.34.5 - AWS CLI Session Manager Plugin 1.2.779.0 - AWS SAM CLI 1.155.2 - Azure CLI 2.84.0 - Azure CLI (azure-devops) 1.0.2 - GitHub CLI 2.87.3 - Google Cloud CLI 559.0.0 - Netlify CLI 24.0.1 - OpenShift CLI 4.21.4 - ORAS CLI 1.3.0 - Vercel CLI 50.29.0 ### Java | Version | Environment Variable | | ------------------- | -------------------- | | 8.0.482+8 | JAVA_HOME_8_X64 | | 11.0.30+7 (default) | JAVA_HOME_11_X64 | | 17.0.18+8 | JAVA_HOME_17_X64 | | 21.0.10+7 | JAVA_HOME_21_X64 | | 25.0.2+10 | JAVA_HOME_25_X64 | ### PHP Tools - PHP: 8.1.2 - Composer 2.9.5 - PHPUnit 8.5.52 ``` Both Xdebug and PCOV extensions are installed, but only Xdebug is enabled. ``` ### Haskell Tools - Cabal 3.16.1.0 - GHC 9.14.1 - GHCup 0.1.50.2 - Stack 3.9.3 ### Rust Tools - Cargo 1.94.0 - Rust 1.94.0 - Rustdoc 1.94.0 - Rustup 1.28.2 #### Packages - Bindgen 0.72.1 - Cargo audit 0.22.1 - Cargo clippy 0.1.94 - Cargo outdated 0.17.0 - Cbindgen 0.29.2 - Rustfmt 1.8.0 ### Browsers and Drivers - Google Chrome 145.0.7632.159 - ChromeDriver 145.0.7632.117 - Chromium 145.0.7632.0 - Microsoft Edge 145.0.3800.97 - Microsoft Edge WebDriver 145.0.3800.97 - Selenium server 4.41.0 - Mozilla Firefox 148.0 - Geckodriver 0.36.0 #### Environment variables | Name | Value | | ----------------- | ------------------------------------- | | CHROMEWEBDRIVER | /usr/local/share/chromedriver-linux64 | | EDGEWEBDRIVER | /usr/local/share/edge_driver | | GECKOWEBDRIVER | /usr/local/share/gecko_driver | | SELENIUM_JAR_PATH | /usr/share/java/selenium-server.jar | ### .NET Tools - .NET Core SDK: 8.0.124, 8.0.206, 8.0.319, 8.0.418, 9.0.114, 9.0.205, 9.0.311, 10.0.102 - nbgv 3.9.50+6feeb89450 ### Databases - sqlite3 3.37.2 #### PostgreSQL - PostgreSQL 14.22 ``` User: postgres PostgreSQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start postgresql.service' ``` #### MySQL - MySQL 8.0.45-0ubuntu0.22.04.1 ``` User: root Password: root MySQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service' ``` #### MS SQL - sqlcmd 17.10.0001.1 - SqlPackage 170.3.93.6 ### Cached Tools #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.8 #### Node.js - 20.20.1 - 22.22.1 - 24.14.0 #### Python - 3.10.20 - 3.11.15 - 3.12.13 - 3.13.12 - 3.14.3 #### PyPy - 3.7.13 [PyPy 7.3.9] - 3.8.16 [PyPy 7.3.11] - 3.9.19 [PyPy 7.3.16] - 3.10.16 [PyPy 7.3.19] - 3.11.13 [PyPy 7.3.20] #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 ### PowerShell Tools - PowerShell 7.4.13 #### PowerShell Modules - Az: 14.6.0 - MarkdownPS: 1.10 - Microsoft.Graph: 2.35.1 - Pester: 5.7.1 - PSScriptAnalyzer: 1.24.0 ### Web Servers | Name | Version | ConfigFile | ServiceStatus | ListenPort | | ------- | ------- | ------------------------- | ------------- | ---------- | | apache2 | 2.4.52 | /etc/apache2/apache2.conf | inactive | 80 | | nginx | 1.18.0 | /etc/nginx/nginx.conf | inactive | 80 | ### Android | Package Name | Version | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 9.0 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1
34.0.0 | | Android SDK Platform-Tools | 37.0.0 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3) | | Android Support Repository | 47.0.0 | | CMake | 3.18.1
3.22.1
3.31.5 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724 (default)
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | -------------------------------------------- | | ANDROID_HOME | /usr/local/lib/android/sdk | | ANDROID_NDK | /usr/local/lib/android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_HOME | /usr/local/lib/android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_LATEST_HOME | /usr/local/lib/android/sdk/ndk/29.0.14206865 | | ANDROID_NDK_ROOT | /usr/local/lib/android/sdk/ndk/27.3.13750724 | | ANDROID_SDK_ROOT | /usr/local/lib/android/sdk | ### Installed apt packages | Name | Version | | ---------------------- | ----------------------------------- | | acl | 2.3.1-1 | | aria2 | 1.36.0-1 | | autoconf | 2.71-2 | | automake | 1:1.16.5-1.3 | | binutils | 2.38-4ubuntu2.12 | | bison | 2:3.8.2+dfsg-1build1 | | brotli | 1.0.9-2build6 | | bzip2 | 1.0.8-5build1 | | coreutils | 8.32-4.1ubuntu1.2 | | curl | 7.81.0-1ubuntu1.22 | | dbus | 1.12.20-2ubuntu4.1 | | dnsutils | 1:9.18.39-0ubuntu0.22.04.2 | | dpkg | 1.21.1ubuntu2.6 | | dpkg-dev | 1.21.1ubuntu2.6 | | fakeroot | 1.28-1ubuntu1 | | file | 1:5.41-3ubuntu0.1 | | findutils | 4.8.0-1ubuntu3 | | flex | 2.6.4-8build2 | | fonts-noto-color-emoji | 2.047-0ubuntu0.22.04.1 | | ftp | 20210827-4build1 | | g++ | 4:11.2.0-1ubuntu1 | | gcc | 4:11.2.0-1ubuntu1 | | gnupg2 | 2.2.27-3ubuntu2.5 | | haveged | 1.9.14-1ubuntu1 | | imagemagick | 8:6.9.11.60+dfsg-1.3ubuntu0.22.04.5 | | iproute2 | 5.15.0-1ubuntu2 | | iputils-ping | 3:20211215-1ubuntu0.1 | | jq | 1.6-2.1ubuntu3.1 | | lib32z1 | 1:1.2.11.dfsg-2ubuntu9.2 | | libc++-dev | 1:14.0-55\~exp2 | | libc++abi-dev | 1:14.0-55\~exp2 | | libc6-dev | 2.35-0ubuntu3.13 | | libcurl4 | 7.81.0-1ubuntu1.22 | | libgbm-dev | 23.2.1-1ubuntu3.1\~22.04.3 | | libgconf-2-4 | 3.2.6-7ubuntu2 | | libgsl-dev | 2.7.1+dfsg-3 | | libgtk-3-0 | 3.24.33-1ubuntu2.2 | | libmagic-dev | 1:5.41-3ubuntu0.1 | | libmagickcore-dev | 8:6.9.11.60+dfsg-1.3ubuntu0.22.04.5 | | libmagickwand-dev | 8:6.9.11.60+dfsg-1.3ubuntu0.22.04.5 | | libnss3-tools | 2:3.98-0ubuntu0.22.04.3 | | libsecret-1-dev | 0.20.5-2 | | libsqlite3-dev | 3.37.2-2ubuntu0.5 | | libssl-dev | 3.0.2-0ubuntu1.21 | | libtool | 2.4.6-15build2 | | libunwind8 | 1.3.2-2build2.1 | | libxkbfile-dev | 1:1.1.0-1build3 | | libxss1 | 1:1.2.3-1build2 | | libyaml-dev | 0.2.2-1build2 | | locales | 2.35-0ubuntu3.13 | | lz4 | 1.9.3-2build2 | | m4 | 1.4.18-5ubuntu2 | | make | 4.3-4.1build1 | | mediainfo | 22.03-1 | | mercurial | 6.1.1-1ubuntu1 | | net-tools | 1.60+git20181103.0eebece-1ubuntu5.4 | | netcat | 1.218-4ubuntu1 | | openssh-client | 1:8.9p1-3ubuntu0.13 | | p7zip-full | 16.02+dfsg-8 | | p7zip-rar | 16.02-3build1 | | parallel | 20210822+ds-2 | | pass | 1.7.4-5 | | patchelf | 0.14.3-1 | | pigz | 2.6-1 | | pkg-config | 0.29.2-1ubuntu3 | | pollinate | 4.33-3ubuntu2.1 | | python-is-python3 | 3.9.2-2 | | rpm | 4.17.0+dfsg1-4build1 | | rsync | 3.2.7-0ubuntu0.22.04.4 | | shellcheck | 0.8.0-2 | | sphinxsearch | 2.2.11-8 | | sqlite3 | 3.37.2-2ubuntu0.5 | | ssh | 1:8.9p1-3ubuntu0.13 | | sshpass | 1.09-1 | | subversion | 1.14.1-3ubuntu0.22.04.1 | | sudo | 1.9.9-1ubuntu2.5 | | swig | 4.0.2-1ubuntu1 | | systemd-coredump | 249.11-0ubuntu3.17 | | tar | 1.34+dfsg-1ubuntu0.1.22.04.2 | | telnet | 0.17-44build1 | | texinfo | 6.8-4build1 | | time | 1.9-0.1build2 | | tk | 8.6.11+1build2 | | tzdata | 2025b-0ubuntu0.22.04.1 | | unzip | 6.0-26ubuntu3.2 | | upx | 3.96-3 | | wget | 1.21.2-2ubuntu1.1 | | xorriso | 1.5.4-2 | | xvfb | 2:21.1.4-2ubuntu1.7\~22.04.16 | | xz-utils | 5.2.5-2ubuntu1 | | zip | 3.0-12build2 | | zsync | 0.6.2-3ubuntu1 | ================================================ FILE: images/ubuntu/Ubuntu2404-Readme.md ================================================ | Announcements | |-| | [[Windows/Ubuntu] Docker Server and Client will be updated to version 29.1.*, Docker Compose will be updated to version 2.40.3 on February 9th, 2026](https://github.com/actions/runner-images/issues/13474) | *** # Ubuntu 24.04 - OS Version: 24.04.3 LTS - Kernel Version: 6.14.0-1017-azure - Image Version: 20260309.50.1 - Systemd version: 255.4-1ubuntu8.12 ## Installed Software ### Language and Runtime - Bash 5.2.21(1)-release - Clang: 16.0.6, 17.0.6, 18.1.3 - Clang-format: 16.0.6, 17.0.6, 18.1.3 - Clang-tidy: 16.0.6, 17.0.6, 18.1.3 - Dash 0.5.12-6ubuntu5 - GNU C++: 12.4.0, 13.3.0, 14.2.0 - GNU Fortran: 12.4.0, 13.3.0, 14.2.0 - Julia 1.12.5 - Kotlin 2.3.10-release-465 - Node.js 20.20.1 - Perl 5.38.2 - Python 3.12.3 - Ruby 3.2.3 - Swift 6.2.4 ### Package Management - cpan 1.64 - Helm 3.20.0 - Homebrew 5.0.16 - Miniconda 26.1.1 - Npm 10.8.2 - Pip 24.0 - Pip3 24.0 - Pipx 1.8.0 - RubyGems 3.4.20 - Vcpkg (build from commit 751fdf7bbc) - Yarn 1.22.22 #### Environment variables | Name | Value | | ----------------------- | ---------------------- | | CONDA | /usr/share/miniconda | | VCPKG_INSTALLATION_ROOT | /usr/local/share/vcpkg | #### Homebrew note ``` Location: /home/linuxbrew Note: Homebrew is pre-installed on image but not added to PATH. run the eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" command to accomplish this. ``` ### Project Management - Ant 1.10.14 - Gradle 9.4.0 - Lerna 9.0.5 - Maven 3.9.13 ### Tools - Ansible 2.20.3 - AzCopy 10.32.1 - available by `azcopy` and `azcopy10` aliases - Bazel 9.0.0 - Bazelisk 1.28.1 - Bicep 0.41.2 - Buildah 1.33.7 - CMake 3.31.6 - CodeQL Action Bundle 2.24.3 - Docker Amazon ECR Credential Helper 0.12.0 - Docker Compose v2 2.38.2 - Docker-Buildx 0.32.1 - Docker Client 28.0.4 - Docker Server 28.0.4 - Fastlane 2.232.2 - Git 2.53.0 - Git LFS 3.7.1 - Git-ftp 1.6.0 - Haveged 1.9.14 - jq 1.7 - Kind 0.31.0 - Kubectl 1.35.2 - Kustomize 5.8.1 - MediaInfo 24.01 - Mercurial 6.7.2 - Minikube 1.38.1 - n 10.2.0 - Newman 6.2.2 - nvm 0.40.4 - OpenSSL 3.0.13-0ubuntu3.7 - Packer 1.15.0 - Parcel 2.16.4 - Podman 4.9.3 - Pulumi 3.225.1 - Skopeo 1.13.3 - Sphinx Open Source Search Server 2.2.11 - yamllint 1.38.0 - yq 4.52.4 - zstd 1.5.7 - Ninja 1.13.2 ### CLI Tools - AWS CLI 2.34.5 - AWS CLI Session Manager Plugin 1.2.779.0 - AWS SAM CLI 1.155.2 - Azure CLI 2.84.0 - Azure CLI (azure-devops) 1.0.2 - GitHub CLI 2.87.3 - Google Cloud CLI 559.0.0 ### Java | Version | Environment Variable | | ------------------- | -------------------- | | 8.0.482+8 | JAVA_HOME_8_X64 | | 11.0.30+7 | JAVA_HOME_11_X64 | | 17.0.18+8 (default) | JAVA_HOME_17_X64 | | 21.0.10+7 | JAVA_HOME_21_X64 | | 25.0.2+10 | JAVA_HOME_25_X64 | ### PHP Tools - PHP: 8.3.6 - Composer 2.9.5 - PHPUnit 8.5.52 ``` Both Xdebug and PCOV extensions are installed, but only Xdebug is enabled. ``` ### Haskell Tools - Cabal 3.16.1.0 - GHC 9.14.1 - GHCup 0.1.50.2 - Stack 3.9.3 ### Rust Tools - Cargo 1.94.0 - Rust 1.94.0 - Rustdoc 1.94.0 - Rustup 1.28.2 #### Packages - Rustfmt 1.8.0 ### Browsers and Drivers - Google Chrome 145.0.7632.159 - ChromeDriver 145.0.7632.117 - Chromium 145.0.7632.0 - Microsoft Edge 145.0.3800.97 - Microsoft Edge WebDriver 145.0.3800.97 - Selenium server 4.41.0 - Mozilla Firefox 148.0 - Geckodriver 0.36.0 #### Environment variables | Name | Value | | ----------------- | ------------------------------------- | | CHROMEWEBDRIVER | /usr/local/share/chromedriver-linux64 | | EDGEWEBDRIVER | /usr/local/share/edge_driver | | GECKOWEBDRIVER | /usr/local/share/gecko_driver | | SELENIUM_JAR_PATH | /usr/share/java/selenium-server.jar | ### .NET Tools - .NET Core SDK: 8.0.124, 8.0.206, 8.0.319, 8.0.418, 9.0.114, 9.0.205, 9.0.311, 10.0.102 - nbgv 3.9.50+6feeb89450 ### Databases - sqlite3 3.45.1 #### PostgreSQL - PostgreSQL 16.13 ``` User: postgres PostgreSQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start postgresql.service' ``` #### MySQL - MySQL 8.0.45-0ubuntu0.24.04.1 ``` User: root Password: root MySQL service is disabled by default. Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service' ``` ### Cached Tools #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.8 #### Node.js - 20.20.1 - 22.22.1 - 24.14.0 #### Python - 3.10.20 - 3.11.15 - 3.12.13 - 3.13.12 - 3.14.3 #### PyPy - 3.9.19 [PyPy 7.3.16] - 3.10.16 [PyPy 7.3.19] - 3.11.13 [PyPy 7.3.20] #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 ### PowerShell Tools - PowerShell 7.4.13 #### PowerShell Modules - Az: 14.6.0 - Microsoft.Graph: 2.35.1 - Pester: 5.7.1 - PSScriptAnalyzer: 1.24.0 ### Web Servers | Name | Version | ConfigFile | ServiceStatus | ListenPort | | ------- | ------- | ------------------------- | ------------- | ---------- | | apache2 | 2.4.58 | /etc/apache2/apache2.conf | inactive | 80 | | nginx | 1.24.0 | /etc/nginx/nginx.conf | inactive | 80 | ### Android | Package Name | Version | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 12.0 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1
34.0.0 | | Android SDK Platform-Tools | 37.0.0 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3) | | Android Support Repository | 47.0.0 | | CMake | 3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724 (default)
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | -------------------------------------------- | | ANDROID_HOME | /usr/local/lib/android/sdk | | ANDROID_NDK | /usr/local/lib/android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_HOME | /usr/local/lib/android/sdk/ndk/27.3.13750724 | | ANDROID_NDK_LATEST_HOME | /usr/local/lib/android/sdk/ndk/29.0.14206865 | | ANDROID_NDK_ROOT | /usr/local/lib/android/sdk/ndk/27.3.13750724 | | ANDROID_SDK_ROOT | /usr/local/lib/android/sdk | ### Installed apt packages | Name | Version | | ---------------------- | ---------------------------- | | acl | 2.3.2-1build1.1 | | aria2 | 1.37.0+debian-1build3 | | autoconf | 2.71-3 | | automake | 1:1.16.5-1.3ubuntu1 | | binutils | 2.42-4ubuntu2.8 | | bison | 2:3.8.2+dfsg-1build2 | | brotli | 1.1.0-2build2 | | bzip2 | 1.0.8-5.1build0.1 | | coreutils | 9.4-3ubuntu6.1 | | curl | 8.5.0-2ubuntu10.7 | | dbus | 1.14.10-4ubuntu4.1 | | dnsutils | 1:9.18.39-0ubuntu0.24.04.2 | | dpkg | 1.22.6ubuntu6.5 | | dpkg-dev | 1.22.6ubuntu6.5 | | fakeroot | 1.33-1 | | file | 1:5.45-3build1 | | findutils | 4.9.0-5build1 | | flex | 2.6.4-8.2build1 | | fonts-noto-color-emoji | 2.047-0ubuntu0.24.04.1 | | ftp | 20230507-2build3 | | g++ | 4:13.2.0-7ubuntu1 | | gcc | 4:13.2.0-7ubuntu1 | | gnupg2 | 2.4.4-2ubuntu17.4 | | haveged | 1.9.14-1ubuntu2 | | iproute2 | 6.1.0-1ubuntu6.2 | | iputils-ping | 3:20240117-1ubuntu0.1 | | jq | 1.7.1-3ubuntu0.24.04.1 | | libnss3-tools | 2:3.98-1ubuntu0.1 | | libsqlite3-dev | 3.45.1-1ubuntu2.5 | | libssl-dev | 3.0.13-0ubuntu3.7 | | libtool | 2.4.7-7build1 | | libyaml-dev | 0.2.5-1build1 | | locales | 2.39-0ubuntu8.7 | | lz4 | 1.9.4-1build1.1 | | m4 | 1.4.19-4build1 | | make | 4.3-4.1build2 | | mediainfo | 24.01.1-1build2 | | mercurial | 6.7.2-1ubuntu2.2 | | net-tools | 2.10-0.1ubuntu4.4 | | netcat | 1.226-1ubuntu2 | | openssh-client | 1:9.6p1-3ubuntu13.14 | | p7zip-full | 16.02+transitional.1 | | p7zip-rar | 16.02+transitional.1 | | parallel | 20231122+ds-1 | | patchelf | 0.18.0-1.1build1 | | pigz | 2.8-1 | | pkg-config | 1.8.1-2build1 | | pollinate | 4.33-3.1ubuntu1.1 | | python-is-python3 | 3.11.4-1 | | rpm | 4.18.2+dfsg-2.1build2 | | rsync | 3.2.7-1ubuntu1.2 | | shellcheck | 0.9.0-1 | | sphinxsearch | 2.2.11-8build1 | | sqlite3 | 3.45.1-1ubuntu2.5 | | ssh | 1:9.6p1-3ubuntu13.14 | | sshpass | 1.09-1 | | sudo | 1.9.15p5-3ubuntu5.24.04.1 | | swig | 4.2.0-2ubuntu1 | | systemd-coredump | 255.4-1ubuntu8.12 | | tar | 1.35+dfsg-3build1 | | telnet | 0.17+2.5-3ubuntu4.1 | | texinfo | 7.1-3build2 | | time | 1.9-0.2build1 | | tk | 8.6.14build1 | | tree | 2.1.1-2ubuntu3.24.04.2 | | tzdata | 2025b-0ubuntu0.24.04.1 | | unzip | 6.0-28ubuntu4.1 | | upx | 4.2.2-3 | | wget | 1.21.4-1ubuntu4.1 | | xvfb | 2:21.1.12-1ubuntu1.5 | | xz-utils | 5.6.1+really5.4.5-1ubuntu0.2 | | zip | 3.0-13ubuntu0.2 | | zsync | 0.6.2-5build1 | ================================================ FILE: images/ubuntu/assets/post-gen/cleanup-logs.sh ================================================ #!/bin/bash # journalctl if command -v journalctl; then journalctl --rotate journalctl --vacuum-time=1s fi # delete all .gz and rotated file find /var/log -type f -regex ".*\.gz$" -delete find /var/log -type f -regex ".*\.[0-9]$" -delete # wipe log files find /var/log/ -type f -exec cp /dev/null {} \; ================================================ FILE: images/ubuntu/assets/post-gen/environment-variables.sh ================================================ #!/bin/bash # Replace $HOME with the default user's home directory for environmental variables related to the default user home directory homeDir=$(cut -d: -f6 /etc/passwd | tail -1) sed -i "s|\$HOME|$homeDir|g" /etc/environment ================================================ FILE: images/ubuntu/assets/post-gen/systemd-linger.sh ================================================ #!/bin/bash # Enable user session on boot, not on login UserId=$(cut -d: -f3 /etc/passwd | tail -1) loginctl enable-linger $UserId ================================================ FILE: images/ubuntu/assets/ubuntu2204.conf ================================================ # Name of pool supported by this image POOL_NAME="Ubuntu 2204" ================================================ FILE: images/ubuntu/scripts/build/Configure-Toolset.ps1 ================================================ ################################################################################ ## File: Configure-Toolset.ps1 ## Team: CI-Build ## Desc: Configure toolset ################################################################################ Import-Module "$env:HELPER_SCRIPTS/../tests/Helpers.psm1" function Get-TCToolVersionPath { param( [Parameter(Mandatory)] [string] $ToolName, [Parameter(Mandatory)] [string] $ToolVersion, [Parameter(Mandatory)] [string] $ToolArchitecture ) $toolPath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath $ToolName $toolPathVersion = Join-Path -Path $toolPath -ChildPath $ToolVersion $foundVersion = Get-Item $toolPathVersion | Sort-Object -Property { [version] $_.name } -Descending | Select-Object -First 1 $installationDir = Join-Path -Path $foundVersion -ChildPath $ToolArchitecture return $installationDir } function Add-GlobalEnvironmentVariable { param( [Parameter(Mandatory)] [string] $Name, [Parameter(Mandatory)] [string] $Value, [string] $FilePath = "/etc/environment" ) $envVar = "{0}={1}" -f $Name, $Value Tee-Object -InputObject $envVar -FilePath $FilePath -Append } $ErrorActionPreference = "Stop" Write-Host "Configure toolcache tools environment..." $toolEnvConfigs = @{ go = @{ command = "ln -s {0}/bin/* /usr/bin/" variableTemplate = "GOROOT_{0}_{1}_X64" } } # Get toolcache content from toolset $tools = (Get-ToolsetContent).toolcache | Where-Object { $toolEnvConfigs.Keys -contains $_.name } foreach ($tool in $tools) { $toolEnvConfig = $toolEnvConfigs[$tool.name] if (-not ([string]::IsNullOrEmpty($toolEnvConfig.variableTemplate))) { foreach ($toolVersion in $tool.versions) { Write-Host "Set $($tool.name) $toolVersion environment variable..." $toolPath = Get-TCToolVersionPath -ToolName $tool.name -ToolVersion $toolVersion -ToolArchitecture $tool.arch $envVariableName = $toolEnvConfig.variableTemplate -f $toolVersion.split(".") Add-GlobalEnvironmentVariable -Name $envVariableName -Value $toolPath } } # Invoke command and add env variable for the default tool version if (-not ([string]::IsNullOrEmpty($tool.default))) { $toolDefaultPath = Get-TCToolVersionPath -ToolName $tool.name -ToolVersion $tool.default -ToolArchitecture $tool.arch if (-not ([string]::IsNullOrEmpty($toolEnvConfig.defaultVariable))) { Write-Host "Set default $($toolEnvConfig.defaultVariable) for $($tool.name) $($tool.default) environment variable..." Add-GlobalEnvironmentVariable -Name $toolEnvConfig.defaultVariable -Value $toolDefaultPath } if (-not ([string]::IsNullOrEmpty($toolEnvConfig.command))) { $command = $toolEnvConfig.command -f $toolDefaultPath Write-Host "Invoke $command command for default $($tool.name) $($tool.default) ..." Invoke-Expression -Command $command } } } Invoke-PesterTests -TestFile "Toolset" -TestName "Toolset" ================================================ FILE: images/ubuntu/scripts/build/Install-PowerShellAzModules.ps1 ================================================ ################################################################################ ## File: Install-PowerShellAzModules.ps1 ## Desc: Install Az modules for PowerShell ################################################################################ $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" Import-Module "$env:HELPER_SCRIPTS/../tests/Helpers.psm1" # Get modules content from toolset $modules = (Get-ToolsetContent).azureModules $installPSModulePath = "/usr/share" $psModuleMachinePath = $env:PSModulePath + ":" foreach ($module in $modules) { $moduleName = $module.name Write-Host "Installing ${moduleName} to the ${installPSModulePath} path..." foreach ($version in $module.versions) { $modulePath = Join-Path -Path $installPSModulePath -ChildPath "${moduleName}_${version}" Write-Host " - $version [$modulePath]" $psModuleMachinePath += ($modulePath + ":") Save-Module -Path $modulePath -Name $moduleName -RequiredVersion $version -Force } } $finalLine = "PSModulePath=$($psModuleMachinePath.TrimEnd(':').Replace("\root", '$HOME'))" Add-Content -Path "/etc/environment" -Value $finalLine Invoke-PesterTests -TestFile "PowerShellModules" -TestName "AzureModules" ================================================ FILE: images/ubuntu/scripts/build/Install-PowerShellModules.ps1 ================================================ ################################################################################ ## File: Install-PowerShellModules.ps1 ## Desc: Install modules for PowerShell ################################################################################ $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" Import-Module "$env:HELPER_SCRIPTS/../tests/Helpers.psm1" # Specifies the installation policy Set-PSRepository -InstallationPolicy Trusted -Name PSGallery # Try to update PowerShellGet before the actual installation Install-Module -Name PowerShellGet -Force Update-Module -Name PowerShellGet -Force # Install PowerShell modules $modules = (Get-ToolsetContent).powershellModules foreach($module in $modules) { $moduleName = $module.name Write-Host "Installing ${moduleName} module" if ($module.versions) { foreach ($version in $module.versions) { Write-Host " - $version" Install-Module -Name $moduleName -RequiredVersion $version -Scope AllUsers -SkipPublisherCheck -Force } } else { Install-Module -Name $moduleName -Scope AllUsers -SkipPublisherCheck -Force } } Invoke-PesterTests -TestFile "PowerShellModules" -TestName "PowerShellModules" ================================================ FILE: images/ubuntu/scripts/build/Install-Toolset.ps1 ================================================ ################################################################################ ## File: Install-Toolset.ps1 ## Team: CI-Build ## Desc: Install toolset ################################################################################ Import-Module "$env:HELPER_SCRIPTS/../tests/Helpers.psm1" function Install-Asset { param( [Parameter(Mandatory = $true)] [object] $ReleaseAsset ) Write-Host "Download $($ReleaseAsset.filename)" $assetArchivePath = Invoke-DownloadWithRetry $ReleaseAsset.download_url Write-Host "Extract $($ReleaseAsset.filename) content..." $assetFolderPath = Join-Path "/tmp" "$($ReleaseAsset.filename)-temp-dir" New-Item -ItemType Directory -Path $assetFolderPath | Out-Null tar -xzf $assetArchivePath -C $assetFolderPath Write-Host "Invoke installation script..." Push-Location -Path $assetFolderPath Invoke-Expression "bash ./setup.sh" Pop-Location } $ErrorActionPreference = "Stop" # Get toolcache content from toolset $tools = (Get-ToolsetContent).toolcache | Where-Object { $_.url -ne $null } foreach ($tool in $tools) { # Get versions manifest for current tool $assets = Invoke-RestMethod $tool.url # Get github release asset for each version foreach ($toolVersion in $tool.versions) { $asset = $assets | Where-Object version -like $toolVersion ` | Select-Object -ExpandProperty files ` | Where-Object { ($_.platform -eq $tool.platform) -and ($_.arch -eq $tool.arch) -and ($_.platform_version -eq $tool.platform_version)} ` | Select-Object -First 1 if (-not $asset) { Write-Host "Asset for $($tool.name) $toolVersion $($tool.arch) not found in versions manifest" exit 1 } Write-Host "Installing $($tool.name) $toolVersion $($tool.arch)..." Install-Asset -ReleaseAsset $asset } chown -R "$($env:SUDO_USER):$($env:SUDO_USER)" "/opt/hostedtoolcache/$($tool.name)" } ================================================ FILE: images/ubuntu/scripts/build/cleanup.sh ================================================ #!/bin/bash -e ################################################################################ ## File: cleanup.sh ## Desc: Perform cleanup ################################################################################ # before cleanup before=$(df / -Pm | awk 'NR==2{print $4}') # clears out the local repository of retrieved package files # It removes everything but the lock file from /var/cache/apt/archives/ and /var/cache/apt/archives/partial apt-get clean rm -rf /tmp/* rm -rf /root/.cache # journalctl if command -v journalctl; then journalctl --rotate journalctl --vacuum-time=1s fi # delete all .gz and rotated file find /var/log -type f -regex ".*\.gz$" -delete find /var/log -type f -regex ".*\.[0-9]$" -delete # wipe log files find /var/log/ -type f -exec cp /dev/null {} \; # delete symlink for tests running rm -f /usr/local/bin/invoke_tests # remove apt mock prefix=/usr/local/bin for tool in apt apt-get apt-key;do sudo rm -f $prefix/$tool done # after cleanup after=$(df / -Pm | awk 'NR==2{print $4}') # display size echo "Before: $before MB" echo "After : $after MB" echo "Delta : $(($after-$before)) MB" ================================================ FILE: images/ubuntu/scripts/build/configure-apt-mock.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-apt-mock.sh ## Desc: A temporary workaround for https://github.com/Azure/azure-linux-extensions/issues/1238. ## Cleaned up during cleanup.sh. ################################################################################ prefix=/usr/local/bin for real_tool in /usr/bin/apt /usr/bin/apt-get /usr/bin/apt-key; do tool=$(basename $real_tool) cat >$prefix/$tool <\$err # no errors, break the loop and continue normal flow test -f \$err || break cat \$err >&2 retry=false if grep -q 'Could not get lock' \$err;then # apt db locked needs retry retry=true elif grep -q 'Could not get lock /var/lib/apt/lists/lock' \$err;then # apt update did not complete (race condition), needs retry retry=true elif grep -q 'Problem renaming the file /var/cache/apt/pkgcache.bin.* to /var/cache/apt/pkgcache.bin' \$err;then # apt-get did not complete (race condition), needs retry retry=true elif grep -q 'Problem renaming the file /var/cache/apt/srcpkgcache.bin.* to /var/cache/apt/srcpkgcache.bin' \$err;then # apt update did not complete (race condition), needs retry retry=true elif grep -q 'Could not open file /var/lib/apt/lists' \$err;then # apt update is not completed, needs retry retry=true elif grep -q 'IPC connect call failed' \$err;then # the delay should help with gpg-agent not ready retry=true elif grep -q 'Temporary failure in name resolution' \$err;then # It looks like DNS is not updated with random generated hostname yet retry=true elif grep -q 'dpkg frontend is locked by another process' \$err;then # dpkg process is busy by another process retry=true fi rm \$err if [ \$retry = false ]; then break fi sleep 5 echo "...retry \$i" i=\$((i + 1)) done EOT chmod +x $prefix/$tool done ================================================ FILE: images/ubuntu/scripts/build/configure-apt-sources.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-apt-sources.sh ## Desc: Configure apt sources with failover from Azure to Ubuntu archives. ################################################################################ source $HELPER_SCRIPTS/os.sh touch /etc/apt/apt-mirrors.txt printf "http://azure.archive.ubuntu.com/ubuntu/\tpriority:1\n" | tee -a /etc/apt/apt-mirrors.txt printf "https://archive.ubuntu.com/ubuntu/\tpriority:2\n" | tee -a /etc/apt/apt-mirrors.txt printf "https://security.ubuntu.com/ubuntu/\tpriority:3\n" | tee -a /etc/apt/apt-mirrors.txt if is_ubuntu24; then sed -i 's|http://azure\.archive\.ubuntu\.com/ubuntu/|mirror+file:/etc/apt/apt-mirrors.txt|' /etc/apt/sources.list.d/ubuntu.sources # Apt changes to survive Cloud Init cp -f /etc/apt/sources.list.d/ubuntu.sources /etc/cloud/templates/sources.list.ubuntu.deb822.tmpl else sed -i 's|http://azure\.archive\.ubuntu\.com/ubuntu/|mirror+file:/etc/apt/apt-mirrors.txt|' /etc/apt/sources.list # Apt changes to survive Cloud Init cp -f /etc/apt/sources.list /etc/cloud/templates/sources.list.ubuntu.tmpl fi ================================================ FILE: images/ubuntu/scripts/build/configure-apt.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-apt.sh ## Desc: Configure apt, install jq and apt-fast packages. ################################################################################ source $HELPER_SCRIPTS/os.sh # Stop and disable apt-daily upgrade services; systemctl stop apt-daily.timer systemctl disable apt-daily.timer systemctl disable apt-daily.service systemctl stop apt-daily-upgrade.timer systemctl disable apt-daily-upgrade.timer systemctl disable apt-daily-upgrade.service # Enable retry logic for apt up to 10 times echo "APT::Acquire::Retries \"10\";" > /etc/apt/apt.conf.d/80-retries # Configure apt to always assume Y echo "APT::Get::Assume-Yes \"true\";" > /etc/apt/apt.conf.d/90assumeyes # APT understands a field called Phased-Update-Percentage which can be used to control the rollout of a new version. It is an integer between 0 and 100. # In case you have multiple systems that you want to receive the same set of updates, # you can set APT::Machine-ID to a UUID such that they all phase the same, # or set APT::Get::Never-Include-Phased-Updates or APT::Get::Always-Include-Phased-Updates to true such that APT will never/always consider phased updates. # apt-cache policy pkgname echo 'APT::Get::Always-Include-Phased-Updates "true";' > /etc/apt/apt.conf.d/99-phased-updates # Fix bad proxy and http headers settings cat <> /etc/apt/apt.conf.d/99bad_proxy Acquire::http::Pipeline-Depth 0; Acquire::http::No-Cache true; Acquire::https::Pipeline-Depth 0; Acquire::https::No-Cache true; Acquire::BrokenProxy true; EOF # Uninstall unattended-upgrades apt-get purge unattended-upgrades echo 'APT sources' if ! is_ubuntu24; then cat /etc/apt/sources.list else cat /etc/apt/sources.list.d/ubuntu.sources fi apt-get update # Install jq apt-get install jq if ! is_ubuntu24; then # Install apt-fast using quick-install.sh # https://github.com/ilikenwf/apt-fast bash -c "$(curl -fsSL https://raw.githubusercontent.com/ilikenwf/apt-fast/master/quick-install.sh)" fi ================================================ FILE: images/ubuntu/scripts/build/configure-dpkg.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-dpkg.sh ## Desc: Configure dpkg ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh source $HELPER_SCRIPTS/os.sh # This is the anti-frontend. It never interacts with you at all, # and makes the default answers be used for all questions. It # might mail error messages to root, but that's it; otherwise it # is completely silent and unobtrusive, a perfect frontend for # automatic installs. If you are using this front-end, and require # non-default answers to questions, you will need to pre-seed the # debconf database set_etc_environment_variable "DEBIAN_FRONTEND" "noninteractive" # dpkg can be instructed not to ask for confirmation # when replacing a configuration file (with the --force-confdef --force-confold options) cat <> /etc/apt/apt.conf.d/10dpkg-options Dpkg::Options { "--force-confdef"; "--force-confold"; } EOF # hide information about packages that are no longer required cat <> /etc/apt/apt.conf.d/10apt-autoremove APT::Get::AutomaticRemove "0"; APT::Get::HideAutoRemove "1"; EOF # Install libicu70 package for Ubuntu 24 if is_ubuntu24 ; then wget https://archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu70_70.1-2_amd64.deb EXPECTED_LIBICU_SHA512="a6315482d93606e375c272718d2458870b95e4ed4b672ea8640cf7bc2d2c2f41aea13b798b1e417e1ffc472a90c6aad150d3d293aa9bddec48e39106e4042807" ACTUAL_LIBICU_SHA512="$(sha512sum "./libicu70_70.1-2_amd64.deb" | awk '{print $1}')" [ "$EXPECTED_LIBICU_SHA512" = "$ACTUAL_LIBICU_SHA512" ] || { echo "libicu checksum mismatch in configure-dpkg.sh"; exit 1;} sudo apt-get install -y ./libicu70_70.1-2_amd64.deb fi ================================================ FILE: images/ubuntu/scripts/build/configure-environment.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-environment.sh ## Desc: Configure system and environment ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/etc-environment.sh # Set ImageVersion and ImageOS env variables set_etc_environment_variable "ImageVersion" "${IMAGE_VERSION}" set_etc_environment_variable "ImageOS" "${IMAGE_OS}" # Set the ACCEPT_EULA variable to Y value to confirm your acceptance of the End-User Licensing Agreement set_etc_environment_variable "ACCEPT_EULA" "Y" # This directory is supposed to be created in $HOME and owned by user(https://github.com/actions/runner-images/issues/491) mkdir -p /etc/skel/.config/configstore set_etc_environment_variable "XDG_CONFIG_HOME" '$HOME/.config' # Change waagent entries to use /mnt for swap file sed -i 's/ResourceDisk.Format=n/ResourceDisk.Format=y/g' /etc/waagent.conf sed -i 's/ResourceDisk.EnableSwap=n/ResourceDisk.EnableSwap=y/g' /etc/waagent.conf sed -i 's/ResourceDisk.SwapSizeMB=0/ResourceDisk.SwapSizeMB=4096/g' /etc/waagent.conf # Add localhost alias to ::1 IPv6 sed -i 's/::1 ip6-localhost ip6-loopback/::1 localhost ip6-localhost ip6-loopback/g' /etc/hosts # Prepare directory and env variable for toolcache AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache mkdir $AGENT_TOOLSDIRECTORY set_etc_environment_variable "AGENT_TOOLSDIRECTORY" "${AGENT_TOOLSDIRECTORY}" set_etc_environment_variable "RUNNER_TOOL_CACHE" "${AGENT_TOOLSDIRECTORY}" chmod -R 777 $AGENT_TOOLSDIRECTORY # https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html # https://www.suse.com/support/kb/doc/?id=000016692 echo 'vm.max_map_count=262144' | tee -a /etc/sysctl.conf # https://kind.sigs.k8s.io/docs/user/known-issues/#pod-errors-due-to-too-many-open-files echo 'fs.inotify.max_user_watches=655360' | tee -a /etc/sysctl.conf echo 'fs.inotify.max_user_instances=1280' | tee -a /etc/sysctl.conf # https://github.com/actions/runner-images/issues/9491 echo 'vm.mmap_rnd_bits=28' | tee -a /etc/sysctl.conf # https://github.com/actions/runner-images/pull/7860 netfilter_rule='/etc/udev/rules.d/50-netfilter.rules' rules_directory="$(dirname "${netfilter_rule}")" mkdir -p $rules_directory touch $netfilter_rule echo 'ACTION=="add", SUBSYSTEM=="module", KERNEL=="nf_conntrack", RUN+="/usr/sbin/sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1"' | tee -a $netfilter_rule # Create symlink for tests running chmod +x $HELPER_SCRIPTS/invoke-tests.sh ln -s $HELPER_SCRIPTS/invoke-tests.sh /usr/local/bin/invoke_tests # Disable motd updates metadata sed -i 's/ENABLED=1/ENABLED=0/g' /etc/default/motd-news # Remove fwupd if installed. We're running on VMs in Azure and the fwupd package is not needed. # Leaving it enable means periodic refreshes show in network traffic and firewall logs # Check if fwupd-refresh.timer exists in systemd if systemctl list-unit-files fwupd-refresh.timer &>/dev/null; then echo "Masking fwupd-refresh.timer..." systemctl mask fwupd-refresh.timer fi # This is a legacy check, leaving for earlier versions of Ubuntu # If fwupd config still exists, disable the motd updates if [[ -f "/etc/fwupd/daemon.conf" ]]; then sed -i 's/UpdateMotd=true/UpdateMotd=false/g' /etc/fwupd/daemon.conf fi # Disable to load providers # https://github.com/microsoft/azure-pipelines-agent/issues/3834 if is_ubuntu22; then sed -i 's/openssl_conf = openssl_init/#openssl_conf = openssl_init/g' /etc/ssl/openssl.cnf fi # Disable man-db auto update echo "set man-db/auto-update false" | debconf-communicate dpkg-reconfigure man-db ================================================ FILE: images/ubuntu/scripts/build/configure-image-data.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-image-data.sh ## Desc: Create a file with image data and documentation links ################################################################################ imagedata_file=$IMAGEDATA_FILE image_version=$IMAGE_VERSION image_version_major=${image_version/.*/} image_version_minor=$(echo $image_version | cut -d "." -f 2) os_name=$(lsb_release -ds | sed "s/ /\\\n/g") os_version=$(lsb_release -rs) image_label="ubuntu-${os_version}" version_major=${os_version/.*/} version_wo_dot=${os_version/./} github_url="https://github.com/actions/runner-images/blob" software_url="${github_url}/ubuntu${version_major}/${image_version_major}.${image_version_minor}/images/ubuntu/Ubuntu${version_wo_dot}-Readme.md" releaseUrl="https://github.com/actions/runner-images/releases/tag/ubuntu${version_major}%2F${image_version_major}.${image_version_minor}" cat < $imagedata_file [ { "group": "Operating System", "detail": "${os_name}" }, { "group": "Runner Image", "detail": "Image: ${image_label}\nVersion: ${image_version}\nIncluded Software: ${software_url}\nImage Release: ${releaseUrl}" } ] EOF ================================================ FILE: images/ubuntu/scripts/build/configure-limits.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-limits.sh ## Desc: Configure limits ################################################################################ echo 'session required pam_limits.so' >> /etc/pam.d/common-session echo 'session required pam_limits.so' >> /etc/pam.d/common-session-noninteractive echo 'DefaultLimitNOFILE=65536' >> /etc/systemd/system.conf echo 'DefaultLimitSTACK=16M:infinity' >> /etc/systemd/system.conf # Raise Number of File Descriptors echo '* soft nofile 65536' >> /etc/security/limits.conf echo '* hard nofile 65536' >> /etc/security/limits.conf # Double stack size from default 8192KB echo '* soft stack 16384' >> /etc/security/limits.conf echo '* hard stack 16384' >> /etc/security/limits.conf ================================================ FILE: images/ubuntu/scripts/build/configure-snap.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-snap.sh ## Desc: Configure snap ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh # Update /etc/environment to include /snap/bin in PATH # because /etc/profile.d is ignored by `--norc` shell launch option prepend_etc_environment_path "/snap/bin" # Put snapd auto refresh on hold # as it may generate too much traffic on Canonical's snap server # when they are rolling a new major update out. # Hold is calculated as today's date + 60 days # snapd is started automatically, but during image generation # a unix socket may die, restart snapd.service (and therefore snapd.socket) # to make sure the socket is alive. systemctl restart snapd.socket systemctl restart snapd snap set system refresh.hold="$(date --date='today+60 days' +%Y-%m-%dT%H:%M:%S%:z)" ================================================ FILE: images/ubuntu/scripts/build/configure-system.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-system.sh ## Desc: Post deployment system configuration actions ################################################################################ source $HELPER_SCRIPT_FOLDER/etc-environment.sh source $HELPER_SCRIPT_FOLDER/os.sh mv -f /imagegeneration/post-generation /opt echo "chmod -R 777 /usr/share" chmod -R 777 /usr/share echo "chmod -R 777 /opt" chmod -R 777 /opt echo "Setting sticky bit on hostedtoolcache Ruby directories due to the changes in Ruby 4.0; see issue: https://github.com/actions/runner-images/issues/13647" find /opt/hostedtoolcache/Ruby -type d -exec chmod +t {} + chmod 755 $IMAGE_FOLDER # Remove quotes around PATH ENVPATH=$(grep 'PATH=' /etc/environment | head -n 1 | sed -z 's/^PATH=*//') ENVPATH=${ENVPATH#"\""} ENVPATH=${ENVPATH%"\""} replace_etc_environment_variable "PATH" "${ENVPATH}" echo "Updated /etc/environment: $(cat /etc/environment)" # Clean yarn and npm cache if yarn --version > /dev/null; then yarn cache clean fi if npm --version; then npm cache clean --force fi if is_ubuntu24; then # Prevent needrestart from restarting the provisioner service. # Currently only happens on Ubuntu 24.04, so make it conditional for the time being # as configuration is too different between Ubuntu versions. sed -i '/^\s*};/i \ qr(^runner-provisioner) => 0,' /etc/needrestart/needrestart.conf fi ================================================ FILE: images/ubuntu/scripts/build/install-actions-cache.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-actions-cache.sh ## Desc: Download latest release from https://github.com/actions/action-versions ## Maintainer: #actions-runtime and @TingluoHuang ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh # Prepare directory and env variable for ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE ACTION_ARCHIVE_CACHE_DIR=/opt/actionarchivecache mkdir -p $ACTION_ARCHIVE_CACHE_DIR chmod -R 777 $ACTION_ARCHIVE_CACHE_DIR echo "Setting up ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE variable to ${ACTION_ARCHIVE_CACHE_DIR}" set_etc_environment_variable "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE" "${ACTION_ARCHIVE_CACHE_DIR}" # Download latest release from github.com/actions/action-versions and untar to /opt/actionarchivecache download_url=$(resolve_github_release_asset_url "actions/action-versions" "endswith(\"action-versions.tar.gz\")" "latest") archive_path=$(download_with_retry "$download_url") tar -xzf "$archive_path" -C $ACTION_ARCHIVE_CACHE_DIR invoke_tests "ActionArchiveCache" ================================================ FILE: images/ubuntu/scripts/build/install-aliyun-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-aliyun-cli.sh ## Desc: Install Alibaba Cloud CLI ## Supply chain security: Alibaba Cloud CLI - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/install.sh # Install Alibaba Cloud CLI download_url=$(resolve_github_release_asset_url "aliyun/aliyun-cli" "contains(\"aliyun-cli-linux\") and endswith(\"amd64.tgz\")" "latest") hash_url="https://github.com/aliyun/aliyun-cli/releases/latest/download/SHASUMS256.txt" archive_path=$(download_with_retry "$download_url") # Supply chain security - Alibaba Cloud CLI external_hash=$(get_checksum_from_url "$hash_url" "aliyun-cli-linux.*amd64.tgz" "SHA256") use_checksum_comparison "$archive_path" "$external_hash" tar xzf "$archive_path" mv aliyun /usr/local/bin invoke_tests "CLI.Tools" "Aliyun CLI" ================================================ FILE: images/ubuntu/scripts/build/install-android-sdk.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-android-sdk.sh ## Desc: Install Android SDK and tools ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh add_filtered_installation_components() { local minimum_version=$1 shift local tools_array=("$@") for item in ${tools_array[@]}; do # Take the last version number that appears after the last '-' or ';' item_version=$(echo "$item" | grep -oE '[-;][0-9.]+' | grep -oE '[0-9.]+') # Semver 'comparison'. Add item to components array, if item's version is greater than or equal to minimum version if [[ "$(printf "${minimum_version}\n${item_version}\n" | sort -V | head -n1)" == "$minimum_version" ]]; then components+=($item) fi done } get_full_ndk_version() { local major_version=$1 ndk_version=$($SDKMANAGER --list | grep "ndk;${major_version}\." | awk '{gsub("ndk;", ""); print $1}' | sort -V | tail -n1) echo "$ndk_version" } # Set env variable for SDK Root (https://developer.android.com/studio/command-line/variables) ANDROID_ROOT=/usr/local/lib/android ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager set_etc_environment_variable "ANDROID_SDK_ROOT" "${ANDROID_SDK_ROOT}" # ANDROID_HOME is deprecated, but older versions of Gradle rely on it set_etc_environment_variable "ANDROID_HOME" "${ANDROID_SDK_ROOT}" # Create android sdk directory mkdir -p ${ANDROID_SDK_ROOT} # Download the latest command line tools so that we can accept all of the licenses. # See https://developer.android.com/studio/#command-tools cmdline_tools_package=$(get_toolset_value '.android."cmdline-tools"') if [[ $cmdline_tools_version == "latest" ]]; then REPOSITORY_XML_URL="https://dl.google.com/android/repository/repository2-1.xml" repository_xml_file=$(download_with_retry "$REPOSITORY_XML_URL") cmdline_tools_package=$( yq -p=xml \ '.sdk-repository.remotePackage[] | select(."+@path" == "cmdline-tools;latest" and .channelRef."+@ref" == "channel-0").archives.archive[].complete.url | select(contains("commandlinetools-linux"))' \ "${repository_xml_file}" ) if [[ -z $cmdline_tools_package ]]; then echo "Failed to parse latest command-line tools version" exit 1 fi fi # Install command line tools archive_path=$(download_with_retry "https://dl.google.com/android/repository/${cmdline_tools_package}") unzip -qq "$archive_path" -d ${ANDROID_SDK_ROOT}/cmdline-tools # Command line tools need to be placed in ${ANDROID_SDK_ROOT}/sdk/cmdline-tools/latest to determine SDK root mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest # Check sdk manager installation if ${SDKMANAGER} --list 1>/dev/null; then echo "Android SDK manager was installed" else echo "Android SDK manager was not installed" exit 1 fi # Get toolset values and prepare environment variables minimum_build_tool_version=$(get_toolset_value '.android.build_tools_min_version') minimum_platform_version=$(get_toolset_value '.android.platform_min_version') android_ndk_major_default=$(get_toolset_value '.android.ndk.default') android_ndk_major_versions=($(get_toolset_value '.android.ndk.versions[]')) android_ndk_major_latest=(${android_ndk_major_versions[-1]}) ndk_default_full_version=$(get_full_ndk_version $android_ndk_major_default) ndk_latest_full_version=$(get_full_ndk_version $android_ndk_major_latest) ANDROID_NDK=${ANDROID_SDK_ROOT}/ndk/${ndk_default_full_version} # ANDROID_NDK, ANDROID_NDK_HOME, and ANDROID_NDK_ROOT variables should be set as many customer builds depend on them https://github.com/actions/runner-images/issues/5879 set_etc_environment_variable "ANDROID_NDK" "${ANDROID_NDK}" set_etc_environment_variable "ANDROID_NDK_HOME" "${ANDROID_NDK}" set_etc_environment_variable "ANDROID_NDK_ROOT" "${ANDROID_NDK}" set_etc_environment_variable "ANDROID_NDK_LATEST_HOME" "${ANDROID_SDK_ROOT}/ndk/${ndk_latest_full_version}" # Prepare components for installation extras=$(get_toolset_value '.android.extra_list[] | "extras;" + .') addons=$(get_toolset_value '.android.addon_list[] | "add-ons;" + .') additional=$(get_toolset_value '.android.additional_tools[]') components=("${extras[@]}" "${addons[@]}" "${additional[@]}") for ndk_major_version in "${android_ndk_major_versions[@]}"; do ndk_full_version=$(get_full_ndk_version $ndk_major_version) components+=("ndk;$ndk_full_version") done available_platforms=($($SDKMANAGER --list | sed -n '/Available Packages:/,/^$/p' | grep "platforms;android-[0-9]" | cut -d"|" -f 1)) all_build_tools=($($SDKMANAGER --list | grep "build-tools;" | cut -d"|" -f 1 | sort -u)) available_build_tools=$(echo ${all_build_tools[@]//*rc[0-9]/}) add_filtered_installation_components $minimum_platform_version "${available_platforms[@]}" add_filtered_installation_components $minimum_build_tool_version "${available_build_tools[@]}" # Add platform tools to the list of components to install components+=("platform-tools") # Install components echo "y" | $SDKMANAGER ${components[@]} # Add required permissions chmod -R a+rwx ${ANDROID_SDK_ROOT} reload_etc_environment invoke_tests "Android" ================================================ FILE: images/ubuntu/scripts/build/install-apache.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-apache.sh ## Desc: Install Apache HTTP Server ################################################################################ # Install Apache apt-get install apache2 # Disable apache2.service systemctl is-active --quiet apache2.service && systemctl stop apache2.service systemctl disable apache2.service invoke_tests "WebServers" "Apache" ================================================ FILE: images/ubuntu/scripts/build/install-apt-common.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-apt-common.sh ## Desc: Install basic command line utilities and dev packages ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh common_packages=$(get_toolset_value .apt.common_packages[]) cmd_packages=$(get_toolset_value .apt.cmd_packages[]) for package in $common_packages $cmd_packages; do echo "Install $package" apt-get install --no-install-recommends $package done invoke_tests "Apt" ================================================ FILE: images/ubuntu/scripts/build/install-apt-vital.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-apt-vital.sh ## Desc: Install vital command line utilities ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh vital_packages=$(get_toolset_value .apt.vital_packages[]) apt-get install --no-install-recommends $vital_packages ================================================ FILE: images/ubuntu/scripts/build/install-aws-tools.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-aws-tools.sh ## Desc: Install the AWS CLI, Session Manager plugin for the AWS CLI, and AWS SAM CLI ## Supply chain security: AWS SAM CLI - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/install.sh awscliv2_archive_path=$(download_with_retry "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip") unzip -qq "$awscliv2_archive_path" -d /tmp /tmp/aws/install -i /usr/local/aws-cli -b /usr/local/bin smplugin_deb_path=$(download_with_retry "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb") apt-get install "$smplugin_deb_path" # Download the latest aws sam cli release aws_sam_cli_archive_name="aws-sam-cli-linux-x86_64.zip" sam_cli_download_url=$(resolve_github_release_asset_url "aws/aws-sam-cli" "endswith(\"$aws_sam_cli_archive_name\")" "latest") aws_sam_cli_archive_path=$(download_with_retry "$sam_cli_download_url") # Supply chain security - AWS SAM CLI aws_sam_cli_hash=$(get_checksum_from_github_release "aws/aws-sam-cli" "${aws_sam_cli_archive_name}.. " "latest" "SHA256") use_checksum_comparison "$aws_sam_cli_archive_path" "$aws_sam_cli_hash" # Install the latest aws sam cli release unzip "$aws_sam_cli_archive_path" -d /tmp /tmp/install invoke_tests "CLI.Tools" "AWS" ================================================ FILE: images/ubuntu/scripts/build/install-azcopy.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-azcopy.sh ## Desc: Install AzCopy ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Install AzCopy10 archive_path=$(download_with_retry "https://aka.ms/downloadazcopy-v10-linux") tar xzf "$archive_path" --strip-components=1 -C /tmp install /tmp/azcopy /usr/local/bin/azcopy # Create azcopy 10 alias for backward compatibility ln -sf /usr/local/bin/azcopy /usr/local/bin/azcopy10 invoke_tests "Tools" "azcopy" ================================================ FILE: images/ubuntu/scripts/build/install-azure-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-azure-cli.sh ## Desc: Install Azure CLI (az) ################################################################################ # Install Azure CLI (instructions taken from https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) curl -fsSL https://aka.ms/InstallAzureCLIDeb | sudo bash echo "azure-cli https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt" >> $HELPER_SCRIPTS/apt-sources.txt rm -f /etc/apt/sources.list.d/azure-cli.list rm -f /etc/apt/sources.list.d/azure-cli.list.save invoke_tests "CLI.Tools" "Azure CLI" ================================================ FILE: images/ubuntu/scripts/build/install-azure-devops-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-azure-devops-cli.sh ## Desc: Install Azure DevOps CLI (az devops) ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh # AZURE_EXTENSION_DIR shell variable defines where modules are installed # https://docs.microsoft.com/en-us/cli/azure/azure-cli-extensions-overview export AZURE_EXTENSION_DIR=/opt/az/azcliextensions set_etc_environment_variable "AZURE_EXTENSION_DIR" "${AZURE_EXTENSION_DIR}" # install azure devops Cli extension az extension add -n azure-devops invoke_tests "CLI.Tools" "Azure DevOps CLI" ================================================ FILE: images/ubuntu/scripts/build/install-bazel.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-bazel.sh ## Desc: Install Bazel and Bazelisk (A user-friendly launcher for Bazel) ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh # Install bazelisk npm install -g @bazel/bazelisk # Run bazelisk once in order to install /usr/local/bin/bazel binary bazel version # Get the installed Bazel version from bazelisk BAZEL_VERSION=$(bazel --version | grep "Build label:" | awk '{print $3}') # Set USE_BAZEL_FALLBACK_VERSION so that users without .bazelversion # get the preinstalled version instead of downloading latest set_etc_environment_variable "USE_BAZEL_FALLBACK_VERSION" "silent:${BAZEL_VERSION}" invoke_tests "Tools" "Bazel" ================================================ FILE: images/ubuntu/scripts/build/install-bicep.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-bicep.sh ## Desc: Install bicep cli ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Install Bicep CLI download_url=$(resolve_github_release_asset_url "Azure/bicep" "endswith(\"bicep-linux-x64\")" "latest") bicep_binary_path=$(download_with_retry "${download_url}") # Mark it as executable install "$bicep_binary_path" /usr/local/bin/bicep invoke_tests "Tools" "Bicep" ================================================ FILE: images/ubuntu/scripts/build/install-clang.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-clang.sh ## Desc: Install Clang compiler ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh install_clang() { local version=$1 echo "Installing clang-$version..." apt-get install "clang-$version" "lldb-$version" "lld-$version" "clang-format-$version" "clang-tidy-$version" } set_default_clang() { local version=$1 echo "Make Clang ${version} default" update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${version} 100 update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${version} 100 update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-${version} 100 update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-${version} 100 update-alternatives --install /usr/bin/run-clang-tidy run-clang-tidy /usr/bin/run-clang-tidy-${version} 100 } versions=$(get_toolset_value '.clang.versions[]') default_clang_version=$(get_toolset_value '.clang.default_version') for version in ${versions[*]}; do if [[ $version != $default_clang_version ]]; then install_clang $version fi done install_clang $default_clang_version set_default_clang $default_clang_version invoke_tests "Tools" "clang" ================================================ FILE: images/ubuntu/scripts/build/install-cmake.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-cmake.sh ## Desc: Install CMake ## Supply chain security: CMake - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Test to see if the software in question is already installed, if not install it echo "Checking to see if the installer script has already been run" if command -v cmake; then echo "cmake is already installed" else # Download script to install CMake download_url=$(resolve_github_release_asset_url "Kitware/CMake" "endswith(\"inux-x86_64.sh\")" "3.31.6") curl -fsSL "${download_url}" -o cmakeinstall.sh # Supply chain security - CMake hash_url=$(resolve_github_release_asset_url "Kitware/CMake" "endswith(\"SHA-256.txt\")" "3.31.6") external_hash=$(get_checksum_from_url "$hash_url" "linux-x86_64.sh" "SHA256") use_checksum_comparison "cmakeinstall.sh" "$external_hash" # Install CMake and remove the install script chmod +x cmakeinstall.sh \ && ./cmakeinstall.sh --prefix=/usr/local --exclude-subdir \ && rm cmakeinstall.sh fi invoke_tests "Tools" "Cmake" ================================================ FILE: images/ubuntu/scripts/build/install-codeql-bundle.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-codeql-bundle.sh ## Desc: Install CodeQL CLI Bundle to the toolcache. ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Retrieve the latest major version of the CodeQL Action to use in the base URL for downloading the bundle. releases=$(curl -s "https://api.github.com/repos/github/codeql-action/releases") # Get the release tags starting with v[0-9] and sort them in descending order, then parse the first one to get the major version. codeql_action_latest_major_version=$(echo "$releases" | jq -r '.[].tag_name' | grep -E '^v[0-9]' | sort -nr | head -n 1 | sed -E 's/^v([0-9]+).*/\1/') if [ -z "$codeql_action_latest_major_version" ]; then echo "Error: Unable to find the latest major version of the CodeQL Action." exit 1 fi # Retrieve the CLI version of the latest CodeQL bundle. base_url="$(curl -fsSL https://raw.githubusercontent.com/github/codeql-action/v"$codeql_action_latest_major_version"/src/defaults.json)" bundle_version="$(echo "$base_url" | jq -r '.cliVersion')" bundle_tag_name="codeql-bundle-v$bundle_version" echo "Downloading CodeQL bundle $bundle_version..." # Note that this is the all-platforms CodeQL bundle, to support scenarios where customers run # different operating systems within containers. codeql_archive=$(download_with_retry "https://github.com/github/codeql-action/releases/download/$bundle_tag_name/codeql-bundle-linux64.tar.gz") codeql_toolcache_path="$AGENT_TOOLSDIRECTORY/CodeQL/$bundle_version/x64" mkdir -p "$codeql_toolcache_path" echo "Unpacking the downloaded CodeQL bundle archive..." tar -xzf "$codeql_archive" -C "$codeql_toolcache_path" # Touch a file to indicate to the CodeQL Action that this bundle shipped with the toolcache. This is # to support overriding the CodeQL version specified in defaults.json on GitHub Enterprise. touch "$codeql_toolcache_path/pinned-version" # Touch a file to indicate to the toolcache that setting up CodeQL is complete. touch "$codeql_toolcache_path.complete" ================================================ FILE: images/ubuntu/scripts/build/install-container-tools.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-container-tools.sh ## Desc: Install container tools: podman, buildah and skopeo onto the image ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh # # pin podman due to https://github.com/actions/runner-images/issues/7753 # https://bugs.launchpad.net/ubuntu/+source/libpod/+bug/2024394 # if ! is_ubuntu22; then install_packages=(podman buildah skopeo) else install_packages=(podman=3.4.4+ds1-1ubuntu1 buildah skopeo) fi if is_ubuntu22; then # Install containernetworking-plugins for Ubuntu 22 curl -O http://archive.ubuntu.com/ubuntu/pool/universe/g/golang-github-containernetworking-plugins/containernetworking-plugins_1.1.1+ds1-3build1_amd64.deb dpkg -i containernetworking-plugins_1.1.1+ds1-3build1_amd64.deb fi # Install podman, buildah, skopeo container's tools apt-get update apt-get install ${install_packages[@]} mkdir -p /etc/containers printf "[registries.search]\nregistries = ['docker.io', 'quay.io']\n" | tee /etc/containers/registries.conf invoke_tests "Tools" "Containers" ================================================ FILE: images/ubuntu/scripts/build/install-docker.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-docker.sh ## Desc: Install docker onto the image ## Supply chain security: amazon-ecr-credential-helper - dynamic checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/os.sh REPO_URL="https://download.docker.com/linux/ubuntu" GPG_KEY="/usr/share/keyrings/docker.gpg" REPO_PATH="/etc/apt/sources.list.d/docker.list" os_codename=$(lsb_release -cs) curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o $GPG_KEY echo "deb [arch=amd64 signed-by=$GPG_KEY] $REPO_URL ${os_codename} stable" > $REPO_PATH apt-get update # Install docker components which available via apt-get # Using toolsets keep installation order to install dependencies before the package in order to control versions components=$(get_toolset_value '.docker.components[] .package') for package in $components; do version=$(get_toolset_value ".docker.components[] | select(.package == \"$package\") | .version") if [[ $version == "latest" ]]; then apt-get install --no-install-recommends "$package" else version_string=$(apt-cache madison "$package" | awk '{ print $3 }' | grep "$version" | grep "$os_codename" | head -1) apt-get install --no-install-recommends "${package}=${version_string}" fi done # Install plugins that are best installed from the GitHub repository # Be aware that `url` built from github repo name and plugin name because of current repo naming for those plugins plugins=$(get_toolset_value '.docker.plugins[] .plugin') for plugin in $plugins; do version=$(get_toolset_value ".docker.plugins[] | select(.plugin == \"$plugin\") | .version") filter=$(get_toolset_value ".docker.plugins[] | select(.plugin == \"$plugin\") | .asset") url=$(resolve_github_release_asset_url "docker/$plugin" "endswith(\"$filter\")" "$version") binary_path=$(download_with_retry "$url" "/tmp/docker-$plugin") mkdir -pv "/usr/libexec/docker/cli-plugins" install "$binary_path" "/usr/libexec/docker/cli-plugins/docker-$plugin" done # docker from official repo introduced different GID generation: https://github.com/actions/runner-images/issues/8157 gid=$(cut -d ":" -f 3 /etc/group | grep "^1..$" | sort -n | tail -n 1 | awk '{ print $1+1 }') groupmod -g "$gid" docker # Create systemd-tmpfiles configuration for Docker cat < $REPO_PATH apt-get update apt-get install --target-release 'o=LP-PPA-mozillateam' firefox rm $REPO_PATH # Document apt source repo's echo "mozillateam $REPO_URL" >> $HELPER_SCRIPTS/apt-sources.txt # add to global system preferences for firefox locale en_US, because other browsers have en_US local. # Default firefox local is en_GB echo 'pref("intl.locale.requested","en_US");' >> "/usr/lib/firefox/browser/defaults/preferences/syspref.js" # Download and unpack latest release of geckodriver download_url=$(resolve_github_release_asset_url "mozilla/geckodriver" "test(\"linux64.tar.gz$\")" "latest") driver_archive_path=$(download_with_retry "$download_url") GECKODRIVER_DIR="/usr/local/share/gecko_driver" GECKODRIVER_BIN="$GECKODRIVER_DIR/geckodriver" mkdir -p $GECKODRIVER_DIR tar -xzf "$driver_archive_path" -C $GECKODRIVER_DIR chmod +x $GECKODRIVER_BIN ln -s "$GECKODRIVER_BIN" /usr/bin/ set_etc_environment_variable "GECKOWEBDRIVER" "${GECKODRIVER_DIR}" invoke_tests "Browsers" "Firefox" ================================================ FILE: images/ubuntu/scripts/build/install-gcc-compilers.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-gcc-compilers.sh ## Desc: Install GNU C++ compilers ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh versions=$(get_toolset_value '.gcc.versions[]') for version in ${versions[*]}; do echo "Installing $version..." apt-get install $version done invoke_tests "Tools" "gcc" ================================================ FILE: images/ubuntu/scripts/build/install-gfortran.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-gfortran.sh ## Desc: Install GNU Fortran ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh versions=$(get_toolset_value '.gfortran.versions[]') for version in ${versions[*]}; do echo "Installing $version..." apt-get install $version done echo "Install versionless gfortran (latest)" apt-get install gfortran invoke_tests "Tools" "gfortran" ================================================ FILE: images/ubuntu/scripts/build/install-git-lfs.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-git-lfs.sh ## Desc: Install Git-lfs ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh GIT_LFS_REPO="https://packagecloud.io/install/repositories/github/git-lfs" # Install git-lfs curl -fsSL $GIT_LFS_REPO/script.deb.sh | bash apt-get install git-lfs # Remove source repo's rm /etc/apt/sources.list.d/github_git-lfs.list # Document apt source repo's echo "git-lfs $GIT_LFS_REPO" >> $HELPER_SCRIPTS/apt-sources.txt invoke_tests "Tools" "Git-lfs" ================================================ FILE: images/ubuntu/scripts/build/install-git.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-git.sh ## Desc: Install Git and Git-FTP ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh GIT_REPO="ppa:git-core/ppa" ## Install git add-apt-repository $GIT_REPO -y apt-get update apt-get install git # Git version 2.35.2 introduces security fix that breaks action\checkout https://github.com/actions/checkout/issues/760 cat <> /etc/gitconfig [safe] directory = * EOF # Install git-ftp apt-get install git-ftp # Remove source repo's add-apt-repository --remove $GIT_REPO # Document apt source repo's echo "git-core $GIT_REPO" >> $HELPER_SCRIPTS/apt-sources.txt # Add well-known SSH host keys to known_hosts ssh-keyscan -t rsa,ecdsa,ed25519 github.com >> /etc/ssh/ssh_known_hosts ssh-keyscan -t rsa ssh.dev.azure.com >> /etc/ssh/ssh_known_hosts invoke_tests "Tools" "Git" ================================================ FILE: images/ubuntu/scripts/build/install-github-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-github-cli.sh ## Desc: Install GitHub CLI ## Must be run as non-root user after homebrew ## Supply chain security: GitHub CLI - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Download GitHub CLI gh_cli_url=$(resolve_github_release_asset_url "cli/cli" "contains(\"linux\") and contains(\"amd64\") and endswith(\".deb\")" "latest") gh_cli_deb_path=$(download_with_retry "$gh_cli_url") # Supply chain security - GitHub CLI hash_url=$(resolve_github_release_asset_url "cli/cli" "endswith(\"checksums.txt\")" "latest") external_hash=$(get_checksum_from_url "$hash_url" "linux_amd64.deb" "SHA256") use_checksum_comparison "$gh_cli_deb_path" "$external_hash" # Install GitHub CLI apt-get install "$gh_cli_deb_path" invoke_tests "CLI.Tools" "GitHub CLI" ================================================ FILE: images/ubuntu/scripts/build/install-google-chrome.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-google-chrome.sh ## Desc: Install google-chrome, chromedriver and chromium ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh get_chromium_revision() { local chrome_revision=$1 # Take the first part of the revision variable to search not only for a specific version, # but also for similar ones, so that we can get a previous one if the required revision is not found chrome_revision_prefix=${chrome_revision:0:${#chrome_revision}/2+1} SEARCH_URL="https://www.googleapis.com/storage/v1/b/chromium-browser-snapshots/o?delimiter=/&prefix=Linux_x64" # Revision can include a hash instead of a number. Need to filter it out https://github.com/actions/runner-images/issues/5256 revisions_available=$(curl -s $SEARCH_URL/${chrome_revision_prefix} | jq -r '.prefixes[]' | grep -E "Linux_x64\/[0-9]+\/"| cut -d "/" -f 2 | sort --version-sort) # If required Chromium revision is not found in the list # we should have to decrement the revision number until we find one. # This is mentioned in the documentation we use for this installation: # https://www.chromium.org/getting-involved/download-chromium latest_valid_revision=$(echo $revisions_available | cut -f 1 -d " ") for revision in $revisions_available; do if [ "$chrome_revision" -lt "$revision" ]; then break fi latest_valid_revision=$revision done echo $latest_valid_revision } # Download and install Google Chrome CHROME_DEB_URL="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" chrome_deb_path=$(download_with_retry "$CHROME_DEB_URL") apt-get install "$chrome_deb_path" -f set_etc_environment_variable "CHROME_BIN" "/usr/bin/google-chrome" # Remove Google Chrome repo rm -f /etc/cron.daily/google-chrome /etc/apt/sources.list.d/google-chrome.list /etc/apt/sources.list.d/google-chrome.list.save # Parse Google Chrome version full_chrome_version=$(google-chrome --product-version) chrome_version=${full_chrome_version%.*} echo "Chrome version is $full_chrome_version" # Get chrome versions information CHROME_PLATFORM="linux64" CHROME_VERSIONS_URL="https://googlechromelabs.github.io/chrome-for-testing/latest-patch-versions-per-build-with-downloads.json" chrome_versions_json=$(curl -fsSL "${CHROME_VERSIONS_URL}") # Download and unpack the latest release of chromedriver chromedriver_version=$(echo "${chrome_versions_json}" | jq -r '.builds["'"$chrome_version"'"].version') chromedriver_url=$(echo "${chrome_versions_json}" | jq -r '.builds["'"$chrome_version"'"].downloads.chromedriver[] | select(.platform=="'"${CHROME_PLATFORM}"'").url') CHROMEDRIVER_DIR="/usr/local/share/chromedriver-linux64" chromedriver_bin="$CHROMEDRIVER_DIR/chromedriver" echo "Installing chromedriver version $chromedriver_version" driver_archive_path=$(download_with_retry "$chromedriver_url") unzip -qq "$driver_archive_path" -d /usr/local/share chmod +x $chromedriver_bin ln -s "$chromedriver_bin" /usr/bin/ set_etc_environment_variable "CHROMEWEBDRIVER" "${CHROMEDRIVER_DIR}" # Download and unpack Chromium chrome_revision=$(echo "${chrome_versions_json}" | jq -r '.builds["'"$chrome_version"'"].revision') chromium_revision=$(get_chromium_revision $chrome_revision) chromium_url="https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F${chromium_revision}%2Fchrome-linux.zip?alt=media" CHROMIUM_DIR="/usr/local/share/chromium" chromium_bin="${CHROMIUM_DIR}/chrome-linux/chrome" echo "Installing chromium revision $chromium_revision" chromium_archive_path=$(download_with_retry "$chromium_url") mkdir $CHROMIUM_DIR unzip -qq "$chromium_archive_path" -d $CHROMIUM_DIR ln -s $chromium_bin /usr/bin/chromium ln -s $chromium_bin /usr/bin/chromium-browser invoke_tests "Browsers" "Chrome" invoke_tests "Browsers" "Chromium" ================================================ FILE: images/ubuntu/scripts/build/install-google-cloud-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-google-cloud-cli.sh ## Desc: Install the Google Cloud CLI ################################################################################ REPO_URL="https://packages.cloud.google.com/apt" # Install the Google Cloud CLI echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] $REPO_URL cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list wget -qO- https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor > /usr/share/keyrings/cloud.google.gpg apt-get update apt-get install google-cloud-cli # remove apt rm /etc/apt/sources.list.d/google-cloud-sdk.list rm /usr/share/keyrings/cloud.google.gpg # add repo to the apt-sources.txt echo "google-cloud-sdk $REPO_URL" >> $HELPER_SCRIPTS/apt-sources.txt invoke_tests "CLI.Tools" "Google Cloud CLI" ================================================ FILE: images/ubuntu/scripts/build/install-haskell.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-haskell.sh ## Desc: Install Haskell, GHCup, Cabal and Stack ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh # Any nonzero value for non-interactive installation export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 export BOOTSTRAP_HASKELL_INSTALL_NO_STACK_HOOK=1 export GHCUP_INSTALL_BASE_PREFIX=/usr/local export BOOTSTRAP_HASKELL_GHC_VERSION=0 ghcup_bin=$GHCUP_INSTALL_BASE_PREFIX/.ghcup/bin set_etc_environment_variable "BOOTSTRAP_HASKELL_NONINTERACTIVE" $BOOTSTRAP_HASKELL_NONINTERACTIVE set_etc_environment_variable "GHCUP_INSTALL_BASE_PREFIX" $GHCUP_INSTALL_BASE_PREFIX # Install GHCup curl --proto '=https' --tlsv1.2 -fsSL https://get-ghcup.haskell.org | sh > /dev/null 2>&1 || true export PATH="$ghcup_bin:$PATH" prepend_etc_environment_path $ghcup_bin available_versions=$(ghcup list -t ghc -r | grep -v "prerelease" | awk '{print $2}') # Install latest Haskell Major.Minor version major_minor_versions=$(echo "$available_versions" | cut -d"." -f 1,2 | uniq | tail -n1) for major_minor_version in $major_minor_versions; do full_version=$(echo "$available_versions" | grep "$major_minor_version." | tail -n1) echo "install ghc version $full_version..." ghcup install ghc $full_version ghcup set ghc $full_version done echo "install cabal..." ghcup install cabal latest chmod -R 777 $GHCUP_INSTALL_BASE_PREFIX/.ghcup ln -s $GHCUP_INSTALL_BASE_PREFIX/.ghcup /etc/skel/.ghcup # Install the latest stable release of haskell stack curl -fsSL https://get.haskellstack.org/ | bash invoke_tests "Haskell" ================================================ FILE: images/ubuntu/scripts/build/install-heroku.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-heroku.sh ## Desc: Install Heroku CLI. Based on instructions found here: https://devcenter.heroku.com/articles/heroku-cli ################################################################################ REPO_URL="https://cli-assets.heroku.com/channels/stable/apt" GPG_KEY="/usr/share/keyrings/heroku.gpg" REPO_PATH="/etc/apt/sources.list.d/heroku.list" # add heroku repository to apt curl -fsSL "${REPO_URL}/release.key" | gpg --dearmor -o $GPG_KEY echo "deb [trusted=yes] $REPO_URL ./" > $REPO_PATH # install heroku apt-get update apt-get install heroku # remove heroku's apt repository rm $REPO_PATH rm $GPG_KEY invoke_tests "Tools" "Heroku" ================================================ FILE: images/ubuntu/scripts/build/install-homebrew.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-homebrew.sh ## Desc: Install Homebrew on Linux ## Caveat: Brew MUST NOT be used to install any tool during the image build to avoid dependencies, which may come along with the tool ################################################################################ # Source the helpers source $HELPER_SCRIPTS/etc-environment.sh source $HELPER_SCRIPTS/install.sh # Install the Homebrew on Linux /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # Invoke shellenv to make brew available during running session eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" set_etc_environment_variable HOMEBREW_NO_AUTO_UPDATE 1 set_etc_environment_variable HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS 3650 # Validate the installation ad hoc echo "Validate the installation reloading /etc/environment" reload_etc_environment gfortran=$(brew --prefix)/bin/gfortran # Remove gfortran symlink, not to conflict with system gfortran if [[ -e $gfortran ]]; then rm $gfortran fi invoke_tests "Tools" "Homebrew" ================================================ FILE: images/ubuntu/scripts/build/install-java-tools.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-java-tools.sh ## Desc: Install Java and related tooling (Ant, Gradle, Maven) ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh create_java_environment_variable() { local java_version=$1 local default=$2 local install_path_pattern="/usr/lib/jvm/temurin-${java_version}-jdk-amd64" if [[ ${default} == "True" ]]; then echo "Setting up JAVA_HOME variable to ${install_path_pattern}" set_etc_environment_variable "JAVA_HOME" "${install_path_pattern}" echo "Setting up default symlink" update-java-alternatives -s ${install_path_pattern} fi echo "Setting up JAVA_HOME_${java_version}_X64 variable to ${install_path_pattern}" set_etc_environment_variable "JAVA_HOME_${java_version}_X64" "${install_path_pattern}" } install_open_jdk() { local java_version=$1 # Install Java from PPA repositories. apt-get -y install temurin-${java_version}-jdk=\* java_version_path="/usr/lib/jvm/temurin-${java_version}-jdk-amd64" java_toolcache_path="${AGENT_TOOLSDIRECTORY}/Java_Temurin-Hotspot_jdk" full_java_version=$(cat "${java_version_path}/release" | grep "^SEMANTIC" | cut -d "=" -f 2 | tr -d "\"" | tr "+" "-") # If there is no semver in java release, then extract java version from -fullversion [[ -z ${full_java_version} ]] && full_java_version=$(${java_version_path}/bin/java -fullversion 2>&1 | tr -d "\"" | tr "+" "-" | awk '{print $4}') # Convert non valid semver like 11.0.14.1-9 -> 11.0.14-9 # https://github.com/adoptium/temurin-build/issues/2248 [[ ${full_java_version} =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]] && full_java_version=$(echo $full_java_version | sed -E 's/\.[0-9]+-/-/') # When version string is too short, add extra ".0" to make it valid semver [[ ${full_java_version} =~ ^[0-9]+- ]] && full_java_version=$(echo $full_java_version | sed -E 's/-/.0-/') [[ ${full_java_version} =~ ^[0-9]+\.[0-9]+- ]] && full_java_version=$(echo $full_java_version | sed -E 's/-/.0-/') java_toolcache_version_path="${java_toolcache_path}/${full_java_version}" echo "Java ${java_version} Toolcache Version Path: ${java_toolcache_version_path}" mkdir -p "${java_toolcache_version_path}" # Create a complete file touch "${java_toolcache_version_path}/x64.complete" # Create symlink for Java ln -s ${java_version_path} "${java_toolcache_version_path}/x64" # add extra permissions to be able execute command without sudo chmod -R 777 /usr/lib/jvm } # Add Adoptium PPA # apt-key is deprecated, dearmor and add manually wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor > /usr/share/keyrings/adoptium.gpg echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/adoptium.list # Get all the updates from enabled repositories. apt-get update # While Ubuntu 24.04 binaries are not released in the Adoptium repo, we will not install Java defaultVersion=$(get_toolset_value '.java.default') jdkVersionsToInstall=($(get_toolset_value ".java.versions[]")) for jdkVersionToInstall in ${jdkVersionsToInstall[@]}; do install_open_jdk ${jdkVersionToInstall} if [[ ${jdkVersionToInstall} == ${defaultVersion} ]] then create_java_environment_variable ${jdkVersionToInstall} True else create_java_environment_variable ${jdkVersionToInstall} False fi done # Install Ant apt-get install --no-install-recommends ant ant-optional set_etc_environment_variable "ANT_HOME" "/usr/share/ant" # Install Maven mavenVersion=$(get_toolset_value '.java.maven') mavenDownloadUrl="https://dlcdn.apache.org/maven/maven-3/${mavenVersion}/binaries/apache-maven-${mavenVersion}-bin.zip" maven_archive_path=$(download_with_retry "$mavenDownloadUrl") unzip -qq -d /usr/share "$maven_archive_path" ln -s /usr/share/apache-maven-${mavenVersion}/bin/mvn /usr/bin/mvn # Install Gradle # This script founds the latest gradle release from https://services.gradle.org/versions/all # The release is downloaded, extracted, a symlink is created that points to it, and GRADLE_HOME is set. gradleJson=$(curl -fsSL https://services.gradle.org/versions/all) gradleLatestVersion=$(echo ${gradleJson} | jq -r '.[] | select(.version | contains("-") | not).version' | sort -V | tail -n1) gradleDownloadUrl=$(echo ${gradleJson} | jq -r ".[] | select(.version==\"$gradleLatestVersion\") | .downloadUrl") echo "gradleUrl=${gradleDownloadUrl}" echo "gradleVersion=${gradleLatestVersion}" gradle_archive_path=$(download_with_retry "$gradleDownloadUrl") unzip -qq -d /usr/share "$gradle_archive_path" ln -s /usr/share/gradle-"${gradleLatestVersion}"/bin/gradle /usr/bin/gradle gradle_home_dir=$(find /usr/share -depth -maxdepth 1 -name "gradle*") set_etc_environment_variable "GRADLE_HOME" "${gradle_home_dir}" # Delete java repositories and keys rm -f /etc/apt/sources.list.d/adoptium.list rm -f /etc/apt/sources.list.d/zulu.list rm -f /usr/share/keyrings/adoptium.gpg rm -f /usr/share/keyrings/zulu.gpg reload_etc_environment invoke_tests "Java" ================================================ FILE: images/ubuntu/scripts/build/install-julia.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-julia.sh ## Desc: Install Julia and add to the path ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # get the latest julia version json=$(curl -fsSL "https://julialang-s3.julialang.org/bin/versions.json") julia_version=$(echo $json | jq -r '.[].files[] | select(.triplet=="x86_64-linux-gnu" and (.version | contains("-") | not)).version' | sort -V | tail -n1) # download julia archive julia_tar_url=$(echo $json | jq -r ".[].files[].url | select(endswith(\"julia-${julia_version}-linux-x86_64.tar.gz\"))") julia_archive_path=$(download_with_retry "$julia_tar_url") # extract files and make symlink julia_installation_path="/usr/local/julia${julia_version}" mkdir -p "${julia_installation_path}" tar -C "${julia_installation_path}" -xzf "$julia_archive_path" --strip-components=1 ln -s "${julia_installation_path}/bin/julia" /usr/bin/julia invoke_tests "Tools" "Julia" ================================================ FILE: images/ubuntu/scripts/build/install-kotlin.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-kotlin.sh ## Desc: Install Kotlin ## Supply chain security: Kotlin - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh KOTLIN_ROOT="/usr/share" download_url=$(resolve_github_release_asset_url "JetBrains/kotlin" "contains(\"kotlin-compiler\") and endswith(\".zip\")" "latest") archive_path=$(download_with_retry "$download_url") # Supply chain security - Kotlin kotlin_hash_file=$(download_with_retry "${download_url}.sha256") kotlin_hash=$(cat "$kotlin_hash_file") use_checksum_comparison "$archive_path" "$kotlin_hash" unzip -qq "$archive_path" -d $KOTLIN_ROOT rm $KOTLIN_ROOT/kotlinc/bin/*.bat ln -sf $KOTLIN_ROOT/kotlinc/bin/* /usr/bin invoke_tests "Tools" "Kotlin" ================================================ FILE: images/ubuntu/scripts/build/install-kubernetes-tools.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-kubernetes-tools.sh ## Desc: Installs kubectl, helm, kustomize ## Supply chain security: KIND, minikube - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Download KIND kind_url=$(resolve_github_release_asset_url "kubernetes-sigs/kind" "endswith(\"kind-linux-amd64\")" "latest") kind_binary_path=$(download_with_retry "${kind_url}") # Supply chain security - KIND kind_external_hash=$(get_checksum_from_url "${kind_url}.sha256sum" "kind-linux-amd64" "SHA256") use_checksum_comparison "${kind_binary_path}" "${kind_external_hash}" # Install KIND install "${kind_binary_path}" /usr/local/bin/kind ## Install kubectl # Ensure keyrings directory exists only if it doesn't already [ -d /etc/apt/keyrings ] || sudo mkdir -p -m 755 /etc/apt/keyrings kubectl_minor_version=$(curl -fsSL --retry 5 --retry-delay 10 "https://dl.k8s.io/release/stable.txt" | cut -d'.' -f1,2 ) # Download and validate GPG key key_url="https://pkgs.k8s.io/core:/stable:/$kubectl_minor_version/deb/Release.key" if curl -fsSL --retry 5 --retry-delay 10 -A "Mozilla/5.0" "$key_url" | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg; then echo "Key downloaded and stored successfully." else echo "Failed to download valid GPG key from: $key_url" exit 1 fi echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/'$kubectl_minor_version'/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list apt-get update apt-get install kubectl rm -f /etc/apt/sources.list.d/kubernetes.list # Install Helm curl -fsSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash # Download and install minikube minikube_version="latest" minikube_binary_path=$(download_with_retry "https://storage.googleapis.com/minikube/releases/${minikube_version}/minikube-linux-amd64") # Supply chain security - Minikube minikube_hash=$(get_checksum_from_github_release "kubernetes/minikube" "linux-amd64" "${minikube_version}" "SHA256") use_checksum_comparison "${minikube_binary_path}" "${minikube_hash}" install "${minikube_binary_path}" /usr/local/bin/minikube # Install kustomize download_url="https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" curl -fsSL "$download_url" | bash mv kustomize /usr/local/bin invoke_tests "Tools" "Kubernetes tools" ================================================ FILE: images/ubuntu/scripts/build/install-leiningen.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-leiningen.sh ## Desc: Install Leiningen ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh LEIN_BIN=/usr/local/bin/lein curl -fsSL https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein > $LEIN_BIN chmod 0755 $LEIN_BIN # Run lein to trigger self-install export LEIN_HOME=/usr/local/lib/lein lein LEIN_JAR=$(find $LEIN_HOME -name "leiningen-*-standalone.jar") set_etc_environment_variable "LEIN_JAR" "${LEIN_JAR}" set_etc_environment_variable "LEIN_HOME" "${LEIN_HOME}" invoke_tests "Tools" "Leiningen" ================================================ FILE: images/ubuntu/scripts/build/install-microsoft-edge.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-microsoft-edge.sh ## Desc: Install Microsoft Edge and WebDriver ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh REPO_URL="https://packages.microsoft.com/repos/edge" GPG_KEY="/usr/share/keyrings/microsoft-edge.gpg" REPO_PATH="/etc/apt/sources.list.d/microsoft-edge.list" wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > $GPG_KEY # Specify an arch as Microsoft repository supports armhf and arm64 as well echo "deb [arch=amd64 signed-by=$GPG_KEY] $REPO_URL stable main" > $REPO_PATH apt-get update apt-get install --no-install-recommends microsoft-edge-stable rm $GPG_KEY rm $REPO_PATH echo "microsoft-edge $REPO_URL" >> $HELPER_SCRIPTS/apt-sources.txt # Install Microsoft Edge Webdriver EDGEDRIVER_DIR="/usr/local/share/edge_driver" edgedriver_bin="$EDGEDRIVER_DIR/msedgedriver" mkdir -p $EDGEDRIVER_DIR edge_version=$(microsoft-edge --version | cut -d' ' -f 3) edge_version_major=$(echo $edge_version | cut -d'.' -f 1) edgedriver_version_url="https://msedgedriver.microsoft.com/LATEST_RELEASE_${edge_version_major}_LINUX" # Convert a resulting file to normal UTF-8 edgedriver_latest_version=$(curl -fsSL "$edgedriver_version_url" | iconv -f utf-16 -t utf-8 | tr -d '\r') edgedriver_url="https://msedgedriver.microsoft.com/${edgedriver_latest_version}/edgedriver_linux64.zip" edgedriver_archive_path=$(download_with_retry "$edgedriver_url") unzip -qq "$edgedriver_archive_path" -d "$EDGEDRIVER_DIR" chmod +x $edgedriver_bin ln -s $edgedriver_bin /usr/bin set_etc_environment_variable "EDGEWEBDRIVER" "${EDGEDRIVER_DIR}" invoke_tests "Browsers" "Edge" ================================================ FILE: images/ubuntu/scripts/build/install-miniconda.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-miniconda.sh ## Desc: Install miniconda ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh # Install Miniconda curl -fsSL https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -o miniconda.sh \ && chmod +x miniconda.sh \ && ./miniconda.sh -b -p /usr/share/miniconda \ && rm miniconda.sh CONDA=/usr/share/miniconda set_etc_environment_variable "CONDA" "${CONDA}" ln -s $CONDA/bin/conda /usr/bin/conda invoke_tests "Tools" "Conda" ================================================ FILE: images/ubuntu/scripts/build/install-mono.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-mono.sh ## Desc: Install Mono ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh os_label=$(lsb_release -cs) REPO_URL="https://download.mono-project.com/repo/ubuntu" GPG_KEY="/usr/share/keyrings/mono-official-stable.gpg" REPO_PATH="/etc/apt/sources.list.d/mono-official-stable.list" # There are no packages for Ubuntu 22 in the repo, but developers confirmed that packages from Ubuntu 20 should work if is_ubuntu22; then os_label="focal" fi # Install Mono repo curl -fsSL https://download.mono-project.com/repo/xamarin.gpg | gpg --dearmor -o $GPG_KEY echo "deb [signed-by=$GPG_KEY] $REPO_URL stable-$os_label main" > $REPO_PATH # Install Mono apt-get update apt-get install --no-install-recommends apt-transport-https mono-complete nuget # Remove Mono's apt repo rm $REPO_PATH rm -f "${REPO_PATH}.save" rm $GPG_KEY # Document source repo echo "mono https://download.mono-project.com/repo/ubuntu stable-$os_label main" >> $HELPER_SCRIPTS/apt-sources.txt invoke_tests "Tools" "Mono" ================================================ FILE: images/ubuntu/scripts/build/install-ms-repos.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-ms-repos.sh ## Desc: Install official Microsoft package repos for the distribution ################################################################################ os_label=$(lsb_release -rs) # Install Microsoft repository wget https://packages.microsoft.com/config/ubuntu/$os_label/packages-microsoft-prod.deb dpkg -i packages-microsoft-prod.deb # update apt-get install apt-transport-https ca-certificates curl software-properties-common apt-get update apt-get dist-upgrade ================================================ FILE: images/ubuntu/scripts/build/install-mssql-tools.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-mssql-tools.sh ## Desc: Install MS SQL Server client tools (https://docs.microsoft.com/en-us/sql/linux/sql-server-linux-setup-tools?view=sql-server-2017) ################################################################################ export ACCEPT_EULA=Y apt-get update apt-get install mssql-tools unixodbc-dev apt-get -f install ln -s /opt/mssql-tools/bin/* /usr/local/bin/ invoke_tests "Tools" "MSSQLCommandLineTools" ================================================ FILE: images/ubuntu/scripts/build/install-mysql.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-mysql.sh ## Desc: Install MySQL Client ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh # Mysql setting up root password MYSQL_ROOT_PASSWORD=root echo "mysql-server mysql-server/root_password password $MYSQL_ROOT_PASSWORD" | debconf-set-selections echo "mysql-server mysql-server/root_password_again password $MYSQL_ROOT_PASSWORD" | debconf-set-selections export ACCEPT_EULA=Y # Install MySQL Client apt-get install mysql-client # Install MySQL Server apt-get install mysql-server # Install MySQL Dev tools apt-get install libmysqlclient-dev # Disable mysql.service systemctl is-active --quiet mysql.service && systemctl stop mysql.service systemctl disable mysql.service invoke_tests "Databases" "MySQL" ================================================ FILE: images/ubuntu/scripts/build/install-nginx.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-nginx.sh ## Desc: Install Nginx ################################################################################ # Install Nginx apt-get install nginx # Disable nginx.service systemctl is-active --quiet nginx.service && systemctl stop nginx.service systemctl disable nginx.service invoke_tests "WebServers" "Nginx" ================================================ FILE: images/ubuntu/scripts/build/install-ninja.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-ninja.sh ## Desc: Install ninja-build ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Install ninja download_url=$(resolve_github_release_asset_url "ninja-build/ninja" "endswith(\"ninja-linux.zip\")" "latest") ninja_binary_path=$(download_with_retry "${download_url}") # Unzip the ninja binary unzip -qq "$ninja_binary_path" -d /usr/local/bin invoke_tests "Tools" "Ninja" ================================================ FILE: images/ubuntu/scripts/build/install-nodejs.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-nodejs.sh ## Desc: Install Node.js LTS and related tooling (Gulp, Grunt) ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Install default Node.js default_version=$(get_toolset_value '.node.default') curl -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n -o ~/n bash ~/n $default_version # Install node modules node_modules=$(get_toolset_value '.node_modules[].name') npm install -g $node_modules echo "Creating the symlink for [now] command to vercel CLI" ln -s /usr/local/bin/vercel /usr/local/bin/now # fix global modules installation as regular user # related issue https://github.com/actions/runner-images/issues/3727 sudo chmod -R 777 /usr/local/lib/node_modules sudo chmod -R 777 /usr/local/bin rm -rf ~/n invoke_tests "Node" "Node.js" ================================================ FILE: images/ubuntu/scripts/build/install-nvm.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-nvm.sh ## Desc: Install Nvm ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh export NVM_DIR="/etc/skel/.nvm" mkdir $NVM_DIR nvm_version=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name') curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh | bash set_etc_environment_variable "NVM_DIR" '$HOME/.nvm' echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' | tee -a /etc/skel/.bash_profile [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" invoke_tests "Tools" "nvm" # set system node.js as default one nvm alias default system ================================================ FILE: images/ubuntu/scripts/build/install-oc-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-oc-cli.sh ## Desc: Install the OC CLI ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/install.sh # Install the oc CLI download_url="https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-linux.tar.gz" archive_path=$(download_with_retry "$download_url") tar xzf "$archive_path" -C "/usr/local/bin" oc invoke_tests "CLI.Tools" "OC CLI" ================================================ FILE: images/ubuntu/scripts/build/install-oras-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-oras-cli.sh ## Desc: Install ORAS CLI ## Supply chain security: ORAS CLI - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Determine latest ORAS CLI version download_url=$(resolve_github_release_asset_url "oras-project/oras" "endswith(\"linux_amd64.tar.gz\")" "latest") # Download ORAS CLI archive_path=$(download_with_retry "$download_url") # Supply chain security - ORAS CLI hash_url=$(resolve_github_release_asset_url "oras-project/oras" "endswith(\"checksums.txt\")" "latest") external_hash=$(get_checksum_from_url "${hash_url}" "linux_amd64.tar.gz" "SHA256") use_checksum_comparison "$archive_path" "${external_hash}" # Unzip ORAS CLI tar xzf "$archive_path" -C /usr/local/bin oras invoke_tests "CLI.Tools" "Oras CLI" ================================================ FILE: images/ubuntu/scripts/build/install-packer.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-packer.sh ## Desc: Install packer ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Install Packer download_url=$(curl -fsSL https://api.releases.hashicorp.com/v1/releases/packer/latest | jq -r '.builds[] | select((.arch=="amd64") and (.os=="linux")).url') archive_path=$(download_with_retry "$download_url") unzip -o -qq "$archive_path" -d /usr/local/bin invoke_tests "Tools" "Packer" ================================================ FILE: images/ubuntu/scripts/build/install-php.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-php.sh ## Desc: Install php ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/install.sh # Install PHP php_versions=$(get_toolset_value '.php.versions[]') for version in $php_versions; do echo "Installing PHP $version" apt-get install --no-install-recommends \ php$version \ php$version-amqp \ php$version-apcu \ php$version-bcmath \ php$version-bz2 \ php$version-cgi \ php$version-cli \ php$version-common \ php$version-curl \ php$version-dba \ php$version-dev \ php$version-enchant \ php$version-fpm \ php$version-gd \ php$version-gmp \ php$version-igbinary \ php$version-imagick \ php$version-imap \ php$version-interbase \ php$version-intl \ php$version-ldap \ php$version-mbstring \ php$version-memcache \ php$version-memcached \ php$version-mongodb \ php$version-mysql \ php$version-odbc \ php$version-opcache \ php$version-pgsql \ php$version-phpdbg \ php$version-pspell \ php$version-readline \ php$version-redis \ php$version-snmp \ php$version-soap \ php$version-sqlite3 \ php$version-sybase \ php$version-tidy \ php$version-xdebug \ php$version-xml \ php$version-xsl \ php$version-yaml \ php$version-zip \ php$version-zmq apt-get install --no-install-recommends php$version-pcov # Disable PCOV, as Xdebug is enabled by default # https://github.com/krakjoe/pcov#interoperability phpdismod -v $version pcov if [[ $version == "7.2" || $version == "7.3" || $version == "7.4" ]]; then apt-get install --no-install-recommends php$version-recode fi if [[ $version != "8.0" && $version != "8.1" && $version != "8.2" && $version != "8.3" ]]; then apt-get install --no-install-recommends php$version-xmlrpc php$version-json fi done apt-get install --no-install-recommends php-pear apt-get install --no-install-recommends snmp # Install composer php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('sha384', 'composer-setup.php') === file_get_contents('https://composer.github.io/installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php sudo mv composer.phar /usr/bin/composer php -r "unlink('composer-setup.php');" # Add composer bin folder to path prepend_etc_environment_path '$HOME/.config/composer/vendor/bin' #Create composer folder for user to preserve folder permissions mkdir -p /etc/skel/.composer # Install phpunit (for PHP) wget -q -O phpunit https://phar.phpunit.de/phpunit-8.phar install phpunit /usr/local/bin/phpunit invoke_tests "Common" "PHP" ================================================ FILE: images/ubuntu/scripts/build/install-pipx-packages.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-pipx-packages.sh ## Desc: Install tools via pipx ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh export PATH="$PATH:/opt/pipx_bin" pipx_packages=$(get_toolset_value ".pipx[] .package") for package in $pipx_packages; do echo "Install $package into default python" pipx install $package # https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html # Install ansible into an existing ansible-core Virtual Environment if [[ $package == "ansible-core" ]]; then pipx inject $package ansible fi done invoke_tests "Common" "PipxPackages" ================================================ FILE: images/ubuntu/scripts/build/install-postgresql.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-postgresql.sh ## Desc: Install PostgreSQL ################################################################################ # Source the helpers source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/install.sh REPO_URL="https://apt.postgresql.org/pub/repos/apt/" # Preparing repo for PostgreSQL wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] $REPO_URL $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list # Fetch PostgreSQL version to install from the toolset toolset_version=$(get_toolset_value '.postgresql.version') # Install PostgreSQL echo "Install PostgreSQL" apt update apt-get install postgresql-$toolset_version echo "Install libpq-dev" apt-get install libpq-dev # Disable postgresql.service systemctl is-active --quiet postgresql.service && systemctl stop postgresql.service systemctl disable postgresql.service rm /etc/apt/sources.list.d/pgdg.list rm /usr/share/keyrings/postgresql.gpg echo "postgresql $REPO_URL" >> $HELPER_SCRIPTS/apt-sources.txt invoke_tests "Databases" "PostgreSQL" ================================================ FILE: images/ubuntu/scripts/build/install-powershell.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-powershell.sh ## Desc: Install PowerShell Core ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/os.sh pwsh_version=$(get_toolset_value .pwsh.version) # Install Powershell apt-get install powershell=$pwsh_version* ================================================ FILE: images/ubuntu/scripts/build/install-pulumi.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-pulumi.sh ## Desc: Install Pulumi ## Supply chain security: Pulumi - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Dowload Pulumi version=$(curl -fsSL "https://www.pulumi.com/latest-version") download_url="https://get.pulumi.com/releases/sdk/pulumi-v${version}-linux-x64.tar.gz" archive_path=$(download_with_retry "$download_url") # Supply chain security - Pulumi external_hash=$(get_checksum_from_url "https://github.com/pulumi/pulumi/releases/download/v${version}/SHA512SUMS" "linux-x64.tar.gz" "SHA512") use_checksum_comparison "$archive_path" "$external_hash" "512" # Unzipping Pulumi tar --strip=1 -xf "$archive_path" -C /usr/local/bin invoke_tests "Tools" "Pulumi" ================================================ FILE: images/ubuntu/scripts/build/install-pypy.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-pypy.sh ## Desc: Install PyPy ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # This function installs PyPy using the specified arguments: # $1=package_url install_pypy() { local package_url=$1 package_tar_name=$(echo "$package_url" | awk -F/ '{print $NF}') package_name=${package_tar_name/.tar.bz2/} echo "Downloading tar archive '$package_name'" package_tar_temp_path=$(download_with_retry $package_url) echo "Expand '$package_name' to the /tmp folder" tar xf "$package_tar_temp_path" -C /tmp # Get Python version major_version=$(echo ${package_name/pypy/} | cut -d. -f1) python_major="python$major_version" if [ $major_version != 2 ]; then pypy_major="pypy$major_version" else pypy_major="pypy" fi package_temp_folder="/tmp/$package_name" python_full_version=$("$package_temp_folder/bin/$pypy_major" -c "import sys;print('{}.{}.{}'.format(sys.version_info[0],sys.version_info[1],sys.version_info[2]))") pypy_full_version=$("$package_temp_folder/bin/$pypy_major" -c "import sys;print('{}.{}.{}'.format(*sys.pypy_version_info[0:3]))") echo "Put '$pypy_full_version' to PYPY_VERSION file" echo $pypy_full_version > "$package_temp_folder/PYPY_VERSION" # PyPy folder structure pypy_toolcache_path=$AGENT_TOOLSDIRECTORY/PyPy pypy_toolcache_version_path=$pypy_toolcache_path/$python_full_version pypy_toolcache_version_arch_path=$pypy_toolcache_version_path/x64 echo "Check if PyPy hostedtoolcache folder exist..." if [ ! -d $pypy_toolcache_path ]; then mkdir -p $pypy_toolcache_path fi echo "Create PyPy '$pypy_toolcache_version_path' folder" mkdir $pypy_toolcache_version_path echo "Move PyPy '$package_temp_folder' binaries to '$pypy_toolcache_version_arch_path' folder" mv $package_temp_folder $pypy_toolcache_version_arch_path echo "Create additional symlinks (Required for UsePythonVersion Azure DevOps task)" cd $pypy_toolcache_version_arch_path/bin # Starting from PyPy 7.3.4 these links are already included in the package [ -f ./$python_major ] || ln -s $pypy_major $python_major [ -f ./python ] || ln -s $python_major python chmod +x ./python ./$python_major echo "Install latest Pip" ./python -m ensurepip ./python -m pip install --ignore-installed pip echo "Create complete file" touch $pypy_toolcache_version_path/x64.complete echo "Remove '$package_tar_temp_path'" rm -f $package_tar_temp_path } # Installation PyPy pypy_versions_json=$(curl -fsSL https://downloads.python.org/pypy/versions.json) toolset_versions=$(get_toolset_value '.toolcache[] | select(.name | contains("PyPy")) | .versions[]') for toolset_version in $toolset_versions; do latest_major_pypy_version=$(echo $pypy_versions_json | jq -r --arg toolset_version $toolset_version '.[] | select((.python_version | startswith($toolset_version)) and .stable == true).files[] | select(.arch == "x64" and .platform == "linux").download_url' | head -1) if [[ -z "$latest_major_pypy_version" ]]; then echo "Failed to get PyPy version '$toolset_version'" exit 1 fi install_pypy $latest_major_pypy_version done chown -R "$SUDO_USER:$SUDO_USER" "$AGENT_TOOLSDIRECTORY/PyPy" ================================================ FILE: images/ubuntu/scripts/build/install-python.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-python.sh ## Desc: Install Python 3 ################################################################################ set -e # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh source $HELPER_SCRIPTS/os.sh # Install Python, Python 3, pip, pip3 apt-get install --no-install-recommends python3 python3-dev python3-pip python3-venv if is_ubuntu24; then # Create temporary workaround to allow user to continue using pip sudo cat < /etc/pip.conf [global] break-system-packages = true EOF fi # Install pipx # Set pipx custom directory export PIPX_BIN_DIR=/opt/pipx_bin export PIPX_HOME=/opt/pipx python3 -m pip install pipx python3 -m pipx ensurepath # Update /etc/environment set_etc_environment_variable "PIPX_BIN_DIR" $PIPX_BIN_DIR set_etc_environment_variable "PIPX_HOME" $PIPX_HOME prepend_etc_environment_path $PIPX_BIN_DIR # Test pipx if ! command -v pipx; then echo "pipx was not installed or not found on PATH" exit 1 fi # Adding this dir to PATH will make installed pip commands are immediately available. prepend_etc_environment_path '$HOME/.local/bin' invoke_tests "Tools" "Python" ================================================ FILE: images/ubuntu/scripts/build/install-rlang.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-rlang.sh ## Desc: Install R ################################################################################ # install R os_label=$(lsb_release -cs) wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | gpg --dearmor > /usr/share/keyrings/rlang.gpg echo "deb [signed-by=/usr/share/keyrings/rlang.gpg] https://cloud.r-project.org/bin/linux/ubuntu $os_label-cran40/" > /etc/apt/sources.list.d/rlang.list apt-get update apt-get install r-base rm /etc/apt/sources.list.d/rlang.list rm /usr/share/keyrings/rlang.gpg invoke_tests "Tools" "R" ================================================ FILE: images/ubuntu/scripts/build/install-ruby.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-ruby.sh ## Desc: Install Ruby requirements and ruby gems ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/install.sh apt-get install ruby-full # Install ruby gems from toolset gems_to_install=$(get_toolset_value ".rubygems[] .name") if [[ -n "$gems_to_install" ]]; then for gem in $gems_to_install; do echo "Installing gem $gem" gem install --no-document $gem done fi # Install Ruby requirements apt-get install libz-dev openssl libssl-dev echo "Install Ruby from toolset..." toolset_versions=$(get_toolset_value '.toolcache[] | select(.name | contains("Ruby")) | .versions[]') platform_version=$(get_toolset_value '.toolcache[] | select(.name | contains("Ruby")) | .platform_version') arch=$(get_toolset_value '.toolcache[] | select(.name | contains("Ruby")) | .arch') ruby_path="$AGENT_TOOLSDIRECTORY/Ruby" echo "Check if Ruby hostedtoolcache folder exist..." if [[ ! -d $ruby_path ]]; then mkdir -p $ruby_path fi for toolset_version in ${toolset_versions[@]}; do download_url=$(resolve_github_release_asset_url "ruby/ruby-builder" "test(\"ruby-${toolset_version}-ubuntu-${platform_version}-${arch}.tar.gz\")" "${toolset_version}" "false" "true") package_tar_name="${download_url##*/}" ruby_version=$(echo "$package_tar_name" | cut -d'-' -f 2) ruby_version_path="$ruby_path/$ruby_version" echo "Create Ruby $ruby_version directory..." mkdir -p $ruby_version_path echo "Downloading tar archive $package_tar_name" package_archive_path=$(download_with_retry "$download_url") echo "Expand '$package_tar_name' to the '$ruby_version_path' folder" tar xf "$package_archive_path" -C $ruby_version_path complete_file_path="$ruby_version_path/x64.complete" if [[ ! -f $complete_file_path ]]; then echo "Create complete file" touch $complete_file_path fi done invoke_tests "Tools" "Ruby" ================================================ FILE: images/ubuntu/scripts/build/install-rust.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-rust.sh ## Desc: Install Rust ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh source $HELPER_SCRIPTS/os.sh export RUSTUP_HOME=/etc/skel/.rustup export CARGO_HOME=/etc/skel/.cargo curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain=stable --profile=minimal # Initialize environment variables source $CARGO_HOME/env # Install common tools rustup component add rustfmt clippy if is_ubuntu22; then cargo install --locked bindgen-cli cbindgen cargo-audit cargo-outdated fi # Cleanup Cargo cache rm -rf ${CARGO_HOME}/registry/* # Update /etc/environment prepend_etc_environment_path '$HOME/.cargo/bin' invoke_tests "Tools" "Rust" ================================================ FILE: images/ubuntu/scripts/build/install-sbt.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-sbt.sh ## Desc: Install sbt ################################################################################ source $HELPER_SCRIPTS/install.sh # Install latest sbt release download_url=$(resolve_github_release_asset_url "sbt/sbt" "endswith(\".tgz\")" "latest") archive_path=$(download_with_retry "$download_url") tar zxf "$archive_path" -C /usr/share ln -s /usr/share/sbt/bin/sbt /usr/bin/sbt invoke_tests "Tools" "Sbt" ================================================ FILE: images/ubuntu/scripts/build/install-selenium.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-selenium.sh ## Desc: Install selenium server ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh selenium_major_version=$(get_toolset_value '.selenium.version') # Download Selenium server selenium_download_url=$(resolve_github_release_asset_url "SeleniumHQ/selenium" "contains(\"selenium-server-\") and endswith(\".jar\")" "$selenium_major_version\.+" "" "true") selenium_jar_path=$(download_with_retry "$selenium_download_url" "/usr/share/java/selenium-server.jar") # Create an empty file to retrieve selenium version selenium_full_version=$(echo $selenium_download_url | awk -F"selenium-server-|.jar" '{print $2}') touch "/usr/share/java/selenium-server-$selenium_full_version" # Add SELENIUM_JAR_PATH environment variable set_etc_environment_variable "SELENIUM_JAR_PATH" "$selenium_jar_path" invoke_tests "Tools" "Selenium" ================================================ FILE: images/ubuntu/scripts/build/install-sqlpackage.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-sqlpackage.sh ## Desc: Install SqlPackage CLI to DacFx (https://docs.microsoft.com/sql/tools/sqlpackage/sqlpackage-download#get-sqlpackage-net-core-for-linux) ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/os.sh # Install libssl1.1 dependency if is_ubuntu22; then focal_list=/etc/apt/sources.list.d/focal-security.list echo "deb https://archive.ubuntu.com/ubuntu/ focal-security main" | tee "${focal_list}" apt-get update --quiet apt-get install --no-install-recommends libssl1.1 rm "${focal_list}" apt-get update --quiet fi # Install SqlPackage archive_path=$(download_with_retry "https://aka.ms/sqlpackage-linux") unzip -qq "$archive_path" -d /usr/local/sqlpackage chmod +x /usr/local/sqlpackage/sqlpackage ln -sf /usr/local/sqlpackage/sqlpackage /usr/local/bin invoke_tests "Tools" "SqlPackage" ================================================ FILE: images/ubuntu/scripts/build/install-swift.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-swift.sh ## Desc: Install Swift ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh # Install image_label="ubuntu$(lsb_release -rs)" swift_version=$(curl -fsSL "https://api.github.com/repos/apple/swift/releases/latest" | jq -r '.tag_name | match("[0-9.]+").string') swift_release_name="swift-${swift_version}-RELEASE-${image_label}" archive_url="https://swift.org/builds/swift-${swift_version}-release/${image_label//./}/swift-${swift_version}-RELEASE/${swift_release_name}.tar.gz" archive_path=$(download_with_retry "$archive_url") # Verifying PGP signature using official Swift PGP key. Referring to https://www.swift.org/install/linux/#Installation-via-Tarball # Download and import Swift PGP keys gpg --keyserver hkps://keyserver.ubuntu.com:443 \ --recv-keys \ '7463 A81A 4B2E EA1B 551F FBCF D441 C977 412B 37AD' \ '1BE1 E29A 084C B305 F397 D62A 9F59 7F4D 21A5 6D5F' \ 'A3BA FD35 56A5 9079 C068 94BD 63BC 1CFE 91D3 06C6' \ '5E4D F843 FB06 5D7F 7E24 FBA2 EF54 30F0 71E1 B235' \ '8513 444E 2DA3 6B7C 1659 AF4D 7638 F1FB 2B2B 08C4' \ 'A62A E125 BBBF BB96 A6E0 42EC 925C C1CC ED3D 1561' \ '8A74 9566 2C3C D4AE 18D9 5637 FAF6 989E 1BC1 6FEA' \ 'E813 C892 820A 6FA1 3755 B268 F167 DF1A CF9C E069' \ '52BB 7E3D E28A 71BE 22EC 05FF EF80 A866 B47A 981F' gpg --keyserver hkps://keyserver.ubuntu.com:443 --refresh-keys Swift # Download and verify signature signature_path=$(download_with_retry "${archive_url}.sig") gpg --verify "$signature_path" "$archive_path" # Remove Swift PGP public key with temporary keyring rm -rf ~/.gnupg # Extract and install swift tar xzf "$archive_path" -C /tmp SWIFT_INSTALL_ROOT="/usr/share/swift" swift_bin_root="$SWIFT_INSTALL_ROOT/usr/bin" swift_lib_root="$SWIFT_INSTALL_ROOT/usr/lib" mv "/tmp/${swift_release_name}" $SWIFT_INSTALL_ROOT mkdir -p /usr/local/lib ln -s "$swift_bin_root/swift" /usr/local/bin/swift ln -s "$swift_bin_root/swiftc" /usr/local/bin/swiftc ln -s "$swift_lib_root/libsourcekitdInProc.so" /usr/local/lib/libsourcekitdInProc.so set_etc_environment_variable "SWIFT_PATH" "${swift_bin_root}" invoke_tests "Common" "Swift" ================================================ FILE: images/ubuntu/scripts/build/install-terraform.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-terraform.sh ## Desc: Install terraform ################################################################################ source $HELPER_SCRIPTS/install.sh # Install Terraform download_url=$(curl -fsSL https://api.releases.hashicorp.com/v1/releases/terraform/latest | jq -r '.builds[] | select((.arch=="amd64") and (.os=="linux")).url') archive_path=$(download_with_retry "${download_url}") unzip -qq "$archive_path" -d /usr/local/bin invoke_tests "Tools" "Terraform" ================================================ FILE: images/ubuntu/scripts/build/install-vcpkg.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-vcpkg.sh ## Desc: Install vcpkg ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh # Set env variable for vcpkg VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg set_etc_environment_variable "VCPKG_INSTALLATION_ROOT" "${VCPKG_INSTALLATION_ROOT}" # Install vcpkg git clone https://github.com/Microsoft/vcpkg $VCPKG_INSTALLATION_ROOT $VCPKG_INSTALLATION_ROOT/bootstrap-vcpkg.sh # workaround https://github.com/microsoft/vcpkg/issues/27786 mkdir -p /root/.vcpkg/ $HOME/.vcpkg touch /root/.vcpkg/vcpkg.path.txt $HOME/.vcpkg/vcpkg.path.txt $VCPKG_INSTALLATION_ROOT/vcpkg integrate install chmod 0777 -R $VCPKG_INSTALLATION_ROOT ln -sf $VCPKG_INSTALLATION_ROOT/vcpkg /usr/local/bin rm -rf /root/.vcpkg $HOME/.vcpkg invoke_tests "Tools" "Vcpkg" ================================================ FILE: images/ubuntu/scripts/build/install-yq.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-yq.sh ## Desc: Install yq - a command-line YAML, JSON and XML processor ## Supply chain security: yq - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Download yq yq_url=$(resolve_github_release_asset_url "mikefarah/yq" "endswith(\"yq_linux_amd64\")" "latest") binary_path=$(download_with_retry "${yq_url}") # Supply chain security - yq hash_url=$(resolve_github_release_asset_url "mikefarah/yq" "endswith(\"checksums\")" "latest") external_hash=$(get_checksum_from_url "${hash_url}" "yq_linux_amd64 " "SHA256" "true" " " "19") use_checksum_comparison "$binary_path" "$external_hash" # Install yq install "$binary_path" /usr/bin/yq invoke_tests "Tools" "yq" ================================================ FILE: images/ubuntu/scripts/build/install-zstd.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-zstd.sh ## Desc: Install zstd ## Supply chain security: zstd - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Download zstd release_tag=$(curl -fsSL https://api.github.com/repos/facebook/zstd/releases/latest | jq -r '.tag_name') release_name="zstd-${release_tag//v}" download_url="https://github.com/facebook/zstd/releases/download/${release_tag}/${release_name}.tar.gz" archive_path=$(download_with_retry "${download_url}") # Supply chain security - zstd external_hash=$(get_checksum_from_url "${download_url}.sha256" "${release_name}.tar.gz" "SHA256") use_checksum_comparison "$archive_path" "$external_hash" # Install zstd apt-get install liblz4-dev tar xzf "$archive_path" -C /tmp make -C "/tmp/${release_name}/contrib/pzstd" -j $(nproc) all make -C "/tmp/${release_name}" -j $(nproc) zstd-release for copyprocess in zstd zstdless zstdgrep; do cp "/tmp/${release_name}/programs/${copyprocess}" /usr/local/bin/ done cp "/tmp/${release_name}/contrib/pzstd/pzstd" /usr/local/bin/ for symlink in zstdcat zstdmt unzstd; do ln -sf /usr/local/bin/zstd /usr/local/bin/${symlink} done invoke_tests "Tools" "Zstd" ================================================ FILE: images/ubuntu/scripts/build/list-dpkg.sh ================================================ #!/bin/bash -e ################################################################################ ## File: list-dpkg.sh ## Desc: List all installed dpkg packages ################################################################################ echo "Listing all installed dpkg packages..." dpkg-query -W -f='${Package} ${Version}\n' | sort ================================================ FILE: images/ubuntu/scripts/build/post-build-validation.sh ================================================ #!/bin/bash -e ################################################################################ ## File: post-build-validation.sh ## Desc: Validate different aspects of the image after build ################################################################################ echo "Test microsoft defender not installed using '-d /opt/microsoft/mdatp'" # Validate Defender not installed test 1 if [ -d /opt/microsoft/mdatp ]; then echo "Microsoft Defender for Endpoint is installed." exit 1 fi echo "Test microsoft defender not installed using 'systemctl list-units --type=service --all | grep mdatp'" # Validate Defender not installed test 2 if systemctl list-units --type=service --all | grep -w mdatp &>/dev/null; then echo "Microsoft Defender for Endpoint is installed." exit 1 fi ================================================ FILE: images/ubuntu/scripts/docs-gen/Generate-SoftwareReport.ps1 ================================================ using module ./software-report-base/SoftwareReport.psm1 using module ./software-report-base/SoftwareReport.Nodes.psm1 param ( [Parameter(Mandatory)] [string] $OutputDirectory ) $global:ErrorActionPreference = "Stop" $global:ErrorView = "NormalView" Set-StrictMode -Version Latest Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Android.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Browsers.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.CachedTools.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Common.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Databases.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Helpers.psm1") -DisableNameChecking Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Java.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Rust.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Tools.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.WebServers.psm1") -DisableNameChecking # Restore file owner in user profile sudo chown -R ${env:USER}: $env:HOME # Software report $softwareReport = [SoftwareReport]::new("Ubuntu $(Get-OSVersionShort)") $softwareReport.Root.AddToolVersion("OS Version:", $(Get-OSVersionFull)) $softwareReport.Root.AddToolVersion("Kernel Version:", $(Get-KernelVersion)) $softwareReport.Root.AddToolVersion("Image Version:", $env:IMAGE_VERSION) $softwareReport.Root.AddToolVersion("Systemd version:", $(Get-SystemdVersion)) $installedSoftware = $softwareReport.Root.AddHeader("Installed Software") # Language and Runtime $languageAndRuntime = $installedSoftware.AddHeader("Language and Runtime") $languageAndRuntime.AddToolVersion("Bash", $(Get-BashVersion)) $languageAndRuntime.AddToolVersionsListInline("Clang", $(Get-ClangToolVersions -ToolName "clang"), "^\d+") $languageAndRuntime.AddToolVersionsListInline("Clang-format", $(Get-ClangToolVersions -ToolName "clang-format"), "^\d+") $languageAndRuntime.AddToolVersionsListInline("Clang-tidy", $(Get-ClangTidyVersions), "^\d+") $languageAndRuntime.AddToolVersion("Dash", $(Get-DashVersion)) $languageAndRuntime.AddToolVersionsListInline("GNU C++", $(Get-CPPVersions), "^\d+") $languageAndRuntime.AddToolVersionsListInline("GNU Fortran", $(Get-FortranVersions), "^\d+") $languageAndRuntime.AddToolVersion("Julia", $(Get-JuliaVersion)) $languageAndRuntime.AddToolVersion("Kotlin", $(Get-KotlinVersion)) if (-not $(Test-IsUbuntu24)) { $languageAndRuntime.AddToolVersion("Mono", $(Get-MonoVersion)) $languageAndRuntime.AddToolVersion("MSBuild", $(Get-MsbuildVersion)) } $languageAndRuntime.AddToolVersion("Node.js", $(Get-NodeVersion)) $languageAndRuntime.AddToolVersion("Perl", $(Get-PerlVersion)) $languageAndRuntime.AddToolVersion("Python", $(Get-PythonVersion)) $languageAndRuntime.AddToolVersion("Ruby", $(Get-RubyVersion)) $languageAndRuntime.AddToolVersion("Swift", $(Get-SwiftVersion)) # Package Management $packageManagement = $installedSoftware.AddHeader("Package Management") $packageManagement.AddToolVersion("cpan", $(Get-CpanVersion)) $packageManagement.AddToolVersion("Helm", $(Get-HelmVersion)) $packageManagement.AddToolVersion("Homebrew", $(Get-HomebrewVersion)) $packageManagement.AddToolVersion("Miniconda", $(Get-MinicondaVersion)) $packageManagement.AddToolVersion("Npm", $(Get-NpmVersion)) if (-not $(Test-IsUbuntu24)) { $packageManagement.AddToolVersion("NuGet", $(Get-NuGetVersion)) } $packageManagement.AddToolVersion("Pip", $(Get-PipVersion)) $packageManagement.AddToolVersion("Pip3", $(Get-Pip3Version)) $packageManagement.AddToolVersion("Pipx", $(Get-PipxVersion)) $packageManagement.AddToolVersion("RubyGems", $(Get-GemVersion)) $packageManagement.AddToolVersion("Vcpkg", $(Get-VcpkgVersion)) $packageManagement.AddToolVersion("Yarn", $(Get-YarnVersion)) $packageManagement.AddHeader("Environment variables").AddTable($(Build-PackageManagementEnvironmentTable)) $packageManagement.AddHeader("Homebrew note").AddNote(@' Location: /home/linuxbrew Note: Homebrew is pre-installed on image but not added to PATH. run the eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" command to accomplish this. '@) # Project Management $projectManagement = $installedSoftware.AddHeader("Project Management") $projectManagement.AddToolVersion("Ant", $(Get-AntVersion)) $projectManagement.AddToolVersion("Gradle", $(Get-GradleVersion)) $projectManagement.AddToolVersion("Lerna", $(Get-LernaVersion)) $projectManagement.AddToolVersion("Maven", $(Get-MavenVersion)) if (Test-IsUbuntu22) { $projectManagement.AddToolVersion("Sbt", $(Get-SbtVersion)) } # Tools $tools = $installedSoftware.AddHeader("Tools") $tools.AddToolVersion("Ansible", $(Get-AnsibleVersion)) if (Test-IsUbuntu22) { $tools.AddToolVersion("apt-fast", $(Get-AptFastVersion)) } $tools.AddToolVersion("AzCopy", $(Get-AzCopyVersion)) $tools.AddToolVersion("Bazel", $(Get-BazelVersion)) $tools.AddToolVersion("Bazelisk", $(Get-BazeliskVersion)) $tools.AddToolVersion("Bicep", $(Get-BicepVersion)) $tools.AddToolVersion("Buildah", $(Get-BuildahVersion)) $tools.AddToolVersion("CMake", $(Get-CMakeVersion)) $tools.AddToolVersion("CodeQL Action Bundle", $(Get-CodeQLBundleVersion)) $tools.AddToolVersion("Docker Amazon ECR Credential Helper", $(Get-DockerAmazonECRCredHelperVersion)) $tools.AddToolVersion("Docker Compose v2", $(Get-DockerComposeV2Version)) $tools.AddToolVersion("Docker-Buildx", $(Get-DockerBuildxVersion)) $tools.AddToolVersion("Docker Client", $(Get-DockerClientVersion)) $tools.AddToolVersion("Docker Server", $(Get-DockerServerVersion)) $tools.AddToolVersion("Fastlane", $(Get-FastlaneVersion)) $tools.AddToolVersion("Git", $(Get-GitVersion)) $tools.AddToolVersion("Git LFS", $(Get-GitLFSVersion)) $tools.AddToolVersion("Git-ftp", $(Get-GitFTPVersion)) $tools.AddToolVersion("Haveged", $(Get-HavegedVersion)) if (Test-IsUbuntu22) { $tools.AddToolVersion("Heroku", $(Get-HerokuVersion)) } $tools.AddToolVersion("jq", $(Get-JqVersion)) $tools.AddToolVersion("Kind", $(Get-KindVersion)) $tools.AddToolVersion("Kubectl", $(Get-KubectlVersion)) $tools.AddToolVersion("Kustomize", $(Get-KustomizeVersion)) if (Test-IsUbuntu22) { $tools.AddToolVersion("Leiningen", $(Get-LeiningenVersion)) } $tools.AddToolVersion("MediaInfo", $(Get-MediainfoVersion)) $tools.AddToolVersion("Mercurial", $(Get-HGVersion)) $tools.AddToolVersion("Minikube", $(Get-MinikubeVersion)) $tools.AddToolVersion("n", $(Get-NVersion)) $tools.AddToolVersion("Newman", $(Get-NewmanVersion)) $tools.AddToolVersion("nvm", $(Get-NvmVersion)) $tools.AddToolVersion("OpenSSL", $(Get-OpensslVersion)) $tools.AddToolVersion("Packer", $(Get-PackerVersion)) $tools.AddToolVersion("Parcel", $(Get-ParcelVersion)) $tools.AddToolVersion("Podman", $(Get-PodManVersion)) $tools.AddToolVersion("Pulumi", $(Get-PulumiVersion)) if (Test-IsUbuntu22) { $tools.AddToolVersion("R", $(Get-RVersion)) } $tools.AddToolVersion("Skopeo", $(Get-SkopeoVersion)) $tools.AddToolVersion("Sphinx Open Source Search Server", $(Get-SphinxVersion)) if (Test-IsUbuntu22) { $tools.AddToolVersion("SVN", $(Get-SVNVersion)) $tools.AddToolVersion("Terraform", $(Get-TerraformVersion)) } $tools.AddToolVersion("yamllint", $(Get-YamllintVersion)) $tools.AddToolVersion("yq", $(Get-YqVersion)) $tools.AddToolVersion("zstd", $(Get-ZstdVersion)) $tools.AddToolVersion("Ninja", $(Get-NinjaVersion)) # CLI Tools $cliTools = $installedSoftware.AddHeader("CLI Tools") if (Test-IsUbuntu22) { $cliTools.AddToolVersion("Alibaba Cloud CLI", $(Get-AlibabaCloudCliVersion)) } $cliTools.AddToolVersion("AWS CLI", $(Get-AWSCliVersion)) $cliTools.AddToolVersion("AWS CLI Session Manager Plugin", $(Get-AWSCliSessionManagerPluginVersion)) $cliTools.AddToolVersion("AWS SAM CLI", $(Get-AWSSAMVersion)) $cliTools.AddToolVersion("Azure CLI", $(Get-AzureCliVersion)) $cliTools.AddToolVersion("Azure CLI (azure-devops)", $(Get-AzureDevopsVersion)) $cliTools.AddToolVersion("GitHub CLI", $(Get-GitHubCliVersion)) $cliTools.AddToolVersion("Google Cloud CLI", $(Get-GoogleCloudCLIVersion)) if (Test-IsUbuntu22) { $cliTools.AddToolVersion("Netlify CLI", $(Get-NetlifyCliVersion)) $cliTools.AddToolVersion("OpenShift CLI", $(Get-OCCliVersion)) $cliTools.AddToolVersion("ORAS CLI", $(Get-ORASCliVersion)) $cliTools.AddToolVersion("Vercel CLI", $(Get-VerselCliversion)) } # Java $installedSoftware.AddHeader("Java").AddTable($(Get-JavaVersionsTable)) # PHP Tools $phpTools = $installedSoftware.AddHeader("PHP Tools") $phpTools.AddToolVersionsListInline("PHP", $(Get-PHPVersions), "^\d+\.\d+") $phpTools.AddToolVersion("Composer", $(Get-ComposerVersion)) $phpTools.AddToolVersion("PHPUnit", $(Get-PHPUnitVersion)) $phpTools.AddNote("Both Xdebug and PCOV extensions are installed, but only Xdebug is enabled.") # Haskell Tools $haskellTools = $installedSoftware.AddHeader("Haskell Tools") $haskellTools.AddToolVersion("Cabal", $(Get-CabalVersion)) $haskellTools.AddToolVersion("GHC", $(Get-GHCVersion)) $haskellTools.AddToolVersion("GHCup", $(Get-GHCupVersion)) $haskellTools.AddToolVersion("Stack", $(Get-StackVersion)) # Rust Tools Initialize-RustEnvironment $rustTools = $installedSoftware.AddHeader("Rust Tools") $rustTools.AddToolVersion("Cargo", $(Get-CargoVersion)) $rustTools.AddToolVersion("Rust", $(Get-RustVersion)) $rustTools.AddToolVersion("Rustdoc", $(Get-RustdocVersion)) $rustTools.AddToolVersion("Rustup", $(Get-RustupVersion)) # Packages $rustToolsPackages = $rustTools.AddHeader("Packages") if (Test-IsUbuntu22) { $rustToolsPackages.AddToolVersion("Bindgen", $(Get-BindgenVersion)) $rustToolsPackages.AddToolVersion("Cargo audit", $(Get-CargoAuditVersion)) $rustToolsPackages.AddToolVersion("Cargo clippy", $(Get-CargoClippyVersion)) $rustToolsPackages.AddToolVersion("Cargo outdated", $(Get-CargoOutdatedVersion)) $rustToolsPackages.AddToolVersion("Cbindgen", $(Get-CbindgenVersion)) } $rustToolsPackages.AddToolVersion("Rustfmt", $(Get-RustfmtVersion)) # Browsers and Drivers $browsersTools = $installedSoftware.AddHeader("Browsers and Drivers") $browsersTools.AddToolVersion("Google Chrome", $(Get-ChromeVersion)) $browsersTools.AddToolVersion("ChromeDriver", $(Get-ChromeDriverVersion)) $browsersTools.AddToolVersion("Chromium", $(Get-ChromiumVersion)) $browsersTools.AddToolVersion("Microsoft Edge", $(Get-EdgeVersion)) $browsersTools.AddToolVersion("Microsoft Edge WebDriver", $(Get-EdgeDriverVersion)) $browsersTools.AddToolVersion("Selenium server", $(Get-SeleniumVersion)) $browsersTools.AddToolVersion("Mozilla Firefox", $(Get-FirefoxVersion)) $browsersTools.AddToolVersion("Geckodriver", $(Get-GeckodriverVersion)) # Environment variables $browsersTools.AddHeader("Environment variables").AddTable($(Build-BrowserWebdriversEnvironmentTable)) # .NET Tools $netCoreTools = $installedSoftware.AddHeader(".NET Tools") $netCoreTools.AddToolVersionsListInline(".NET Core SDK", $(Get-DotNetCoreSdkVersions), "^\d+\.\d+\.\d") $netCoreTools.AddNodes($(Get-DotnetTools)) # Databases $databasesTools = $installedSoftware.AddHeader("Databases") $databasesTools.AddToolVersion("sqlite3", $(Get-SqliteVersion)) $databasesTools.AddNode($(Build-PostgreSqlSection)) $databasesTools.AddNode($(Build-MySQLSection)) if (-not $(Test-IsUbuntu24)) { $databasesTools.AddNode($(Build-MSSQLToolsSection)) } # Cached Tools $cachedTools = $installedSoftware.AddHeader("Cached Tools") $cachedTools.AddToolVersionsList("Go", $(Get-ToolcacheGoVersions), "^\d+\.\d+") $cachedTools.AddToolVersionsList("Node.js", $(Get-ToolcacheNodeVersions), "^\d+") $cachedTools.AddToolVersionsList("Python", $(Get-ToolcachePythonVersions), "^\d+\.\d+") $cachedTools.AddToolVersionsList("PyPy", $(Get-ToolcachePyPyVersions), "^\d+\.\d+") $cachedTools.AddToolVersionsList("Ruby", $(Get-ToolcacheRubyVersions), "^\d+\.\d+") # PowerShell Tools $powerShellTools = $installedSoftware.AddHeader("PowerShell Tools") $powerShellTools.AddToolVersion("PowerShell", $(Get-PowershellVersion)) $powerShellTools.AddHeader("PowerShell Modules").AddNodes($(Get-PowerShellModules)) $installedSoftware.AddHeader("Web Servers").AddTable($(Build-WebServersTable)) $androidTools = $installedSoftware.AddHeader("Android") $androidTools.AddTable($(Build-AndroidTable)) $androidTools.AddHeader("Environment variables").AddTable($(Build-AndroidEnvironmentTable)) $installedSoftware.AddHeader("Installed apt packages").AddTable($(Get-AptPackages)) $softwareReport.ToJson() | Out-File -FilePath "${OutputDirectory}/software-report.json" -Encoding UTF8NoBOM $softwareReport.ToMarkdown() | Out-File -FilePath "${OutputDirectory}/software-report.md" -Encoding UTF8NoBOM ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.Android.psm1 ================================================ function Split-TableRowByColumns { param( [string] $Row ) return $Row.Split("|") | ForEach-Object { $_.trim() } } function Get-AndroidSDKRoot { return "/usr/local/lib/android/sdk" } function Get-AndroidSDKManagerPath { $androidSDKDir = Get-AndroidSDKRoot return Join-Path $androidSDKDir "cmdline-tools" "latest" "bin" "sdkmanager" } function Get-AndroidInstalledPackages { $androidSDKManagerPath = Get-AndroidSDKManagerPath $androidSDKManagerList = Invoke-Expression "$androidSDKManagerPath --list_installed --include_obsolete" return $androidSDKManagerList } function Build-AndroidTable { $packageInfo = Get-AndroidInstalledPackages return @( @{ "Package" = "Android Command Line Tools" "Version" = Get-AndroidCommandLineToolsVersion }, @{ "Package" = "Android Emulator" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android Emulator" }, @{ "Package" = "Android SDK Build-tools" "Version" = Get-AndroidBuildToolVersions -PackageInfo $packageInfo }, @{ "Package" = "Android SDK Platform-Tools" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android SDK Platform-Tools" }, @{ "Package" = "Android SDK Platforms" "Version" = Get-AndroidPlatformVersions -PackageInfo $packageInfo }, @{ "Package" = "Android Support Repository" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android Support Repository" }, @{ "Package" = "CMake" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "cmake" }, @{ "Package" = "Google APIs" "Version" = Get-AndroidGoogleAPIsVersions -PackageInfo $packageInfo }, @{ "Package" = "Google Play services" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Google Play services" }, @{ "Package" = "Google Repository" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Google Repository" }, @{ "Package" = "NDK" "Version" = Get-AndroidNDKVersions }, @{ "Package" = "SDK Patch Applier v4" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "SDK Patch Applier v4" } ) | Where-Object { $_.Version } | ForEach-Object { [PSCustomObject] @{ "Package Name" = $_.Package "Version" = $_.Version } } } function Get-AndroidPackageVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo, [Parameter(Mandatory)] [object] $MatchedString ) $versions = $PackageInfo | Where-Object { $_ -Match $MatchedString } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[1] } return ($versions -Join "
") } function Get-AndroidPlatformVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $PackageInfo | Where-Object { $_ -Match "Android SDK Platform " } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ $revision = $packageInfoParts[1] $version = $packageInfoParts[0].split(";")[1] return "$version (rev $revision)" } [array]::Reverse($versions) return ($versions -Join "
") } function Get-AndroidCommandLineToolsVersion { $commandLineTools = Get-AndroidSDKManagerPath (& $commandLineTools --version | Out-String).Trim() -match "(?^(\d+\.){1,}\d+$)" | Out-Null $commandLineToolsVersion = $Matches.Version return $commandLineToolsVersion } function Get-AndroidBuildToolVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $PackageInfo | Where-Object { $_ -Match "Android SDK Build-Tools" } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[1] } $groupVersions = @() $versions | ForEach-Object { $majorVersion = $_.Split(".")[0] $groupVersions += $versions | Where-Object { $_.StartsWith($majorVersion) } | Join-String -Separator " " } return ($groupVersions | Sort-Object -Descending -Unique | Join-String -Separator "
") } function Get-AndroidGoogleAPIsVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $PackageInfo | Where-Object { $_ -Match "Google APIs" } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[0].split(";")[1] } return ($versions -Join "
") } function Get-AndroidNDKVersions { $ndkFolderPath = Join-Path (Get-AndroidSDKRoot) "ndk" $versions = Get-ChildItem -Path $ndkFolderPath -Name $ndkDefaultVersion = (Get-ToolsetContent).android.ndk.default $ndkDefaultFullVersion = Get-ChildItem "$env:ANDROID_HOME/ndk/$ndkDefaultVersion.*" -Name | Select-Object -Last 1 return ($versions | ForEach-Object { $defaultPostfix = ( $_ -eq $ndkDefaultFullVersion ) ? " (default)" : "" $_ + $defaultPostfix } | Join-String -Separator "
") } function Build-AndroidEnvironmentTable { $androidVersions = Get-Item env:ANDROID_* $shouldResolveLink = 'ANDROID_NDK', 'ANDROID_NDK_HOME', 'ANDROID_NDK_ROOT', 'ANDROID_NDK_LATEST_HOME' return $androidVersions | Sort-Object -Property Name | ForEach-Object { [PSCustomObject] @{ "Name" = $_.Name "Value" = if ($shouldResolveLink.Contains($_.Name )) { Get-PathWithLink($_.Value) } else {$_.Value} } } } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.Browsers.psm1 ================================================ function Get-ChromeVersion { $googleChromeVersion = google-chrome --version | Get-StringPart -Part 2 return $googleChromeVersion } function Get-ChromeDriverVersion { $chromeDriverVersion = chromedriver --version | Get-StringPart -Part 1 return $chromeDriverVersion } function Get-FirefoxVersion { $firefoxVersion = $(firefox --version) | Get-StringPart -Part 2 return $firefoxVersion } function Get-GeckodriverVersion { $geckodriverVersion = geckodriver --version | Select-Object -First 1 | Get-StringPart -Part 1 return $geckodriverVersion } function Get-ChromiumVersion { $chromiumVersion = chromium-browser --version | Get-StringPart -Part 1 return $chromiumVersion } function Get-EdgeVersion { $edgeVersion = (microsoft-edge --version).Trim() | Get-StringPart -Part 2 return $edgeVersion } function Get-EdgeDriverVersion { $edgeDriverVersion = msedgedriver --version | Get-StringPart -Part 3 return $edgeDriverVersion } function Get-SeleniumVersion { $fullSeleniumVersion = (Get-ChildItem "/usr/share/java/selenium-server-*").Name -replace "selenium-server-" return $fullSeleniumVersion } function Build-BrowserWebdriversEnvironmentTable { return @( [PSCustomObject] @{ "Name" = "CHROMEWEBDRIVER" "Value" = $env:CHROMEWEBDRIVER }, [PSCustomObject] @{ "Name" = "EDGEWEBDRIVER" "Value" = $env:EDGEWEBDRIVER }, [PSCustomObject] @{ "Name" = "GECKOWEBDRIVER" "Value" = $env:GECKOWEBDRIVER }, [PSCustomObject] @{ "Name" = "SELENIUM_JAR_PATH" "Value" = $env:SELENIUM_JAR_PATH } ) } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.CachedTools.psm1 ================================================ function Get-ToolcacheRubyVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "Ruby" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version] $_ } } function Get-ToolcachePythonVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "Python" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version] $_ } } function Get-ToolcachePyPyVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "PyPy" Get-ChildItem -Path $toolcachePath -Name | Sort-Object { [Version] $_ } | ForEach-Object { $pypyRootPath = Join-Path $toolcachePath $_ "x64" [string] $pypyVersionOutput = & "$pypyRootPath/bin/python" -c "import sys;print(sys.version)" $pypyVersionOutput -match "^([\d\.]+) \(.+\) \[PyPy ([\d\.]+\S*) .+]$" | Out-Null return "{0} [PyPy {1}]" -f $Matches[1], $Matches[2] } } function Get-ToolcacheNodeVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "node" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version] $_ } } function Get-ToolcacheGoVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "go" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version] $_ } } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.Common.psm1 ================================================ function Get-BashVersion { $version = bash -c 'echo ${BASH_VERSION}' return $version } function Get-DashVersion { $version = dpkg-query -W -f '${Version}' dash return $version } function Get-CPPVersions { $result = Get-CommandResult "apt list --installed" -Multiline $cppVersions = $result.Output | Where-Object { $_ -match "g\+\+-\d\d\/" } | ForEach-Object { & $_.Split("/")[0] --version | Select-Object -First 1 | Get-StringPart -Part 3 } | Sort-Object {[Version] $_} return $cppVersions } function Get-FortranVersions { $result = Get-CommandResult "apt list --installed" -Multiline $fortranVersions = $result.Output | Where-Object { $_ -match "^gfortran-\d\d\/" } | ForEach-Object { & $_.Split("/")[0] --version | Select-Object -First 1 | Get-StringPart -Part 4 } | Sort-Object {[Version] $_} return $fortranVersions } function Get-ClangToolVersions { param ( [Parameter(Mandatory = $true)] [string] $ToolName, [string] $VersionLineMatcher = "${ToolName} version", [string] $VersionPattern = "\d+\.\d+\.\d+)" ) $result = Get-CommandResult "apt list --installed" -Multiline $toolVersions = $result.Output | Where-Object { $_ -match "^${ToolName}-\d+" } | ForEach-Object { $clangCommand = ($_ -Split "/")[0] Invoke-Expression "$clangCommand --version" | Where-Object { $_ -match "${VersionLineMatcher}" } | ForEach-Object { $_ -match "${VersionLineMatcher} (?${VersionPattern}" | Out-Null $Matches.version } } | Sort-Object {[Version] $_} return $toolVersions } function Get-ClangTidyVersions { $clangVersions = Get-ClangToolVersions -ToolName "clang-tidy" -VersionLineMatcher "LLVM version" -VersionPattern "\d+\.\d+\.\d+)" return $clangVersions } function Get-MonoVersion { $monoVersion = mono --version | Out-String | Get-StringPart -Part 4 return $monoVersion } function Get-MsbuildVersion { $msbuildVersion = msbuild -version | Select-Object -Last 1 $monoVersion = Get-MonoVersion return "$msbuildVersion (Mono $monoVersion)" } function Get-NuGetVersion { $nugetVersion = nuget help | Select-Object -First 1 | Get-StringPart -Part 2 return $nugetVersion } function Get-NodeVersion { $nodeVersion = $(node --version).Substring(1) return $nodeVersion } function Get-OpensslVersion { $opensslVersion = $(dpkg-query -W -f '${Version}' openssl) return $opensslVersion } function Get-PerlVersion { $version = $(perl -e 'print substr($^V,1)') return $version } function Get-PythonVersion { $result = Get-CommandResult "python --version" $version = $result.Output | Get-StringPart -Part 1 return $version } function Get-PowershellVersion { $pwshVersion = $(pwsh --version) | Get-StringPart -Part 1 return $pwshVersion } function Get-RubyVersion { $rubyVersion = ruby --version | Out-String | Get-StringPart -Part 1 return $rubyVersion } function Get-SwiftVersion { $swiftVersion = swift --version | Out-String | Get-StringPart -Part 2 return $swiftVersion } function Get-KotlinVersion { $kotlinVersion = kotlin -version | Out-String | Get-StringPart -Part 2 return $kotlinVersion } function Get-JuliaVersion { $juliaVersion = julia --version | Get-StringPart -Part 2 return $juliaVersion } function Get-LernaVersion { $version = lerna -v return $version } function Get-HomebrewVersion { $result = Get-CommandResult "/home/linuxbrew/.linuxbrew/bin/brew --version" $result.Output -match "Homebrew (?\d+\.\d+\.\d+)" | Out-Null return $Matches.version } function Get-CpanVersion { $result = Get-CommandResult "cpan --version" -ExpectedExitCode @(25, 255) $result.Output -match "version (?\d+\.\d+) " | Out-Null return $Matches.version } function Get-GemVersion { $result = Get-CommandResult "gem --version" $result.Output -match "(?\d+\.\d+\.\d+)" | Out-Null return $Matches.version } function Get-MinicondaVersion { $condaVersion = conda --version | Get-StringPart -Part 1 return $condaVersion } function Get-HelmVersion { $(helm version) -match 'Version:"v(?\d+\.\d+\.\d+)"' | Out-Null return $Matches.version } function Get-NpmVersion { $npmVersion = npm --version return $npmVersion } function Get-YarnVersion { $yarnVersion = yarn --version return $yarnVersion } function Get-ParcelVersion { $parcelVersion = parcel --version return $parcelVersion } function Get-PipVersion { $pipVersion = pip --version | Get-StringPart -Part 1 return $pipVersion } function Get-Pip3Version { $pip3Version = pip3 --version | Get-StringPart -Part 1 return $pip3Version } function Get-VcpkgVersion { $commitId = git -C "/usr/local/share/vcpkg" rev-parse --short HEAD return "(build from commit $commitId)" } function Get-AntVersion { $result = ant -version | Out-String $result -match "version (?\d+\.\d+\.\d+)" | Out-Null return $Matches.version } function Get-GradleVersion { $gradleVersion = (gradle -v) -match "^Gradle \d" | Get-StringPart -Part 1 return $gradleVersion } function Get-MavenVersion { $result = mvn -version | Out-String $result -match "Apache Maven (?\d+\.\d+\.\d+)" | Out-Null return $Matches.version } function Get-SbtVersion { $result = Get-CommandResult "sbt -version" $result.Output -match "sbt runner version: (?\d+\.\d+\.\d+)" | Out-Null return $Matches.version } function Get-PHPVersions { $result = Get-CommandResult "apt list --installed" -Multiline return $result.Output | Where-Object { $_ -match "^php\d+\.\d+/" } | ForEach-Object { $_ -match "now (\d+:)?(?\d+\.\d+\.\d+)" | Out-Null $Matches.version } } function Get-ComposerVersion { $composerVersion = (composer --version) -replace " version" | Get-StringPart -Part 1 return $composerVersion } function Get-PHPUnitVersion { $(phpunit --version | Out-String) -match "PHPUnit (?\d+\.\d+\.\d+)\s" | Out-Null return $Matches.version } function Get-GHCVersion { $(ghc --version) -match "version (?\d+\.\d+\.\d+)" | Out-Null return $Matches.version } function Get-GHCupVersion { $(ghcup --version) -match "version (?\d+(\.\d+){2,})" | Out-Null return $Matches.version } function Get-CabalVersion { $(cabal --version | Out-String) -match "cabal-install version (?\d+\.\d+\.\d+\.\d+)" | Out-Null return $Matches.version } function Get-StackVersion { $(stack --version | Out-String) -match "Version (?\d+\.\d+\.\d+)" | Out-Null return $Matches.version } function Get-PowerShellModules { [Array] $result = @() [Array] $azureInstalledModules = Get-ChildItem -Path "/usr/share/az_*" -Directory | ForEach-Object { $_.Name.Split("_")[1] } if ($azureInstalledModules.Count -gt 0) { $result += [ToolVersionsListNode]::new("Az", $azureInstalledModules, "^\d+\.\d+", "Inline") } (Get-ToolsetContent).powershellModules.name | ForEach-Object { $moduleName = $_ $moduleVersions = Get-Module -Name $moduleName -ListAvailable | Select-Object -ExpandProperty Version | Sort-Object -Unique $result += [ToolVersionsListNode]::new($moduleName, $moduleVersions, "^\d+", "Inline") } return $result } function Get-DotNetCoreSdkVersions { $dotNetCoreSdkVersion = dotnet --list-sdks list | ForEach-Object { $_ | Get-StringPart -Part 0 } return $dotNetCoreSdkVersion } function Get-DotnetTools { $env:PATH = "/etc/skel/.dotnet/tools:$($env:PATH)" $dotnetTools = (Get-ToolsetContent).dotnet.tools return $dotnetTools | ForEach-Object { $version = Invoke-Expression $_.getversion return [ToolVersionNode]::new($_.name, $version) } } function Get-CachedDockerImages { $toolsetJson = Get-ToolsetContent $images = $toolsetJson.docker.images return $images } function Get-CachedDockerImagesTableData { $allImages = sudo docker images --digests --format "*{{.Repository}}:{{.Tag}}|{{.Digest}} |{{.CreatedAt}}" $allImages.Split("*") | Where-Object { $_ } | ForEach-Object { $parts = $_.Split("|") [PSCustomObject] @{ "Repository:Tag" = $parts[0] "Digest" = $parts[1] "Created" = $parts[2].split(' ')[0] } } | Sort-Object -Property "Repository:Tag" } function Get-AptPackages { $apt = (Get-ToolsetContent).Apt $output = @() ForEach ($pkg in ($apt.vital_packages + $apt.common_packages + $apt.cmd_packages)) { $version = $(dpkg-query -W -f '${Version}' $pkg) if ($null -eq $version) { $version = $(dpkg-query -W -f '${Version}' "$pkg*") } $version = $version -replace '~','\~' $output += [PSCustomObject] @{ Name = $pkg Version = $version } } return ($output | Sort-Object Name) } function Get-PipxVersion { $result = (Get-CommandResult "pipx --version").Output $result -match "(?\d+\.\d+\.\d+\.?\d*)" | Out-Null return $Matches.Version } function Build-PackageManagementEnvironmentTable { return @( [PSCustomObject] @{ "Name" = "CONDA" "Value" = $env:CONDA }, [PSCustomObject] @{ "Name" = "VCPKG_INSTALLATION_ROOT" "Value" = $env:VCPKG_INSTALLATION_ROOT } ) } function Get-SystemdVersion { $matchCollection = [regex]::Matches((systemctl --version | head -n 1), "\((.*?)\)") $result = foreach ($match in $matchCollection) {$match.Groups[1].Value} return $result } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.Databases.psm1 ================================================ function Get-PostgreSqlVersion { $postgreSQLVersion = psql --version | Get-StringPart -Part 2 return $postgreSQLVersion } function Get-SqliteVersion { $sqliteVersion = sqlite3 --version | Get-StringPart -Part 0 return $sqliteVersion } function Get-MySQLVersion { $mySQLVersion = mysqld --version | Get-StringPart -Part 2 return $mySQLVersion } function Get-SQLCmdVersion { $sqlcmdVersion = sqlcmd -? | Select-String -Pattern "Version" | Get-StringPart -Part 1 return $sqlcmdVersion } function Get-SqlPackageVersion { $sqlPackageVersion = sqlpackage /version return $sqlPackageVersion } function Build-PostgreSqlSection { $node = [HeaderNode]::new("PostgreSQL") $node.AddToolVersion("PostgreSQL", $(Get-PostgreSqlVersion)) $node.AddNote(@( "User: postgres", "PostgreSQL service is disabled by default.", "Use the following command as a part of your job to start the service: 'sudo systemctl start postgresql.service'" ) -join "`n") return $node } function Build-MySQLSection { $node = [HeaderNode]::new("MySQL") $node.AddToolVersion("MySQL", $(Get-MySQLVersion)) $node.AddNote(@( "User: root", "Password: root", "MySQL service is disabled by default.", "Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'" ) -join "`n") return $node } function Build-MSSQLToolsSection { $node = [HeaderNode]::new("MS SQL") $node.AddToolVersion("sqlcmd", $(Get-SQLCmdVersion)) $node.AddToolVersion("SqlPackage", $(Get-SqlPackageVersion)) return $node } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.Helpers.psm1 ================================================ function Get-StringPart { param ( [Parameter(ValueFromPipeline)] [string] $ToolOutput, [string] $Delimiter = " ", [int[]] $Part ) $parts = $ToolOutput.Split($Delimiter, [System.StringSplitOptions]::RemoveEmptyEntries) $selectedParts = $parts[$Part] return [string]::Join($Delimiter, $selectedParts) } function Get-PathWithLink { param ( [string] $InputPath ) $link = Get-Item $InputPath | Select-Object -ExpandProperty Target if (-not [string]::IsNullOrEmpty($link)) { return "${InputPath} -> ${link}" } return "${InputPath}" } function Get-OSVersionShort { $(Get-OSVersionFull) | Get-StringPart -Delimiter '.' -Part 0,1 } function Get-OSVersionFull { lsb_release -ds | Get-StringPart -Part 1, 2 } function Get-KernelVersion { $kernelVersion = uname -r return $kernelVersion } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.Java.psm1 ================================================ function Get-JavaVersionsTable { $javaToolcacheVersions = Get-ChildItem $env:AGENT_TOOLSDIRECTORY/Java*/* -Directory | Sort-Object { [int] $_.Name.Split(".")[0] } return $javaToolcacheVersions | ForEach-Object { $majorVersion = $_.Name.split(".")[0] $fullVersion = $_.Name.Replace("-", "+") $defaultJavaPath = $env:JAVA_HOME $javaPath = Get-Item env:JAVA_HOME_${majorVersion}_X64 $defaultPostfix = ($javaPath.Value -eq $defaultJavaPath) ? " (default)" : "" [PSCustomObject] @{ "Version" = $fullVersion + $defaultPostfix "Environment Variable" = $javaPath.Name } } } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.Rust.psm1 ================================================ function Initialize-RustEnvironment { $env:PATH = "/etc/skel/.cargo/bin:/etc/skel/.rustup/bin:$($env:PATH)" $env:RUSTUP_HOME = "/etc/skel/.rustup" $env:CARGO_HOME = "/etc/skel/.cargo" } function Get-RustVersion { $rustVersion = $(rustc --version) | Get-StringPart -Part 1 return $rustVersion } function Get-BindgenVersion { $bindgenVersion = $(bindgen --version) | Get-StringPart -Part 1 return $bindgenVersion } function Get-CargoVersion { $cargoVersion = $(cargo --version) | Get-StringPart -Part 1 return $cargoVersion } function Get-CargoAuditVersion { $cargoAuditVersion = $(cargo-audit --version) | Get-StringPart -Part 1 return $cargoAuditVersion } function Get-CargoOutdatedVersion { $cargoOutdatedVersion = cargo outdated --version | Get-StringPart -Part 1 return $cargoOutdatedVersion } function Get-CargoClippyVersion { $cargoClippyVersion = $(cargo-clippy --version) | Get-StringPart -Part 1 return $cargoClippyVersion } function Get-CbindgenVersion { $cbindgenVersion = $(cbindgen --version) | Get-StringPart -Part 1 return $cbindgenVersion } function Get-RustupVersion { $rustupVersion = $(rustup --version) | Get-StringPart -Part 1 return $rustupVersion } function Get-RustdocVersion { $rustdocVersion = $(rustdoc --version) | Get-StringPart -Part 1 return $rustdocVersion } function Get-RustfmtVersion { $rustfmtVersion = $(rustfmt --version) | Get-StringPart -Part 1 | Get-StringPart -Part 0 -Delimiter "-" return $rustfmtVersion } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.Tools.psm1 ================================================ function Get-AnsibleVersion { $ansibleVersion = (ansible --version)[0] -replace "[^\d.]" return $ansibleVersion } function Get-AptFastVersion { $versionFileContent = Get-Content (which apt-fast) -Raw $match = [Regex]::Match($versionFileContent, '# apt-fast v(.+)\n') return $match.Groups[1].Value } function Get-AzCopyVersion { $azcopyVersion = [string]$(azcopy --version) | Get-StringPart -Part 2 return "$azcopyVersion - available by ``azcopy`` and ``azcopy10`` aliases" } function Get-BazelVersion { $bazelVersion = bazel --version | Select-String "bazel" | Get-StringPart -Part 1 return $bazelVersion } function Get-BazeliskVersion { $result = Get-CommandResult "bazelisk version" -Multiline $bazeliskVersion = $result.Output | Select-String "Bazelisk version:" | Get-StringPart -Part 2 | Get-StringPart -Part 0 -Delimiter "v" return $bazeliskVersion } function Get-BicepVersion { (bicep --version | Out-String) -match "bicep cli version (?\d+\.\d+\.\d+)" | Out-Null return $Matches.Version } function Get-CodeQLBundleVersion { $CodeQLVersionsWildcard = Join-Path $Env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath "*" $CodeQLVersionPath = Get-ChildItem $CodeQLVersionsWildcard | Select-Object -First 1 -Expand FullName $CodeQLPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "codeql" $CodeQLVersion = & $CodeQLPath version --quiet return $CodeQLVersion } function Get-PodManVersion { $podmanVersion = podman --version | Get-StringPart -Part 2 return $podmanVersion } function Get-BuildahVersion { $buildahVersion = buildah --version | Get-StringPart -Part 2 return $buildahVersion } function Get-SkopeoVersion { $skopeoVersion = skopeo --version | Get-StringPart -Part 2 return $skopeoVersion } function Get-CMakeVersion { $cmakeVersion = cmake --version | Select-Object -First 1 | Get-StringPart -Part 2 return $cmakeVersion } function Get-DockerComposeV2Version { $composeVersion = docker compose version | Get-StringPart -Part 3 | Get-StringPart -Part 0 -Delimiter "v" return $composeVersion } function Get-DockerClientVersion { $dockerClientVersion = sudo docker version --format '{{.Client.Version}}' return $dockerClientVersion } function Get-DockerServerVersion { $dockerServerVersion = sudo docker version --format '{{.Server.Version}}' return $dockerServerVersion } function Get-DockerBuildxVersion { $buildxVersion = docker buildx version | Get-StringPart -Part 1 | Get-StringPart -Part 0 -Delimiter "v" return $buildxVersion } function Get-DockerAmazonECRCredHelperVersion { $ecrVersion = docker-credential-ecr-login -v | Select-String "Version:" | Get-StringPart -Part 1 return $ecrVersion } function Get-GitVersion { $gitVersion = git --version | Get-StringPart -Part -1 return $gitVersion } function Get-GitLFSVersion { $result = Get-CommandResult "git-lfs --version" $gitlfsversion = $result.Output | Get-StringPart -Part 0 | Get-StringPart -Part 1 -Delimiter "/" return $gitlfsversion } function Get-GitFTPVersion { $gitftpVersion = git-ftp --version | Get-StringPart -Part 2 return $gitftpVersion } function Get-GoogleCloudCLIVersion { return (gcloud --version | Select-Object -First 1) | Get-StringPart -Part 3 } function Get-HavegedVersion { $havegedVersion = dpkg-query --showformat='${Version}' --show haveged | Get-StringPart -Part 0 -Delimiter "-" return $havegedVersion } function Get-HerokuVersion { $herokuVersion = heroku version | Get-StringPart -Part 0 | Get-StringPart -Part 1 -Delimiter "/" return $herokuVersion } function Get-SVNVersion { $svnVersion = svn --version | Select-Object -First 1 | Get-StringPart -Part 2 return $svnVersion } function Get-KustomizeVersion { $kustomizeVersion = kustomize version --short | Get-StringPart -Part 0 | Get-StringPart -Part 1 -Delimiter "v" return $kustomizeVersion } function Get-KindVersion { $kindVersion = kind version | Get-StringPart -Part 1 | Get-StringPart -Part 0 -Delimiter "v" return $kindVersion } function Get-KubectlVersion { $kubectlVersion = (kubectl version --client --output=json | ConvertFrom-Json).clientVersion.gitVersion.Replace('v','') return $kubectlVersion } function Get-MinikubeVersion { $minikubeVersion = minikube version --short | Get-StringPart -Part 0 -Delimiter "v" return $minikubeVersion } function Get-HGVersion { $hgVersion = hg --version | Select-Object -First 1 | Get-StringPart -Part -1 | Get-StringPart -Part 0 -Delimiter ")" return $hgVersion } function Get-LeiningenVersion { return "$(lein -v | Get-StringPart -Part 1)" } function Get-MediainfoVersion { $mediainfoVersion = (mediainfo --version | Select-Object -Index 1 | Get-StringPart -Part 2).Replace('v', '') return $mediainfoVersion } function Get-NewmanVersion { return $(newman --version) } function Get-NVersion { $nVersion = (n --version).Replace('v', '') return $nVersion } function Get-NvmVersion { $nvmVersion = bash -c "source /etc/skel/.nvm/nvm.sh && nvm --version" return $nvmVersion } function Get-PackerVersion { $packerVersion = (packer --version | Select-String "^Packer").Line.Replace('v','') | Get-StringPart -Part 1 return $packerVersion } function Get-TerraformVersion { return (terraform version | Select-String "^Terraform").Line.Replace('v','') | Get-StringPart -Part 1 } function Get-JqVersion { $jqVersion = jq --version | Get-StringPart -Part 1 -Delimiter "-" return $jqVersion } function Get-AzureCliVersion { $azcliVersion = (az version | ConvertFrom-Json).'azure-cli' return $azcliVersion } function Get-AzureDevopsVersion { $azdevopsVersion = (az version | ConvertFrom-Json).extensions.'azure-devops' return $azdevopsVersion } function Get-AlibabaCloudCliVersion { return $(aliyun version) } function Get-AWSCliVersion { $result = Get-CommandResult "aws --version" $awsVersion = $result.Output | Get-StringPart -Part 0 | Get-StringPart -Part 1 -Delimiter "/" return $awsVersion } function Get-AWSCliSessionManagerPluginVersion { $result = (Get-CommandResult "session-manager-plugin --version").Output return $result } function Get-AWSSAMVersion { return $(sam --version | Get-StringPart -Part -1) } function Get-FastlaneVersion { $fastlaneVersion = fastlane --version | Select-String "^fastlane [0-9]" | Get-StringPart -Part 1 return $fastlaneVersion } function Get-GitHubCliVersion { $ghVersion = gh --version | Select-String "gh version" | Get-StringPart -Part 2 return $ghVersion } function Get-NetlifyCliVersion { $netlifyVersion = netlify --version | Get-StringPart -Part 0 | Get-StringPart -Part 1 -Delimiter "/" return $netlifyVersion } function Get-OCCliVersion { $ocVersion = oc version -o=json | jq -r '.releaseClientVersion' return $ocVersion } function Get-ORASCliVersion { $orasVersion = oras version | Select-String "^Version:" | Get-StringPart -Part 1 return $orasVersion } function Get-VerselCliversion { $result = Get-CommandResult "vercel --version" -Multiline return $result.Output | Select-Object -Skip 1 -First 1 } function Get-PulumiVersion { $pulumiVersion = pulumi version | Get-StringPart -Part 0 -Delimiter "v" return $pulumiVersion } function Get-RVersion { $rVersion = (Get-CommandResult "R --version | grep 'R version'").Output | Get-StringPart -Part 2 return $rVersion } function Get-SphinxVersion { $sphinxVersion = searchd -h | Select-Object -First 1 | Get-StringPart -Part 1 | Get-StringPart -Part 0 -Delimiter "-" return $sphinxVersion } function Get-YamllintVersion { return $(yamllint --version) | Get-StringPart -Part 1 } function Get-ZstdVersion { $zstdVersion = zstd --version | Get-StringPart -Part 1 -Delimiter "v" | Get-StringPart -Part 0 -Delimiter "," return "$zstdVersion" } function Get-NinjaVersion { return $(ninja --version) } function Get-YqVersion { $yqVersion = $(yq -V) | Get-StringPart -Part 3 return $yqVersion.TrimStart("v").Trim() } ================================================ FILE: images/ubuntu/scripts/docs-gen/SoftwareReport.WebServers.psm1 ================================================ function Get-ApacheVersion { $name = "apache2" $port = 80 $version = bash -c "apache2 -v | grep -Po 'Apache/(\d+.){2}\d+'" | Get-StringPart -Part 1 -Delimiter "/" $serviceStatus = systemctl status apache2 | grep "Active:" | Get-StringPart -Part 1 $configFile = "/etc/apache2/apache2.conf" return [PsCustomObject]@{ "Name" = $name "Version" = $version "ConfigFile" = $configFile "ServiceStatus" = $serviceStatus "ListenPort" = $port } } function Get-NginxVersion { $name = "nginx" $port = 80 $version = (dpkg-query --showformat='${Version}' --show nginx).Split('-')[0] $serviceStatus = systemctl status nginx | grep "Active:" | Get-StringPart -Part 1 $configFile = "/etc/nginx/nginx.conf" return [PsCustomObject]@{ "Name" = $name "Version" = $version "ConfigFile" = $configFile "ServiceStatus" = $serviceStatus "ListenPort" = $port } } function Build-WebServersTable { $servers = @() $servers += (Get-ApacheVersion) $servers += (Get-NginxVersion) return $servers } ================================================ FILE: images/ubuntu/scripts/helpers/Common.Helpers.psm1 ================================================ function Get-CommandResult { <# .SYNOPSIS Runs a command in bash and returns the output and exit code. .DESCRIPTION Function runs a provided command in bash and returns the output and exit code as hashtable. .PARAMETER Command The command to run. .PARAMETER ExpectedExitCode The expected exit code. If the actual exit code does not match, an exception is thrown. .PARAMETER Multiline If true, the output is returned as an array of strings. Otherwise, the output is returned as a single string. .PARAMETER ValidateExitCode If true, the actual exit code is compared to the expected exit code. .EXAMPLE $result = Get-CommandResult "ls -la" This command runs "ls -la" in bash and returns the output and exit code as hashtable. #> param( [Parameter(Mandatory=$true)] [string] $Command, [int[]] $ExpectedExitCode = 0, [switch] $Multiline, [bool] $ValidateExitCode = $true ) # Bash trick to suppress and show error output because some commands write to stderr (for example, "python --version") $stdout = & bash -c "$Command 2>&1" $exitCode = $LASTEXITCODE if ($ValidateExitCode) { if ($ExpectedExitCode -notcontains $exitCode) { try { throw "StdOut: '$stdout' ExitCode: '$exitCode'" } catch { Write-Host $_.Exception.Message Write-Host $_.ScriptStackTrace exit $LASTEXITCODE } } } return @{ Output = If ($Multiline -eq $true) { $stdout } else { [string] $stdout } ExitCode = $exitCode } } function Test-IsUbuntu22 { return (lsb_release -rs) -eq "22.04" } function Test-IsUbuntu24 { return (lsb_release -rs) -eq "24.04" } function Get-ToolsetContent { <# .SYNOPSIS Retrieves the content of the toolset.json file. .DESCRIPTION This function reads the toolset.json in path provided by INSTALLER_SCRIPT_FOLDER environment variable and returns the content as a PowerShell object. #> $toolsetPath = Join-Path $env:INSTALLER_SCRIPT_FOLDER "toolset.json" $toolsetJson = Get-Content -Path $toolsetPath -Raw ConvertFrom-Json -InputObject $toolsetJson } function Invoke-DownloadWithRetry { <# .SYNOPSIS Downloads a file from a given URL with retry functionality. .DESCRIPTION The Invoke-DownloadWithRetry function downloads a file from the specified URL to the specified path. It includes retry functionality in case the download fails. .PARAMETER Url The URL of the file to download. .PARAMETER Path The path where the downloaded file will be saved. If not provided, a temporary path will be used. .EXAMPLE Invoke-DownloadWithRetry -Url "https://example.com/file.zip" -Path "/usr/local/bin" Downloads the file from the specified URL and saves it to the specified path. .EXAMPLE Invoke-DownloadWithRetry -Url "https://example.com/file.zip" Downloads the file from the specified URL and saves it to a temporary path. .OUTPUTS The path where the downloaded file is saved. #> param( [Parameter(Mandatory)] [string] $Url, [Alias("Destination")] [string] $DestinationPath ) if (-not $DestinationPath) { $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join '' $re = "[{0}]" -f [RegEx]::Escape($invalidChars) $fileName = [IO.Path]::GetFileName($Url) -replace $re if ([String]::IsNullOrEmpty($fileName)) { $fileName = [System.IO.Path]::GetRandomFileName() } $DestinationPath = Join-Path -Path "/tmp" -ChildPath $fileName } Write-Host "Downloading package from $Url to $DestinationPath..." $interval = 30 $downloadStartTime = Get-Date for ($retries = 20; $retries -gt 0; $retries--) { try { $attemptStartTime = Get-Date Invoke-WebRequest -Uri $Url -Outfile $DestinationPath $attemptSeconds = [math]::Round(($(Get-Date) - $attemptStartTime).TotalSeconds, 2) Write-Host "Package downloaded in $attemptSeconds seconds" break } catch { $attemptSeconds = [math]::Round(($(Get-Date) - $attemptStartTime).TotalSeconds, 2) Write-Warning "Package download failed in $attemptSeconds seconds" Write-Warning $_.Exception.Message } if ($retries -eq 0) { $totalSeconds = [math]::Round(($(Get-Date) - $downloadStartTime).TotalSeconds, 2) throw "Package download failed after $totalSeconds seconds" } Write-Warning "Waiting $interval seconds before retrying (retries left: $retries)..." Start-Sleep -Seconds $interval } return $DestinationPath } ================================================ FILE: images/ubuntu/scripts/helpers/etc-environment.sh ================================================ #!/bin/bash -e ################################################################################ ## File: etc-environment.sh ## Desc: Helper functions for source and modify /etc/environment ################################################################################ # NB: sed expression use '%' as a delimiter in order to simplify handling # values containing slashes (i.e. directory path) # The values containing '%' will break the functions get_etc_environment_variable() { local variable_name=$1 # remove `variable_name=` and possible quotes from the line grep "^${variable_name}=" /etc/environment | sed -E "s%^${variable_name}=\"?([^\"]+)\"?.*$%\1%" } add_etc_environment_variable() { local variable_name=$1 local variable_value=$2 echo "${variable_name}=${variable_value}" | sudo tee -a /etc/environment } replace_etc_environment_variable() { local variable_name=$1 local variable_value=$2 # modify /etc/environment in place by replacing a string that begins with variable_name sudo sed -i -e "s%^${variable_name}=.*$%${variable_name}=${variable_value}%" /etc/environment } set_etc_environment_variable() { local variable_name=$1 local variable_value=$2 if grep "^${variable_name}=" /etc/environment > /dev/null; then replace_etc_environment_variable $variable_name $variable_value else add_etc_environment_variable $variable_name $variable_value fi } prepend_etc_environment_variable() { local variable_name=$1 local element=$2 # TODO: handle the case if the variable does not exist existing_value=$(get_etc_environment_variable "${variable_name}") set_etc_environment_variable "${variable_name}" "${element}:${existing_value}" } append_etc_environment_variable() { local variable_name=$1 local element=$2 # TODO: handle the case if the variable does not exist existing_value=$(get_etc_environment_variable "${variable_name}") set_etc_environment_variable "${variable_name}" "${existing_value}:${element}" } prepend_etc_environment_path() { local element=$1 prepend_etc_environment_variable PATH "${element}" } append_etc_environment_path() { local element=$1 append_etc_environment_variable PATH "${element}" } # Process /etc/environment as if it were shell script with `export VAR=...` expressions # The PATH variable is handled specially in order to do not override the existing PATH # variable. The value of PATH variable read from /etc/environment is added to the end # of value of the exiting PATH variable exactly as it would happen with real PAM app read # /etc/environment # # TODO: there might be the others variables to be processed in the same way as "PATH" variable # ie MANPATH, INFOPATH, LD_*, etc. In the current implementation the values from /etc/environment # replace the values of the current environment reload_etc_environment() { # add `export ` to every variable of /etc/environment except PATH and eval the result shell script eval $(grep -v '^PATH=' /etc/environment | sed -e 's%^%export %') # handle PATH specially etc_path=$(get_etc_environment_variable PATH) export PATH="$PATH:$etc_path" } ================================================ FILE: images/ubuntu/scripts/helpers/install.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install.sh ## Desc: Helper functions for installing tools ################################################################################ download_with_retry() { local url=$1 local download_path=$2 if [ -z "$download_path" ]; then download_path="/tmp/$(basename "$url")" fi echo "Downloading package from $url to $download_path..." >&2 interval=30 download_start_time=$(date +%s) for ((retries=20; retries>0; retries--)); do attempt_start_time=$(date +%s) if http_code=$(curl -4sSLo "$download_path" "$url" -w '%{http_code}'); then attempt_seconds=$(($(date +%s) - attempt_start_time)) if [ "$http_code" -eq 200 ]; then echo "Package downloaded in $attempt_seconds seconds" >&2 break else echo "Received HTTP status code $http_code after $attempt_seconds seconds" >&2 fi else attempt_seconds=$(($(date +%s) - attempt_start_time)) echo "Package download failed in $attempt_seconds seconds" >&2 fi if [ "$retries" -le 1 ]; then total_seconds=$(($(date +%s) - download_start_time)) echo "Package download failed after $total_seconds seconds" >&2 exit 1 fi echo "Waiting $interval seconds before retrying (retries left: $retries)..." >&2 sleep $interval done echo "$download_path" } get_toolset_value() { local toolset_path="${INSTALLER_SCRIPT_FOLDER}/toolset.json" local query=$1 echo "$(jq -r "$query" $toolset_path)" } get_github_releases_by_version() { local repo=$1 local version=${2:-".+"} local allow_pre_release=${3:-false} local with_assets_only=${4:-false} page_size="100" json=$(curl -fsSL "https://api.github.com/repos/${repo}/releases?per_page=${page_size}") if [[ -z "$json" ]]; then echo "Failed to get releases" >&2 exit 1 fi if [[ $with_assets_only == "true" ]]; then json=$(echo $json | jq -r '.[] | select(.assets | length > 0)') else json=$(echo $json | jq -r '.[]') fi if [[ $allow_pre_release == "true" ]]; then json=$(echo $json | jq -r '.') else json=$(echo $json | jq -r '. | select(.prerelease==false)') fi # Filter out rc/beta/etc releases, convert to numeric version and sort json=$(echo $json | jq '. | select(.tag_name | test(".*-[a-z]|beta") | not)' | jq '.tag_name |= gsub("[^\\d.]"; "")' | jq -s 'sort_by(.tag_name | split(".") | map(tonumber))') # Select releases matching version if [[ $version == "latest" ]]; then json_filtered=$(echo $json | jq .[-1]) elif [[ $version == *"+"* ]] || [[ $version == *"*"* ]]; then json_filtered=$(echo $json | jq --arg version $version '.[] | select(.tag_name | test($version))') else json_filtered=$(echo $json | jq --arg version $version '.[] | select(.tag_name | contains($version))') fi if [[ -z "$json_filtered" ]]; then echo "Failed to get releases from ${repo} matching version ${version}" >&2 echo "Available versions: $(echo "$json" | jq -r '.tag_name')" >&2 exit 1 fi echo $json_filtered } resolve_github_release_asset_url() { local repo=$1 local url_filter=$2 local version=${3:-".+"} local allow_pre_release=${4:-false} local allow_multiple_matches=${5:-false} matching_releases=$(get_github_releases_by_version "${repo}" "${version}" "${allow_pre_release}" "true") matched_url=$(echo $matching_releases | jq -r ".assets[].browser_download_url | select(${url_filter})") if [[ -z "$matched_url" ]]; then echo "Found no download urls matching pattern: ${url_filter}" >&2 echo "Available download urls: $(echo "$matching_releases" | jq -r '.assets[].browser_download_url')" >&2 exit 1 fi if [[ "$(echo "$matched_url" | wc -l)" -gt 1 ]]; then if [[ $allow_multiple_matches == "true" ]]; then matched_url=$(echo "$matched_url" | tail -n 1) else echo "Multiple matches found for ${version} version and ${url_filter} URL filter. Please make filters more specific" >&2 exit 1 fi fi echo $matched_url } get_checksum_from_github_release() { local repo=$1 local file_name=$2 local version=${3:-".+"} local hash_type=$4 local allow_pre_release=${5:-false} if [[ -z "$file_name" ]]; then echo "File name is not specified." >&2 exit 1 fi if [[ "$hash_type" == "SHA256" ]]; then hash_pattern="[A-Fa-f0-9]{64}" elif [[ "$hash_type" == "SHA512" ]]; then hash_pattern="[A-Fa-f0-9]{128}" else echo "Unknown hash type: ${hash_type}" >&2 exit 1 fi matching_releases=$(get_github_releases_by_version "${repo}" "${version}" "${allow_pre_release}" "true") matched_line=$(printf "$(echo $matching_releases | jq '.body')\n" | grep "$file_name") if [[ -z "$matched_line" ]]; then echo "File name ${file_name} not found in release body" >&2 exit 1 fi if [[ "$(echo "$matched_line" | wc -l)" -gt 1 ]]; then echo "Multiple matches found for ${file_name} in release body: ${matched_line}" >&2 exit 1 fi hash=$(echo $matched_line | grep -oP "$hash_pattern") if [[ -z "$hash" ]]; then echo "Found ${file_name} in body of release, but failed to get hash from it: ${matched_line}" >&2 exit 1 fi echo "$hash" } get_checksum_from_url() { local url=$1 local file_name=$2 local hash_type=$3 local use_custom_search_pattern=${4:-false} local delimiter=${5:-' '} local word_number=${6:-1} if [[ "$hash_type" == "SHA256" ]]; then hash_pattern="[A-Fa-f0-9]{64}" elif [[ "$hash_type" == "SHA512" ]]; then hash_pattern="[A-Fa-f0-9]{128}" else echo "Unknown hash type: ${hash_type}" >&2 exit 1 fi checksums_file_path=$(download_with_retry "$url") checksums=$(cat "$checksums_file_path") rm "$checksums_file_path" matched_line=$(printf "$checksums\n" | grep "$file_name") if [[ "$(echo "$matched_line" | wc -l)" -gt 1 ]]; then echo "Found multiple lines matching file name ${file_name} in checksum file." >&2 exit 1 fi if [[ -z "$matched_line" ]]; then echo "File name ${file_name} not found in checksum file." >&2 exit 1 fi if [[ $use_custom_search_pattern == "true" ]]; then hash=$(echo "$matched_line" | sed 's/ */ /g' | cut -d "$delimiter" -f "$word_number" | tr -d -c '[:alnum:]') else hash=$(echo $matched_line | grep -oP "$hash_pattern") fi if [[ -z "$hash" ]]; then echo "Found ${file_name} in checksum file, but failed to get hash from it: ${matched_line}" >&2 exit 1 fi echo "$hash" } use_checksum_comparison() { local file_path=$1 local checksum=$2 local sha_type=${3:-"256"} echo "Performing checksum verification" if [[ ! -f "$file_path" ]]; then echo "File not found: $file_path" exit 1 fi local_file_hash=$(shasum --algorithm "$sha_type" "$file_path" | awk '{print $1}') if [[ "$local_file_hash" != "$checksum" ]]; then echo "Checksum verification failed. Expected hash: $checksum; Actual hash: $local_file_hash." exit 1 else echo "Checksum verification passed" fi } ================================================ FILE: images/ubuntu/scripts/helpers/invoke-tests.sh ================================================ #!/bin/bash -e ################################################################################ ## File: invoke-tests.sh ## Desc: Helper function for invoking tests ################################################################################ pwsh -Command "Import-Module '$HELPER_SCRIPTS/../tests/Helpers.psm1' -DisableNameChecking Invoke-PesterTests -TestFile \"$1\" -TestName \"$2\"" ================================================ FILE: images/ubuntu/scripts/helpers/os.sh ================================================ #!/bin/bash -e ################################################################################ ## File: os.sh ## Desc: Helper functions for OS releases ################################################################################ is_ubuntu22() { lsb_release -rs | grep -q '22.04' } is_ubuntu24() { lsb_release -rs | grep -q '24.04' } ================================================ FILE: images/ubuntu/scripts/tests/ActionArchiveCache.Tests.ps1 ================================================ Describe "ActionArchiveCache" { BeforeDiscovery { $actionArchiveCachePath = "/opt/actionarchivecache" $tarballTestCases = Get-ChildItem -Path "$actionArchiveCachePath/*.tar.gz" -Recurse | ForEach-Object { @{ ActionTarball = $_.FullName } } } Context "Action archive cache directory not empty" { It " not empty" -TestCases @{ ActionArchiveCachepath = $actionArchiveCachePath } { (Get-ChildItem -Path "$ActionArchiveCachepath/*.tar.gz" -Recurse).Count | Should -BeGreaterThan 0 } } Context "Action tarball not empty" { It "" -TestCases $tarballTestCases { (Get-Item "$ActionTarball").Length | Should -BeGreaterThan 0 } } } ================================================ FILE: images/ubuntu/scripts/tests/Android.Tests.ps1 ================================================ Describe "Android" { function Get-AndroidPackages { <# .SYNOPSIS This function returns a list of available Android packages. .DESCRIPTION The Get-AndroidPackages function checks if a list of packages is already available in a file. If not, it uses the sdkmanager to generate a list of available packages and saves it to a file. It then returns the content of this file. .PARAMETER SDKRootPath The root path of the Android SDK installation. If not specified, the function uses the ANDROID_HOME environment variable. .EXAMPLE Get-AndroidPackages -SDKRootPath "/usr/local/lib/android/sdk" This command returns a list of available Android packages for the specified SDK root path. .NOTES This function requires the Android SDK to be installed. #> param ( [Parameter(Mandatory=$false)] [string] $SDKRootPath ) if (-not $SDKRootPath) { $SDKRootPath = $env:ANDROID_HOME } $packagesListFile = "$SDKRootPath/packages-list.txt" if (-not (Test-Path -Path $packagesListFile -PathType Leaf)) { (/usr/local/lib/android/sdk/cmdline-tools/latest/bin/sdkmanager --list --verbose 2>&1) | Where-Object { $_ -Match "^[^\s]" } | Where-Object { $_ -NotMatch "^(Loading |Info: Parsing |---|\[=+|Installed |Available )" } | Where-Object { $_ -NotMatch "^[^;]*$" } | Out-File -FilePath $packagesListFile Write-Host "Android packages list:" Get-Content $packagesListFile } return Get-Content $packagesListFile } $androidSdkManagerPackages = Get-AndroidPackages [int]$platformMinVersion = (Get-ToolsetContent).android.platform_min_version [version]$buildToolsMinVersion = (Get-ToolsetContent).android.build_tools_min_version [array]$ndkVersions = (Get-ToolsetContent).android.ndk.versions $ndkFullVersions = $ndkVersions | ForEach-Object { (Get-ChildItem "/usr/local/lib/android/sdk/ndk/${_}.*" | Select-Object -Last 1).Name } | ForEach-Object { "ndk/${_}" } # Platforms starting with a letter are the preview versions, which is not installed on the image $platformVersionsList = ($androidSdkManagerPackages | Where-Object { "$_".StartsWith("platforms;") }) -replace 'platforms;android-', '' | Where-Object { $_ -match "^\d" } | Sort-Object -Unique $platformsInstalled = $platformVersionsList | Where-Object { [int]($_.Split("-")[0]) -ge $platformMinVersion } | ForEach-Object { "platforms/android-${_}" } $buildToolsList = ($androidSdkManagerPackages | Where-Object { "$_".StartsWith("build-tools;") }) -replace 'build-tools;', '' $buildTools = $buildToolsList | Where-Object { $_ -match "\d+(\.\d+){2,}$"} | Where-Object { [version]$_ -ge $buildToolsMinVersion } | Sort-Object -Unique | ForEach-Object { "build-tools/${_}" } $androidPackages = @( $platformsInstalled, $buildTools, $ndkFullVersions, "platform-tools", ((Get-ToolsetContent).android.extra_list | ForEach-Object { "extras/${_}" }), ((Get-ToolsetContent).android.addon_list | ForEach-Object { "add-ons/${_}" }), ((Get-ToolsetContent).android.additional_tools | ForEach-Object { "${_}" }) ) $androidPackages = $androidPackages | ForEach-Object { $_ } Context "SDKManagers" { $testCases = @( @{ PackageName = "Command-line tools" Sdkmanager = "$env:ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" } ) It "Sdkmanager from is available" -TestCases $testCases { "$Sdkmanager --version" | Should -ReturnZeroExitCode } } Context "Packages" { $testCases = $androidPackages | ForEach-Object { @{ PackageName = $_ } } It "" -TestCases $testCases { # Convert 'cmake;3.6.4111459' -> 'cmake/3.6.4111459' $PackageName = $PackageName.Replace(";", "/") $targetPath = Join-Path $env:ANDROID_HOME $PackageName $targetPath | Should -Exist } } } ================================================ FILE: images/ubuntu/scripts/tests/Apt.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Describe "Apt" { $packages = (Get-ToolsetContent).apt.cmd_packages + (Get-ToolsetContent).apt.vital_packages $testCases = $packages | ForEach-Object { @{ toolName = $_ } } It " is available" -TestCases $testCases { switch ($toolName) { "acl" { $toolName = "getfacl"; break } "aria2" { $toolName = "aria2c"; break } "libnss3-tools" { $toolName = "certutil"; break } "p7zip-full" { $toolName = "p7zip"; break } "subversion" { $toolName = "svn"; break } "sphinxsearch" { $toolName = "searchd"; break } "binutils" { $toolName = "strings"; break } "coreutils" { $toolName = "tr"; break } "net-tools" { $toolName = "netstat"; break } "mercurial" { $toolName = "hg"; break } "findutils" { $toolName = "find"; break } "systemd-coredump" { $toolName = "coredumpctl"; break } } (Get-Command -Name $toolName).CommandType | Should -BeExactly "Application" } } ================================================ FILE: images/ubuntu/scripts/tests/Browsers.Tests.ps1 ================================================ Describe "Firefox" { It "Firefox" { "firefox --version" | Should -ReturnZeroExitCode } It "Geckodriver" { "geckodriver --version" | Should -ReturnZeroExitCode } } Describe "Chrome" { It "Chrome" { "google-chrome --version" | Should -ReturnZeroExitCode } It "Chrome Driver" { "chromedriver --version" | Should -ReturnZeroExitCode } It "Chrome and Chrome Driver major versions are the same" { $chromeMajor = (google-chrome --version).Trim("Google Chrome ").Split(".")[0] $chromeDriverMajor = (chromedriver --version).Trim("ChromeDriver ").Split(".")[0] $chromeMajor | Should -BeExactly $chromeDriverMajor } } Describe "Edge" { It "Edge" { "microsoft-edge --version" | Should -ReturnZeroExitCode } It "Edge Driver" { "msedgedriver --version" | Should -ReturnZeroExitCode } } Describe "Chromium" { It "Chromium" { "chromium-browser --version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/ubuntu/scripts/tests/CLI.Tools.Tests.ps1 ================================================ Describe "Azure CLI" { It "Azure CLI" { "az --version" | Should -ReturnZeroExitCode } } Describe "Azure DevOps CLI" { It "az devops" { "az devops -h" | Should -ReturnZeroExitCode } } Describe "Aliyun CLI" -Skip:((-not (Test-IsUbuntu22))) { It "Aliyun CLI" { "aliyun version" | Should -ReturnZeroExitCode } } Describe "AWS" { It "AWS CLI" { "aws --version" | Should -ReturnZeroExitCode } It "Session Manager Plugin for the AWS CLI" { session-manager-plugin 2>&1 | Out-String | Should -Match "plugin was installed successfully" } It "AWS SAM CLI" { "sam --version" | Should -ReturnZeroExitCode } } Describe "GitHub CLI" { It "gh cli" { "gh --version" | Should -ReturnZeroExitCode } } Describe "Google Cloud CLI" { It "Google Cloud CLI" { "gcloud --version" | Should -ReturnZeroExitCode } } Describe "OC CLI" -Skip:((-not (Test-IsUbuntu22))) { It "OC CLI" { "oc version" | Should -ReturnZeroExitCode } } Describe "Oras CLI" -Skip:((-not (Test-IsUbuntu22))) { It "Oras CLI" { "oras version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/ubuntu/scripts/tests/Common.Tests.ps1 ================================================ Describe "PHP" { $testCases = (Get-ToolsetContent).php.versions | ForEach-Object { @{PhpVersion = $_} } It "php " -TestCases $testCases { "php${PhpVersion} --version" | Should -ReturnZeroExitCode "php-config${PhpVersion} --version" | Should -ReturnZeroExitCode "phpize${PhpVersion} --version" | Should -ReturnZeroExitCode } It "PHPUnit" { "phpunit --version" | Should -ReturnZeroExitCode } It "Composer" { "composer --version" | Should -ReturnZeroExitCode } It "Pear" { "pear" | Should -ReturnZeroExitCode } It "Pecl" { "pecl" | Should -ReturnZeroExitCode } } Describe "Swift" { It "swift" { "swift --version" | Should -ReturnZeroExitCode } It "swiftc" { "swiftc --version" | Should -ReturnZeroExitCode } It "libsourcekitd" { "/usr/local/lib/libsourcekitdInProc.so" | Should -Exist } } Describe "PipxPackages" { $testCases = (Get-ToolsetContent).pipx | ForEach-Object { @{package=$_.package; cmd = $_.cmd} } It "" -TestCases $testCases { "$cmd --version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/ubuntu/scripts/tests/Databases.Tests.ps1 ================================================ Describe "PostgreSQL" { It "PostgreSQL Service" { "sudo systemctl start postgresql" | Should -ReturnZeroExitCode "pg_isready" | Should -OutputTextMatchingRegex "/var/run/postgresql:5432 - accepting connections" "sudo systemctl stop postgresql" | Should -ReturnZeroExitCode } It "PostgreSQL version should correspond to the version in the toolset" { $toolsetVersion = (Get-ToolsetContent).postgresql.version # Client version (psql --version).split()[-1] | Should -BeLike "$toolsetVersion*" # Server version (pg_config --version).split()[-1] | Should -BeLike "$toolsetVersion*" } } Describe "MySQL" { It "MySQL CLI" { "mysql -V" | Should -ReturnZeroExitCode } It "MySQL Service" { "sudo systemctl start mysql" | Should -ReturnZeroExitCode mysql -s -N -h localhost -uroot -proot -e "select count(*) from mysql.user where user='root' and authentication_string is null;" | Should -BeExactly 0 "sudo mysql -vvv -e 'CREATE DATABASE smoke_test' -uroot -proot" | Should -ReturnZeroExitCode "sudo mysql -vvv -e 'DROP DATABASE smoke_test' -uroot -proot" | Should -ReturnZeroExitCode "sudo systemctl stop mysql" | Should -ReturnZeroExitCode } } ================================================ FILE: images/ubuntu/scripts/tests/DotnetSDK.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Describe "Dotnet and tools" { BeforeAll { $env:PATH = "/etc/skel/.dotnet/tools:$($env:PATH)" $dotnetSDKs = dotnet --list-sdks | ConvertTo-Json $dotnetRuntimes = dotnet --list-runtimes | ConvertTo-Json } $dotnetVersions = (Get-ToolsetContent).dotnet.versions Context "Default" { It "Default Dotnet SDK is available" { "dotnet --version" | Should -ReturnZeroExitCode } } foreach ($version in $dotnetVersions) { Context "Dotnet $version" { $dotnet = @{ dotnetVersion = $version } It "SDK is available" -TestCases $dotnet { $dotnetSDKs | Should -Match "$dotnetVersion.[1-9]*" } It "Runtime is available" -TestCases $dotnet { $dotnetRuntimes | Should -Match "$dotnetVersion.[1-9]*" } } } Context "Dotnet tools" { $dotnetTools = (Get-ToolsetContent).dotnet.tools $testCases = $dotnetTools | ForEach-Object { @{ ToolName = $_.name; TestInstance = $_.test }} It " is available" -TestCases $testCases { "$TestInstance" | Should -ReturnZeroExitCode } } } ================================================ FILE: images/ubuntu/scripts/tests/Haskell.Tests.ps1 ================================================ Describe "Haskell" { $GHCCommonPath = "/usr/local/.ghcup/ghc" $GHCVersions = Get-ChildItem -Path $GHCCommonPath | Where-Object { $_.Name -match "\d+\.\d+" } $testCases = $GHCVersions | ForEach-Object { @{ GHCPath = "${_}/bin/ghc"} } It "GHC version " -TestCases $testCases { "$GHCPath --version" | Should -ReturnZeroExitCode } It "GHCup" { "ghcup --version" | Should -ReturnZeroExitCode } It "Default GHC" { "ghc --version" | Should -ReturnZeroExitCode } It "Cabal" { "cabal --version" | Should -ReturnZeroExitCode } It "Stack" { "stack --version" | Should -ReturnZeroExitCode } It "Stack hook is not installed" { "$HOME/.stack/hooks/ghc-install.sh" | Should -Not -Exist } } ================================================ FILE: images/ubuntu/scripts/tests/Helpers.psm1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" -DisableNameChecking function Invoke-PesterTests { <# .SYNOPSIS Runs Pester tests based on the provided test file and test name. .DESCRIPTION The Invoke-PesterTests function runs Pester tests based on the provided test file and test name. .PARAMETER TestFile The name of the test file to run. This should be the base name of the test file without the extension. Using "*" will run all tests from all test files. .PARAMETER TestName The name of the specific test to run. If provided, only the test with the matching name will be executed. .EXAMPLE Invoke-PesterTests -TestFile "MyTests" -TestName "Test1" Runs the test named "Test1" from the test file "MyTests.Tests.ps1". .EXAMPLE Invoke-PesterTests -TestFile "*" Runs all tests from all test files #> Param( [Parameter(Mandatory = $true)] [string] $TestFile, [string] $TestName ) $testPath = "/imagegeneration/tests/${TestFile}.Tests.ps1" if (-not (Test-Path $testPath)) { throw "Unable to find test file '$TestFile' on '$testPath'." } # Check that Pester module is imported if (-not (Get-Module "Pester")) { Import-Module Pester } $configuration = [PesterConfiguration] @{ Run = @{ Path = $testPath; PassThru = $true } Output = @{ Verbosity = "Detailed"; RenderMode = "Plaintext" } } if ($TestName) { $configuration.Filter.FullName = $TestName } # Switch ErrorActionPreference to Stop temporary to make sure that tests will fail on silent errors too $backupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = "Stop" $results = Invoke-Pester -Configuration $configuration $ErrorActionPreference = $backupErrorActionPreference # Fail in case if no tests are run if (-not ($results -and ($results.FailedCount -eq 0) -and (($results.PassedCount + $results.SkippedCount) -gt 0))) { $results throw "Test run has failed" } } function ShouldReturnZeroExitCode { <# .SYNOPSIS Implements a custom Should-operator for the Pester framework. .DESCRIPTION This function is used to check if a command has returned a zero exit code. It can be used by registering it using the Add-ShouldOperator function in Pester. .PARAMETER ActualValue The actual value to be checked. .PARAMETER Negate A switch parameter that, when specified, negates the result of the check. .PARAMETER Because An optional string that provides additional context or explanation for the check. .NOTES This function is designed to be used with the Pester framework. .LINK https://pester.dev/docs/assertions/custom-assertions #> Param( [string] $ActualValue, [switch] $Negate, [string] $Because # This parameter is unused but we need it to match Pester asserts signature ) $result = Get-CommandResult $ActualValue -ValidateExitCode $false [bool] $succeeded = $result.ExitCode -eq 0 if ($Negate) { $succeeded = -not $succeeded } if (-not $succeeded) { $commandOutputIndent = " " * 4 $commandOutput = ($result.Output | ForEach-Object { "${commandOutputIndent}${_}" }) -join "`n" $failureMessage = "Command '${ActualValue}' has finished with exit code`n${commandOutput}" } return [PSCustomObject] @{ Succeeded = $succeeded FailureMessage = $failureMessage } } function ShouldOutputTextMatchingRegex { <# .SYNOPSIS Implements a custom Should-operator for the Pester framework. .DESCRIPTION This function is used to check if a command outputs text that matches a regular expression. It can be used by registering it using the Add-ShouldOperator function in Pester. .PARAMETER ActualValue The actual value to be checked. .PARAMETER RegularExpression The regular expression to be used for the check. .PARAMETER Negate A switch parameter that, when specified, negates the result of the check. .NOTES This function is designed to be used with the Pester framework. .LINK https://pester.dev/docs/assertions/custom-assertions #> Param( [string] $ActualValue, [string] $RegularExpression, [switch] $Negate ) $output = (Get-CommandResult $ActualValue -ValidateExitCode $false).Output | Out-String [bool] $succeeded = $output -cmatch $RegularExpression if ($Negate) { $succeeded = -not $succeeded } $failureMessage = '' if (-not $succeeded) { if ($Negate) { $failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to not match '$output', but it did match." } else { $failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to match '$output', but it did not match." } } return [PSCustomObject] @{ Succeeded = $succeeded FailureMessage = $failureMessage } } If (Get-Command -Name Add-ShouldOperator -ErrorAction SilentlyContinue) { Add-ShouldOperator -Name ReturnZeroExitCode -InternalName ShouldReturnZeroExitCode -Test ${function:ShouldReturnZeroExitCode} Add-ShouldOperator -Name OutputTextMatchingRegex -InternalName ShouldOutputTextMatchingRegex -Test ${function:ShouldOutputTextMatchingRegex} } ================================================ FILE: images/ubuntu/scripts/tests/Java.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" -DisableNameChecking Describe "Java" { $toolsetJava = (Get-ToolsetContent).java $defaultVersion = $toolsetJava.default $jdkVersions = $toolsetJava.versions It "Java is default" -TestCases @{ DefaultJavaVersion = $defaultVersion } { $actualJavaPath = [System.Environment]::GetEnvironmentVariable("JAVA_HOME") $expectedJavaPath = [System.Environment]::GetEnvironmentVariable("JAVA_HOME_${DefaultJavaVersion}_X64") $actualJavaPath | Should -Not -BeNullOrEmpty $expectedJavaPath | Should -Not -BeNullOrEmpty $actualJavaPath | Should -Be $expectedJavaPath } It "" -TestCases @( @{ ToolName = "java" } @{ ToolName = "javac" } ) { "$ToolName -version" | Should -ReturnZeroExitCode } $testCases = $jdkVersions | ForEach-Object { @{Version = $_ } } It "Java " -TestCases $testCases { $javaVariableValue = [System.Environment]::GetEnvironmentVariable("JAVA_HOME_${Version}_X64") $javaVariableValue | Should -Not -BeNullOrEmpty $javaPath = Join-Path $javaVariableValue "bin/java" "`"$javaPath`" -version" | Should -ReturnZeroExitCode if ($Version -eq 8) { $Version = "1.${Version}" } "`"$javaPath`" -version" | Should -OutputTextMatchingRegex "openjdk\ version\ `"${Version}(\.[0-9_\.]+)?`"" } } Describe "Java-Tools" { It "Gradle" { "gradle -version" | Should -ReturnZeroExitCode $gradleVariableValue = [System.Environment]::GetEnvironmentVariable("GRADLE_HOME") $gradleVariableValue | Should -BeLike "/usr/share/gradle-*" $gradlePath = Join-Path $env:GRADLE_HOME "bin/gradle" "`"$GradlePath`" -version" | Should -ReturnZeroExitCode } It "" -TestCases @( @{ ToolName = "mvn" } @{ ToolName = "ant" } ) { "$ToolName -version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/ubuntu/scripts/tests/Node.Tests.ps1 ================================================ Describe "Node.js" { $binaries = @("node") $module_commands = (Get-ToolsetContent).node_modules | ForEach-Object { $_.command } $testCases = $binaries + $module_commands | ForEach-Object { @{NodeCommand = $_} } It "" -TestCases $testCases { "$NodeCommand --version" | Should -ReturnZeroExitCode } It "Node.js version should correspond to the version in the toolset" { node --version | Should -BeLike "v$((Get-ToolsetContent).node.default).*" } } ================================================ FILE: images/ubuntu/scripts/tests/PowerShellModules.Tests.ps1 ================================================ Describe "PowerShellModules" { $modules = (Get-ToolsetContent).powershellModules $modulesWithoutVersions = $modules | Where-Object { -not $_.versions } | ForEach-Object { @{moduleName = $_.name} } $modulesWithVersions = $modules | Where-Object { $_.versions } | ForEach-Object { $moduleName = $_.name $_.versions | ForEach-Object { @{moduleName = $moduleName; expectedVersion = $_} } } It " is installed" -TestCases $modulesWithoutVersions { Get-Module -Name $moduleName -ListAvailable | Should -BeTrue } if ($modulesWithVersions) { It " with is installed" -TestCases $modulesWithVersions { (Get-Module -Name $moduleName -ListAvailable).Version -contains $expectedVersion | Should -BeTrue } } } Describe "AzureModules" { $modules = (Get-ToolsetContent).azureModules $modulesRootPath = "/usr/share" foreach ($module in $modules) { $moduleName = $module.name Context "$moduleName" { foreach ($version in $module.versions) { $modulePath = Join-Path -Path $modulesRootPath -ChildPath "${moduleName}_${version}" $moduleInfo = @{ moduleName = $moduleName; modulePath = $modulePath; expectedVersion = $version } It " exists in modules directory" -TestCases $moduleInfo { $testJob = Start-Job -ScriptBlock { param ( $modulePath, $moduleName ) $env:PSModulePath = "${modulePath}:${env:PSModulePath}" (Get-Module -ListAvailable -Name $moduleName).Version.ToString() } -ArgumentList $modulePath, $moduleName $moduleVersion = $testJob | Wait-Job | Receive-Job Remove-Job $testJob $moduleVersion | Should -Match $expectedVersion } } } } } ================================================ FILE: images/ubuntu/scripts/tests/RunAll-Tests.ps1 ================================================ Import-Module "$PSScriptRoot/Helpers.psm1" -DisableNameChecking Invoke-PesterTests "*" ================================================ FILE: images/ubuntu/scripts/tests/System.Tests.ps1 ================================================ # The $env:AGENT_NAME and $env:RUNNER_NAME are predefined variables for the ADO pipelines and for the GitHub actions respectively. # If the test is running on the ADO pipeline or on the GitHub actions, the test will be skipped Describe "Disk free space" -Skip:(-not [String]::IsNullOrEmpty($env:AGENT_NAME) -or -not [String]::IsNullOrEmpty($env:RUNNER_NAME)) { It "Image has enough free space" { $diskInfo = Get-PSDrive "/" $totalSpaceGB = [math]::Floor(($diskInfo.Used + $diskInfo.Free) / 1GB) $freeSpaceGB = [math]::Floor($diskInfo.Free / 1GB) Write-Host " [i] Disk size: ${totalSpaceGB} GB; Free space: ${freeSpaceGB} GB" $freeSpaceGB | Should -BeGreaterOrEqual 17 } } Describe "fwupd removed" { It "Is not present on box" { $systemctlOutput = & systemctl list-units fwupd-refresh.timer --no-legend # When disabled the output looks like this: #❯ systemctl list-units fwupd-refresh.timer --no-legend #● fwupd-refresh.timer masked failed failed fwupd-refresh.timer # When enabled the output looks like this: #❯ systemctl list-units fwupd-refresh.timer --no-legend #fwupd-refresh.timer loaded active waiting Refresh fwupd metadata regularly $systemctlOutput | Should -Not -Match "active" } } ================================================ FILE: images/ubuntu/scripts/tests/Tools.Tests.ps1 ================================================ Import-Module "$PSScriptRoot/../helpers/Common.Helpers.psm1" Describe "azcopy" { It "azcopy" { "azcopy --version" | Should -ReturnZeroExitCode } It "azcopy10 link exists" { "azcopy10 --version" | Should -ReturnZeroExitCode } } Describe "Bicep" { It "Bicep" { "bicep --version" | Should -ReturnZeroExitCode } } Describe "Rust" { BeforeAll { $env:PATH = "/etc/skel/.cargo/bin:/etc/skel/.rustup/bin:$($env:PATH)" $env:RUSTUP_HOME = "/etc/skel/.rustup" $env:CARGO_HOME = "/etc/skel/.cargo" } It "Rustup is installed" { "rustup --version" | Should -ReturnZeroExitCode } It "Rustc is installed" { "rustc --version" | Should -ReturnZeroExitCode } It "Rustdoc is installed" { "rustdoc --version" | Should -ReturnZeroExitCode } It "Rustfmt is installed" { "rustfmt --version" | Should -ReturnZeroExitCode } It "cargo" { "cargo --version" | Should -ReturnZeroExitCode } Context "Cargo dependencies" -Skip:((-not (Test-IsUbuntu22))) { It "bindgen" { "bindgen --version" | Should -ReturnZeroExitCode } It "cbindgen" { "cbindgen --version" | Should -ReturnZeroExitCode } It "cargo-clippy" { "cargo-clippy --version" | Should -ReturnZeroExitCode } It "Cargo audit" { "cargo audit --version" | Should -ReturnZeroExitCode } It "Cargo outdated" { "cargo outdated --version" | Should -ReturnZeroExitCode } } } Describe "Docker" { It "docker client" { $version=(Get-ToolsetContent).docker.components | Where-Object { $_.package -eq 'docker-ce-cli' } | Select-Object -ExpandProperty version If ($version -ne "latest") { $(sudo docker version --format '{{.Client.Version}}') | Should -BeLike "*$version*" }else{ "sudo docker version --format '{{.Client.Version}}'" | Should -ReturnZeroExitCode } } It "docker server" { $version=(Get-ToolsetContent).docker.components | Where-Object { $_.package -eq 'docker-ce' } | Select-Object -ExpandProperty version If ($version -ne "latest") { $(sudo docker version --format '{{.Server.Version}}') | Should -BeLike "*$version*" }else{ "sudo docker version --format '{{.Server.Version}}'" | Should -ReturnZeroExitCode } } It "docker client/server versions match" { $clientVersion = $(sudo docker version --format '{{.Client.Version}}') $serverVersion = $(sudo docker version --format '{{.Server.Version}}') $clientVersion | Should -Be $serverVersion } It "docker buildx" { $version=(Get-ToolsetContent).docker.plugins | Where-Object { $_.plugin -eq 'buildx' } | Select-Object -ExpandProperty version If ($version -ne "latest") { $(docker buildx version) | Should -BeLike "*$version*" }else{ "docker buildx" | Should -ReturnZeroExitCode } } It "docker compose v2" { $version=(Get-ToolsetContent).docker.plugins | Where-Object { $_.plugin -eq 'compose' } | Select-Object -ExpandProperty version If ($version -ne "latest") { $(docker compose version --short) | Should -BeLike "*$version*" }else{ "docker compose version --short" | Should -ReturnZeroExitCode } } It "docker-credential-ecr-login" { "docker-credential-ecr-login -v" | Should -ReturnZeroExitCode } } Describe "Ansible" { It "Ansible" { "ansible --version" | Should -ReturnZeroExitCode } } Describe "Bazel" { It "" -TestCases @( @{ ToolName = "bazel" } @{ ToolName = "bazelisk" } ) { "$ToolName --version"| Should -ReturnZeroExitCode } } Describe "clang" { $testCases = (Get-ToolsetContent).clang.Versions | ForEach-Object { @{ClangVersion = $_} } It "clang " -TestCases $testCases { "clang-$ClangVersion --version" | Should -ReturnZeroExitCode "clang++-$ClangVersion --version" | Should -ReturnZeroExitCode "clang-format-$ClangVersion --version" | Should -ReturnZeroExitCode "clang-tidy-$ClangVersion --version" | Should -ReturnZeroExitCode "run-clang-tidy-$ClangVersion --help" | Should -ReturnZeroExitCode } } Describe "Cmake" { It "cmake" { "cmake --version" | Should -ReturnZeroExitCode } } Describe "gcc" { $testCases = (Get-ToolsetContent).gcc.Versions | ForEach-Object { @{GccVersion = $_} } It "gcc " -TestCases $testCases { "$GccVersion --version" | Should -ReturnZeroExitCode } } Describe "gfortran" { $testCases = (Get-ToolsetContent).gfortran.Versions | ForEach-Object { @{GfortranVersion = $_} } It "gfortran " -TestCases $testCases { "$GfortranVersion --version" | Should -ReturnZeroExitCode } } Describe "Mono" -Skip:(Test-IsUbuntu24) { It "mono" { "mono --version" | Should -ReturnZeroExitCode } It "msbuild" { "msbuild -version" | Should -ReturnZeroExitCode } It "nuget" { "nuget" | Should -ReturnZeroExitCode } } Describe "MSSQLCommandLineTools" -Skip:((-not (Test-IsUbuntu22))) { It "sqlcmd" { "sqlcmd -?" | Should -ReturnZeroExitCode } } Describe "SqlPackage" -Skip:((-not (Test-IsUbuntu22))) { It "sqlpackage" { "sqlpackage /version" | Should -ReturnZeroExitCode } } Describe "R" -Skip:((-not (Test-IsUbuntu22))) { It "r" { "R --version" | Should -ReturnZeroExitCode } } Describe "Sbt" -Skip:((-not (Test-IsUbuntu22))) { It "sbt" { "sbt --version" | Should -ReturnZeroExitCode } } Describe "Selenium" { It "Selenium is installed" { $seleniumPath = Join-Path "/usr/share/java" "selenium-server.jar" $seleniumPath | Should -Exist } } Describe "Terraform" -Skip:((-not (Test-IsUbuntu22))) { It "terraform" { "terraform --version" | Should -ReturnZeroExitCode } } Describe "Zstd" { It "zstd" { "zstd --version" | Should -ReturnZeroExitCode } It "pzstd" { "pzstd --version" | Should -ReturnZeroExitCode } } Describe "Vcpkg" { It "vcpkg" { "vcpkg version" | Should -ReturnZeroExitCode } } Describe "Git" { It "git" { "git --version" | Should -ReturnZeroExitCode } It "git-ftp" { "git-ftp --version" | Should -ReturnZeroExitCode } } Describe "Git-lfs" { It "git-lfs" { "git-lfs --version" | Should -ReturnZeroExitCode } } Describe "Heroku" -Skip:((-not (Test-IsUbuntu22))) { It "heroku" { "heroku --version" | Should -ReturnZeroExitCode } } Describe "Homebrew" { It "homebrew" { "/home/linuxbrew/.linuxbrew/bin/brew --version" | Should -ReturnZeroExitCode } } Describe "Julia" { It "julia" { "julia --version" | Should -ReturnZeroExitCode } } Describe "Kubernetes tools" { It "kind" { "kind version" | Should -ReturnZeroExitCode } It "kubectl" { "kubectl version --client=true" | Should -OutputTextMatchingRegex "Client Version: v" } It "helm" { "helm version --short" | Should -ReturnZeroExitCode } It "minikube" { "minikube version --short" | Should -ReturnZeroExitCode } It "kustomize" { "kustomize version" | Should -ReturnZeroExitCode } } Describe "Leiningen" -Skip:((-not (Test-IsUbuntu22))) { It "leiningen" { "lein --version" | Should -ReturnZeroExitCode } } Describe "Conda" { It "conda" { "conda --version" | Should -ReturnZeroExitCode } } Describe "Packer" { It "packer" { "packer --version" | Should -ReturnZeroExitCode } } Describe "Pulumi" { It "pulumi" { "pulumi version" | Should -ReturnZeroExitCode } } Describe "Containers" { $testCases = @("podman", "buildah", "skopeo") | ForEach-Object { @{ContainerCommand = $_} } It "" -TestCases $testCases { "$ContainerCommand -v" | Should -ReturnZeroExitCode } # https://github.com/actions/runner-images/issues/7753 It "podman networking" -TestCases "podman CNI plugins" { "podman network create -d bridge test-net" | Should -ReturnZeroExitCode "podman network ls" | Should -Not -OutputTextMatchingRegex "Error" "podman network rm test-net" | Should -ReturnZeroExitCode } } Describe "nvm" { It "nvm" { "source /etc/skel/.nvm/nvm.sh && nvm --version" | Should -ReturnZeroExitCode } } Describe "Python" { $testCases = @("python", "pip", "python3", "pip3") | ForEach-Object { @{PythonCommand = $_} } It "" -TestCases $testCases { "$PythonCommand --version" | Should -ReturnZeroExitCode } } Describe "Ruby" { $testCases = @("ruby", "gem") | ForEach-Object { @{RubyCommand = $_} } It "" -TestCases $testCases { "$RubyCommand --version" | Should -ReturnZeroExitCode } $gemTestCases = (Get-ToolsetContent).rubygems | ForEach-Object { @{gemName = $_.name} } if ($gemTestCases) { It "Gem is installed" -TestCases $gemTestCases { "gem list -i '^$gemName$'" | Should -OutputTextMatchingRegex "true" } } } Describe "yq" { It "yq" { "yq -V" | Should -ReturnZeroExitCode } } Describe "Kotlin" { It "kapt" { "kapt -version" | Should -ReturnZeroExitCode } It "kotlin" { "kotlin -version" | Should -ReturnZeroExitCode } It "kotlinc" { "kotlinc -version" | Should -ReturnZeroExitCode } It "kotlinc-jvm" { "kotlinc-jvm -version" | Should -ReturnZeroExitCode } It "kotlinc-js" { "kotlinc-js -help" | Should -ReturnZeroExitCode } } Describe "Ninja" { BeforeAll { New-item -Path "/tmp/ninjaproject" -ItemType Directory -Force @' cmake_minimum_required(VERSION 3.10) project(NinjaTest NONE) '@ | Out-File -FilePath "/tmp/ninjaproject/CMakeLists.txt" } It "Make a simple ninja project" { "cmake -GNinja -S /tmp/ninjaproject -B /tmp/ninjaproject" | Should -ReturnZeroExitCode } It "build.ninja file should exist" { $buildFilePath = Join-Path "/tmp/ninjaproject" "build.ninja" $buildFilePath | Should -Exist } It "Ninja" { "ninja --version" | Should -ReturnZeroExitCode } AfterAll { Remove-Item -Path "/tmp/ninjaproject" -Recurse -Force } } ================================================ FILE: images/ubuntu/scripts/tests/Toolset.Tests.ps1 ================================================ Describe "Toolset" { $tools = (Get-ToolsetContent).toolcache $toolsExecutables = @{ Python = @{ tools = @("python", "bin/pip") command = "--version" } node = @{ tools = @("bin/node", "bin/npm") command = "--version" } PyPy = @{ tools = @("bin/python", "bin/pip") command = "--version" } go = @{ tools = @("bin/go") command = "version" } Ruby = @{ tools = @("bin/ruby") command = "--version" } CodeQL = @{ tools = @("codeql/codeql") command = "version" } } foreach ($tool in $tools) { $toolName = $tool.Name Context "$toolName" { $toolExecs = $toolsExecutables[$toolName] foreach ($version in $tool.versions) { # Add wildcard if missing if ($version.Split(".").Length -lt 3) { $version += ".*" } $expectedVersionPath = Join-Path $env:AGENT_TOOLSDIRECTORY $toolName $version It "$version version folder exists" -TestCases @{ ExpectedVersionPath = $expectedVersionPath} { $ExpectedVersionPath | Should -Exist } $foundVersion = Get-Item $expectedVersionPath ` | Sort-Object -Property {[SemVer]$_.name} -Descending ` | Select-Object -First 1 $foundVersionPath = Join-Path $foundVersion $tool.arch if ($toolExecs) { foreach ($executable in $toolExecs["tools"]) { $executablePath = Join-Path $foundVersionPath $executable It "Validate $executable" -TestCases @{ExecutablePath = $executablePath} { $ExecutablePath | Should -Exist } } } } } } } ================================================ FILE: images/ubuntu/scripts/tests/WebServers.Tests.ps1 ================================================ Describe "Apache" { It "Apache CLI" { "apache2 -v" | Should -ReturnZeroExitCode } It "Apache Service" { "sudo systemctl start apache2" | Should -ReturnZeroExitCode "apachectl configtest" | Should -ReturnZeroExitCode "sudo systemctl stop apache2" | Should -ReturnZeroExitCode } } Describe "Nginx" { It "Nginx CLI" { "nginx -v" | Should -ReturnZeroExitCode } It "Nginx Service" { "sudo systemctl start nginx" | Should -ReturnZeroExitCode "sudo nginx -t" | Should -ReturnZeroExitCode "sudo systemctl stop nginx" | Should -ReturnZeroExitCode } } ================================================ FILE: images/ubuntu/templates/build.ubuntu-22_04.pkr.hcl ================================================ build { sources = ["source.azure-arm.image"] name = "ubuntu-22_04" provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = ["mkdir ${var.image_folder}", "chmod 777 ${var.image_folder}"] } provisioner "file" { destination = "${var.helper_script_folder}" source = "${path.root}/../scripts/helpers" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" script = "${path.root}/../scripts/build/configure-apt-mock.sh" } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}","DEBIAN_FRONTEND=noninteractive"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = [ "${path.root}/../scripts/build/install-ms-repos.sh", "${path.root}/../scripts/build/configure-apt-sources.sh", "${path.root}/../scripts/build/configure-apt.sh" ] } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" script = "${path.root}/../scripts/build/configure-limits.sh" } provisioner "file" { destination = "${var.installer_script_folder}" source = "${path.root}/../scripts/build" } provisioner "file" { destination = "${var.image_folder}" sources = [ "${path.root}/../assets/post-gen", "${path.root}/../scripts/tests", "${path.root}/../scripts/docs-gen" ] } provisioner "file" { destination = "${var.image_folder}/docs-gen/" source = "${path.root}/../../../helpers/software-report-base" } provisioner "file" { destination = "${var.installer_script_folder}/toolset.json" source = "${path.root}/../toolsets/toolset-2204.json" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = [ "mv ${var.image_folder}/docs-gen ${var.image_folder}/SoftwareReport", "mv ${var.image_folder}/post-gen ${var.image_folder}/post-generation" ] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGEDATA_FILE=${var.imagedata_file}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/configure-image-data.sh"] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGE_OS=${var.image_os}", "HELPER_SCRIPTS=${var.helper_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/configure-environment.sh"] } provisioner "shell" { environment_vars = ["DEBIAN_FRONTEND=noninteractive", "HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-apt-vital.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-powershell.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} pwsh -f {{ .Path }}'" scripts = ["${path.root}/../scripts/build/Install-PowerShellModules.ps1", "${path.root}/../scripts/build/Install-PowerShellAzModules.ps1"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}", "DEBIAN_FRONTEND=noninteractive"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = [ "${path.root}/../scripts/build/install-actions-cache.sh", "${path.root}/../scripts/build/install-apt-common.sh", "${path.root}/../scripts/build/install-azcopy.sh", "${path.root}/../scripts/build/install-azure-cli.sh", "${path.root}/../scripts/build/install-azure-devops-cli.sh", "${path.root}/../scripts/build/install-bicep.sh", "${path.root}/../scripts/build/install-aliyun-cli.sh", "${path.root}/../scripts/build/install-apache.sh", "${path.root}/../scripts/build/install-aws-tools.sh", "${path.root}/../scripts/build/install-clang.sh", "${path.root}/../scripts/build/install-swift.sh", "${path.root}/../scripts/build/install-cmake.sh", "${path.root}/../scripts/build/install-codeql-bundle.sh", "${path.root}/../scripts/build/install-container-tools.sh", "${path.root}/../scripts/build/install-dotnetcore-sdk.sh", "${path.root}/../scripts/build/install-firefox.sh", "${path.root}/../scripts/build/install-microsoft-edge.sh", "${path.root}/../scripts/build/install-gcc-compilers.sh", "${path.root}/../scripts/build/install-gfortran.sh", "${path.root}/../scripts/build/install-git.sh", "${path.root}/../scripts/build/install-git-lfs.sh", "${path.root}/../scripts/build/install-github-cli.sh", "${path.root}/../scripts/build/install-google-chrome.sh", "${path.root}/../scripts/build/install-google-cloud-cli.sh", "${path.root}/../scripts/build/install-haskell.sh", "${path.root}/../scripts/build/install-heroku.sh", "${path.root}/../scripts/build/install-java-tools.sh", "${path.root}/../scripts/build/install-kubernetes-tools.sh", "${path.root}/../scripts/build/install-oc-cli.sh", "${path.root}/../scripts/build/install-leiningen.sh", "${path.root}/../scripts/build/install-miniconda.sh", "${path.root}/../scripts/build/install-mono.sh", "${path.root}/../scripts/build/install-kotlin.sh", "${path.root}/../scripts/build/install-mysql.sh", "${path.root}/../scripts/build/install-mssql-tools.sh", "${path.root}/../scripts/build/install-sqlpackage.sh", "${path.root}/../scripts/build/install-nginx.sh", "${path.root}/../scripts/build/install-nvm.sh", "${path.root}/../scripts/build/install-nodejs.sh", "${path.root}/../scripts/build/install-bazel.sh", "${path.root}/../scripts/build/install-oras-cli.sh", "${path.root}/../scripts/build/install-php.sh", "${path.root}/../scripts/build/install-postgresql.sh", "${path.root}/../scripts/build/install-pulumi.sh", "${path.root}/../scripts/build/install-ruby.sh", "${path.root}/../scripts/build/install-rlang.sh", "${path.root}/../scripts/build/install-rust.sh", "${path.root}/../scripts/build/install-julia.sh", "${path.root}/../scripts/build/install-sbt.sh", "${path.root}/../scripts/build/install-selenium.sh", "${path.root}/../scripts/build/install-terraform.sh", "${path.root}/../scripts/build/install-packer.sh", "${path.root}/../scripts/build/install-vcpkg.sh", "${path.root}/../scripts/build/configure-dpkg.sh", "${path.root}/../scripts/build/install-yq.sh", "${path.root}/../scripts/build/install-android-sdk.sh", "${path.root}/../scripts/build/install-pypy.sh", "${path.root}/../scripts/build/install-python.sh", "${path.root}/../scripts/build/install-zstd.sh", "${path.root}/../scripts/build/install-ninja.sh" ] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-docker.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} pwsh -f {{ .Path }}'" scripts = ["${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-pipx-packages.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "DEBIAN_FRONTEND=noninteractive", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "/bin/sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-homebrew.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/configure-snap.sh"] } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" script = "${path.root}/../scripts/build/list-dpkg.sh" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" expect_disconnect = true inline = ["echo 'Reboot VM'", "sudo reboot"] } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" pause_before = "5m0s" scripts = ["${path.root}/../scripts/build/cleanup.sh"] start_retry_timeout = "10m" } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] inline = ["pwsh -File ${var.image_folder}/SoftwareReport/Generate-SoftwareReport.ps1 -OutputDirectory ${var.image_folder}", "pwsh -File ${var.image_folder}/tests/RunAll-Tests.ps1 -OutputDirectory ${var.image_folder}"] } provisioner "file" { destination = "${path.root}/../Ubuntu2204-Readme.md" direction = "download" source = "${var.image_folder}/software-report.md" } provisioner "file" { destination = "${path.root}/../software-report.json" direction = "download" source = "${var.image_folder}/software-report.json" } provisioner "shell" { environment_vars = ["HELPER_SCRIPT_FOLDER=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}", "IMAGE_FOLDER=${var.image_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/configure-system.sh"] } provisioner "file" { destination = "/tmp/" source = "${path.root}/../assets/ubuntu2204.conf" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = ["mkdir -p /etc/vsts", "cp /tmp/ubuntu2204.conf /etc/vsts/machine_instance.conf"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/post-build-validation.sh"] } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = ["sleep 30", "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"] } } ================================================ FILE: images/ubuntu/templates/build.ubuntu-24_04.pkr.hcl ================================================ build { sources = ["source.azure-arm.image"] name = "ubuntu-24_04" provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = ["mkdir ${var.image_folder}", "chmod 777 ${var.image_folder}"] } provisioner "file" { destination = "${var.helper_script_folder}" source = "${path.root}/../scripts/helpers" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" script = "${path.root}/../scripts/build/configure-apt-mock.sh" } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}","DEBIAN_FRONTEND=noninteractive"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = [ "${path.root}/../scripts/build/install-ms-repos.sh", "${path.root}/../scripts/build/configure-apt-sources.sh", "${path.root}/../scripts/build/configure-apt.sh" ] } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" script = "${path.root}/../scripts/build/configure-limits.sh" } provisioner "file" { destination = "${var.installer_script_folder}" source = "${path.root}/../scripts/build" } provisioner "file" { destination = "${var.image_folder}" sources = [ "${path.root}/../assets/post-gen", "${path.root}/../scripts/tests", "${path.root}/../scripts/docs-gen" ] } provisioner "file" { destination = "${var.image_folder}/docs-gen/" source = "${path.root}/../../../helpers/software-report-base" } provisioner "file" { destination = "${var.installer_script_folder}/toolset.json" source = "${path.root}/../toolsets/toolset-2404.json" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = [ "mv ${var.image_folder}/docs-gen ${var.image_folder}/SoftwareReport", "mv ${var.image_folder}/post-gen ${var.image_folder}/post-generation" ] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGEDATA_FILE=${var.imagedata_file}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/configure-image-data.sh"] } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGE_OS=${var.image_os}", "HELPER_SCRIPTS=${var.helper_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/configure-environment.sh"] } provisioner "shell" { environment_vars = ["DEBIAN_FRONTEND=noninteractive", "HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-apt-vital.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-powershell.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} pwsh -f {{ .Path }}'" scripts = ["${path.root}/../scripts/build/Install-PowerShellModules.ps1", "${path.root}/../scripts/build/Install-PowerShellAzModules.ps1"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}", "DEBIAN_FRONTEND=noninteractive"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = [ "${path.root}/../scripts/build/install-actions-cache.sh", "${path.root}/../scripts/build/install-apt-common.sh", "${path.root}/../scripts/build/install-azcopy.sh", "${path.root}/../scripts/build/install-azure-cli.sh", "${path.root}/../scripts/build/install-azure-devops-cli.sh", "${path.root}/../scripts/build/install-bicep.sh", "${path.root}/../scripts/build/install-apache.sh", "${path.root}/../scripts/build/install-aws-tools.sh", "${path.root}/../scripts/build/install-clang.sh", "${path.root}/../scripts/build/install-swift.sh", "${path.root}/../scripts/build/install-cmake.sh", "${path.root}/../scripts/build/install-codeql-bundle.sh", "${path.root}/../scripts/build/install-container-tools.sh", "${path.root}/../scripts/build/install-dotnetcore-sdk.sh", "${path.root}/../scripts/build/install-microsoft-edge.sh", "${path.root}/../scripts/build/install-gcc-compilers.sh", "${path.root}/../scripts/build/install-firefox.sh", "${path.root}/../scripts/build/install-gfortran.sh", "${path.root}/../scripts/build/install-git.sh", "${path.root}/../scripts/build/install-git-lfs.sh", "${path.root}/../scripts/build/install-github-cli.sh", "${path.root}/../scripts/build/install-google-chrome.sh", "${path.root}/../scripts/build/install-google-cloud-cli.sh", "${path.root}/../scripts/build/install-haskell.sh", "${path.root}/../scripts/build/install-java-tools.sh", "${path.root}/../scripts/build/install-kubernetes-tools.sh", "${path.root}/../scripts/build/install-miniconda.sh", "${path.root}/../scripts/build/install-kotlin.sh", "${path.root}/../scripts/build/install-mysql.sh", "${path.root}/../scripts/build/install-nginx.sh", "${path.root}/../scripts/build/install-nvm.sh", "${path.root}/../scripts/build/install-nodejs.sh", "${path.root}/../scripts/build/install-bazel.sh", "${path.root}/../scripts/build/install-php.sh", "${path.root}/../scripts/build/install-postgresql.sh", "${path.root}/../scripts/build/install-pulumi.sh", "${path.root}/../scripts/build/install-ruby.sh", "${path.root}/../scripts/build/install-rust.sh", "${path.root}/../scripts/build/install-julia.sh", "${path.root}/../scripts/build/install-selenium.sh", "${path.root}/../scripts/build/install-packer.sh", "${path.root}/../scripts/build/install-vcpkg.sh", "${path.root}/../scripts/build/configure-dpkg.sh", "${path.root}/../scripts/build/install-yq.sh", "${path.root}/../scripts/build/install-android-sdk.sh", "${path.root}/../scripts/build/install-pypy.sh", "${path.root}/../scripts/build/install-python.sh", "${path.root}/../scripts/build/install-zstd.sh", "${path.root}/../scripts/build/install-ninja.sh" ] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-docker.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} pwsh -f {{ .Path }}'" scripts = ["${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-pipx-packages.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}", "DEBIAN_FRONTEND=noninteractive", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] execute_command = "/bin/sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/install-homebrew.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/configure-snap.sh"] } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" script = "${path.root}/../scripts/build/list-dpkg.sh" } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" expect_disconnect = true inline = ["echo 'Reboot VM'", "sudo reboot"] } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" pause_before = "5m0s" scripts = ["${path.root}/../scripts/build/cleanup.sh"] start_retry_timeout = "10m" } provisioner "shell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}"] inline = ["pwsh -File ${var.image_folder}/SoftwareReport/Generate-SoftwareReport.ps1 -OutputDirectory ${var.image_folder}", "pwsh -File ${var.image_folder}/tests/RunAll-Tests.ps1 -OutputDirectory ${var.image_folder}"] } provisioner "file" { destination = "${path.root}/../Ubuntu2404-Readme.md" direction = "download" source = "${var.image_folder}/software-report.md" } provisioner "file" { destination = "${path.root}/../software-report.json" direction = "download" source = "${var.image_folder}/software-report.json" } provisioner "shell" { environment_vars = ["HELPER_SCRIPT_FOLDER=${var.helper_script_folder}", "INSTALLER_SCRIPT_FOLDER=${var.installer_script_folder}", "IMAGE_FOLDER=${var.image_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/configure-system.sh"] } provisioner "shell" { environment_vars = ["HELPER_SCRIPTS=${var.helper_script_folder}"] execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" scripts = ["${path.root}/../scripts/build/post-build-validation.sh"] } provisioner "shell" { execute_command = "sudo sh -c '{{ .Vars }} {{ .Path }}'" inline = ["sleep 30", "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"] } } ================================================ FILE: images/ubuntu/templates/locals.ubuntu.pkr.hcl ================================================ locals { image_properties_map = { "ubuntu22" = { source_image_marketplace_sku = "canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2" os_disk_size_gb = 75 }, "ubuntu24" = { source_image_marketplace_sku = "canonical:ubuntu-24_04-lts:server" os_disk_size_gb = 75 } } source_image_marketplace_sku = local.image_properties_map[var.image_os].source_image_marketplace_sku os_disk_size_gb = coalesce(var.os_disk_size_gb, local.image_properties_map[var.image_os].os_disk_size_gb) } ================================================ FILE: images/ubuntu/templates/source.ubuntu.pkr.hcl ================================================ source "azure-arm" "image" { client_cert_path = var.client_cert_path client_id = var.client_id client_secret = var.client_secret object_id = var.object_id oidc_request_token = var.oidc_request_token oidc_request_url = var.oidc_request_url subscription_id = var.subscription_id tenant_id = var.tenant_id use_azure_cli_auth = var.use_azure_cli_auth allowed_inbound_ip_addresses = var.allowed_inbound_ip_addresses build_resource_group_name = var.build_resource_group_name image_publisher = split(":", local.source_image_marketplace_sku)[0] image_offer = split(":", local.source_image_marketplace_sku)[1] image_sku = split(":", local.source_image_marketplace_sku)[2] image_version = var.source_image_version location = var.location managed_image_name = var.managed_image_name managed_image_resource_group_name = var.managed_image_resource_group_name managed_image_storage_account_type = var.managed_image_storage_account_type os_disk_size_gb = local.os_disk_size_gb os_type = var.image_os_type private_virtual_network_with_public_ip = var.private_virtual_network_with_public_ip ssh_clear_authorized_keys = var.ssh_clear_authorized_keys temp_resource_group_name = var.temp_resource_group_name virtual_network_name = var.virtual_network_name virtual_network_resource_group_name = var.virtual_network_resource_group_name virtual_network_subnet_name = var.virtual_network_subnet_name vm_size = var.vm_size winrm_username = var.winrm_username shared_image_gallery_destination { subscription = var.subscription_id gallery_name = var.gallery_name resource_group = var.gallery_resource_group_name image_name = var.gallery_image_name image_version = var.gallery_image_version storage_account_type = var.gallery_storage_account_type } dynamic "azure_tag" { for_each = var.azure_tags content { name = azure_tag.key value = azure_tag.value } } } ================================================ FILE: images/ubuntu/templates/variable.ubuntu.pkr.hcl ================================================ // Authentication related variables variable "client_cert_path" { type = string default = "${env("ARM_CLIENT_CERT_PATH")}" } variable "client_id" { type = string default = "${env("ARM_CLIENT_ID")}" } variable "client_secret" { type = string default = "${env("ARM_CLIENT_SECRET")}" sensitive = true } variable "object_id" { type = string default = "${env("ARM_OBJECT_ID")}" } variable "oidc_request_token" { type = string default = "" } variable "oidc_request_url" { type = string default = "" } variable "subscription_id" { type = string default = "${env("ARM_SUBSCRIPTION_ID")}" } variable "tenant_id" { type = string default = "${env("ARM_TENANT_ID")}" } variable "use_azure_cli_auth" { type = bool default = false } // Azure environment related variables variable "allowed_inbound_ip_addresses" { type = list(string) default = [] } variable "azure_tags" { type = map(string) default = {} } variable "build_resource_group_name" { type = string default = "${env("BUILD_RG_NAME")}" } variable "gallery_image_name" { type = string default = "${env("GALLERY_IMAGE_NAME")}" } variable "gallery_image_version" { type = string default = "${env("GALLERY_IMAGE_VERSION")}" } variable "gallery_name" { type = string default = "${env("GALLERY_NAME")}" } variable "gallery_resource_group_name" { type = string default = "${env("GALLERY_RG_NAME")}" } variable "gallery_storage_account_type" { type = string default = "${env("GALLERY_STORAGE_ACCOUNT_TYPE")}" } variable "image_os_type" { type = string default = "Linux" } variable "location" { type = string default = "" } variable "managed_image_name" { type = string default = "" } variable "managed_image_resource_group_name" { type = string default = "${env("ARM_RESOURCE_GROUP")}" } variable "managed_image_storage_account_type" { type = string default = "Premium_LRS" } variable "private_virtual_network_with_public_ip" { type = bool default = false } variable "os_disk_size_gb" { type = number default = null } variable "source_image_version" { type = string default = "latest" } variable "ssh_clear_authorized_keys" { type = bool default = true } variable "temp_resource_group_name" { type = string default = "${env("TEMP_RESOURCE_GROUP_NAME")}" } variable "virtual_network_name" { type = string default = "${env("VNET_NAME")}" } variable "virtual_network_resource_group_name" { type = string default = "${env("VNET_RESOURCE_GROUP")}" } variable "virtual_network_subnet_name" { type = string default = "${env("VNET_SUBNET")}" } variable "vm_size" { type = string default = "Standard_D4s_v4" } variable "winrm_username" { // The username used to connect to the VM via WinRM type = string // Also applies to the username used to create the VM default = "packer" } // Image related variables variable "helper_script_folder" { type = string default = "/imagegeneration/helpers" } variable "image_folder" { type = string default = "/imagegeneration" } variable "image_os" { type = string default = "" } variable "image_version" { type = string default = "dev" } variable "imagedata_file" { type = string default = "/imagegeneration/imagedata.json" } variable "installer_script_folder" { type = string default = "/imagegeneration/installers" } variable "install_password" { type = string default = "" sensitive = true } variable "install_user" { type = string default = "installer" } ================================================ FILE: images/ubuntu/toolsets/toolset-2204.json ================================================ { "toolcache": [ { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "platform" : "linux", "platform_version": "22.04", "arch": "x64", "versions": [ "3.10.*", "3.11.*", "3.12.*", "3.13.*", "3.14.*" ] }, { "name": "PyPy", "arch": "x64", "platform" : "linux", "versions": [ "3.7", "3.8", "3.9", "3.10", "3.11" ] }, { "name": "node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "platform" : "linux", "arch": "x64", "versions": [ "20.*", "22.*", "24.*" ] }, { "name": "go", "url" : "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json", "arch": "x64", "platform" : "linux", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ], "default": "1.24.*" }, { "name": "Ruby", "platform_version": "22.04", "arch": "x64", "versions": [ "3.2.*", "3.3.*", "3.4.*", "4.0.*" ] }, { "name": "CodeQL", "platform" : "linux", "arch": "x64", "versions": [ "*" ] } ], "java": { "default": "11", "versions": [ "8", "11", "17", "21", "25"], "maven": "3.9.14" }, "android": { "cmdline-tools": "commandlinetools-linux-9477386_latest.zip", "platform_min_version": "34", "build_tools_min_version": "34.0.0", "extra_list": [ "android;m2repository", "google;m2repository", "google;google_play_services" ], "addon_list": [ ], "additional_tools": [ "cmake;3.18.1", "cmake;3.22.1", "cmake;3.31.5" ], "ndk": { "default": "27", "versions": [ "27", "28", "29" ] } }, "powershellModules": [ {"name": "MarkdownPS"}, {"name": "Microsoft.Graph"}, {"name": "Pester"}, {"name": "PSScriptAnalyzer"} ], "azureModules": [ { "name": "az", "versions": [ "14.6.0" ] } ], "apt": { "vital_packages": [ "bzip2", "curl", "g++", "gcc", "make", "jq", "tar", "unzip", "wget" ], "common_packages": [ "autoconf", "automake", "dbus", "dnsutils", "dpkg", "dpkg-dev", "fakeroot", "fonts-noto-color-emoji", "gnupg2", "imagemagick", "iproute2", "iputils-ping", "lib32z1", "libc++abi-dev", "libc++-dev", "libc6-dev", "libcurl4", "libgbm-dev", "libgconf-2-4", "libgsl-dev", "libgtk-3-0", "libmagic-dev", "libmagickcore-dev", "libmagickwand-dev", "libsecret-1-dev", "libsqlite3-dev", "libyaml-dev", "libtool", "libunwind8", "libxkbfile-dev", "libxss1", "libssl-dev", "locales", "mercurial", "openssh-client", "p7zip-rar", "pkg-config", "python-is-python3", "rpm", "texinfo", "tk", "tzdata", "upx", "xorriso", "xvfb", "xz-utils", "zsync" ], "cmd_packages": [ "acl", "aria2", "binutils", "bison", "brotli", "libnss3-tools", "coreutils", "file", "findutils", "flex", "ftp", "haveged", "lz4", "m4", "mediainfo", "netcat", "net-tools", "p7zip-full", "parallel", "pass", "patchelf", "pigz", "pollinate", "rsync", "shellcheck", "sphinxsearch", "sqlite3", "ssh", "sshpass", "subversion", "sudo", "systemd-coredump", "swig", "telnet", "time", "zip" ] }, "brew": [ ], "docker": { "components": [ { "package": "containerd.io", "version": "latest" }, { "package": "docker-ce-cli", "version": "28.0.4" }, { "package": "docker-ce", "version": "28.0.4" } ], "plugins": [ { "plugin": "buildx", "version": "latest", "asset": "linux-amd64" }, { "plugin": "compose", "version": "2.38.2", "asset": "linux-x86_64" } ] }, "pipx": [ { "package": "yamllint", "cmd": "yamllint" }, { "package": "ansible-core", "cmd": "ansible" } ], "dotnet": { "versions": [ "8.0", "9.0", "10.0" ], "tools": [ { "name": "nbgv", "test": "nbgv --version", "getversion" : "nbgv --version" } ] }, "clang": { "versions": [ "13", "14", "15" ], "default_version": "14" }, "gcc": { "versions": [ "g++-10", "g++-12" ] }, "gfortran": { "versions": [ "gfortran-9", "gfortran-10", "gfortran-12" ] }, "php": { "versions": [ "8.1" ] }, "rubygems": [ {"name": "fastlane"} ], "selenium": { "version": "4" }, "node": { "default": "20" }, "node_modules": [ { "name": "grunt", "command": "grunt" }, { "name": "gulp", "command": "gulp" }, { "name": "n", "command": "n" }, { "name": "parcel", "command": "parcel" }, { "name": "typescript", "command": "tsc" }, { "name": "newman", "command": "newman" }, { "name": "vercel", "command": "vercel" }, { "name": "webpack", "command": "webpack" }, { "name": "webpack-cli", "command": "webpack-cli" }, { "name": "netlify-cli", "command": "netlify" }, { "name": "lerna", "command": "lerna" }, { "name": "yarn", "command": "yarn" } ], "postgresql": { "version": "14" }, "pwsh": { "version": "7.4" } } ================================================ FILE: images/ubuntu/toolsets/toolset-2404.json ================================================ { "toolcache": [ { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "platform" : "linux", "platform_version": "24.04", "arch": "x64", "versions": [ "3.10.*", "3.11.*", "3.12.*", "3.13.*", "3.14.*" ] }, { "name": "PyPy", "arch": "x64", "platform" : "linux", "versions": [ "3.9", "3.10", "3.11" ] }, { "name": "node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "platform" : "linux", "arch": "x64", "versions": [ "20.*", "22.*", "24.*" ] }, { "name": "go", "url" : "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json", "arch": "x64", "platform" : "linux", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ], "default": "1.24.*" }, { "name": "Ruby", "platform_version": "24.04", "arch": "x64", "versions": [ "3.2.*", "3.3.*", "3.4.*", "4.0.*" ] }, { "name": "CodeQL", "platform" : "linux", "arch": "x64", "versions": [ "*" ] } ], "java": { "default": "17", "versions": [ "8", "11", "17", "21", "25"], "maven": "3.9.14" }, "android": { "cmdline-tools": "commandlinetools-linux-11076708_latest.zip", "platform_min_version": "34", "build_tools_min_version": "34.0.0", "extra_list": [ "android;m2repository", "google;m2repository", "google;google_play_services" ], "addon_list": [ ], "additional_tools": [ "cmake;3.31.5", "cmake;4.1.2" ], "ndk": { "default": "27", "versions": [ "27", "28", "29" ] } }, "powershellModules": [ {"name": "Microsoft.Graph"}, {"name": "Pester"}, {"name": "PSScriptAnalyzer"} ], "azureModules": [ { "name": "az", "versions": [ "14.6.0" ] } ], "apt": { "vital_packages": [ "bzip2", "curl", "g++", "gcc", "make", "jq", "tar", "unzip", "wget" ], "common_packages": [ "autoconf", "automake", "dbus", "dnsutils", "dpkg", "dpkg-dev", "fakeroot", "fonts-noto-color-emoji", "gnupg2", "iproute2", "iputils-ping", "libyaml-dev", "libtool", "libssl-dev", "libsqlite3-dev", "locales", "mercurial", "openssh-client", "p7zip-rar", "pkg-config", "python-is-python3", "rpm", "texinfo", "tk", "tree", "tzdata", "upx", "xvfb", "xz-utils", "zsync" ], "cmd_packages": [ "acl", "aria2", "binutils", "bison", "brotli", "libnss3-tools", "coreutils", "file", "findutils", "flex", "ftp", "haveged", "lz4", "m4", "mediainfo", "netcat", "net-tools", "p7zip-full", "parallel", "patchelf", "pigz", "pollinate", "rsync", "shellcheck", "sphinxsearch", "sqlite3", "ssh", "sshpass", "sudo", "systemd-coredump", "swig", "telnet", "time", "zip" ] }, "brew": [ ], "docker": { "components": [ { "package": "containerd.io", "version": "latest" }, { "package": "docker-ce-cli", "version": "28.0.4" }, { "package": "docker-ce", "version": "28.0.4" } ], "plugins": [ { "plugin": "buildx", "version": "latest", "asset": "linux-amd64" }, { "plugin": "compose", "version": "2.38.2", "asset": "linux-x86_64" } ] }, "pipx": [ { "package": "yamllint", "cmd": "yamllint" }, { "package": "ansible-core", "cmd": "ansible" } ], "dotnet": { "versions": [ "8.0", "9.0", "10.0" ], "tools": [ { "name": "nbgv", "test": "nbgv --version", "getversion" : "nbgv --version" } ] }, "clang": { "versions": [ "16", "17", "18" ], "default_version": "18" }, "gcc": { "versions": [ "g++-12", "g++-13", "g++-14" ] }, "gfortran": { "versions": [ "gfortran-12", "gfortran-13", "gfortran-14" ] }, "php": { "versions": [ "8.3" ] }, "rubygems": [ {"name": "fastlane"} ], "selenium": { "version": "4" }, "node": { "default": "20" }, "node_modules": [ { "name": "grunt", "command": "grunt" }, { "name": "gulp", "command": "gulp" }, { "name": "n", "command": "n" }, { "name": "parcel", "command": "parcel" }, { "name": "typescript", "command": "tsc" }, { "name": "newman", "command": "newman" }, { "name": "webpack", "command": "webpack" }, { "name": "webpack-cli", "command": "webpack-cli" }, { "name": "lerna", "command": "lerna" }, { "name": "yarn", "command": "yarn" } ], "postgresql": { "version": "16" }, "pwsh": { "version": "7.4" } } ================================================ FILE: images/ubuntu-slim/Dockerfile ================================================ FROM ubuntu:24.04 AS base ARG IMAGE_VERSION=1.0.0 ARG IMAGE_OWNER="GitHub" ENV IMAGE_OWNER=$IMAGE_OWNER ENV ImageVersion=$IMAGE_VERSION ENV IMAGE_VERSION=$IMAGE_VERSION ENV ImageOS="Linux" ENV IMAGE_TARGET_PLATFORM="GitHub" ENV POWERSHELL_DISTRIBUTION_CHANNEL="GitHub-Actions-$ImageOS" ENV IMAGEDATA_NAME="ubuntu:24.04" ENV NVM_DIR="/etc/skel/.nvm" ENV HELPER_SCRIPTS="/tmp/scripts/helpers" ENV INSTALLER_SCRIPT_FOLDER="/tmp/toolsets" # Avoid interactive prompts ENV DEBIAN_FRONTEND=noninteractive COPY scripts/build /tmp/scripts/build COPY scripts/helpers /tmp/scripts/helpers COPY toolsets/ /tmp/toolsets/ RUN find /tmp/scripts -name "*.sh" -type f -exec chmod +x {} \; COPY scripts/entrypoint.sh /opt/entrypoint.sh RUN chmod +x /opt/entrypoint.sh RUN echo 'set -eo pipefail' >> /etc/bash.bashrc RUN apt-get update && apt-get upgrade -y && apt-get install -y sudo lsb-release jq dpkg && \ touch /run/.containerenv && \ /tmp/scripts/build/configure-apt-sources.sh && \ /tmp/scripts/build/configure-apt.sh && \ /tmp/scripts/build/install-apt-vital.sh && \ /tmp/scripts/build/install-ms-repos.sh && \ /tmp/scripts/build/configure-image-data-file.sh && \ /tmp/scripts/build/configure-environment.sh && \ /tmp/scripts/build/install-actions-cache.sh && \ /tmp/scripts/build/install-apt-common.sh && \ /tmp/scripts/build/install-azcopy.sh && \ /tmp/scripts/build/install-azure-cli.sh && \ /tmp/scripts/build/install-azure-devops-cli.sh && \ /tmp/scripts/build/install-bicep.sh && \ /tmp/scripts/build/install-aws-tools.sh && \ /tmp/scripts/build/install-git.sh && \ /tmp/scripts/build/install-git-lfs.sh && \ /tmp/scripts/build/install-github-cli.sh && \ /tmp/scripts/build/install-google-cloud-cli.sh && \ /tmp/scripts/build/install-nvm.sh && \ /tmp/scripts/build/install-nodejs.sh && \ /tmp/scripts/build/install-powershell.sh && \ /tmp/scripts/build/configure-dpkg.sh && \ /tmp/scripts/build/install-yq.sh && \ /tmp/scripts/build/install-python.sh && \ /tmp/scripts/build/install-zstd.sh && \ /tmp/scripts/build/install-pipx-packages.sh && \ /tmp/scripts/build/install-docker-cli.sh && \ /tmp/scripts/build/configure-system.sh && \ /tmp/scripts/helpers/cleanup.sh RUN sed -i '/set -eo pipefail/d' /etc/bash.bashrc ENTRYPOINT ["/opt/entrypoint.sh"] CMD [ "bash" ] ================================================ FILE: images/ubuntu-slim/generate-software-report.sh ================================================ #!/bin/bash -e show_help() { echo "Usage: $0 [IMAGE_NAME]" echo "" echo "Generate a software report for a Docker image." echo "" echo "Arguments:" echo " IMAGE_NAME Docker image name to generate report for (default: ubuntu-slim:test)" echo "" echo "Examples:" echo " $0 # Generate report for ubuntu-slim:test (builds image first)" echo " $0 my-registry/ubuntu:latest # Generate report for existing image" echo " $0 ubuntu-slim:v1.2.3 # Generate report for tagged image" echo "" echo "Options:" echo " -h, --help Show this help message" } # Handle help flags if [[ "$1" == "-h" || "$1" == "--help" ]]; then show_help exit 0 fi # Set the image name from parameter or use default IMAGE_NAME="${1:-ubuntu-slim:test}" # Build the image only if using the default name (for backward compatibility) if [[ "$IMAGE_NAME" == "ubuntu-slim:test" ]]; then echo "Building image: $IMAGE_NAME" docker build --debug --progress plain -t "$IMAGE_NAME" . else # Check if the image exists if ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then echo "Error: Image '$IMAGE_NAME' does not exist. Please build it first or provide a valid image name." echo "Run '$0 --help' for usage information." exit 1 fi fi echo "Generating software report for image: $IMAGE_NAME" # Get the script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BASE_DIR="$(cd ../../helpers/software-report-base && pwd)" echo $BASE_DIR # Create a temporary directory for output OUTPUT_DIR=$(mktemp -d) echo "Using temporary directory: $OUTPUT_DIR" # Run the container and execute the PowerShell script inside it echo "Running Generate-SoftwareReport.ps1 inside the container..." docker run --rm \ -v "$OUTPUT_DIR:/output" \ -v "$SCRIPT_DIR/scripts/docs-gen:/scripts/docs-gen:ro" \ -v "$BASE_DIR:/scripts/software-report-base:ro" \ "$IMAGE_NAME" \ pwsh /scripts/docs-gen/Generate-SoftwareReport.ps1 -OutputDirectory /output if [ -f "$OUTPUT_DIR/software-report.md" ]; then cp "$OUTPUT_DIR/software-report.md" ubuntu-slim-Readme.md echo "✓ Copied software-report.md to current directory" else echo "✗ Error: software-report.md was not generated" rm -rf "$OUTPUT_DIR" exit 1 fi if [ -f "$OUTPUT_DIR/software-report.json" ]; then cp "$OUTPUT_DIR/software-report.json" ubuntu-slim-Report.json echo "✓ Copied software-report.json to current directory" else echo "✗ Error: software-report.json was not generated" rm -rf "$OUTPUT_DIR" exit 1 fi # Clean up temporary directory rm -rf "$OUTPUT_DIR" echo "✓ Software report generation complete" ================================================ FILE: images/ubuntu-slim/scripts/build/configure-apt-sources.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-apt-sources.sh ## Desc: Configure apt sources with failover from Azure to Ubuntu archives. ################################################################################ source $HELPER_SCRIPTS/os.sh touch /etc/apt/apt-mirrors.txt printf "http://azure.archive.ubuntu.com/ubuntu/\tpriority:1\n" | tee -a /etc/apt/apt-mirrors.txt printf "https://archive.ubuntu.com/ubuntu/\tpriority:2\n" | tee -a /etc/apt/apt-mirrors.txt printf "https://security.ubuntu.com/ubuntu/\tpriority:3\n" | tee -a /etc/apt/apt-mirrors.txt if is_ubuntu24; then sed -i 's|http://archive\.ubuntu\.com/ubuntu/|mirror+file:/etc/apt/apt-mirrors.txt|' /etc/apt/sources.list.d/ubuntu.sources else sed -i 's|http://archive\.ubuntu\.com/ubuntu/|mirror+file:/etc/apt/apt-mirrors.txt|' /etc/apt/sources.list fi ================================================ FILE: images/ubuntu-slim/scripts/build/configure-apt.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-apt.sh ## Desc: Configure apt, install jq and apt-fast packages. ################################################################################ source $HELPER_SCRIPTS/os.sh # Stop and disable apt-daily upgrade services; # systemctl stop apt-daily.timer # systemctl disable apt-daily.timer # systemctl disable apt-daily.service # systemctl stop apt-daily-upgrade.timer # systemctl disable apt-daily-upgrade.timer # systemctl disable apt-daily-upgrade.service # Enable retry logic for apt up to 10 times echo "APT::Acquire::Retries \"10\";" > /etc/apt/apt.conf.d/80-retries # Configure apt to always assume Y echo "APT::Get::Assume-Yes \"true\";" > /etc/apt/apt.conf.d/90assumeyes # APT understands a field called Phased-Update-Percentage which can be used to control the rollout of a new version. It is an integer between 0 and 100. # In case you have multiple systems that you want to receive the same set of updates, # you can set APT::Machine-ID to a UUID such that they all phase the same, # or set APT::Get::Never-Include-Phased-Updates or APT::Get::Always-Include-Phased-Updates to true such that APT will never/always consider phased updates. # apt-cache policy pkgname echo 'APT::Get::Always-Include-Phased-Updates "true";' > /etc/apt/apt.conf.d/99-phased-updates # Fix bad proxy and http headers settings cat <> /etc/apt/apt.conf.d/99bad_proxy Acquire::http::Pipeline-Depth 0; Acquire::http::No-Cache true; Acquire::https::Pipeline-Depth 0; Acquire::https::No-Cache true; Acquire::BrokenProxy true; EOF echo 'APT sources' if ! is_ubuntu24; then cat /etc/apt/sources.list else cat /etc/apt/sources.list.d/ubuntu.sources fi apt-get update echo "ubuntu ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers ================================================ FILE: images/ubuntu-slim/scripts/build/configure-dpkg.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-dpkg.sh ## Desc: Configure dpkg ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh source $HELPER_SCRIPTS/os.sh # This is the anti-frontend. It never interacts with you at all, # and makes the default answers be used for all questions. It # might mail error messages to root, but that's it; otherwise it # is completely silent and unobtrusive, a perfect frontend for # automatic installs. If you are using this front-end, and require # non-default answers to questions, you will need to pre-seed the # debconf database set_etc_environment_variable "DEBIAN_FRONTEND" "noninteractive" # dpkg can be instructed not to ask for confirmation # when replacing a configuration file (with the --force-confdef --force-confold options) cat <> /etc/apt/apt.conf.d/10dpkg-options Dpkg::Options { "--force-confdef"; "--force-confold"; } EOF # hide information about packages that are no longer required cat <> /etc/apt/apt.conf.d/10apt-autoremove APT::Get::AutomaticRemove "0"; APT::Get::HideAutoRemove "1"; EOF # Install libicu70 package for Ubuntu 24 if is_ubuntu24 ; then wget https://archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu70_70.1-2_amd64.deb EXPECTED_LIBICU_SHA512="a6315482d93606e375c272718d2458870b95e4ed4b672ea8640cf7bc2d2c2f41aea13b798b1e417e1ffc472a90c6aad150d3d293aa9bddec48e39106e4042807" ACTUAL_LIBICU_SHA512="$(sha512sum "./libicu70_70.1-2_amd64.deb" | awk '{print $1}')" [ "$EXPECTED_LIBICU_SHA512" = "$ACTUAL_LIBICU_SHA512" ] || { echo "libicu checksum mismatch in configure-dpkg.sh"; exit 1;} sudo apt-get install -y ./libicu70_70.1-2_amd64.deb fi ================================================ FILE: images/ubuntu-slim/scripts/build/configure-environment.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-environment.sh ## Desc: Configure system and environment ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/os.sh source $HELPER_SCRIPTS/etc-environment.sh whoami # Set ImageVersion and ImageOS env variables set_etc_environment_variable "ImageVersion" "${IMAGE_VERSION}" set_etc_environment_variable "ImageOS" "${IMAGE_OS}" # Set the ACCEPT_EULA variable to Y value to confirm your acceptance of the End-User Licensing Agreement set_etc_environment_variable "ACCEPT_EULA" "Y" # This directory is supposed to be created in $HOME and owned by user(https://github.com/actions/runner-images/issues/491) mkdir -p /etc/skel/.config/configstore set_etc_environment_variable "XDG_CONFIG_HOME" '$HOME/.config' # Prepare directory and env variable for toolcache echo "Setting up AGENT_TOOLSDIRECTORY and RUNNER_TOOL_CACHE variable to /opt/hostedtoolcache" AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache mkdir $AGENT_TOOLSDIRECTORY set_etc_environment_variable "AGENT_TOOLSDIRECTORY" "${AGENT_TOOLSDIRECTORY}" set_etc_environment_variable "RUNNER_TOOL_CACHE" "${AGENT_TOOLSDIRECTORY}" chmod -R 777 $AGENT_TOOLSDIRECTORY # https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html # https://www.suse.com/support/kb/doc/?id=000016692 echo 'vm.max_map_count=262144' | tee -a /etc/sysctl.conf # https://kind.sigs.k8s.io/docs/user/known-issues/#pod-errors-due-to-too-many-open-files echo 'fs.inotify.max_user_watches=655360' | tee -a /etc/sysctl.conf echo 'fs.inotify.max_user_instances=1280' | tee -a /etc/sysctl.conf # https://github.com/actions/runner-images/issues/9491 echo 'vm.mmap_rnd_bits=28' | tee -a /etc/sysctl.conf # https://github.com/actions/runner-images/pull/7860 netfilter_rule='/etc/udev/rules.d/50-netfilter.rules' rules_directory="$(dirname "${netfilter_rule}")" mkdir -p $rules_directory touch $netfilter_rule echo 'ACTION=="add", SUBSYSTEM=="module", KERNEL=="nf_conntrack", RUN+="/usr/sbin/sysctl net.netfilter.nf_conntrack_tcp_be_liberal=1"' | tee -a $netfilter_rule # Remove fwupd if installed. We're running on VMs in Azure and the fwupd package is not needed. # Leaving it enable means periodic refreshes show in network traffic and firewall logs # Check if fwupd-refresh.timer exists in systemd if systemctl list-unit-files fwupd-refresh.timer &>/dev/null; then echo "Masking fwupd-refresh.timer..." systemctl mask fwupd-refresh.timer fi # This is a legacy check, leaving for earlier versions of Ubuntu # If fwupd config still exists, disable the motd updates if [[ -f "/etc/fwupd/daemon.conf" ]]; then sed -i 's/UpdateMotd=true/UpdateMotd=false/g' /etc/fwupd/daemon.conf fi # Disable to load providers # https://github.com/microsoft/azure-pipelines-agent/issues/3834 if is_ubuntu22; then sed -i 's/openssl_conf = openssl_init/#openssl_conf = openssl_init/g' /etc/ssl/openssl.cnf fi # # Disable man-db auto update # echo "set man-db/auto-update false" | debconf-communicate # dpkg-reconfigure man-db ================================================ FILE: images/ubuntu-slim/scripts/build/configure-image-data-file.sh ================================================ #!/bin/bash -e function create_imagedata_json() { arch=$(uname -m) if [[ $arch == "x86_64" ]]; then arch="x64" elif [[ $arch == "aarch64" ]]; then arch="arm64" else echo "Unsupported architecture: $arch" exit 1 fi if [[ -n "$IMAGEDATA_INCLUDED_SOFTWARE" ]]; then included_software="- Included Software: ${IMAGEDATA_INCLUDED_SOFTWARE}" fi imagedata_file="/imagegeneration/imagedata.json" cat < $imagedata_file [ { "group": "VM Image", "detail": "- OS: Linux (${arch})\n- Source: Docker\n- Name: ${IMAGEDATA_NAME}\n- Version: ${IMAGE_VERSION}\n${included_software}" } ] EOF } mkdir -p /imagegeneration # Generate the imagedata JSON file displayed on workflow initialization if [[ -n "$IMAGEDATA_NAME" ]]; then echo "Generating imagedata JSON file" create_imagedata_json else echo "IMAGEDATA_NAME is null or empty. Skipping imagedata JSON generation." fi ================================================ FILE: images/ubuntu-slim/scripts/build/configure-system.sh ================================================ #!/bin/bash -e ################################################################################ ## File: configure-system.sh ## Desc: Post deployment system configuration actions ################################################################################ source $HELPER_SCRIPTS/etc-environment.sh source $HELPER_SCRIPTS/os.sh echo "chmod -R 777 /opt" chmod -R 777 /opt echo "chmod -R 777 /usr/share" chmod -R 777 /usr/share # Remove quotes around PATH ENVPATH=$(grep 'PATH=' /etc/environment | head -n 1 | sed -z 's/^PATH=*//') ENVPATH=${ENVPATH#"\""} ENVPATH=${ENVPATH%"\""} replace_etc_environment_variable "PATH" "${ENVPATH}" echo "Updated /etc/environment: $(cat /etc/environment)" ================================================ FILE: images/ubuntu-slim/scripts/build/install-actions-cache.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-actions-cache.sh ## Desc: Download latest release from https://github.com/actions/action-versions ## Maintainer: #actions-runtime and @TingluoHuang ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/etc-environment.sh # Prepare directory and env variable for ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE ACTION_ARCHIVE_CACHE_DIR=/opt/actionarchivecache mkdir -p $ACTION_ARCHIVE_CACHE_DIR chmod -R 777 $ACTION_ARCHIVE_CACHE_DIR echo "Setting up ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE variable to ${ACTION_ARCHIVE_CACHE_DIR}" set_etc_environment_variable "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE" "${ACTION_ARCHIVE_CACHE_DIR}" # Download latest release from github.com/actions/action-versions and untar to /opt/actionarchivecache download_url=$(resolve_github_release_asset_url "actions/action-versions" "endswith(\"action-versions.tar.gz\")" "latest") archive_path=$(download_with_retry "$download_url") tar -xzf "$archive_path" -C $ACTION_ARCHIVE_CACHE_DIR ================================================ FILE: images/ubuntu-slim/scripts/build/install-apt-common.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-apt-common.sh ## Desc: Install basic command line utilities and dev packages ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh common_packages=$(get_toolset_value .apt.common_packages[]) cmd_packages=$(get_toolset_value .apt.cmd_packages[]) apt-get install --no-install-recommends $common_packages $cmd_packages # for package in $common_packages $cmd_packages; do # echo "Install $package" # apt-get install --no-install-recommends $package # done ================================================ FILE: images/ubuntu-slim/scripts/build/install-apt-vital.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-apt-vital.sh ## Desc: Install vital command line utilities ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh vital_packages=$(get_toolset_value .apt.vital_packages[]) apt-get install --no-install-recommends $vital_packages ================================================ FILE: images/ubuntu-slim/scripts/build/install-aws-tools.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-aws-tools.sh ## Desc: Install the AWS CLI, Session Manager plugin for the AWS CLI, and AWS SAM CLI ## Supply chain security: AWS SAM CLI - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh awscliv2_archive_path=$(download_with_retry "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip") unzip -qq "$awscliv2_archive_path" -d /tmp/installers/ /tmp/installers/aws/install -i /usr/local/aws-cli -b /usr/local/bin smplugin_deb_path=$(download_with_retry "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb") apt-get install "$smplugin_deb_path" # Download the latest aws sam cli release aws_sam_cli_archive_name="aws-sam-cli-linux-x86_64.zip" sam_cli_download_url=$(resolve_github_release_asset_url "aws/aws-sam-cli" "endswith(\"$aws_sam_cli_archive_name\")" "latest") aws_sam_cli_archive_path=$(download_with_retry "$sam_cli_download_url") # Supply chain security - AWS SAM CLI aws_sam_cli_hash=$(get_checksum_from_github_release "aws/aws-sam-cli" "${aws_sam_cli_archive_name}.. " "latest" "SHA256") use_checksum_comparison "$aws_sam_cli_archive_path" "$aws_sam_cli_hash" # Install the latest aws sam cli release mkdir -p /tmp/installers/aws-sam-cli unzip "$aws_sam_cli_archive_path" -d /tmp/installers/aws-sam-cli /tmp/installers/aws-sam-cli/install -i /usr/local/aws-sam-cli -b /usr/local/bin ================================================ FILE: images/ubuntu-slim/scripts/build/install-azcopy.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-azcopy.sh ## Desc: Install AzCopy ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Install AzCopy10 archive_path=$(download_with_retry "https://aka.ms/downloadazcopy-v10-linux") tar xzf "$archive_path" --strip-components=1 -C /tmp install /tmp/azcopy /usr/local/bin/azcopy # Create azcopy 10 alias for backward compatibility ln -sf /usr/local/bin/azcopy /usr/local/bin/azcopy10 ================================================ FILE: images/ubuntu-slim/scripts/build/install-azure-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-azure-cli.sh ## Desc: Install Azure CLI (az) ################################################################################ # Install Azure CLI (instructions taken from https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) curl -fsSL https://aka.ms/InstallAzureCLIDeb | sudo bash echo "azure-cli https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt" >> $HELPER_SCRIPTS/apt-sources.txt rm -f /etc/apt/sources.list.d/azure-cli.list rm -f /etc/apt/sources.list.d/azure-cli.list.save ================================================ FILE: images/ubuntu-slim/scripts/build/install-azure-devops-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-azure-devops-cli.sh ## Desc: Install Azure DevOps CLI (az devops) ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh # AZURE_EXTENSION_DIR shell variable defines where modules are installed # https://docs.microsoft.com/en-us/cli/azure/azure-cli-extensions-overview export AZURE_EXTENSION_DIR=/opt/az/azcliextensions set_etc_environment_variable "AZURE_EXTENSION_DIR" "${AZURE_EXTENSION_DIR}" # install azure devops Cli extension az extension add -n azure-devops ================================================ FILE: images/ubuntu-slim/scripts/build/install-bicep.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-bicep.sh ## Desc: Install bicep cli ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Install Bicep CLI download_url=$(resolve_github_release_asset_url "Azure/bicep" "endswith(\"bicep-linux-x64\")" "latest") bicep_binary_path=$(download_with_retry "${download_url}") # Mark it as executable install "$bicep_binary_path" /usr/local/bin/bicep ================================================ FILE: images/ubuntu-slim/scripts/build/install-docker-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-docker-cli.sh ## Desc: Install Docker CLI and plugins (Compose, Buildx) but not the engine. ## The Docker daemon is not included since ubuntu-slim runs as a container. ## ubuntu-slim does not run in Privileged mode, so functionality from these tools is limited. ## It cannot build or run containers locally. ################################################################################ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo \ "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null apt-get update # Install Docker CLI components (not the daemon) # docker-ce-cli: Docker command line interface # docker-buildx-plugin: Build with BuildKit # docker-compose-plugin: Docker Compose V2 apt-get install --no-install-recommends -y \ docker-ce-cli \ docker-buildx-plugin \ docker-compose-plugin ================================================ FILE: images/ubuntu-slim/scripts/build/install-git-lfs.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-git-lfs.sh ## Desc: Install Git-lfs ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh GIT_LFS_REPO="https://packagecloud.io/install/repositories/github/git-lfs" # Install git-lfs curl -fsSL $GIT_LFS_REPO/script.deb.sh | bash apt-get install git-lfs # Remove source repo's rm /etc/apt/sources.list.d/github_git-lfs.list # Document apt source repo's echo "git-lfs $GIT_LFS_REPO" >> $HELPER_SCRIPTS/apt-sources.txt ================================================ FILE: images/ubuntu-slim/scripts/build/install-git.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-git.sh ## Desc: Install Git and Git-FTP ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh GIT_REPO="ppa:git-core/ppa" ## Install git add-apt-repository $GIT_REPO -y apt-get update apt-get install git # Git version 2.35.2 introduces security fix that breaks action\checkout https://github.com/actions/checkout/issues/760 cat <> /etc/gitconfig [safe] directory = * EOF # Install git-ftp apt-get install git-ftp # Remove source repo's add-apt-repository --remove $GIT_REPO # Document apt source repo's echo "git-core $GIT_REPO" >> $HELPER_SCRIPTS/apt-sources.txt # Add well-known SSH host keys to known_hosts ssh-keyscan -t rsa,ecdsa,ed25519 github.com >> /etc/ssh/ssh_known_hosts ssh-keyscan -t rsa ssh.dev.azure.com >> /etc/ssh/ssh_known_hosts ================================================ FILE: images/ubuntu-slim/scripts/build/install-github-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-github-cli.sh ## Desc: Install GitHub CLI ## Must be run as non-root user after homebrew ## Supply chain security: GitHub CLI - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Download GitHub CLI gh_cli_url=$(resolve_github_release_asset_url "cli/cli" "contains(\"linux\") and contains(\"amd64\") and endswith(\".deb\")" "latest") gh_cli_deb_path=$(download_with_retry "$gh_cli_url") # Supply chain security - GitHub CLI hash_url=$(resolve_github_release_asset_url "cli/cli" "endswith(\"checksums.txt\")" "latest") external_hash=$(get_checksum_from_url "$hash_url" "linux_amd64.deb" "SHA256") use_checksum_comparison "$gh_cli_deb_path" "$external_hash" # Install GitHub CLI apt-get install "$gh_cli_deb_path" ================================================ FILE: images/ubuntu-slim/scripts/build/install-google-cloud-cli.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-google-cloud-cli.sh ## Desc: Install the Google Cloud CLI ################################################################################ REPO_URL="https://packages.cloud.google.com/apt" # Install the Google Cloud CLI echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] $REPO_URL cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list wget -qO- https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor > /usr/share/keyrings/cloud.google.gpg apt-get update apt-get install google-cloud-cli # remove apt rm /etc/apt/sources.list.d/google-cloud-sdk.list rm /usr/share/keyrings/cloud.google.gpg # add repo to the apt-sources.txt echo "google-cloud-sdk $REPO_URL" >> $HELPER_SCRIPTS/apt-sources.txt ================================================ FILE: images/ubuntu-slim/scripts/build/install-ms-repos.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-ms-repos.sh ## Desc: Install official Microsoft package repos for the distribution ################################################################################ os_label=$(lsb_release -rs) # Install Microsoft repository wget https://packages.microsoft.com/config/ubuntu/$os_label/packages-microsoft-prod.deb dpkg -i packages-microsoft-prod.deb # update apt-get install apt-transport-https ca-certificates curl software-properties-common apt-get update apt-get dist-upgrade ================================================ FILE: images/ubuntu-slim/scripts/build/install-nodejs.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-nodejs.sh ## Desc: Install Node.js LTS and related tooling (Gulp, Grunt) ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Install default Node.js default_version=$(get_toolset_value '.node.default') curl -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n -o ~/n sudo bash ~/n $default_version # Install node modules node_modules=$(get_toolset_value '.node_modules[].name') if [ -n "$node_modules" ]; then npm install -g $node_modules else echo "No node modules to install" fi # fix global modules installation as regular user # related issue https://github.com/actions/runner-images/issues/3727 sudo chmod -R 777 /usr/local/lib/node_modules sudo chmod -R 777 /usr/local/bin rm -rf ~/n ================================================ FILE: images/ubuntu-slim/scripts/build/install-nvm.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-nvm.sh ## Desc: Install Nvm ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh export NVM_DIR="/etc/skel/.nvm" mkdir ${NVM_DIR} nvm_version=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name') curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh | bash set_etc_environment_variable "NVM_DIR" '$HOME/.nvm' echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' | tee -a /etc/skel/.bash_profile [ -s "${NVM_DIR}/nvm.sh" ] && \. "${NVM_DIR}/nvm.sh" echo "source ${NVM_DIR}/nvm.sh" | tee -a /etc/skel/.bashrc # set system node.js as default one nvm alias default system ================================================ FILE: images/ubuntu-slim/scripts/build/install-pipx-packages.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-pipx-packages.sh ## Desc: Install tools via pipx ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh export PATH="$PATH:/opt/pipx_bin" pipx_packages=$(get_toolset_value ".pipx[] .package") if [ -z "$pipx_packages" ]; then echo "No pipx packages defined in toolset. Skipping pipx installation." exit 0 fi for package in $pipx_packages; do echo "Install $package into default python" pipx install $package # https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html # Install ansible into an existing ansible-core Virtual Environment if [[ $package == "ansible-core" ]]; then pipx inject $package ansible fi done ================================================ FILE: images/ubuntu-slim/scripts/build/install-powershell.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-powershell.sh ## Desc: Install PowerShell Core ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh source $HELPER_SCRIPTS/os.sh pwsh_version=$(get_toolset_value .pwsh.version) # Install Powershell apt-get install powershell=$pwsh_version* ================================================ FILE: images/ubuntu-slim/scripts/build/install-python.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-python.sh ## Desc: Install Python 3 ################################################################################ set -e # Source the helpers for use with the script source $HELPER_SCRIPTS/etc-environment.sh source $HELPER_SCRIPTS/os.sh # Install Python, Python 3, pip, pip3 apt-get install -y --no-install-recommends python3 python3-dev python3-pip python3-venv if is_ubuntu24; then # Create temporary workaround to allow user to continue using pip sudo cat < /etc/pip.conf [global] break-system-packages = true EOF fi # Install pipx # Set pipx custom directory export PIPX_BIN_DIR=/opt/pipx_bin export PIPX_HOME=/opt/pipx python3 -m pip install pipx python3 -m pipx ensurepath # Update /etc/environment set_etc_environment_variable "PIPX_BIN_DIR" $PIPX_BIN_DIR set_etc_environment_variable "PIPX_HOME" $PIPX_HOME prepend_etc_environment_path $PIPX_BIN_DIR # Adding this dir to PATH will make installed pip commands are immediately available. prepend_etc_environment_path '$HOME/.local/bin' ================================================ FILE: images/ubuntu-slim/scripts/build/install-yq.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-yq.sh ## Desc: Install yq - a command-line YAML, JSON and XML processor ## Supply chain security: yq - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Download yq yq_url=$(resolve_github_release_asset_url "mikefarah/yq" "endswith(\"yq_linux_amd64\")" "latest") binary_path=$(download_with_retry "${yq_url}") # Supply chain security - yq hash_url=$(resolve_github_release_asset_url "mikefarah/yq" "endswith(\"checksums\")" "latest") external_hash=$(get_checksum_from_url "${hash_url}" "yq_linux_amd64 " "SHA256" "true" " " "19") use_checksum_comparison "$binary_path" "$external_hash" # Install yq install "$binary_path" /usr/bin/yq ================================================ FILE: images/ubuntu-slim/scripts/build/install-zstd.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install-zstd.sh ## Desc: Install zstd ## Supply chain security: zstd - checksum validation ################################################################################ # Source the helpers for use with the script source $HELPER_SCRIPTS/install.sh # Download zstd release_tag=$(curl -fsSL https://api.github.com/repos/facebook/zstd/releases/latest | jq -r '.tag_name') release_name="zstd-${release_tag//v}" download_url="https://github.com/facebook/zstd/releases/download/${release_tag}/${release_name}.tar.gz" archive_path=$(download_with_retry "${download_url}") # Supply chain security - zstd external_hash=$(get_checksum_from_url "${download_url}.sha256" "${release_name}.tar.gz" "SHA256") use_checksum_comparison "$archive_path" "$external_hash" # Install zstd apt-get install liblz4-dev tar xzf "$archive_path" -C /tmp make -C "/tmp/${release_name}/contrib/pzstd" -j $(nproc) all make -C "/tmp/${release_name}" -j $(nproc) zstd-release for copyprocess in zstd zstdless zstdgrep; do cp "/tmp/${release_name}/programs/${copyprocess}" /usr/local/bin/ done cp "/tmp/${release_name}/contrib/pzstd/pzstd" /usr/local/bin/ for symlink in zstdcat zstdmt unzstd; do ln -sf /usr/local/bin/zstd /usr/local/bin/${symlink} done ================================================ FILE: images/ubuntu-slim/scripts/docs-gen/Common.Helpers.psm1 ================================================ function Get-CommandResult { <# .SYNOPSIS Runs a command in bash and returns the output and exit code. .DESCRIPTION Function runs a provided command in bash and returns the output and exit code as hashtable. .PARAMETER Command The command to run. .PARAMETER ExpectedExitCode The expected exit code. If the actual exit code does not match, an exception is thrown. .PARAMETER Multiline If true, the output is returned as an array of strings. Otherwise, the output is returned as a single string. .PARAMETER ValidateExitCode If true, the actual exit code is compared to the expected exit code. .EXAMPLE $result = Get-CommandResult "ls -la" This command runs "ls -la" in bash and returns the output and exit code as hashtable. #> param( [Parameter(Mandatory=$true)] [string] $Command, [int[]] $ExpectedExitCode = 0, [switch] $Multiline, [bool] $ValidateExitCode = $true ) # Bash trick to suppress and show error output because some commands write to stderr (for example, "python --version") $stdout = & bash -c "$Command 2>&1" $exitCode = $LASTEXITCODE if ($ValidateExitCode) { if ($ExpectedExitCode -notcontains $exitCode) { try { throw "StdOut: '$stdout' ExitCode: '$exitCode'" } catch { Write-Host $_.Exception.Message Write-Host $_.ScriptStackTrace exit $LASTEXITCODE } } } return @{ Output = If ($Multiline -eq $true) { $stdout } else { [string] $stdout } ExitCode = $exitCode } } function Test-IsUbuntu22 { return (lsb_release -rs) -eq "22.04" } function Test-IsUbuntu24 { return (lsb_release -rs) -eq "24.04" } function Get-ToolsetContent { <# .SYNOPSIS Retrieves the content of the toolset.json file. .DESCRIPTION This function reads the toolset.json in path provided by INSTALLER_SCRIPT_FOLDER environment variable and returns the content as a PowerShell object. #> $toolsetPath = Join-Path $env:INSTALLER_SCRIPT_FOLDER "toolset.json" $toolsetJson = Get-Content -Path $toolsetPath -Raw ConvertFrom-Json -InputObject $toolsetJson } function Invoke-DownloadWithRetry { <# .SYNOPSIS Downloads a file from a given URL with retry functionality. .DESCRIPTION The Invoke-DownloadWithRetry function downloads a file from the specified URL to the specified path. It includes retry functionality in case the download fails. .PARAMETER Url The URL of the file to download. .PARAMETER Path The path where the downloaded file will be saved. If not provided, a temporary path will be used. .EXAMPLE Invoke-DownloadWithRetry -Url "https://example.com/file.zip" -Path "/usr/local/bin" Downloads the file from the specified URL and saves it to the specified path. .EXAMPLE Invoke-DownloadWithRetry -Url "https://example.com/file.zip" Downloads the file from the specified URL and saves it to a temporary path. .OUTPUTS The path where the downloaded file is saved. #> param( [Parameter(Mandatory)] [string] $Url, [Alias("Destination")] [string] $DestinationPath ) if (-not $DestinationPath) { $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join '' $re = "[{0}]" -f [RegEx]::Escape($invalidChars) $fileName = [IO.Path]::GetFileName($Url) -replace $re if ([String]::IsNullOrEmpty($fileName)) { $fileName = [System.IO.Path]::GetRandomFileName() } $DestinationPath = Join-Path -Path "/tmp" -ChildPath $fileName } Write-Host "Downloading package from $Url to $DestinationPath..." $interval = 30 $downloadStartTime = Get-Date for ($retries = 20; $retries -gt 0; $retries--) { try { $attemptStartTime = Get-Date Invoke-WebRequest -Uri $Url -Outfile $DestinationPath $attemptSeconds = [math]::Round(($(Get-Date) - $attemptStartTime).TotalSeconds, 2) Write-Host "Package downloaded in $attemptSeconds seconds" break } catch { $attemptSeconds = [math]::Round(($(Get-Date) - $attemptStartTime).TotalSeconds, 2) Write-Warning "Package download failed in $attemptSeconds seconds" Write-Warning $_.Exception.Message } if ($retries -eq 0) { $totalSeconds = [math]::Round(($(Get-Date) - $downloadStartTime).TotalSeconds, 2) throw "Package download failed after $totalSeconds seconds" } Write-Warning "Waiting $interval seconds before retrying (retries left: $retries)..." Start-Sleep -Seconds $interval } return $DestinationPath } ================================================ FILE: images/ubuntu-slim/scripts/docs-gen/Generate-SoftwareReport.ps1 ================================================ using module ../software-report-base/SoftwareReport.psm1 using module ../software-report-base/SoftwareReport.Nodes.psm1 param ( [Parameter(Mandatory)] [string] $OutputDirectory ) $global:ErrorActionPreference = "Stop" $global:ErrorView = "NormalView" Set-StrictMode -Version Latest Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Common.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Helpers.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "Common.Helpers.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Tools.psm1") -DisableNameChecking # Restore file owner in user profile sudo chown -R ${env:USER}: $env:HOME # Software report $softwareReport = [SoftwareReport]::new("Ubuntu-Slim") $softwareReport.Root.AddToolVersion("OS Version:", $(Get-OSVersionFull)) $softwareReport.Root.AddToolVersion("Image Version:", $env:IMAGE_VERSION) $softwareReport.Root.AddToolVersion("Systemd version:", $(Get-SystemdVersion)) $installedSoftware = $softwareReport.Root.AddHeader("Installed Software") # Language and Runtime $languageAndRuntime = $installedSoftware.AddHeader("Language and Runtime") $languageAndRuntime.AddToolVersion("Bash", $(Get-BashVersion)) $languageAndRuntime.AddToolVersion("Dash", $(Get-DashVersion)) $languageAndRuntime.AddToolVersion("Node.js", $(Get-NodeVersion)) $languageAndRuntime.AddToolVersion("Perl", $(Get-PerlVersion)) $languageAndRuntime.AddToolVersion("Python", $(Get-PythonVersion)) # Package Management $packageManagement = $installedSoftware.AddHeader("Package Management") $packageManagement.AddToolVersion("Npm", $(Get-NpmVersion)) $packageManagement.AddToolVersion("Pip", $(Get-PipVersion)) $packageManagement.AddToolVersion("Pip3", $(Get-Pip3Version)) $packageManagement.AddToolVersion("Pipx", $(Get-PipxVersion)) # Tools $tools = $installedSoftware.AddHeader("Tools") $tools.AddToolVersion("AzCopy", $(Get-AzCopyVersion)) $tools.AddToolVersion("Bicep", $(Get-BicepVersion)) $tools.AddToolVersion("Docker Compose v2", $(Get-DockerComposeV2Version)) $tools.AddToolVersion("Docker-Buildx", $(Get-DockerBuildxVersion)) $tools.AddToolVersion("Docker Client", $(Get-DockerClientVersion)) $tools.AddToolVersion("Git", $(Get-GitVersion)) $tools.AddToolVersion("Git LFS", $(Get-GitLFSVersion)) $tools.AddToolVersion("Git-ftp", $(Get-GitFTPVersion)) $tools.AddToolVersion("jq", $(Get-JqVersion)) $tools.AddToolVersion("nvm", $(Get-NvmVersion)) $tools.AddToolVersion("OpenSSL", $(Get-OpensslVersion)) $tools.AddToolVersion("yq", $(Get-YqVersion)) $tools.AddToolVersion("zstd", $(Get-ZstdVersion)) # CLI Tools $cliTools = $installedSoftware.AddHeader("CLI Tools") $cliTools.AddToolVersion("AWS CLI", $(Get-AWSCliVersion)) $cliTools.AddToolVersion("AWS CLI Session Manager Plugin", $(Get-AWSCliSessionManagerPluginVersion)) $cliTools.AddToolVersion("AWS SAM CLI", $(Get-AWSSAMVersion)) $cliTools.AddToolVersion("Azure CLI", $(Get-AzureCliVersion)) $cliTools.AddToolVersion("Azure CLI (azure-devops)", $(Get-AzureDevopsVersion)) $cliTools.AddToolVersion("GitHub CLI", $(Get-GitHubCliVersion)) $cliTools.AddToolVersion("Google Cloud CLI", $(Get-GoogleCloudCLIVersion)) # PowerShell Tools $powerShellTools = $installedSoftware.AddHeader("PowerShell Tools") $powerShellTools.AddToolVersion("PowerShell", $(Get-PowershellVersion)) $installedSoftware.AddHeader("Installed apt packages").AddTable($(Get-AptPackages)) $softwareReport.ToJson() | Out-File -FilePath "${OutputDirectory}/software-report.json" -Encoding UTF8NoBOM $softwareReport.ToMarkdown() | Out-File -FilePath "${OutputDirectory}/software-report.md" -Encoding UTF8NoBOM ================================================ FILE: images/ubuntu-slim/scripts/docs-gen/SoftwareReport.Common.psm1 ================================================ function Get-BashVersion { $version = bash -c 'echo ${BASH_VERSION}' return $version } function Get-DashVersion { $version = dpkg-query -W -f '${Version}' dash return $version } function Get-NodeVersion { $nodeVersion = $(node --version).Substring(1) return $nodeVersion } function Get-OpensslVersion { $opensslVersion = $(dpkg-query -W -f '${Version}' openssl) return $opensslVersion } function Get-PerlVersion { $version = $(perl -e 'print substr($^V,1)') return $version } function Get-PythonVersion { $result = Get-CommandResult "python --version" $version = $result.Output | Get-StringPart -Part 1 return $version } function Get-PowershellVersion { $pwshVersion = $(pwsh --version) | Get-StringPart -Part 1 return $pwshVersion } function Get-NpmVersion { $npmVersion = npm --version return $npmVersion } function Get-PipVersion { $pipVersion = pip --version | Get-StringPart -Part 1 return $pipVersion } function Get-Pip3Version { $pip3Version = pip3 --version | Get-StringPart -Part 1 return $pip3Version } function Get-AptPackages { $apt = (Get-ToolsetContent).Apt $output = @() ForEach ($pkg in ($apt.vital_packages + $apt.common_packages + $apt.cmd_packages)) { $version = $(dpkg-query -W -f '${Version}' $pkg) if ($null -eq $version) { $version = $(dpkg-query -W -f '${Version}' "$pkg*") } $version = $version -replace '~','\~' $output += [PSCustomObject] @{ Name = $pkg Version = $version } } return ($output | Sort-Object Name) } function Get-PipxVersion { $result = (Get-CommandResult "pipx --version").Output $result -match "(?\d+\.\d+\.\d+\.?\d*)" | Out-Null return $Matches.Version } function Get-SystemdVersion { $matchCollection = [regex]::Matches((systemctl --version | head -n 1), "\((.*?)\)") $result = foreach ($match in $matchCollection) {$match.Groups[1].Value} return $result } ================================================ FILE: images/ubuntu-slim/scripts/docs-gen/SoftwareReport.Helpers.psm1 ================================================ function Get-StringPart { param ( [Parameter(ValueFromPipeline)] [string] $ToolOutput, [string] $Delimiter = " ", [int[]] $Part ) $parts = $ToolOutput.Split($Delimiter, [System.StringSplitOptions]::RemoveEmptyEntries) $selectedParts = $parts[$Part] return [string]::Join($Delimiter, $selectedParts) } function Get-PathWithLink { param ( [string] $InputPath ) $link = Get-Item $InputPath | Select-Object -ExpandProperty Target if (-not [string]::IsNullOrEmpty($link)) { return "${InputPath} -> ${link}" } return "${InputPath}" } function Get-OSVersionShort { $(Get-OSVersionFull) | Get-StringPart -Delimiter '.' -Part 0,1 } function Get-OSVersionFull { lsb_release -ds | Get-StringPart -Part 1, 2 } function Get-KernelVersion { $kernelVersion = uname -r return $kernelVersion } ================================================ FILE: images/ubuntu-slim/scripts/docs-gen/SoftwareReport.Tools.psm1 ================================================ function Get-AzCopyVersion { $azcopyVersion = [string]$(azcopy --version) | Get-StringPart -Part 2 return "$azcopyVersion - available by ``azcopy`` and ``azcopy10`` aliases" } function Get-BicepVersion { (bicep --version | Out-String) -match "bicep cli version (?\d+\.\d+\.\d+)" | Out-Null return $Matches.Version } function Get-GitVersion { $gitVersion = git --version | Get-StringPart -Part -1 return $gitVersion } function Get-GitLFSVersion { $result = Get-CommandResult "git-lfs --version" $gitlfsversion = $result.Output | Get-StringPart -Part 0 | Get-StringPart -Part 1 -Delimiter "/" return $gitlfsversion } function Get-GitFTPVersion { $gitftpVersion = git-ftp --version | Get-StringPart -Part 2 return $gitftpVersion } function Get-GoogleCloudCLIVersion { return (gcloud --version | Select-Object -First 1) | Get-StringPart -Part 3 } function Get-NvmVersion { $nvmVersion = bash -c "source /etc/skel/.nvm/nvm.sh && nvm --version" return $nvmVersion } function Get-JqVersion { $jqVersion = jq --version | Get-StringPart -Part 1 -Delimiter "-" return $jqVersion } function Get-AzureCliVersion { $azcliVersion = (az version | ConvertFrom-Json).'azure-cli' return $azcliVersion } function Get-AzureDevopsVersion { $azdevopsVersion = (az version | ConvertFrom-Json).extensions.'azure-devops' return $azdevopsVersion } function Get-AWSCliVersion { $result = Get-CommandResult "aws --version" $awsVersion = $result.Output | Get-StringPart -Part 0 | Get-StringPart -Part 1 -Delimiter "/" return $awsVersion } function Get-AWSCliSessionManagerPluginVersion { $result = (Get-CommandResult "session-manager-plugin --version").Output return $result } function Get-AWSSAMVersion { return $(sam --version | Get-StringPart -Part -1) } function Get-GitHubCliVersion { $ghVersion = gh --version | Select-String "gh version" | Get-StringPart -Part 2 return $ghVersion } function Get-ZstdVersion { $zstdVersion = zstd --version | Get-StringPart -Part 1 -Delimiter "v" | Get-StringPart -Part 0 -Delimiter "," return "$zstdVersion" } function Get-YqVersion { $yqVersion = $(yq -V) | Get-StringPart -Part 3 return $yqVersion.TrimStart("v").Trim() } function Get-DockerComposeV2Version { $composeVersion = docker compose version | Get-StringPart -Part 3 | Get-StringPart -Part 0 -Delimiter "v" return $composeVersion } function Get-DockerClientVersion { $dockerClientVersion = sudo docker version --format '{{.Client.Version}}' return $dockerClientVersion } function Get-DockerBuildxVersion { $buildxVersion = docker buildx version | Get-StringPart -Part 1 | Get-StringPart -Part 0 -Delimiter "v" return $buildxVersion } ================================================ FILE: images/ubuntu-slim/scripts/entrypoint.sh ================================================ #!/bin/bash # /opt/entrypoint.sh # Load environment variables from file set -a source /etc/environment set +a # Execute the actual command exec "$@" ================================================ FILE: images/ubuntu-slim/scripts/helpers/cleanup.sh ================================================ #!/bin/bash -e # delete all .gz and rotated file find /var/log -type f -regex ".*\.gz$" -delete find /var/log -type f -regex ".*\.[0-9]$" -delete # wipe log files find /var/log/ -type f -exec cp /dev/null {} \; rm -rf /tmp/downloads /tmp/installers apt-get clean ================================================ FILE: images/ubuntu-slim/scripts/helpers/etc-environment.sh ================================================ #!/bin/bash -e ################################################################################ ## File: etc-environment.sh ## Desc: Helper functions for source and modify /etc/environment ################################################################################ # NB: sed expression use '%' as a delimiter in order to simplify handling # values containing slashes (i.e. directory path) # The values containing '%' will break the functions get_etc_environment_variable() { local variable_name=$1 # remove `variable_name=` and possible quotes from the line grep "^${variable_name}=" /etc/environment | sed -E "s%^${variable_name}=\"?([^\"]+)\"?.*$%\1%" } add_etc_environment_variable() { local variable_name=$1 local variable_value=$2 echo "${variable_name}=${variable_value}" | sudo tee -a /etc/environment } replace_etc_environment_variable() { local variable_name=$1 local variable_value=$2 # modify /etc/environment in place by replacing a string that begins with variable_name sudo sed -i -e "s%^${variable_name}=.*$%${variable_name}=${variable_value}%" /etc/environment } set_etc_environment_variable() { local variable_name=$1 local variable_value=$2 if grep "^${variable_name}=" /etc/environment > /dev/null; then replace_etc_environment_variable $variable_name $variable_value else add_etc_environment_variable $variable_name $variable_value fi } prepend_etc_environment_variable() { local variable_name=$1 local element=$2 # TODO: handle the case if the variable does not exist existing_value=$(get_etc_environment_variable "${variable_name}") set_etc_environment_variable "${variable_name}" "${element}:${existing_value}" } append_etc_environment_variable() { local variable_name=$1 local element=$2 # TODO: handle the case if the variable does not exist existing_value=$(get_etc_environment_variable "${variable_name}") set_etc_environment_variable "${variable_name}" "${existing_value}:${element}" } prepend_etc_environment_path() { local element=$1 prepend_etc_environment_variable PATH "${element}" } append_etc_environment_path() { local element=$1 append_etc_environment_variable PATH "${element}" } # Process /etc/environment as if it were shell script with `export VAR=...` expressions # The PATH variable is handled specially in order to do not override the existing PATH # variable. The value of PATH variable read from /etc/environment is added to the end # of value of the exiting PATH variable exactly as it would happen with real PAM app read # /etc/environment # # TODO: there might be the others variables to be processed in the same way as "PATH" variable # ie MANPATH, INFOPATH, LD_*, etc. In the current implementation the values from /etc/environment # replace the values of the current environment reload_etc_environment() { # add `export ` to every variable of /etc/environment except PATH and eval the result shell script eval $(grep -v '^PATH=' /etc/environment | sed -e 's%^%export %') # handle PATH specially etc_path=$(get_etc_environment_variable PATH) export PATH="$PATH:$etc_path" } ================================================ FILE: images/ubuntu-slim/scripts/helpers/install.sh ================================================ #!/bin/bash -e ################################################################################ ## File: install.sh ## Desc: Helper functions for installing tools ################################################################################ download_with_retry() { local url=$1 local download_path=$2 if [ -z "$download_path" ]; then mkdir -p /tmp/downloads download_path="/tmp/downloads/$(basename "$url")" fi echo "Downloading package from $url to $download_path..." >&2 interval=30 download_start_time=$(date +%s) for ((retries=20; retries>0; retries--)); do attempt_start_time=$(date +%s) if http_code=$(curl -4sSLo "$download_path" "$url" -w '%{http_code}'); then attempt_seconds=$(($(date +%s) - attempt_start_time)) if [ "$http_code" -eq 200 ]; then echo "Package downloaded in $attempt_seconds seconds" >&2 break else echo "Received HTTP status code $http_code after $attempt_seconds seconds" >&2 fi else attempt_seconds=$(($(date +%s) - attempt_start_time)) echo "Package download failed in $attempt_seconds seconds" >&2 fi if [ "$retries" -le 1 ]; then total_seconds=$(($(date +%s) - download_start_time)) echo "Package download failed after $total_seconds seconds" >&2 exit 1 fi echo "Waiting $interval seconds before retrying (retries left: $retries)..." >&2 sleep $interval done echo "$download_path" } get_github_releases_by_version() { local repo=$1 local version=${2:-".+"} local allow_pre_release=${3:-false} local with_assets_only=${4:-false} page_size="100" json=$(curl -fsSL "https://api.github.com/repos/${repo}/releases?per_page=${page_size}") if [[ -z "$json" ]]; then echo "Failed to get releases" >&2 exit 1 fi if [[ $with_assets_only == "true" ]]; then json=$(echo $json | jq -r '.[] | select(.assets | length > 0)') else json=$(echo $json | jq -r '.[]') fi if [[ $allow_pre_release == "true" ]]; then json=$(echo $json | jq -r '.') else json=$(echo $json | jq -r '. | select(.prerelease==false)') fi # Filter out rc/beta/etc releases, convert to numeric version and sort json=$(echo $json | jq '. | select(.tag_name | test(".*-[a-z]|beta") | not)' | jq '.tag_name |= gsub("[^\\d.]"; "")' | jq -s 'sort_by(.tag_name | split(".") | map(tonumber))') # Select releases matching version if [[ $version == "latest" ]]; then json_filtered=$(echo $json | jq .[-1]) elif [[ $version == *"+"* ]] || [[ $version == *"*"* ]]; then json_filtered=$(echo $json | jq --arg version $version '.[] | select(.tag_name | test($version))') else json_filtered=$(echo $json | jq --arg version $version '.[] | select(.tag_name | contains($version))') fi if [[ -z "$json_filtered" ]]; then echo "Failed to get releases from ${repo} matching version ${version}" >&2 echo "Available versions: $(echo "$json" | jq -r '.tag_name')" >&2 exit 1 fi echo $json_filtered } resolve_github_release_asset_url() { local repo=$1 local url_filter=$2 local version=${3:-".+"} local allow_pre_release=${4:-false} local allow_multiple_matches=${5:-false} matching_releases=$(get_github_releases_by_version "${repo}" "${version}" "${allow_pre_release}" "true") matched_url=$(echo $matching_releases | jq -r ".assets[].browser_download_url | select(${url_filter})") if [[ -z "$matched_url" ]]; then echo "Found no download urls matching pattern: ${url_filter}" >&2 echo "Available download urls: $(echo "$matching_releases" | jq -r '.assets[].browser_download_url')" >&2 exit 1 fi if [[ "$(echo "$matched_url" | wc -l)" -gt 1 ]]; then if [[ $allow_multiple_matches == "true" ]]; then matched_url=$(echo "$matched_url" | tail -n 1) else echo "Multiple matches found for ${version} version and ${url_filter} URL filter. Please make filters more specific" >&2 exit 1 fi fi echo $matched_url } get_checksum_from_github_release() { local repo=$1 local file_name=$2 local version=${3:-".+"} local hash_type=$4 local allow_pre_release=${5:-false} if [[ -z "$file_name" ]]; then echo "File name is not specified." >&2 exit 1 fi if [[ "$hash_type" == "SHA256" ]]; then hash_pattern="[A-Fa-f0-9]{64}" elif [[ "$hash_type" == "SHA512" ]]; then hash_pattern="[A-Fa-f0-9]{128}" else echo "Unknown hash type: ${hash_type}" >&2 exit 1 fi matching_releases=$(get_github_releases_by_version "${repo}" "${version}" "${allow_pre_release}" "true") matched_line=$(printf "$(echo $matching_releases | jq '.body')\n" | grep "$file_name") if [[ -z "$matched_line" ]]; then echo "File name ${file_name} not found in release body" >&2 exit 1 fi if [[ "$(echo "$matched_line" | wc -l)" -gt 1 ]]; then echo "Multiple matches found for ${file_name} in release body: ${matched_line}" >&2 exit 1 fi hash=$(echo $matched_line | grep -oP "$hash_pattern") if [[ -z "$hash" ]]; then echo "Found ${file_name} in body of release, but failed to get hash from it: ${matched_line}" >&2 exit 1 fi echo "$hash" } get_checksum_from_url() { local url=$1 local file_name=$2 local hash_type=$3 local use_custom_search_pattern=${4:-false} local delimiter=${5:-' '} local word_number=${6:-1} if [[ "$hash_type" == "SHA256" ]]; then hash_pattern="[A-Fa-f0-9]{64}" elif [[ "$hash_type" == "SHA512" ]]; then hash_pattern="[A-Fa-f0-9]{128}" else echo "Unknown hash type: ${hash_type}" >&2 exit 1 fi checksums_file_path=$(download_with_retry "$url") checksums=$(cat "$checksums_file_path") rm "$checksums_file_path" matched_line=$(printf "$checksums\n" | grep "$file_name") if [[ "$(echo "$matched_line" | wc -l)" -gt 1 ]]; then echo "Found multiple lines matching file name ${file_name} in checksum file." >&2 exit 1 fi if [[ -z "$matched_line" ]]; then echo "File name ${file_name} not found in checksum file." >&2 exit 1 fi if [[ $use_custom_search_pattern == "true" ]]; then hash=$(echo "$matched_line" | sed 's/ */ /g' | cut -d "$delimiter" -f "$word_number" | tr -d -c '[:alnum:]') else hash=$(echo $matched_line | grep -oP "$hash_pattern") fi if [[ -z "$hash" ]]; then echo "Found ${file_name} in checksum file, but failed to get hash from it: ${matched_line}" >&2 exit 1 fi echo "$hash" } use_checksum_comparison() { local file_path=$1 local checksum=$2 local sha_type=${3:-"256"} echo "Performing checksum verification" if [[ ! -f "$file_path" ]]; then echo "File not found: $file_path" exit 1 fi local_file_hash=$(shasum --algorithm "$sha_type" "$file_path" | awk '{print $1}') if [[ "$local_file_hash" != "$checksum" ]]; then echo "Checksum verification failed. Expected hash: $checksum; Actual hash: $local_file_hash." exit 1 else echo "Checksum verification passed. Expected hash: $checksum; Actual hash: $local_file_hash." fi } get_toolset_value() { local toolset_path="${INSTALLER_SCRIPT_FOLDER}/toolset.json" local query=$1 echo "$(jq -r "$query" $toolset_path)" } ================================================ FILE: images/ubuntu-slim/scripts/helpers/os.sh ================================================ #!/bin/bash -e ################################################################################ ## File: os.sh ## Desc: Helper functions for OS releases ################################################################################ is_ubuntu22() { lsb_release -rs | grep -q '22.04' } is_ubuntu24() { lsb_release -rs | grep -q '24.04' } ================================================ FILE: images/ubuntu-slim/test.sh ================================================ #!/bin/bash -e # This script builds and runs various tests on the ubuntu-slim Docker image # to ensure it contains the expected software and configurations. # The build and test workflows for docker images expect this script to be present. # # Usage: test.sh [IMAGE_NAME] # If IMAGE_NAME is not provided, defaults to ubuntu-slim:test set -eo pipefail show_help() { echo "Usage: $0 [IMAGE_NAME]" echo "" echo "Test a Docker image to ensure it contains the expected software and configurations." echo "" echo "Arguments:" echo " IMAGE_NAME Docker image name to test (default: ubuntu-slim:test)" echo "" echo "Examples:" echo " $0 # Test ubuntu-slim:test (builds image first)" echo " $0 my-registry/ubuntu:latest # Test existing image" echo " $0 ubuntu-slim:v1.2.3 # Test tagged image" echo "" echo "Options:" echo " -h, --help Show this help message" } # Handle help flags if [[ "$1" == "-h" || "$1" == "--help" ]]; then show_help exit 0 fi # Set the image name from parameter or use default IMAGE_NAME="${1:-ubuntu-slim:test}" echo "Testing image: $IMAGE_NAME" run_test() { local desc="$1" shift if output=$(docker run --rm "$IMAGE_NAME" "$@" 2>&1); then echo "PASS: $desc" echo "$output" | sed 's/^/ /' else echo "FAIL: $desc" echo "$output" | sed 's/^/ /' exit 1 fi } # Build the image only if using the default name (for backward compatibility) if [[ "$IMAGE_NAME" == "ubuntu-slim:test" ]]; then echo "Building image: $IMAGE_NAME" if ! docker build --no-cache --debug --progress plain -t "$IMAGE_NAME" .; then echo "Error: Docker build failed" exit 1 fi else # Check if the image exists if ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then echo "Error: Image '$IMAGE_NAME' does not exist. Please build it first or provide a valid image name." echo "Run '$0 --help' for usage information." exit 1 fi fi echo "Running tests on image: $IMAGE_NAME" docker history --no-trunc "$IMAGE_NAME" docker inspect -f "{{ .Size }}" "$IMAGE_NAME" | numfmt --to=iec | sed 's/^/Image size: /' # Ensure key software is installed and runnable run_test "GitHub CLI is installed" gh --version run_test "Azure CLI is installed" az version run_test "AWS CLI is installed" aws --version run_test "Session Manager plugin is installed" session-manager-plugin --version run_test "AWS SAM CLI is installed" sam --version run_test "jq is installed" jq --version run_test "git is installed" git --version run_test "node is installed" node --version run_test "npm is installed" npm --version run_test "python3 is installed" python3 --version run_test "python is aliased" python --version run_test "pipx is installed" pipx --version run_test "curl is installed" curl --version run_test "wget is installed" wget --version run_test "yq is installed" yq --version run_test "parallel is installed" parallel --version run_test "bc is installed" bc --version run_test "zstd is installed" zstd --version run_test "google cloud SDK is installed" gcloud --version run_test "git lfs is installed" git lfs version run_test "powershell is installed" pwsh --version run_test "docker-cli is installed" docker --version run_test "docker compose is installed" docker compose version run_test "docker buildx is installed" docker buildx version # Quick check: ensure the imagedata JSON file was created during image build run_test "imagedata JSON file exists" test -f /imagegeneration/imagedata.json ================================================ FILE: images/ubuntu-slim/toolsets/toolset.json ================================================ { "toolcache": [ { "name": "node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "platform" : "linux", "arch": "x64", "versions": [ "22.*", "24.*" ] }, { "name": "CodeQL", "platform" : "linux", "arch": "x64", "versions": [ "*" ] } ], "apt": { "vital_packages": [ "apt-utils", "bzip2", "ca-certificates", "curl", "g++", "gcc", "make", "jq", "tar", "unzip", "wget" ], "common_packages": [ "autoconf", "automake", "bc", "dbus", "dnsutils", "dpkg", "dpkg-dev", "fakeroot", "fonts-noto-color-emoji", "gnupg2", "iproute2", "iputils-ping", "libyaml-dev", "libtool", "libssl-dev", "libsqlite3-dev", "locales", "lzma", "mercurial", "openssh-client", "p7zip-rar", "pkg-config", "python-is-python3", "rpm", "texinfo", "tk", "tree", "tzdata", "upx", "xvfb", "xz-utils", "zsync" ], "cmd_packages": [ "acl", "binutils", "libnss3-tools", "coreutils", "file", "findutils", "flex", "ftp", "haveged", "lz4", "netcat-openbsd", "net-tools", "p7zip-full", "parallel", "patchelf", "pigz", "pollinate", "rsync", "shellcheck", "sqlite3", "ssh", "sshpass", "sudo", "systemd-coredump", "telnet", "time", "zip" ] }, "brew": [ ], "node": { "default": "24" }, "node_modules": [ ], "pwsh": { "version": "7.5" } } ================================================ FILE: images/ubuntu-slim/ubuntu-slim-Readme.md ================================================ # Ubuntu-Slim - OS Version: 24.04.3 LTS - Image Version: 20260120.46.1 - Systemd version: 255.4-1ubuntu8.12 ## Installed Software ### Language and Runtime - Bash 5.2.21(1)-release - Dash 0.5.12-6ubuntu5 - Node.js 24.13.0 - Perl 5.38.2 - Python 3.12.3 ### Package Management - Npm 11.6.2 - Pip 24.0 - Pip3 24.0 - Pipx 1.8.0 ### Tools - AzCopy 10.31.1 - available by `azcopy` and `azcopy10` aliases - Bicep 0.39.26 - Docker Compose v2 5.0.1 - Docker-Buildx 0.30.1 - Docker Client 29.1.5 - Git 2.52.0 - Git LFS 3.7.1 - Git-ftp 1.6.0 - jq 1.7 - nvm 0.40.3 - OpenSSL 3.0.13-0ubuntu3.6 - yq 4.50.1 - zstd 1.5.7 ### CLI Tools - AWS CLI 2.33.2 - AWS CLI Session Manager Plugin 1.2.764.0 - AWS SAM CLI 1.151.0 - Azure CLI 2.82.0 - Azure CLI (azure-devops) 1.0.2 - GitHub CLI 2.85.0 - Google Cloud CLI 552.0.0 ### PowerShell Tools - PowerShell 7.5.4 ### Installed apt packages | Name | Version | | ---------------------- | ---------------------------- | | acl | 2.3.2-1build1.1 | | apt-utils | 2.8.3 | | autoconf | 2.71-3 | | automake | 1:1.16.5-1.3ubuntu1 | | bc | 1.07.1-3ubuntu4 | | binutils | 2.42-4ubuntu2.8 | | bzip2 | 1.0.8-5.1build0.1 | | ca-certificates | 20240203 | | coreutils | 9.4-3ubuntu6.1 | | curl | 8.5.0-2ubuntu10.6 | | dbus | 1.14.10-4ubuntu4.1 | | dnsutils | 1:9.18.39-0ubuntu0.24.04.2 | | dpkg | 1.22.6ubuntu6.5 | | dpkg-dev | 1.22.6ubuntu6.5 | | fakeroot | 1.33-1 | | file | 1:5.45-3build1 | | findutils | 4.9.0-5build1 | | flex | 2.6.4-8.2build1 | | fonts-noto-color-emoji | 2.047-0ubuntu0.24.04.1 | | ftp | 20230507-2build3 | | g++ | 4:13.2.0-7ubuntu1 | | gcc | 4:13.2.0-7ubuntu1 | | gnupg2 | 2.4.4-2ubuntu17.4 | | haveged | 1.9.14-1ubuntu2 | | iproute2 | 6.1.0-1ubuntu6.2 | | iputils-ping | 3:20240117-1ubuntu0.1 | | jq | 1.7.1-3ubuntu0.24.04.1 | | libnss3-tools | 2:3.98-1build1 | | libsqlite3-dev | 3.45.1-1ubuntu2.5 | | libssl-dev | 3.0.13-0ubuntu3.6 | | libtool | 2.4.7-7build1 | | libyaml-dev | 0.2.5-1build1 | | locales | 2.39-0ubuntu8.6 | | lz4 | 1.9.4-1build1.1 | | lzma | 9.22-2.2 | | make | 4.3-4.1build2 | | mercurial | 6.7.2-1ubuntu2.2 | | net-tools | 2.10-0.1ubuntu4.4 | | netcat-openbsd | 1.226-1ubuntu2 | | openssh-client | 1:9.6p1-3ubuntu13.14 | | p7zip-full | 16.02+transitional.1 | | p7zip-rar | 16.02+transitional.1 | | parallel | 20231122+ds-1 | | patchelf | 0.18.0-1.1build1 | | pigz | 2.8-1 | | pkg-config | 1.8.1-2build1 | | pollinate | 4.33-3.1ubuntu1.1 | | python-is-python3 | 3.11.4-1 | | rpm | 4.18.2+dfsg-2.1build2 | | rsync | 3.2.7-1ubuntu1.2 | | shellcheck | 0.9.0-1 | | sqlite3 | 3.45.1-1ubuntu2.5 | | ssh | 1:9.6p1-3ubuntu13.14 | | sshpass | 1.09-1 | | sudo | 1.9.15p5-3ubuntu5.24.04.1 | | systemd-coredump | 255.4-1ubuntu8.12 | | tar | 1.35+dfsg-3build1 | | telnet | 0.17+2.5-3ubuntu4 | | texinfo | 7.1-3build2 | | time | 1.9-0.2build1 | | tk | 8.6.14build1 | | tree | 2.1.1-2ubuntu3.24.04.2 | | tzdata | 2025b-0ubuntu0.24.04.1 | | unzip | 6.0-28ubuntu4.1 | | upx | 4.2.2-3 | | wget | 1.21.4-1ubuntu4.1 | | xvfb | 2:21.1.12-1ubuntu1.5 | | xz-utils | 5.6.1+really5.4.5-1ubuntu0.2 | | zip | 3.0-13ubuntu0.2 | | zsync | 0.6.2-5build1 | ================================================ FILE: images/ubuntu-slim/ubuntu-slim-Report.json ================================================ { "NodeType": "HeaderNode", "Title": "Ubuntu-Slim", "Children": [ { "NodeType": "ToolVersionNode", "ToolName": "OS Version:", "Version": "24.04.3 LTS" }, { "NodeType": "ToolVersionNode", "ToolName": "Image Version:", "Version": "20260120.46.1" }, { "NodeType": "ToolVersionNode", "ToolName": "Systemd version:", "Version": "255.4-1ubuntu8.12" }, { "NodeType": "HeaderNode", "Title": "Installed Software", "Children": [ { "NodeType": "HeaderNode", "Title": "Language and Runtime", "Children": [ { "NodeType": "ToolVersionNode", "ToolName": "Bash", "Version": "5.2.21(1)-release" }, { "NodeType": "ToolVersionNode", "ToolName": "Dash", "Version": "0.5.12-6ubuntu5" }, { "NodeType": "ToolVersionNode", "ToolName": "Node.js", "Version": "24.13.0" }, { "NodeType": "ToolVersionNode", "ToolName": "Perl", "Version": "5.38.2" }, { "NodeType": "ToolVersionNode", "ToolName": "Python", "Version": "3.12.3" } ] }, { "NodeType": "HeaderNode", "Title": "Package Management", "Children": [ { "NodeType": "ToolVersionNode", "ToolName": "Npm", "Version": "11.6.2" }, { "NodeType": "ToolVersionNode", "ToolName": "Pip", "Version": "24.0" }, { "NodeType": "ToolVersionNode", "ToolName": "Pip3", "Version": "24.0" }, { "NodeType": "ToolVersionNode", "ToolName": "Pipx", "Version": "1.8.0" } ] }, { "NodeType": "HeaderNode", "Title": "Tools", "Children": [ { "NodeType": "ToolVersionNode", "ToolName": "AzCopy", "Version": "10.31.1 - available by `azcopy` and `azcopy10` aliases" }, { "NodeType": "ToolVersionNode", "ToolName": "Bicep", "Version": "0.39.26" }, { "NodeType": "ToolVersionNode", "ToolName": "Docker Compose v2", "Version": "5.0.1" }, { "NodeType": "ToolVersionNode", "ToolName": "Docker-Buildx", "Version": "0.30.1" }, { "NodeType": "ToolVersionNode", "ToolName": "Docker Client", "Version": "29.1.5" }, { "NodeType": "ToolVersionNode", "ToolName": "Git", "Version": "2.52.0" }, { "NodeType": "ToolVersionNode", "ToolName": "Git LFS", "Version": "3.7.1" }, { "NodeType": "ToolVersionNode", "ToolName": "Git-ftp", "Version": "1.6.0" }, { "NodeType": "ToolVersionNode", "ToolName": "jq", "Version": "1.7" }, { "NodeType": "ToolVersionNode", "ToolName": "nvm", "Version": "0.40.3" }, { "NodeType": "ToolVersionNode", "ToolName": "OpenSSL", "Version": "3.0.13-0ubuntu3.6" }, { "NodeType": "ToolVersionNode", "ToolName": "yq", "Version": "4.50.1" }, { "NodeType": "ToolVersionNode", "ToolName": "zstd", "Version": "1.5.7" } ] }, { "NodeType": "HeaderNode", "Title": "CLI Tools", "Children": [ { "NodeType": "ToolVersionNode", "ToolName": "AWS CLI", "Version": "2.33.2" }, { "NodeType": "ToolVersionNode", "ToolName": "AWS CLI Session Manager Plugin", "Version": "1.2.764.0" }, { "NodeType": "ToolVersionNode", "ToolName": "AWS SAM CLI", "Version": "1.151.0" }, { "NodeType": "ToolVersionNode", "ToolName": "Azure CLI", "Version": "2.82.0" }, { "NodeType": "ToolVersionNode", "ToolName": "Azure CLI (azure-devops)", "Version": "1.0.2" }, { "NodeType": "ToolVersionNode", "ToolName": "GitHub CLI", "Version": "2.85.0" }, { "NodeType": "ToolVersionNode", "ToolName": "Google Cloud CLI", "Version": "552.0.0" } ] }, { "NodeType": "HeaderNode", "Title": "PowerShell Tools", "Children": { "NodeType": "ToolVersionNode", "ToolName": "PowerShell", "Version": "7.5.4" } }, { "NodeType": "HeaderNode", "Title": "Installed apt packages", "Children": { "NodeType": "TableNode", "Headers": "Name|Version", "Rows": [ "acl|2.3.2-1build1.1", "apt-utils|2.8.3", "autoconf|2.71-3", "automake|1:1.16.5-1.3ubuntu1", "bc|1.07.1-3ubuntu4", "binutils|2.42-4ubuntu2.8", "bzip2|1.0.8-5.1build0.1", "ca-certificates|20240203", "coreutils|9.4-3ubuntu6.1", "curl|8.5.0-2ubuntu10.6", "dbus|1.14.10-4ubuntu4.1", "dnsutils|1:9.18.39-0ubuntu0.24.04.2", "dpkg|1.22.6ubuntu6.5", "dpkg-dev|1.22.6ubuntu6.5", "fakeroot|1.33-1", "file|1:5.45-3build1", "findutils|4.9.0-5build1", "flex|2.6.4-8.2build1", "fonts-noto-color-emoji|2.047-0ubuntu0.24.04.1", "ftp|20230507-2build3", "g++|4:13.2.0-7ubuntu1", "gcc|4:13.2.0-7ubuntu1", "gnupg2|2.4.4-2ubuntu17.4", "haveged|1.9.14-1ubuntu2", "iproute2|6.1.0-1ubuntu6.2", "iputils-ping|3:20240117-1ubuntu0.1", "jq|1.7.1-3ubuntu0.24.04.1", "libnss3-tools|2:3.98-1build1", "libsqlite3-dev|3.45.1-1ubuntu2.5", "libssl-dev|3.0.13-0ubuntu3.6", "libtool|2.4.7-7build1", "libyaml-dev|0.2.5-1build1", "locales|2.39-0ubuntu8.6", "lz4|1.9.4-1build1.1", "lzma|9.22-2.2", "make|4.3-4.1build2", "mercurial|6.7.2-1ubuntu2.2", "net-tools|2.10-0.1ubuntu4.4", "netcat-openbsd|1.226-1ubuntu2", "openssh-client|1:9.6p1-3ubuntu13.14", "p7zip-full|16.02+transitional.1", "p7zip-rar|16.02+transitional.1", "parallel|20231122+ds-1", "patchelf|0.18.0-1.1build1", "pigz|2.8-1", "pkg-config|1.8.1-2build1", "pollinate|4.33-3.1ubuntu1.1", "python-is-python3|3.11.4-1", "rpm|4.18.2+dfsg-2.1build2", "rsync|3.2.7-1ubuntu1.2", "shellcheck|0.9.0-1", "sqlite3|3.45.1-1ubuntu2.5", "ssh|1:9.6p1-3ubuntu13.14", "sshpass|1.09-1", "sudo|1.9.15p5-3ubuntu5.24.04.1", "systemd-coredump|255.4-1ubuntu8.12", "tar|1.35+dfsg-3build1", "telnet|0.17+2.5-3ubuntu4", "texinfo|7.1-3build2", "time|1.9-0.2build1", "tk|8.6.14build1", "tree|2.1.1-2ubuntu3.24.04.2", "tzdata|2025b-0ubuntu0.24.04.1", "unzip|6.0-28ubuntu4.1", "upx|4.2.2-3", "wget|1.21.4-1ubuntu4.1", "xvfb|2:21.1.12-1ubuntu1.5", "xz-utils|5.6.1+really5.4.5-1ubuntu0.2", "zip|3.0-13ubuntu0.2", "zsync|0.6.2-5build1" ] } } ] } ] } ================================================ FILE: images/windows/Windows2022-Readme.md ================================================ | Announcements | |-| | [Windows Server 2025 with Visual Studio 2026 is now available as a public beta](https://github.com/actions/runner-images/issues/13638) | | [[Windows/Ubuntu] Docker Server and Client will be updated to version 29.1.*, Docker Compose will be updated to version 2.40.3 on February 9th, 2026](https://github.com/actions/runner-images/issues/13474) | *** # Windows Server 2022 - OS Version: 10.0.20348 Build 4773 - Image Version: 20260301.50.1 ## Windows features - Windows Subsystem for Linux (WSLv1): Enabled ## Installed Software ### Language and Runtime - Bash 5.2.37(1)-release - Go 1.24.13 - Julia 1.12.0 - Kotlin 2.3.10 - LLVM 20.1.8 - Node 20.20.0 - Perl 5.32.1 - PHP 8.5.3 - Python 3.12.10 - Ruby 3.3.10 ### Package Management - Chocolatey 2.6.0 - Composer 2.9.5 - Helm 4.1.0 - Miniconda 25.11.1 (pre-installed on the image but not added to PATH) - NPM 10.8.2 - NuGet 7.3.0.70 - pip 26.0.1 (python 3.12) - Pipx 1.8.0 - RubyGems 3.5.22 - Vcpkg (build from commit 62159a45e1) - Yarn 1.22.22 #### Environment variables | Name | Value | | ----------------------- | ------------ | | VCPKG_INSTALLATION_ROOT | C:\vcpkg | | CONDA | C:\Miniconda | ### Project Management - Ant 1.10.15 - Gradle 9.3 - Maven 3.9.12 - sbt 1.12.4 ### Tools - 7zip 26.00 - aria2 1.37.0 - azcopy 10.32.1 - Bazel 9.0.0 - Bazelisk 1.28.1 - Bicep 0.41.2 - Cabal 3.16.1.0 - CMake 3.31.6 - CodeQL Action Bundle 2.24.2 - Docker 29.1.5 - Docker Compose v2 2.40.3 - Docker-wincred 0.9.5 - ghc 9.14.1 - Git 2.53.0.windows.1 - Git LFS 3.7.1 - ImageMagick 7.1.2-15 - InnoSetup 6.7.1 - jq 1.8.1 - Kind 0.31.0 - Kubectl 1.35.2 - Mercurial 6.3.1 - gcc 14.2.0 - gdb 16.2 - GNU Binutils 2.44 - Newman 6.2.2 - NSIS 3.10 - OpenSSL 3.6.1 - Packer 1.15.0 - Pulumi 3.224.0 - R 4.5.2 - Service Fabric SDK 10.1.2493.9590 - Stack 3.9.3 - Subversion (SVN) 1.14.5 - Swig 4.3.1 - VSWhere 3.1.7 - WinAppDriver 1.2.2009.02003 - WiX Toolset 3.14.1.8722 - yamllint 1.38.0 - zstd 1.5.7 - Ninja 1.13.2 ### CLI Tools - Alibaba Cloud CLI 3.2.10 - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.83.0 - Azure DevOps CLI extension 1.0.2 - GitHub CLI 2.87.3 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - bindgen 0.72.1 - cargo-audit 0.22.1 - cargo-outdated 0.17.0 - cbindgen 0.29.2 - Clippy 0.1.93 - Rustfmt 1.8.0 ### Browsers and Drivers - Google Chrome 145.0.7632.117 - Chrome Driver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge Driver 145.0.3800.82 - Mozilla Firefox 148.0 - Gecko Driver 0.36.0 - IE Driver 4.14.0.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | ----------------- | ---------------------------------- | | CHROMEWEBDRIVER | C:\SeleniumWebDrivers\ChromeDriver | | EDGEWEBDRIVER | C:\SeleniumWebDrivers\EdgeDriver | | GECKOWEBDRIVER | C:\SeleniumWebDrivers\GeckoDriver | | SELENIUM_JAR_PATH | C:\selenium\selenium-server.jar | ### Java | Version | Environment Variable | | ------------------- | -------------------- | | 8.0.482+8 (default) | JAVA_HOME_8_X64 | | 11.0.30+7 | JAVA_HOME_11_X64 | | 17.0.18+8 | JAVA_HOME_17_X64 | | 21.0.10+7.0 | JAVA_HOME_21_X64 | | 25.0.2+10.0 | JAVA_HOME_25_X64 | ### Shells | Name | Target | | ------------- | --------------------------------- | | gitbash.exe | C:\Program Files\Git\bin\bash.exe | | msys2bash.cmd | C:\msys64\usr\bin\bash.exe | | wslbash.exe | C:\Windows\System32\bash.exe | ### MSYS2 - Pacman 6.1.0 #### Notes ``` Location: C:\msys64 Note: MSYS2 is pre-installed on image but not added to PATH. ``` ### Cached Tools #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.7 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Python - 3.10.11 - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### PyPy - 2.7.18 [PyPy 7.3.20] - 3.7.13 [PyPy 7.3.9] - 3.8.16 [PyPy 7.3.11] - 3.9.19 [PyPy 7.3.16] - 3.10.16 [PyPy 7.3.19] #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 ### Databases #### PostgreSQL | Property | Value | | -------------------- | ---------------------------------------------------------------------------------------------------------------------- | | ServiceName | postgresql-x64-14 | | Version | 14.22 | | ServiceStatus | Stopped | | ServiceStartType | Disabled | | EnvironmentVariables | PGBIN=C:\Program Files\PostgreSQL\14\bin
PGDATA=C:\PostgreSQL\14\data
PGROOT=C:\Program Files\PostgreSQL\14 | | Path | C:\Program Files\PostgreSQL\14 | | UserName | postgres | | Password | root | #### MongoDB | Version | ServiceName | ServiceStatus | ServiceStartType | | -------- | ----------- | ------------- | ---------------- | | 7.0.30.0 | MongoDB | Stopped | Disabled | ### Database tools - Azure CosmosDb Emulator 2.14.27.0 - DacFx 170.3.93.6 - MySQL 8.0.45.0 - SQL OLEDB Driver 18 18.7.5.0 - SQL OLEDB Driver 19 19.4.1.0 - SQLPS 1.0 - MongoDB Shell (mongosh) 2.7.0 ### Web Servers | Name | Version | ConfigFile | ServiceName | ServiceStatus | ListenPort | | ------ | ------- | ------------------------------------- | ----------- | ------------- | ---------- | | Apache | 2.4.55 | C:\tools\Apache24\conf\httpd.conf | Apache | Stopped | 80 | | Nginx | 1.29.5 | C:\tools\nginx-1.29.5\conf\nginx.conf | nginx | Stopped | 80 | ### Visual Studio Enterprise 2022 | Name | Version | Path | | ----------------------------- | ------------- | -------------------------------------------------------- | | Visual Studio Enterprise 2022 | 17.14.37012.4 | C:\Program Files\Microsoft Visual Studio\2022\Enterprise | #### Workloads, components and extensions | Package | Version | | ------------------------------------------------------------------------- | --------------- | | android | 35.0.78.0 | | Component.Android.NDK.R23C | 17.14.36510.44 | | Component.Android.SDK.MAUI | 17.14.36510.44 | | Component.Dotfuscator | 17.14.36510.44 | | Component.Linux.CMake | 17.14.36510.44 | | Component.Linux.RemoteFileExplorer | 17.14.36510.44 | | Component.MDD.Android | 17.14.36804.6 | | Component.MDD.Linux | 17.14.36510.44 | | Component.Microsoft.VisualStudio.RazorExtension | 17.14.36510.44 | | Component.Microsoft.VisualStudio.Tools.Applications.amd64 | 17.0.36522.0 | | Component.Microsoft.VisualStudio.Web.AzureFunctions | 17.14.36510.44 | | Component.Microsoft.Web.LibraryManager | 17.14.36510.44 | | Component.Microsoft.WebTools.BrowserLink.WebLivePreview | 17.14.2.50506 | | Component.Microsoft.Windows.DriverKit | 10.0.26100.16 | | Component.OpenJDK | 17.14.36510.44 | | Component.UnityEngine.x64 | 17.14.36510.44 | | Component.Unreal | 17.14.36510.44 | | Component.Unreal.Android | 17.14.36510.44 | | Component.Unreal.Debugger | 17.14.36907.17 | | Component.Unreal.Ide | 17.14.36510.44 | | Component.VisualStudio.GitHub.Copilot | 17.14.37011.9 | | Component.VSInstallerProjects2022 | 3.0.0 | | Component.WixToolset.VisualStudioExtension.Dev17 | 1.0.0.22 | | Component.WixToolset.VisualStudioExtension.Schemas3 | 1.0.0.22 | | Component.Xamarin | 17.14.36510.44 | | ComponentGroup.Microsoft.NET.AppModernization | 17.14.37011.9 | | ios | 26.0.9752.0 | | maccatalyst | 26.0.9752.0 | | maui.blazor | 9.0.111.6930 | | maui.core | 9.0.111.6930 | | maui.windows | 9.0.111.6930 | | Microsoft.Component.Azure.DataLake.Tools | 17.14.36510.44 | | Microsoft.Component.ClickOnce | 17.14.36510.44 | | Microsoft.Component.CodeAnalysis.SDK | 17.14.36510.44 | | Microsoft.Component.MSBuild | 17.14.36510.44 | | Microsoft.Component.NetFX.Native | 17.14.36510.44 | | Microsoft.Component.PythonTools | 17.14.36510.44 | | Microsoft.Component.PythonTools.Web | 17.14.36510.44 | | Microsoft.Component.VC.Runtime.UCRTSDK | 17.14.36510.44 | | Microsoft.ComponentGroup.Blend | 17.14.36510.44 | | Microsoft.ComponentGroup.ClickOnce.Publish | 17.14.36510.44 | | Microsoft.Net.Component.4.5.2.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.6.2.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.6.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.7.1.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.7.2.SDK | 17.14.36510.44 | | Microsoft.Net.Component.4.7.2.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.7.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.8.1.SDK | 17.14.36510.44 | | Microsoft.Net.Component.4.8.1.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.8.SDK | 17.14.36510.44 | | Microsoft.Net.Component.4.8.TargetingPack | 17.14.36510.44 | | Microsoft.Net.ComponentGroup.4.8.DeveloperTools | 17.14.36510.44 | | Microsoft.Net.ComponentGroup.DevelopmentPrerequisites | 17.14.36510.44 | | Microsoft.Net.ComponentGroup.TargetingPacks.Common | 17.14.36510.44 | | microsoft.net.runtime.android | 9.0.1326.6317 | | microsoft.net.runtime.android.aot | 9.0.1326.6317 | | microsoft.net.runtime.android.aot.net8 | 9.0.1326.6317 | | microsoft.net.runtime.android.net8 | 9.0.1326.6317 | | microsoft.net.runtime.ios | 9.0.1326.6317 | | microsoft.net.runtime.maccatalyst | 9.0.1326.6317 | | microsoft.net.runtime.mono.tooling | 9.0.1326.6317 | | microsoft.net.runtime.mono.tooling.net8 | 9.0.1326.6317 | | microsoft.net.sdk.emscripten | 9.0.14.5604 | | Microsoft.NetCore.Component.DevelopmentTools | 17.14.36510.44 | | Microsoft.NetCore.Component.Runtime.8.0 | 17.14.36928.8 | | Microsoft.NetCore.Component.Runtime.9.0 | 17.14.36928.8 | | Microsoft.NetCore.Component.SDK | 17.14.36928.8 | | Microsoft.NetCore.Component.Web | 17.14.36510.44 | | Microsoft.VisualStudio.Component.AppInsights.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.AspNet | 17.14.36510.44 | | Microsoft.VisualStudio.Component.AspNet45 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.AuthoringTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.ClientLibs | 17.14.36907.17 | | Microsoft.VisualStudio.Component.Azure.Compute.Emulator | 17.14.36517.7 | | Microsoft.VisualStudio.Component.Azure.ResourceManager.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.ServiceFabric.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.Waverton | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.Waverton.BuildTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.ClassDesigner | 17.14.36510.44 | | Microsoft.VisualStudio.Component.CodeMap | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Common.Azure.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.CoreEditor | 17.14.36510.44 | | Microsoft.VisualStudio.Component.CppBuildInsights | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Debugger.JustInTime | 17.14.36510.44 | | Microsoft.VisualStudio.Component.DiagnosticTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.DockerTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.DotNetModelBuilder | 17.14.36510.44 | | Microsoft.VisualStudio.Component.DslTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.EntityFramework | 17.14.36510.44 | | Microsoft.VisualStudio.Component.FSharp | 17.14.36510.44 | | Microsoft.VisualStudio.Component.FSharp.Desktop | 17.14.36510.44 | | Microsoft.VisualStudio.Component.FSharp.WebTemplates | 17.14.36510.44 | | Microsoft.VisualStudio.Component.GraphDocument | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Graphics | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Graphics.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.HLSL | 17.14.36510.44 | | Microsoft.VisualStudio.Component.IISExpress | 17.14.36510.44 | | Microsoft.VisualStudio.Component.IntelliCode | 17.14.36621.7 | | Microsoft.VisualStudio.Component.IntelliTrace.FrontEnd | 17.14.36510.44 | | Microsoft.VisualStudio.Component.JavaScript.Diagnostics | 17.14.36510.44 | | Microsoft.VisualStudio.Component.JavaScript.TypeScript | 17.14.36510.44 | | Microsoft.VisualStudio.Component.LinqToSql | 17.14.36510.44 | | Microsoft.VisualStudio.Component.LiveUnitTesting | 17.14.36510.44 | | Microsoft.VisualStudio.Component.ManagedDesktop.Core | 17.14.36510.44 | | Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites | 17.14.36510.44 | | Microsoft.VisualStudio.Component.MSODBC.SQL | 17.14.36510.44 | | Microsoft.VisualStudio.Component.MSSQL.CMDLnUtils | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Node.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.NuGet | 17.14.36510.44 | | Microsoft.VisualStudio.Component.NuGet.BuildTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.PortableLibrary | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Roslyn.Compiler | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Roslyn.LanguageServices | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Sharepoint.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.SQL.CLR | 17.14.36510.44 | | Microsoft.VisualStudio.Component.SQL.DataSources | 17.14.36510.44 | | Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime | 17.14.36510.44 | | Microsoft.VisualStudio.Component.SQL.SSDT | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TeamOffice | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TestTools.CodedUITest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TestTools.WebLoadTest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TextTemplating | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TypeScript.TSServer | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Unity | 17.14.36510.44 | | Microsoft.VisualStudio.Component.UWP.VC.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.UWP.VC.ARM64EC | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.14.29.16.11.ARM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.14.29.16.11.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ASAN | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.ARM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.ARM.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATLMFC | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATLMFC.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.CLI.Support | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.CMake.Project | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.CoreIde | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.DiagnosticTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Llvm.Clang | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.MFC.ARM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.MFC.ARM.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.MFC.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.MFC.ARM64.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Modules.x86.x64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Redist.14.Latest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Redist.MSM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Runtimes.ARM.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Runtimes.ARM64EC.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Tools.ARM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Tools.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Tools.ARM64EC | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Tools.x86.x64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Vcpkg | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VSSDK | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Wcf.Tooling | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Web | 17.14.36510.44 | | Microsoft.VisualStudio.Component.WebDeploy | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Windows10SDK | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Windows10SDK.19041 | 17.14.36809.9 | | Microsoft.VisualStudio.Component.Windows11SDK.22621 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Windows11SDK.26100 | 17.14.37011.9 | | Microsoft.VisualStudio.Component.Windows11Sdk.WindowsPerformanceToolkit | 17.14.36510.44 | | Microsoft.VisualStudio.Component.WindowsAppSdkSupport.CSharp | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Workflow | 17.14.36510.44 | | Microsoft.VisualStudio.Component.WslDebugging | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.ArchitectureTools.Native | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Azure.CloudServices | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Azure.Prerequisites | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Azure.ResourceManager.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.AzureFunctions | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.All | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.Android | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.Blazor | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.iOS | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.MacCatalyst | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.Shared | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.Windows | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang | 17.14.36802.14 | | Microsoft.VisualStudio.ComponentGroup.UWP.NetCoreAndStandard | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.UWP.VC.v142 | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.VC.Tools.142.x86.x64 | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.VisualStudioExtension.Prerequisites | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Web | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Web.CloudTools | 17.14.36614.30 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.TemplateEngine | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.WindowsAppDevelopment.Prerequisites | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs | 17.14.36510.44 | | Microsoft.VisualStudio.Workload.Azure | 17.14.36904.0 | | Microsoft.VisualStudio.Workload.CoreEditor | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.Data | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.DataScience | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.ManagedDesktop | 17.14.36518.2 | | Microsoft.VisualStudio.Workload.ManagedGame | 17.14.36301.6 | | Microsoft.VisualStudio.Workload.NativeCrossPlat | 17.14.36716.0 | | Microsoft.VisualStudio.Workload.NativeDesktop | 17.14.36517.7 | | Microsoft.VisualStudio.Workload.NativeGame | 17.14.36331.10 | | Microsoft.VisualStudio.Workload.NativeMobile | 17.14.36802.14 | | Microsoft.VisualStudio.Workload.NetCrossPlat | 17.14.36518.2 | | Microsoft.VisualStudio.Workload.NetWeb | 17.14.36518.2 | | Microsoft.VisualStudio.Workload.Node | 17.14.36517.7 | | Microsoft.VisualStudio.Workload.Office | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.Python | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.Universal | 17.14.36331.10 | | Microsoft.VisualStudio.Workload.VisualStudioExtension | 17.14.36015.10 | | runtimes.ios | 9.0.1326.6317 | | runtimes.maccatalyst | 9.0.1326.6317 | | wasm.tools | 9.0.1326.6317 | | ProBITools.MicrosoftAnalysisServicesModelingProjects2022 | 4.0.0 | | ProBITools.MicrosoftReportProjectsforVisualStudio2022 | 4.0.0 | | SSIS.MicrosoftDataToolsIntegrationServices | 2.1.2 | | VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects | 3.0.0 | | Windows Driver Kit | 10.1.26100.4202 | | Windows Driver Kit Visual Studio Extension | 10.0.26100.16 | | Windows Software Development Kit | 10.1.26100.7705 | | WixToolset.WixToolsetVisualStudio2022Extension | 1.0.0.22 | #### Microsoft Visual C++ | Name | Architecture | Version | | -------------------------------------------- | ------------ | ----------- | | Microsoft Visual C++ 2013 Additional Runtime | x64 | 12.0.40660 | | Microsoft Visual C++ 2013 Minimum Runtime | x64 | 12.0.40660 | | Microsoft Visual C++ 2022 Additional Runtime | x64 | 14.50.35719 | | Microsoft Visual C++ 2022 Debug Runtime | x64 | 14.44.35211 | | Microsoft Visual C++ 2022 Minimum Runtime | x64 | 14.50.35719 | | Microsoft Visual C++ 2022 Additional Runtime | x86 | 14.50.35719 | | Microsoft Visual C++ 2022 Debug Runtime | x86 | 14.44.35211 | | Microsoft Visual C++ 2022 Minimum Runtime | x86 | 14.50.35719 | #### Installed Windows SDKs - 10.0.17763.0 - 10.0.19041.0 - 10.0.22621.0 - 10.0.26100.0 ### .NET Core Tools - .NET Core SDK: 8.0.124, 8.0.206, 8.0.319, 8.0.418, 9.0.114, 9.0.205, 9.0.311, 10.0.102 - .NET Framework: 4.7.2, 4.8, 4.8.1 - Microsoft.AspNetCore.App: 6.0.40, 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2 - Microsoft.NETCore.App: 6.0.40, 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2 - Microsoft.WindowsDesktop.App: 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2 - nbgv 3.9.50+6feeb89450 ### PowerShell Tools - PowerShell 7.4.13 #### Powershell Modules - Az: 14.6.0 - AWSPowershell: 5.0.165 - DockerMsftProvider: 1.0.0.8 - MarkdownPS: 1.10 - Microsoft.Graph: 2.35.1 - Pester: 3.4.0, 5.7.1 - PowerShellGet: 1.0.0.1, 2.2.5 - PSScriptAnalyzer: 1.24.0 - PSWindowsUpdate: 2.2.1.5 - SqlServer: 22.3.0 - VSSetup: 2.2.16 ### Android | Package Name | Version | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 8.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1
34.0.0
32.0.0 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3)
android-33 (rev 3) | | Android SDK Platform-Tools | 36.0.2 | | Android Support Repository | 47.0.0 | | CMake | 3.22.1
3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | ---------------------------------------- | | ANDROID_HOME | C:\Android\android-sdk | | ANDROID_NDK | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_NDK_HOME | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_NDK_LATEST_HOME | C:\Android\android-sdk\ndk\29.0.14206865 | | ANDROID_NDK_ROOT | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_SDK_ROOT | C:\Android\android-sdk | ### Cached Docker images | Repository:Tag | Digest | Created | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------- | | mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2022 | sha256:ec04e733695f49a0dc9132184f6b06704866b34f422004093c1972512c86259e | 2025-09-09 | | mcr.microsoft.com/dotnet/framework/runtime:4.8-windowsservercore-ltsc2022 | sha256:3983348680840ca6e53ad641e314c3c9184ca2fd19f88bc467600f7d9f6e9d73 | 2025-09-09 | | mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2022 | sha256:460dedaed73224f73ff10dc3ad754d0ed250aa57bcdf6c5052a811b4b7e29345 | 2025-09-09 | | mcr.microsoft.com/windows/nanoserver:ltsc2022 | sha256:60612a30303eb5a15ce7f53fa2eecf70bca41d72657de0482fbde601ae5f3403 | 2026-02-06 | | mcr.microsoft.com/windows/servercore:ltsc2022 | sha256:a264df8cd8c329eed3fd1e0cafcd4f3dc453e2c72a277f9bb140fd6f10a2eefc | 2026-02-06 | ================================================ FILE: images/windows/Windows2025-Readme.md ================================================ | Announcements | |-| | [Windows Server 2025 with Visual Studio 2026 is now available as a public beta](https://github.com/actions/runner-images/issues/13638) | | [[Windows/Ubuntu] Docker Server and Client will be updated to version 29.1.*, Docker Compose will be updated to version 2.40.3 on February 9th, 2026](https://github.com/actions/runner-images/issues/13474) | *** # Windows Server 2025 - OS Version: 10.0.26100 Build 32370 - Image Version: 20260302.43.1 ## Windows features - Windows Subsystem for Linux (WSLv1): Enabled - Windows Subsystem for Linux (Default, WSLv2): 2.6.3.0 ## Installed Software ### Language and Runtime - Bash 5.2.37(1)-release - Go 1.24.13 - Julia 1.12.0 - Kotlin 2.3.10 - LLVM 20.1.8 - Node 22.22.0 - Perl 5.42.0 - PHP 8.5.3 - Python 3.12.10 - Ruby 3.3.10 ### Package Management - Chocolatey 2.6.0 - Composer 2.9.5 - Helm 4.1.0 - Miniconda 25.11.1 (pre-installed on the image but not added to PATH) - NPM 10.9.4 - NuGet 7.3.0.70 - pip 26.0.1 (python 3.12) - Pipx 1.8.0 - RubyGems 3.5.22 - Vcpkg (build from commit 39a6cc0e44) - Yarn 1.22.22 #### Environment variables | Name | Value | | ----------------------- | ------------ | | VCPKG_INSTALLATION_ROOT | C:\vcpkg | | CONDA | C:\Miniconda | ### Project Management - Ant 1.10.15 - Gradle 9.3 - Maven 3.9.12 - sbt 1.12.5 ### Tools - 7zip 26.00 - aria2 1.37.0 - azcopy 10.32.1 - Bazel 9.0.0 - Bazelisk 1.28.1 - Bicep 0.41.2 - Cabal 3.16.1.0 - CMake 3.31.6 - CodeQL Action Bundle 2.24.2 - Docker 29.1.5 - Docker Compose v2 2.40.3 - Docker-wincred 0.9.5 - ghc 9.14.1 - Git 2.53.0.windows.1 - Git LFS 3.7.1 - ImageMagick 7.1.2-15 - InnoSetup 6.7.1 - jq 1.8.1 - Kind 0.31.0 - Kubectl 1.35.2 - gcc 15.2.0 - gdb 17.1 - GNU Binutils 2.46 - Newman 6.2.2 - OpenSSL 3.6.1 - Packer 1.15.0 - Pulumi 3.224.0 - R 4.5.2 - Service Fabric SDK 10.1.2493.9590 - Stack 3.9.3 - Swig 4.3.1 - VSWhere 3.1.7 - WinAppDriver 1.2.2009.02003 - WiX Toolset 3.14.1.8722 - yamllint 1.38.0 - zstd 1.5.7 - Ninja 1.13.2 ### CLI Tools - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.83.0 - Azure DevOps CLI extension 1.0.2 - GitHub CLI 2.87.3 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - Clippy 0.1.93 - Rustfmt 1.8.0 ### Browsers and Drivers - Google Chrome 145.0.7632.117 - Chrome Driver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge Driver 145.0.3800.82 - Mozilla Firefox 148.0 - Gecko Driver 0.36.0 - IE Driver 4.14.0.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | ----------------- | ---------------------------------- | | CHROMEWEBDRIVER | C:\SeleniumWebDrivers\ChromeDriver | | EDGEWEBDRIVER | C:\SeleniumWebDrivers\EdgeDriver | | GECKOWEBDRIVER | C:\SeleniumWebDrivers\GeckoDriver | | SELENIUM_JAR_PATH | C:\selenium\selenium-server.jar | ### Java | Version | Environment Variable | | ------------------- | -------------------- | | 8.0.482+8 | JAVA_HOME_8_X64 | | 11.0.30+7 | JAVA_HOME_11_X64 | | 17.0.18+8 (default) | JAVA_HOME_17_X64 | | 21.0.10+7.0 | JAVA_HOME_21_X64 | | 25.0.2+10.0 | JAVA_HOME_25_X64 | ### Shells | Name | Target | | ------------- | --------------------------------- | | gitbash.exe | C:\Program Files\Git\bin\bash.exe | | msys2bash.cmd | C:\msys64\usr\bin\bash.exe | | wslbash.exe | C:\Windows\System32\bash.exe | ### MSYS2 - Pacman 6.1.0 #### Notes ``` Location: C:\msys64 Note: MSYS2 is pre-installed on image but not added to PATH. ``` ### Cached Tools #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.7 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Python - 3.10.11 - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### PyPy - 3.9.19 [PyPy 7.3.16] - 3.10.16 [PyPy 7.3.19] #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 ### Databases #### PostgreSQL | Property | Value | | -------------------- | ---------------------------------------------------------------------------------------------------------------------- | | ServiceName | postgresql-x64-17 | | Version | 17.9 | | ServiceStatus | Stopped | | ServiceStartType | Disabled | | EnvironmentVariables | PGBIN=C:\Program Files\PostgreSQL\17\bin
PGDATA=C:\PostgreSQL\17\data
PGROOT=C:\Program Files\PostgreSQL\17 | | Path | C:\Program Files\PostgreSQL\17 | | UserName | postgres | | Password | root | #### MongoDB | Version | ServiceName | ServiceStatus | ServiceStartType | | -------- | ----------- | ------------- | ---------------- | | 7.0.30.0 | MongoDB | Stopped | Disabled | ### Database tools - Azure CosmosDb Emulator 2.14.27.0 - DacFx 170.3.93.6 - MySQL 8.0.45.0 - SQL OLEDB Driver 18 18.7.5.0 - SQL OLEDB Driver 19 19.4.1.0 - SQLPS 1.0 - MongoDB Shell (mongosh) 2.7.0 ### Web Servers | Name | Version | ConfigFile | ServiceName | ServiceStatus | ListenPort | | ------ | ------- | ------------------------------------- | ----------- | ------------- | ---------- | | Apache | 2.4.55 | C:\tools\Apache24\conf\httpd.conf | Apache | Stopped | 80 | | Nginx | 1.29.5 | C:\tools\nginx-1.29.5\conf\nginx.conf | nginx | Stopped | 80 | ### Visual Studio Enterprise 2022 | Name | Version | Path | | ----------------------------- | ------------- | -------------------------------------------------------- | | Visual Studio Enterprise 2022 | 17.14.37012.4 | C:\Program Files\Microsoft Visual Studio\2022\Enterprise | #### Workloads, components and extensions | Package | Version | | ------------------------------------------------------------------------- | --------------- | | android | 35.0.78.0 | | Component.Android.NDK.R23C | 17.14.36510.44 | | Component.Android.SDK.MAUI | 17.14.36510.44 | | Component.Dotfuscator | 17.14.36510.44 | | Component.Linux.CMake | 17.14.36510.44 | | Component.Linux.RemoteFileExplorer | 17.14.36510.44 | | Component.MDD.Android | 17.14.36804.6 | | Component.MDD.Linux | 17.14.36510.44 | | Component.Microsoft.VisualStudio.RazorExtension | 17.14.36510.44 | | Component.Microsoft.VisualStudio.Tools.Applications.amd64 | 17.0.36522.0 | | Component.Microsoft.VisualStudio.Web.AzureFunctions | 17.14.36510.44 | | Component.Microsoft.Web.LibraryManager | 17.14.36510.44 | | Component.Microsoft.WebTools.BrowserLink.WebLivePreview | 17.14.2.50506 | | Component.Microsoft.Windows.DriverKit | 10.0.26100.16 | | Component.OpenJDK | 17.14.36510.44 | | Component.UnityEngine.x64 | 17.14.36510.44 | | Component.Unreal.Debugger | 17.14.36907.17 | | Component.Unreal.Ide | 17.14.36510.44 | | Component.VisualStudio.GitHub.Copilot | 17.14.37011.9 | | Component.VSInstallerProjects2022 | 3.0.0 | | Component.WixToolset.VisualStudioExtension.Dev17 | 1.0.0.22 | | Component.WixToolset.VisualStudioExtension.Schemas3 | 1.0.0.22 | | ComponentGroup.Microsoft.NET.AppModernization | 17.14.37011.9 | | ios | 26.0.9752.0 | | maccatalyst | 26.0.9752.0 | | maui.blazor | 9.0.111.6930 | | maui.core | 9.0.111.6930 | | maui.windows | 9.0.111.6930 | | Microsoft.Component.Azure.DataLake.Tools | 17.14.36510.44 | | Microsoft.Component.ClickOnce | 17.14.36510.44 | | Microsoft.Component.CodeAnalysis.SDK | 17.14.36510.44 | | Microsoft.Component.MSBuild | 17.14.36510.44 | | Microsoft.Component.NetFX.Native | 17.14.36510.44 | | Microsoft.Component.PythonTools | 17.14.36510.44 | | Microsoft.Component.PythonTools.Web | 17.14.36510.44 | | Microsoft.Component.VC.Runtime.UCRTSDK | 17.14.36510.44 | | Microsoft.ComponentGroup.Blend | 17.14.36510.44 | | Microsoft.ComponentGroup.ClickOnce.Publish | 17.14.36510.44 | | Microsoft.Net.Component.4.5.2.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.6.2.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.6.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.7.1.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.7.2.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.7.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.8.1.SDK | 17.14.36510.44 | | Microsoft.Net.Component.4.8.1.TargetingPack | 17.14.36510.44 | | Microsoft.Net.Component.4.8.SDK | 17.14.36510.44 | | Microsoft.Net.Component.4.8.TargetingPack | 17.14.36510.44 | | Microsoft.Net.ComponentGroup.4.8.DeveloperTools | 17.14.36510.44 | | Microsoft.Net.ComponentGroup.DevelopmentPrerequisites | 17.14.36510.44 | | Microsoft.Net.ComponentGroup.TargetingPacks.Common | 17.14.36510.44 | | microsoft.net.runtime.android | 9.0.1326.6317 | | microsoft.net.runtime.android.aot | 9.0.1326.6317 | | microsoft.net.runtime.android.aot.net8 | 9.0.1326.6317 | | microsoft.net.runtime.android.net8 | 9.0.1326.6317 | | microsoft.net.runtime.ios | 9.0.1326.6317 | | microsoft.net.runtime.maccatalyst | 9.0.1326.6317 | | microsoft.net.runtime.mono.tooling | 9.0.1326.6317 | | microsoft.net.runtime.mono.tooling.net8 | 9.0.1326.6317 | | microsoft.net.sdk.emscripten | 9.0.14.5604 | | Microsoft.NetCore.Component.DevelopmentTools | 17.14.36510.44 | | Microsoft.NetCore.Component.Runtime.8.0 | 17.14.36928.8 | | Microsoft.NetCore.Component.Runtime.9.0 | 17.14.36928.8 | | Microsoft.NetCore.Component.SDK | 17.14.36928.8 | | Microsoft.NetCore.Component.Web | 17.14.36510.44 | | Microsoft.VisualStudio.Component.AppInsights.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.AspNet | 17.14.36510.44 | | Microsoft.VisualStudio.Component.AspNet45 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.AuthoringTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.ClientLibs | 17.14.36907.17 | | Microsoft.VisualStudio.Component.Azure.Compute.Emulator | 17.14.36517.7 | | Microsoft.VisualStudio.Component.Azure.ResourceManager.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.ServiceFabric.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.Waverton | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Azure.Waverton.BuildTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.ClassDesigner | 17.14.36510.44 | | Microsoft.VisualStudio.Component.CodeMap | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Common.Azure.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.CoreEditor | 17.14.36510.44 | | Microsoft.VisualStudio.Component.CppBuildInsights | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Debugger.JustInTime | 17.14.36510.44 | | Microsoft.VisualStudio.Component.DiagnosticTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.DockerTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.DotNetModelBuilder | 17.14.36510.44 | | Microsoft.VisualStudio.Component.DslTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.EntityFramework | 17.14.36510.44 | | Microsoft.VisualStudio.Component.FSharp | 17.14.36510.44 | | Microsoft.VisualStudio.Component.FSharp.Desktop | 17.14.36510.44 | | Microsoft.VisualStudio.Component.FSharp.WebTemplates | 17.14.36510.44 | | Microsoft.VisualStudio.Component.GraphDocument | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Graphics | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Graphics.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.HLSL | 17.14.36510.44 | | Microsoft.VisualStudio.Component.IISExpress | 17.14.36510.44 | | Microsoft.VisualStudio.Component.IntelliCode | 17.14.36621.7 | | Microsoft.VisualStudio.Component.IntelliTrace.FrontEnd | 17.14.36510.44 | | Microsoft.VisualStudio.Component.JavaScript.Diagnostics | 17.14.36510.44 | | Microsoft.VisualStudio.Component.JavaScript.TypeScript | 17.14.36510.44 | | Microsoft.VisualStudio.Component.LinqToSql | 17.14.36510.44 | | Microsoft.VisualStudio.Component.LiveUnitTesting | 17.14.36510.44 | | Microsoft.VisualStudio.Component.ManagedDesktop.Core | 17.14.36510.44 | | Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites | 17.14.36510.44 | | Microsoft.VisualStudio.Component.MSODBC.SQL | 17.14.36510.44 | | Microsoft.VisualStudio.Component.MSSQL.CMDLnUtils | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Node.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.NuGet | 17.14.36510.44 | | Microsoft.VisualStudio.Component.NuGet.BuildTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.PortableLibrary | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Roslyn.Compiler | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Roslyn.LanguageServices | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Sharepoint.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.SQL.CLR | 17.14.36510.44 | | Microsoft.VisualStudio.Component.SQL.DataSources | 17.14.36510.44 | | Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime | 17.14.36510.44 | | Microsoft.VisualStudio.Component.SQL.SSDT | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TeamOffice | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TestTools.CodedUITest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TestTools.WebLoadTest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TextTemplating | 17.14.36510.44 | | Microsoft.VisualStudio.Component.TypeScript.TSServer | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Unity | 17.14.36510.44 | | Microsoft.VisualStudio.Component.UWP.VC.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.UWP.VC.ARM64EC | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.14.29.16.11.ARM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.14.29.16.11.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ASAN | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.ARM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.ARM.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATL.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATLMFC | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.ATLMFC.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.CLI.Support | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.CMake.Project | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.CoreIde | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.DiagnosticTools | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Llvm.Clang | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.MFC.ARM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.MFC.ARM.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.MFC.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.MFC.ARM64.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Modules.x86.x64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Redist.14.Latest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Redist.MSM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Runtimes.ARM.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Runtimes.ARM64EC.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Tools.ARM | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Tools.ARM64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Tools.ARM64EC | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VC.Tools.x86.x64 | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Vcpkg | 17.14.36510.44 | | Microsoft.VisualStudio.Component.VSSDK | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Wcf.Tooling | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Web | 17.14.36510.44 | | Microsoft.VisualStudio.Component.WebDeploy | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Windows10SDK | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Windows11SDK.26100 | 17.14.37011.9 | | Microsoft.VisualStudio.Component.Windows11Sdk.WindowsPerformanceToolkit | 17.14.36510.44 | | Microsoft.VisualStudio.Component.WindowsAppSdkSupport.CSharp | 17.14.36510.44 | | Microsoft.VisualStudio.Component.Workflow | 17.14.36510.44 | | Microsoft.VisualStudio.Component.WslDebugging | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.ArchitectureTools.Native | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Azure.CloudServices | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Azure.Prerequisites | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Azure.ResourceManager.Tools | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.AzureFunctions | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.All | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.Android | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.Blazor | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.iOS | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.MacCatalyst | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.Shared | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Maui.Windows | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang | 17.14.36802.14 | | Microsoft.VisualStudio.ComponentGroup.UWP.NetCoreAndStandard | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.UWP.VC.v142 | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.VC.Tools.142.x86.x64 | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.VisualStudioExtension.Prerequisites | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Web | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.Web.CloudTools | 17.14.36614.30 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.TemplateEngine | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.WindowsAppDevelopment.Prerequisites | 17.14.36510.44 | | Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs | 17.14.36510.44 | | Microsoft.VisualStudio.Workload.Azure | 17.14.36904.0 | | Microsoft.VisualStudio.Workload.CoreEditor | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.Data | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.DataScience | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.ManagedDesktop | 17.14.36518.2 | | Microsoft.VisualStudio.Workload.ManagedGame | 17.14.36301.6 | | Microsoft.VisualStudio.Workload.NativeCrossPlat | 17.14.36716.0 | | Microsoft.VisualStudio.Workload.NativeDesktop | 17.14.36517.7 | | Microsoft.VisualStudio.Workload.NativeGame | 17.14.36331.10 | | Microsoft.VisualStudio.Workload.NativeMobile | 17.14.36802.14 | | Microsoft.VisualStudio.Workload.NetCrossPlat | 17.14.36518.2 | | Microsoft.VisualStudio.Workload.NetWeb | 17.14.36518.2 | | Microsoft.VisualStudio.Workload.Node | 17.14.36517.7 | | Microsoft.VisualStudio.Workload.Office | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.Python | 17.14.36015.10 | | Microsoft.VisualStudio.Workload.Universal | 17.14.36331.10 | | Microsoft.VisualStudio.Workload.VisualStudioExtension | 17.14.36015.10 | | runtimes.ios | 9.0.1326.6317 | | runtimes.maccatalyst | 9.0.1326.6317 | | wasm.tools | 9.0.1326.6317 | | ProBITools.MicrosoftAnalysisServicesModelingProjects2022 | 4.0.0 | | ProBITools.MicrosoftReportProjectsforVisualStudio2022 | 4.0.0 | | SSIS.MicrosoftDataToolsIntegrationServices | 2.1.2 | | VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects | 3.0.0 | | Windows Driver Kit Visual Studio Extension | 10.0.26100.16 | | Windows Software Development Kit | 10.1.26100.7705 | | WixToolset.WixToolsetVisualStudio2022Extension | 1.0.0.22 | #### Microsoft Visual C++ | Name | Architecture | Version | | -------------------------------------------- | ------------ | ----------- | | Microsoft Visual C++ 2013 Additional Runtime | x64 | 12.0.40660 | | Microsoft Visual C++ 2013 Minimum Runtime | x64 | 12.0.40660 | | Microsoft Visual C++ 2022 Additional Runtime | x64 | 14.50.35719 | | Microsoft Visual C++ 2022 Debug Runtime | x64 | 14.44.35211 | | Microsoft Visual C++ 2022 Minimum Runtime | x64 | 14.50.35719 | | Microsoft Visual C++ 2022 Additional Runtime | x86 | 14.50.35719 | | Microsoft Visual C++ 2022 Debug Runtime | x86 | 14.44.35211 | | Microsoft Visual C++ 2022 Minimum Runtime | x86 | 14.50.35719 | #### Installed Windows SDKs - 10.0.26100.0 ### .NET Core Tools - .NET Core SDK: 8.0.124, 8.0.206, 8.0.319, 8.0.418, 9.0.114, 9.0.205, 9.0.311, 10.0.102 - .NET Framework: 4.8, 4.8.1 - Microsoft.AspNetCore.App: 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2 - Microsoft.NETCore.App: 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2 - Microsoft.WindowsDesktop.App: 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2 - nbgv 3.9.50+6feeb89450 ### PowerShell Tools - PowerShell 7.4.13 #### Powershell Modules - Az: 14.6.0 - AWSPowershell: 5.0.165 - DockerMsftProvider: 1.0.0.8 - MarkdownPS: 1.10 - Microsoft.Graph: 2.35.1 - Pester: 3.4.0, 5.7.1 - PowerShellGet: 1.0.0.1, 2.2.5 - PSScriptAnalyzer: 1.24.0 - PSWindowsUpdate: 2.2.1.5 - SqlServer: 22.3.0 - VSSetup: 2.2.16 ### Android | Package Name | Version | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 16.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1
34.0.0 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3) | | Android SDK Platform-Tools | 37.0.0 | | Android Support Repository | 47.0.0 | | CMake | 3.30.5
3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | ---------------------------------------- | | ANDROID_HOME | C:\Android\android-sdk | | ANDROID_NDK | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_NDK_HOME | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_NDK_LATEST_HOME | C:\Android\android-sdk\ndk\29.0.14206865 | | ANDROID_NDK_ROOT | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_SDK_ROOT | C:\Android\android-sdk | ================================================ FILE: images/windows/Windows2025-VS2026-Readme.md ================================================ | Announcements | |-| | [Windows Server 2025 with Visual Studio 2026 is now available as a public beta](https://github.com/actions/runner-images/issues/13638) | | [[Windows/Ubuntu] Docker Server and Client will be updated to version 29.1.*, Docker Compose will be updated to version 2.40.3 on February 9th, 2026](https://github.com/actions/runner-images/issues/13474) | *** # Windows Server 2025 - OS Version: 10.0.26100 Build 32370 - Image Version: 20260301.27.1 ## Windows features - Windows Subsystem for Linux (WSLv1): Enabled - Windows Subsystem for Linux (Default, WSLv2): 2.6.3.0 ## Installed Software ### Language and Runtime - Bash 5.2.37(1)-release - Go 1.24.13 - Julia 1.12.0 - Kotlin 2.3.10 - LLVM 20.1.8 - Node 22.22.0 - Perl 5.42.0 - PHP 8.5.3 - Python 3.12.10 - Ruby 3.3.10 ### Package Management - Chocolatey 2.6.0 - Composer 2.9.5 - Helm 4.1.0 - Miniconda 25.11.1 (pre-installed on the image but not added to PATH) - NPM 10.9.4 - NuGet 7.3.0.70 - pip 26.0.1 (python 3.12) - Pipx 1.8.0 - RubyGems 3.5.22 - Vcpkg (build from commit 62159a45e1) - Yarn 1.22.22 #### Environment variables | Name | Value | | ----------------------- | ------------ | | VCPKG_INSTALLATION_ROOT | C:\vcpkg | | CONDA | C:\Miniconda | ### Project Management - Ant 1.10.15 - Gradle 9.3 - Maven 3.9.12 - sbt 1.12.4 ### Tools - 7zip 26.00 - aria2 1.37.0 - azcopy 10.32.1 - Bazel 9.0.0 - Bazelisk 1.28.1 - Bicep 0.41.2 - Cabal 3.16.1.0 - CMake 4.2.3 - CodeQL Action Bundle 2.24.2 - Docker 29.1.5 - Docker Compose v2 2.40.3 - Docker-wincred 0.9.5 - ghc 9.14.1 - Git 2.53.0.windows.1 - Git LFS 3.7.1 - ImageMagick 7.1.2-15 - InnoSetup 6.7.1 - jq 1.8.1 - Kind 0.31.0 - Kubectl 1.35.2 - gcc 15.2.0 - gdb 17.1 - GNU Binutils 2.46 - Newman 6.2.2 - OpenSSL 3.6.1 - Packer 1.15.0 - Pulumi 3.224.0 - R 4.5.2 - Service Fabric SDK 10.1.2493.9590 - Stack 3.9.3 - Swig 4.3.1 - VSWhere 3.1.7 - WinAppDriver 1.2.2009.02003 - WiX Toolset 3.14.1.8722 - yamllint 1.38.0 - zstd 1.5.7 - Ninja 1.13.2 ### CLI Tools - AWS CLI 2.34.0 - AWS SAM CLI 1.154.0 - AWS Session Manager CLI 1.2.779.0 - Azure CLI 2.83.0 - Azure DevOps CLI extension 1.0.2 - GitHub CLI 2.87.3 ### Rust Tools - Cargo 1.93.1 - Rust 1.93.1 - Rustdoc 1.93.1 - Rustup 1.28.2 #### Packages - Clippy 0.1.93 - Rustfmt 1.8.0 ### Browsers and Drivers - Google Chrome 145.0.7632.117 - Chrome Driver 145.0.7632.117 - Microsoft Edge 145.0.3800.82 - Microsoft Edge Driver 145.0.3800.82 - Mozilla Firefox 148.0 - Gecko Driver 0.36.0 - IE Driver 4.14.0.0 - Selenium server 4.41.0 #### Environment variables | Name | Value | | ----------------- | ---------------------------------- | | CHROMEWEBDRIVER | C:\SeleniumWebDrivers\ChromeDriver | | EDGEWEBDRIVER | C:\SeleniumWebDrivers\EdgeDriver | | GECKOWEBDRIVER | C:\SeleniumWebDrivers\GeckoDriver | | SELENIUM_JAR_PATH | C:\selenium\selenium-server.jar | ### Java | Version | Environment Variable | | ------------------- | -------------------- | | 8.0.482+8 | JAVA_HOME_8_X64 | | 11.0.30+7 | JAVA_HOME_11_X64 | | 17.0.18+8 (default) | JAVA_HOME_17_X64 | | 21.0.10+7.0 | JAVA_HOME_21_X64 | | 25.0.2+10.0 | JAVA_HOME_25_X64 | ### Shells | Name | Target | | ------------- | --------------------------------- | | gitbash.exe | C:\Program Files\Git\bin\bash.exe | | msys2bash.cmd | C:\msys64\usr\bin\bash.exe | | wslbash.exe | C:\Windows\System32\bash.exe | ### MSYS2 - Pacman 6.1.0 #### Notes ``` Location: C:\msys64 Note: MSYS2 is pre-installed on image but not added to PATH. ``` ### Cached Tools #### Go - 1.22.12 - 1.23.12 - 1.24.13 - 1.25.7 #### Node.js - 20.20.0 - 22.22.0 - 24.14.0 #### Python - 3.10.11 - 3.11.9 - 3.12.10 - 3.13.12 - 3.14.3 #### PyPy - 3.9.19 [PyPy 7.3.16] - 3.10.16 [PyPy 7.3.19] #### Ruby - 3.2.10 - 3.3.10 - 3.4.8 - 4.0.1 ### Databases #### PostgreSQL | Property | Value | | -------------------- | ---------------------------------------------------------------------------------------------------------------------- | | ServiceName | postgresql-x64-17 | | Version | 17.9 | | ServiceStatus | Stopped | | ServiceStartType | Disabled | | EnvironmentVariables | PGBIN=C:\Program Files\PostgreSQL\17\bin
PGDATA=C:\PostgreSQL\17\data
PGROOT=C:\Program Files\PostgreSQL\17 | | Path | C:\Program Files\PostgreSQL\17 | | UserName | postgres | | Password | root | #### MongoDB | Version | ServiceName | ServiceStatus | ServiceStartType | | -------- | ----------- | ------------- | ---------------- | | 7.0.30.0 | MongoDB | Stopped | Disabled | ### Database tools - Azure CosmosDb Emulator 2.14.27.0 - DacFx 170.3.93.6 - MySQL 8.0.45.0 - SQL OLEDB Driver 18 18.7.5.0 - SQL OLEDB Driver 19 19.4.1.0 - SQLPS 1.0 - MongoDB Shell (mongosh) 2.7.0 ### Web Servers | Name | Version | ConfigFile | ServiceName | ServiceStatus | ListenPort | | ------ | ------- | ------------------------------------- | ----------- | ------------- | ---------- | | Apache | 2.4.55 | C:\tools\Apache24\conf\httpd.conf | Apache | Stopped | 80 | | Nginx | 1.29.5 | C:\tools\nginx-1.29.5\conf\nginx.conf | nginx | Stopped | 80 | ### Visual Studio Enterprise 2026 | Name | Version | Path | | ----------------------------- | ------------- | ------------------------------------------------------ | | Visual Studio Enterprise 2026 | 18.3.11520.95 | C:\Program Files\Microsoft Visual Studio\18\Enterprise | #### Workloads, components and extensions | Package | Version | | ------------------------------------------------------------------------- | --------------- | | android | 36.1.2.0 | | Component.Android.NDK.R27C | 18.3.11407.131 | | Component.Android.SDK.MAUI | 18.3.11407.204 | | Component.Linux.CMake | 18.3.11407.204 | | Component.Linux.RemoteFileExplorer | 18.3.11407.204 | | Component.MDD.Linux | 18.3.11407.204 | | Component.Microsoft.NET.AppModernization | 18.3.11520.95 | | Component.Microsoft.VisualStudio.RazorExtension | 18.3.11407.204 | | Component.Microsoft.VisualStudio.Tools.Applications.amd64 | 17.0.36522.0 | | Component.Microsoft.VisualStudio.Web.AzureFunctions | 18.3.11407.204 | | Component.Microsoft.Web.LibraryManager | 18.3.11407.131 | | Component.Microsoft.Windows.DriverKit | 10.0.26100.16 | | Component.OpenJDK | 18.3.11407.204 | | Component.UnityEngine.x64 | 18.3.11407.204 | | Component.Unreal.Debugger | 18.3.11407.204 | | Component.Unreal.Ide | 18.3.11407.204 | | Component.VisualStudio.GitHub.Copilot | 18.3.11407.204 | | Component.VSInstallerProjects2022 | 3.0.0 | | Component.WixToolset.VisualStudioExtension.Dev17 | 1.0.0.22 | | Component.WixToolset.VisualStudioExtension.Schemas3 | 1.0.0.22 | | ComponentGroup.Microsoft.NET.AppModernization | 18.3.11407.131 | | ios | 26.2.10191 | | maccatalyst | 26.2.10191 | | maui.blazor | 10.0.20.7528 | | maui.core | 10.0.20.7528 | | maui.windows | 10.0.20.7528 | | Microsoft.Component.Azure.DataLake.Tools | 18.3.11407.204 | | Microsoft.Component.ClickOnce | 18.3.11407.204 | | Microsoft.Component.CodeAnalysis.SDK | 18.3.11407.204 | | Microsoft.Component.MSBuild | 18.3.11407.204 | | Microsoft.Component.NetFX.Native | 18.3.11407.204 | | Microsoft.Component.PythonTools | 18.3.11407.204 | | Microsoft.Component.PythonTools.Web | 18.3.11407.204 | | Microsoft.Component.VC.Runtime.UCRTSDK | 18.3.11407.204 | | Microsoft.ComponentGroup.Blend | 18.3.11407.131 | | Microsoft.ComponentGroup.ClickOnce.Publish | 18.3.11407.131 | | Microsoft.Net.Component.4.6.2.TargetingPack | 18.3.11407.204 | | Microsoft.Net.Component.4.6.TargetingPack | 18.3.11407.204 | | Microsoft.Net.Component.4.7.1.TargetingPack | 18.3.11407.204 | | Microsoft.Net.Component.4.7.2.TargetingPack | 18.3.11407.204 | | Microsoft.Net.Component.4.7.TargetingPack | 18.3.11407.204 | | Microsoft.Net.Component.4.8.1.SDK | 18.3.11407.204 | | Microsoft.Net.Component.4.8.1.TargetingPack | 18.3.11407.204 | | Microsoft.Net.Component.4.8.SDK | 18.3.11407.204 | | Microsoft.Net.Component.4.8.TargetingPack | 18.3.11407.204 | | Microsoft.Net.ComponentGroup.4.8.DeveloperTools | 18.3.11407.131 | | Microsoft.Net.ComponentGroup.DevelopmentPrerequisites | 18.3.11407.131 | | Microsoft.Net.ComponentGroup.TargetingPacks.Common | 18.3.11407.131 | | microsoft.net.runtime.android | 10.1.326.7603 | | microsoft.net.runtime.android.aot | 10.1.326.7603 | | microsoft.net.runtime.android.aot.net9 | 10.1.326.7603 | | microsoft.net.runtime.android.net9 | 10.1.326.7603 | | microsoft.net.runtime.ios | 10.1.326.7603 | | microsoft.net.runtime.ios.net9 | 10.1.326.7603 | | microsoft.net.runtime.maccatalyst | 10.1.326.7603 | | microsoft.net.runtime.maccatalyst.net9 | 10.1.326.7603 | | microsoft.net.runtime.mono.tooling | 10.1.326.7603 | | microsoft.net.runtime.mono.tooling.net9 | 10.1.326.7603 | | microsoft.net.sdk.emscripten | 10.1.326.7603 | | Microsoft.NetCore.Component.DevelopmentTools | 18.3.11407.204 | | Microsoft.NetCore.Component.Runtime.10.0 | 18.3.11505.172 | | Microsoft.NetCore.Component.Runtime.8.0 | 18.3.11505.172 | | Microsoft.NetCore.Component.SDK | 18.3.11505.172 | | Microsoft.NetCore.Component.Web | 18.3.11407.204 | | Microsoft.VisualStudio.Component.AppInsights.Tools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.AspNet | 18.3.11407.204 | | Microsoft.VisualStudio.Component.AspNet45 | 18.3.11407.204 | | Microsoft.VisualStudio.Component.ClassDesigner | 18.3.11407.204 | | Microsoft.VisualStudio.Component.CodeMap | 18.3.11407.204 | | Microsoft.VisualStudio.Component.CoreEditor | 18.3.11407.204 | | Microsoft.VisualStudio.Component.CppBuildInsights | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Debugger.JustInTime | 18.3.11407.204 | | Microsoft.VisualStudio.Component.DiagnosticTools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.DockerTools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.DotNetModelBuilder | 18.3.11407.204 | | Microsoft.VisualStudio.Component.DslTools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.EntityFramework | 18.3.11407.204 | | Microsoft.VisualStudio.Component.FSharp | 18.3.11407.204 | | Microsoft.VisualStudio.Component.FSharp.Desktop | 18.3.11407.204 | | Microsoft.VisualStudio.Component.FSharp.WebTemplates | 18.3.11407.204 | | Microsoft.VisualStudio.Component.GraphDocument | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Graphics | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Graphics.Tools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.HLSL | 18.3.11407.204 | | Microsoft.VisualStudio.Component.IISExpress | 18.3.11407.204 | | Microsoft.VisualStudio.Component.IntelliCode | 18.3.11407.204 | | Microsoft.VisualStudio.Component.IntelliTrace.FrontEnd | 18.3.11407.204 | | Microsoft.VisualStudio.Component.JavaScript.Diagnostics | 18.3.11407.204 | | Microsoft.VisualStudio.Component.JavaScript.TypeScript | 18.3.11407.204 | | Microsoft.VisualStudio.Component.LinqToSql | 18.3.11407.204 | | Microsoft.VisualStudio.Component.LiveUnitTesting | 18.3.11407.204 | | Microsoft.VisualStudio.Component.ManagedDesktop.Core | 18.3.11407.131 | | Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites | 18.3.11407.131 | | Microsoft.VisualStudio.Component.MSODBC.SQL | 18.3.11407.204 | | Microsoft.VisualStudio.Component.MSSQL.CMDLnUtils | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Node.Tools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.NuGet | 18.3.11407.204 | | Microsoft.VisualStudio.Component.NuGet.BuildTools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.PortableLibrary | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Roslyn.Compiler | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Roslyn.LanguageServices | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Sharepoint.Tools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.SQL.CLR | 18.3.11407.204 | | Microsoft.VisualStudio.Component.SQL.DataSources | 18.3.11407.204 | | Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime | 18.3.11407.204 | | Microsoft.VisualStudio.Component.SQL.SSDT | 18.3.11407.204 | | Microsoft.VisualStudio.Component.TeamOffice | 18.3.11407.204 | | Microsoft.VisualStudio.Component.TextTemplating | 18.3.11407.204 | | Microsoft.VisualStudio.Component.TypeScript.TSServer | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Unity | 18.3.11407.204 | | Microsoft.VisualStudio.Component.UWP.VC.ARM64 | 18.3.11407.204 | | Microsoft.VisualStudio.Component.UWP.VC.ARM64EC | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.14.29.16.11.ARM | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.14.29.16.11.ARM64 | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64 | 18.3.11415.102 | | Microsoft.VisualStudio.Component.VC.ASAN | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.ATL | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.ATL.ARM64 | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.ATL.Spectre | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.ATLMFC | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.ATLMFC.Spectre | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.CLI.Support | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.CMake.Project | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.CoreIde | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.DiagnosticTools | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Llvm.Clang | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.MFC.ARM64 | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.MFC.ARM64.Spectre | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Redist.14.Latest | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Redist.MSM | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Runtimes.ARM64EC.Spectre | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Tools.ARM64 | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Tools.ARM64EC | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VC.Tools.x86.x64 | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Vcpkg | 18.3.11407.204 | | Microsoft.VisualStudio.Component.VSSDK | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Wcf.Tooling | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Web | 18.3.11407.204 | | Microsoft.VisualStudio.Component.WebDeploy | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Windows10SDK | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Windows11SDK.26100 | 18.3.11505.172 | | Microsoft.VisualStudio.Component.Windows11Sdk.WindowsPerformanceToolkit | 18.3.11407.204 | | Microsoft.VisualStudio.Component.WindowsAppSdkSupport.CSharp | 18.3.11407.204 | | Microsoft.VisualStudio.Component.Workflow | 18.3.11407.204 | | Microsoft.VisualStudio.Component.WslDebugging | 18.3.11407.204 | | Microsoft.VisualStudio.ComponentGroup.ArchitectureTools.Native | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Azure.Prerequisites | 18.3.11407.204 | | Microsoft.VisualStudio.ComponentGroup.AzureFunctions | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Maui.All | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Maui.Android | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Maui.Blazor | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Maui.iOS | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Maui.MacCatalyst | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Maui.Shared | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Maui.Windows | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.UWP.NetCoreAndStandard | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.UWP.VC.v142 | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.VC.Tools.142.x86.x64 | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.VisualStudioExtension.Prerequisites | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Web | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.Web.CloudTools | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.TemplateEngine | 18.3.11407.131 | | Microsoft.VisualStudio.ComponentGroup.WindowsAppDevelopment.Prerequisites | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.Azure | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.CoreEditor | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.Data | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.DataScience | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.ManagedDesktop | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.ManagedGame | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.NativeCrossPlat | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.NativeDesktop | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.NativeGame | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.NativeMobile | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.NetCrossPlat | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.NetWeb | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.Node | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.Office | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.Python | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.Universal | 18.3.11407.131 | | Microsoft.VisualStudio.Workload.VisualStudioExtension | 18.3.11407.131 | | runtimes.ios | 10.1.326.7603 | | runtimes.ios.net9 | 10.1.326.7603 | | runtimes.maccatalyst | 10.1.326.7603 | | runtimes.maccatalyst.net9 | 10.1.326.7603 | | wasm.tools | 10.1.326.7603 | | ProBITools.MicrosoftAnalysisServicesModelingProjects2022 | 4.0.0 | | ProBITools.MicrosoftReportProjectsforVisualStudio2022 | 4.0.0 | | SSIS.MicrosoftDataToolsIntegrationServices | 2.1.2 | | VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects | 3.0.0 | | Windows Driver Kit Visual Studio Extension | 10.0.26100.16 | | Windows Software Development Kit | 10.1.26100.7705 | | WixToolset.WixToolsetVisualStudio2022Extension | 1.0.0.22 | #### Microsoft Visual C++ | Name | Architecture | Version | | -------------------------------------------- | ------------ | ----------- | | Microsoft Visual C++ 2013 Additional Runtime | x64 | 12.0.40660 | | Microsoft Visual C++ 2013 Minimum Runtime | x64 | 12.0.40660 | | Microsoft Visual C++ 2022 Additional Runtime | x64 | 14.50.35719 | | Microsoft Visual C++ 2022 Debug Runtime | x64 | 14.50.35719 | | Microsoft Visual C++ 2022 Minimum Runtime | x64 | 14.50.35719 | | Microsoft Visual C++ 2022 Additional Runtime | x86 | 14.50.35719 | | Microsoft Visual C++ 2022 Debug Runtime | x86 | 14.50.35719 | | Microsoft Visual C++ 2022 Minimum Runtime | x86 | 14.50.35719 | #### Installed Windows SDKs - 10.0.26100.0 ### .NET Core Tools - .NET Core SDK: 8.0.124, 8.0.206, 8.0.319, 8.0.418, 9.0.114, 9.0.205, 9.0.311, 10.0.102, 10.0.103 - .NET Framework: 4.8, 4.8.1 - Microsoft.AspNetCore.App: 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2, 10.0.3 - Microsoft.NETCore.App: 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2, 10.0.3 - Microsoft.WindowsDesktop.App: 8.0.6, 8.0.22, 8.0.24, 9.0.6, 9.0.13, 10.0.2, 10.0.3 - nbgv 3.9.50+6feeb89450 ### PowerShell Tools - PowerShell 7.4.13 #### Powershell Modules - Az: 14.6.0 - AWSPowershell: 5.0.165 - DockerMsftProvider: 1.0.0.8 - MarkdownPS: 1.10 - Microsoft.Graph: 2.35.1 - Pester: 3.4.0, 5.7.1 - PowerShellGet: 1.0.0.1, 2.2.5 - PSScriptAnalyzer: 1.24.0 - PSWindowsUpdate: 2.2.1.5 - SqlServer: 22.3.0 - VSSetup: 2.2.16 ### Android | Package Name | Version | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android Command Line Tools | 19.0 | | Android Emulator | 36.4.9 | | Android SDK Build-tools | 36.0.0 36.1.0
35.0.0 35.0.1
34.0.0 | | Android SDK Platforms | android-36.1 (rev 1)
android-36-ext19 (rev 1)
android-36-ext18 (rev 1)
android-36 (rev 2)
android-35-ext15 (rev 1)
android-35-ext14 (rev 1)
android-35 (rev 2)
android-34-ext8 (rev 1)
android-34-ext12 (rev 1)
android-34-ext11 (rev 1)
android-34-ext10 (rev 1)
android-34 (rev 3) | | Android SDK Platform-Tools | 36.0.2 | | Android Support Repository | 47.0.0 | | CMake | 3.30.5
3.31.5
4.1.2 | | Google Play services | 49 | | Google Repository | 58 | | NDK | 27.3.13750724
28.2.13676358
29.0.14206865 | #### Environment variables | Name | Value | | ----------------------- | ---------------------------------------- | | ANDROID_HOME | C:\Android\android-sdk | | ANDROID_NDK | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_NDK_HOME | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_NDK_LATEST_HOME | C:\Android\android-sdk\ndk\29.0.14206865 | | ANDROID_NDK_ROOT | C:\Android\android-sdk\ndk\27.3.13750724 | | ANDROID_SDK_ROOT | C:\Android\android-sdk | ================================================ FILE: images/windows/assets/post-gen/GenerateIISExpressCertificate.ps1 ================================================ $friendlyName = "IIS Express Development Certificate" $certStore = "Cert:\LocalMachine\My" $oldCert = Get-ChildItem $certStore | Where-Object FriendlyName -match $friendlyName if(-not $oldCert) { Write-Host "$friendlyName certificate not found" return } Write-Host "Removing $($oldCert.Thumbprint) certificate" Remove-Item -Path $oldCert.PSPath -Confirm:$false Write-Host "Creating $friendlyName certificate" $selfSignedCertParam = @{ Subject = "localhost" DnsName = "localhost" KeyAlgorithm = "RSA" KeyLength = 2048 NotBefore = (Get-Date) NotAfter = (Get-Date).AddYears(5) CertStoreLocation = $certStore FriendlyName = $friendlyName HashAlgorithm = "SHA256" KeyUsage = "DigitalSignature", "KeyEncipherment", "DataEncipherment" TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.1") } $cert = New-SelfSignedCertificate @selfSignedCertParam # The app ID is the IIS Express app ID $certThumbprint = $cert.Thumbprint $appId = "{214124cd-d05b-4309-9af9-9caa44b2b74a}" $startPort = 44300 $endPort = 44399 Write-Host "Binding ${certThumbprint} certificate using netsh port=${startPort}:${endPort} and appID=${appId}" $startPort..$endPort | ForEach-Object { $port = $_ cmd /c "netsh http delete sslcert ipport=0.0.0.0:$port > nul 2>&1" cmd /c "netsh http add sslcert ipport=0.0.0.0:$port certhash=$certThumbprint appid=$appId certstorename=MY > nul 2>&1" } ================================================ FILE: images/windows/assets/post-gen/InternetExplorerConfiguration.ps1 ================================================ # https://docs.microsoft.com/en-us/troubleshoot/browsers/enhanced-security-configuration-faq#how-to-disable-internet-explorer-esc-by-using-a-script # turn off the Internet Explorer Enhanced Security feature Rundll32 iesetup.dll, IEHardenLMSettings, 1, True Rundll32 iesetup.dll, IEHardenUser, 1, True Rundll32 iesetup.dll, IEHardenAdmin, 1, True $AdminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" $UserKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" Set-ItemProperty -Path $AdminKey -Name "IsInstalled" -Value 0 -Force Set-ItemProperty -Path $UserKey -Name "IsInstalled" -Value 0 -Force # restart Explorer process $ieProcess = Get-Process -Name Explorer -ErrorAction Ignore if ($ieProcess) { Stop-Process -Name Explorer -Force -ErrorAction Ignore } Write-Host "IE Enhanced Security Configuration (ESC) has been disabled." ================================================ FILE: images/windows/assets/post-gen/Msys2FirstLaunch.ps1 ================================================ # create user profile at the first launch $null = cmd /c "C:\msys64\usr\bin\bash.exe -leo pipefail -c 'echo $SHELL' 2>&1" ================================================ FILE: images/windows/assets/post-gen/VSConfiguration.ps1 ================================================ $vsInstallRoot = (Get-VisualStudioInstance).InstallationPath $devEnvPath = "$vsInstallRoot\Common7\IDE\devenv.exe" # Initialize Visual Studio Experimental Instance # The Out-Null cmdlet is required to ensure PowerShell waits until the '/ResetSettings' command fully completes. & "$devEnvPath" /RootSuffix Exp /ResetSettings General.vssettings /Command File.Exit | Out-Null cmd.exe /c "`"$devEnvPath`" /updateconfiguration" # # https://github.com/actions/runner-images/issues/5301 # $warmup_vdproj = $(Join-Path $PSScriptRoot 'warmup.vdproj') & "$devEnvPath" $warmup_vdproj /build Release | Out-Null ================================================ FILE: images/windows/assets/post-gen/warmup.vdproj ================================================ "DeployProject" { "VSVersion" = "3:800" "ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}" "IsWebType" = "8:FALSE" "ProjectName" = "8:Setup1" "LanguageId" = "3:1033" "CodePage" = "3:1252" "UILanguageId" = "3:1033" "SccProjectName" = "8:" "SccLocalPath" = "8:" "SccAuxPath" = "8:" "SccProvider" = "8:" "Hierarchy" { } "Configurations" { "Debug" { "DisplayName" = "8:Debug" "IsDebugOnly" = "11:TRUE" "IsReleaseOnly" = "11:FALSE" "OutputFilename" = "8:Debug\\Setup1.msi" "PackageFilesAs" = "3:2" "PackageFileSize" = "3:-2147483648" "CabType" = "3:1" "Compression" = "3:2" "SignOutput" = "11:FALSE" "CertificateFile" = "8:" "PrivateKeyFile" = "8:" "TimeStampServer" = "8:" "InstallerBootstrapper" = "3:2" "BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}" { "Enabled" = "11:FALSE" "PromptEnabled" = "11:TRUE" "PrerequisitesLocation" = "2:1" "Url" = "8:" "ComponentsUrl" = "8:" "Items" { "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2" { "Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)" "ProductCode" = "8:.NETFramework,Version=v4.7.2" } } } } "Release" { "DisplayName" = "8:Release" "IsDebugOnly" = "11:FALSE" "IsReleaseOnly" = "11:TRUE" "OutputFilename" = "8:Release\\Setup1.msi" "PackageFilesAs" = "3:2" "PackageFileSize" = "3:-2147483648" "CabType" = "3:1" "Compression" = "3:2" "SignOutput" = "11:FALSE" "CertificateFile" = "8:" "PrivateKeyFile" = "8:" "TimeStampServer" = "8:" "InstallerBootstrapper" = "3:2" } } "Deployable" { "CustomAction" { } "DefaultFeature" { "Name" = "8:DefaultFeature" "Title" = "8:" "Description" = "8:" } "ExternalPersistence" { "LaunchCondition" { } } "File" { } "FileType" { } "Folder" { "{1525181F-901A-416C-8A58-119130FE478E}:_2CFC2A48A3B644ED96FB521F59C388D4" { "Name" = "8:#1916" "AlwaysCreate" = "11:FALSE" "Condition" = "8:" "Transitive" = "11:FALSE" "Property" = "8:DesktopFolder" "Folders" { } } "{3C67513D-01DD-4637-8A68-80971EB9504F}:_3389E1C43BBB433484C8A1DBE5968F24" { "DefaultLocation" = "8:[ProgramFilesFolder][Manufacturer]\\[ProductName]" "Name" = "8:#1925" "AlwaysCreate" = "11:FALSE" "Condition" = "8:" "Transitive" = "11:FALSE" "Property" = "8:TARGETDIR" "Folders" { } } "{1525181F-901A-416C-8A58-119130FE478E}:_FA2F90B46134404A830C21AAA6741088" { "Name" = "8:#1919" "AlwaysCreate" = "11:FALSE" "Condition" = "8:" "Transitive" = "11:FALSE" "Property" = "8:ProgramMenuFolder" "Folders" { } } } "LaunchCondition" { } "Locator" { } "MsiBootstrapper" { "LangId" = "3:1033" "RequiresElevation" = "11:FALSE" } "Product" { "Name" = "8:Microsoft Visual Studio" "ProductName" = "8:Setup1" "ProductCode" = "8:{22F710BC-F7AA-4E2C-86A3-63647F82EF0D}" "PackageCode" = "8:{3ADCCB4E-232A-40DF-A810-672AA9AD4B72}" "UpgradeCode" = "8:{19174734-DD62-4C8E-9716-6DCFC3F06116}" "AspNetVersion" = "8:4.0.30319.0" "RestartWWWService" = "11:FALSE" "RemovePreviousVersions" = "11:FALSE" "DetectNewerInstalledVersion" = "11:TRUE" "InstallAllUsers" = "11:FALSE" "ProductVersion" = "8:1.0.0" "Manufacturer" = "8:Default Company Name" "ARPHELPTELEPHONE" = "8:" "ARPHELPLINK" = "8:" "Title" = "8:Setup1" "Subject" = "8:" "ARPCONTACT" = "8:Default Company Name" "Keywords" = "8:" "ARPCOMMENTS" = "8:" "ARPURLINFOABOUT" = "8:" "ARPPRODUCTICON" = "8:" "ARPIconIndex" = "3:0" "SearchPath" = "8:" "UseSystemSearchPath" = "11:TRUE" "TargetPlatform" = "3:0" "PreBuildEvent" = "8:" "PostBuildEvent" = "8:" "RunPostBuildEvent" = "3:0" } "Registry" { "HKLM" { "Keys" { "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_AE076FF564A049948DF2258C70AE5799" { "Name" = "8:Software" "Condition" = "8:" "AlwaysCreate" = "11:FALSE" "DeleteAtUninstall" = "11:FALSE" "Transitive" = "11:FALSE" "Keys" { "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_47FD8B4AFAAC41A48FAC411F8ABDEB59" { "Name" = "8:[Manufacturer]" "Condition" = "8:" "AlwaysCreate" = "11:FALSE" "DeleteAtUninstall" = "11:FALSE" "Transitive" = "11:FALSE" "Keys" { } "Values" { } } } "Values" { } } } } "HKCU" { "Keys" { "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_98C374DEA4C841988657D6A8CC3F6CC3" { "Name" = "8:Software" "Condition" = "8:" "AlwaysCreate" = "11:FALSE" "DeleteAtUninstall" = "11:FALSE" "Transitive" = "11:FALSE" "Keys" { "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_A31118797AC14549912315624636D7C5" { "Name" = "8:[Manufacturer]" "Condition" = "8:" "AlwaysCreate" = "11:FALSE" "DeleteAtUninstall" = "11:FALSE" "Transitive" = "11:FALSE" "Keys" { } "Values" { } } } "Values" { } } } } "HKCR" { "Keys" { } } "HKU" { "Keys" { } } "HKPU" { "Keys" { } } } "Sequences" { } "Shortcut" { } "UserInterface" { "{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_0F20B65AC09E4AA78D95E5A7A7B152ED" { "UseDynamicProperties" = "11:FALSE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdUserInterface.wim" } "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_1450B51044A346A59AD67962D438B359" { "Name" = "8:#1901" "Sequence" = "3:1" "Attributes" = "3:2" "Dialogs" { "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_056EB6D1A4954D7D8D59FCF53B8AF02E" { "Sequence" = "3:100" "DisplayName" = "8:Progress" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdProgressDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } "ShowProgress" { "Name" = "8:ShowProgress" "DisplayName" = "8:#1009" "Description" = "8:#1109" "Type" = "3:5" "ContextData" = "8:1;True=1;False=0" "Attributes" = "3:0" "Setting" = "3:0" "Value" = "3:1" "DefaultValue" = "3:1" "UsePlugInResources" = "11:TRUE" } } } } } "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_3F72D8AE479B4820864530F5C9607524" { "Name" = "8:#1900" "Sequence" = "3:1" "Attributes" = "3:1" "Dialogs" { "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_7DE3964E30704BA99E5B564A29CC3A74" { "Sequence" = "3:300" "DisplayName" = "8:Confirm Installation" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdConfirmDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } } } "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_B3DAD8BF301A400680846A2BB7451D0D" { "Sequence" = "3:200" "DisplayName" = "8:Installation Folder" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdFolderDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } "InstallAllUsersVisible" { "Name" = "8:InstallAllUsersVisible" "DisplayName" = "8:#1059" "Description" = "8:#1159" "Type" = "3:5" "ContextData" = "8:1;True=1;False=0" "Attributes" = "3:0" "Setting" = "3:0" "Value" = "3:1" "DefaultValue" = "3:1" "UsePlugInResources" = "11:TRUE" } } } "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_D2FF63E39B7649E6BB76320118D41624" { "Sequence" = "3:100" "DisplayName" = "8:Welcome" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdWelcomeDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } "CopyrightWarning" { "Name" = "8:CopyrightWarning" "DisplayName" = "8:#1002" "Description" = "8:#1102" "Type" = "3:3" "ContextData" = "8:" "Attributes" = "3:0" "Setting" = "3:1" "Value" = "8:#1202" "DefaultValue" = "8:#1202" "UsePlugInResources" = "11:TRUE" } "Welcome" { "Name" = "8:Welcome" "DisplayName" = "8:#1003" "Description" = "8:#1103" "Type" = "3:3" "ContextData" = "8:" "Attributes" = "3:0" "Setting" = "3:1" "Value" = "8:#1203" "DefaultValue" = "8:#1203" "UsePlugInResources" = "11:TRUE" } } } } } "{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_6069164ECEB944A3AF499EF7E4AF562E" { "UseDynamicProperties" = "11:FALSE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdBasicDialogs.wim" } "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_6E590DA0E3F44E7EB06E9B583A05C84F" { "Name" = "8:#1900" "Sequence" = "3:2" "Attributes" = "3:1" "Dialogs" { "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_38649E4FCD994315918CF9F68EEDFBD4" { "Sequence" = "3:200" "DisplayName" = "8:Installation Folder" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdAdminFolderDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } } } "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_883E4215F7404FD2B27299E3D09CA1EE" { "Sequence" = "3:300" "DisplayName" = "8:Confirm Installation" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdAdminConfirmDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } } } "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_99855627211D468E907FCF6B662C325C" { "Sequence" = "3:100" "DisplayName" = "8:Welcome" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdAdminWelcomeDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } "CopyrightWarning" { "Name" = "8:CopyrightWarning" "DisplayName" = "8:#1002" "Description" = "8:#1102" "Type" = "3:3" "ContextData" = "8:" "Attributes" = "3:0" "Setting" = "3:1" "Value" = "8:#1202" "DefaultValue" = "8:#1202" "UsePlugInResources" = "11:TRUE" } "Welcome" { "Name" = "8:Welcome" "DisplayName" = "8:#1003" "Description" = "8:#1103" "Type" = "3:3" "ContextData" = "8:" "Attributes" = "3:0" "Setting" = "3:1" "Value" = "8:#1203" "DefaultValue" = "8:#1203" "UsePlugInResources" = "11:TRUE" } } } } } "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_90155C3C794546069868B80B82B57EE9" { "Name" = "8:#1902" "Sequence" = "3:1" "Attributes" = "3:3" "Dialogs" { "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_4490713900D94D3B9375F45EE3575B13" { "Sequence" = "3:100" "DisplayName" = "8:Finished" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdFinishedDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } "UpdateText" { "Name" = "8:UpdateText" "DisplayName" = "8:#1058" "Description" = "8:#1158" "Type" = "3:15" "ContextData" = "8:" "Attributes" = "3:0" "Setting" = "3:1" "Value" = "8:#1258" "DefaultValue" = "8:#1258" "UsePlugInResources" = "11:TRUE" } } } } } "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_A7B6301BE8B0403C959609D5053EDF6E" { "Name" = "8:#1902" "Sequence" = "3:2" "Attributes" = "3:3" "Dialogs" { "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_D47DBC39DB33492AA6C089D6D13580FB" { "Sequence" = "3:100" "DisplayName" = "8:Finished" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdAdminFinishedDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } } } } } "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_BA6A3FEC255C49059A6576E2F533202F" { "Name" = "8:#1901" "Sequence" = "3:2" "Attributes" = "3:2" "Dialogs" { "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_A46B26FE90274EBB937E90E163295D0B" { "Sequence" = "3:100" "DisplayName" = "8:Progress" "UseDynamicProperties" = "11:TRUE" "IsDependency" = "11:FALSE" "SourcePath" = "8:\\VsdAdminProgressDlg.wid" "Properties" { "BannerBitmap" { "Name" = "8:BannerBitmap" "DisplayName" = "8:#1001" "Description" = "8:#1101" "Type" = "3:8" "ContextData" = "8:Bitmap" "Attributes" = "3:4" "Setting" = "3:1" "UsePlugInResources" = "11:TRUE" } "ShowProgress" { "Name" = "8:ShowProgress" "DisplayName" = "8:#1009" "Description" = "8:#1109" "Type" = "3:5" "ContextData" = "8:1;True=1;False=0" "Attributes" = "3:0" "Setting" = "3:0" "Value" = "3:1" "DefaultValue" = "3:1" "UsePlugInResources" = "11:TRUE" } } } } } } "MergeModule" { } "ProjectOutput" { } } } ================================================ FILE: images/windows/scripts/build/Configure-BaseImage.ps1 ================================================ ################################################################################ ## File: Configure-BaseImage.ps1 ## Desc: Prepare the base image for software installation ################################################################################ function Disable-InternetExplorerESC { $adminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" $userKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" Set-ItemProperty -Path $adminKey -Name "IsInstalled" -Value 0 -Force Set-ItemProperty -Path $userKey -Name "IsInstalled" -Value 0 -Force $ieProcess = Get-Process -Name Explorer -ErrorAction SilentlyContinue if ($ieProcess) { Stop-Process -Name Explorer -Force -ErrorAction Continue } Write-Host "IE Enhanced Security Configuration (ESC) has been disabled." } function Disable-InternetExplorerWelcomeScreen { $adminKey = "HKLM:\Software\Policies\Microsoft\Internet Explorer\Main" New-Item -Path $adminKey -Value 1 -Force Set-ItemProperty -Path $adminKey -Name "DisableFirstRunCustomize" -Value 1 -Force Write-Host "Disabled IE Welcome screen" } function Disable-UserAccessControl { Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "ConsentPromptBehaviorAdmin" -Value 00000000 -Force Write-Host "User Access Control (UAC) has been disabled." } function Disable-WindowsUpdate { $autoUpdatePath = "HKLM:SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" if (Test-Path -Path $autoUpdatePath) { Set-ItemProperty -Path $autoUpdatePath -Name NoAutoUpdate -Value 1 Write-Host "Disabled Windows Update" } else { Write-Host "Windows Update key does not exist" } } # Enable $ErrorActionPreference='Stop' for AllUsersAllHosts Add-Content -Path $profile.AllUsersAllHosts -Value '$ErrorActionPreference="Stop"' Write-Host "Disable Server Manager on Logon" Get-ScheduledTask -TaskName ServerManager | Disable-ScheduledTask Write-Host "Disable 'Allow your PC to be discoverable by other PCs' popup" New-Item -Path HKLM:\System\CurrentControlSet\Control\Network -Name NewNetworkWindowOff -Force Write-Host 'Disable Windows Update Medic Service' Set-ItemProperty -Path HKLM:\System\CurrentControlSet\Services\WaaSMedicSvc -Name Start -Value 4 -Force Write-Host "Disable Windows Update" Disable-WindowsUpdate Write-Host "Disable UAC" Disable-UserAccessControl Write-Host "Disable IE Welcome Screen" Disable-InternetExplorerWelcomeScreen Write-Host "Disable IE ESC" Disable-InternetExplorerESC Write-Host "Setting local execution policy" Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine -ErrorAction Continue | Out-Null Get-ExecutionPolicy -List Write-Host "Enable long path behavior" # See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#maximum-path-length-limitation Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1 # Expand disk size of OS drive $driveLetter = "C" $size = Get-PartitionSupportedSize -DriveLetter $driveLetter Resize-Partition -DriveLetter $driveLetter -Size $size.SizeMax Get-Volume | Select-Object DriveLetter, SizeRemaining, Size | Sort-Object DriveLetter ================================================ FILE: images/windows/scripts/build/Configure-DeveloperMode.ps1 ================================================ ################################################################################ ## File: Configure-DeveloperMode.ps1 ## Desc: Enables Developer Mode by toggling registry setting. Developer Mode is required to enable certain tools (e.g. WinAppDriver). ################################################################################ # Create AppModelUnlock if it doesn't exist, required for enabling Developer Mode $registryKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" if (-not(Test-Path -Path $registryKeyPath)) { New-Item -Path $registryKeyPath -ItemType Directory -Force } # Add registry value to enable Developer Mode New-ItemProperty -Path $registryKeyPath -Name AllowDevelopmentWithoutDevLicense -PropertyType DWORD -Value 1 ================================================ FILE: images/windows/scripts/build/Configure-Diagnostics.ps1 ================================================ ################################################################################ ## File: Configure-Diagnostics.ps1 ## Desc: Disables Just-In-Time Debugger and Windows Error Reporting ################################################################################ Write-Host "Disable Just-In-Time Debugger" # Turn off Application Error Debugger New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug" -Name Debugger -Value "-" -Type String -Force New-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug" -Name Debugger -Value "-" -Type String -Force # Turn off the Debug dialog New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\.NETFramework" -Name DbgManagedDebugger -Value "-" -Type String -Force New-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\.NETFramework" -Name DbgManagedDebugger -Value "-" -Type String -Force # Disable the WER UI New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting" -Name DontShowUI -Value 1 -Type DWORD -Force # Send all reports to the user's queue New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting" -Name ForceQueue -Value 1 -Type DWORD -Force # Default consent choice 1 - Always ask (default) New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\Consent" -Name DefaultConsent -Value 1 -Type DWORD -Force ================================================ FILE: images/windows/scripts/build/Configure-DotnetSecureChannel.ps1 ================================================ ################################################################################ ## File: Configure-DotnetSecureChannel.ps1 ## Desc: Configure .NET to use TLS 1.2 ################################################################################ $registryPath = "HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" $name = "SchUseStrongCrypto" $value = "1" if (Test-Path $registryPath) { Set-ItemProperty -Path $registryPath -Name $name -Value $value -Type DWORD } $registryPath = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319" if (Test-Path $registryPath) { Set-ItemProperty -Path $registryPath -Name $name -Value $value -Type DWORD } ================================================ FILE: images/windows/scripts/build/Configure-DynamicPort.ps1 ================================================ ################################################################################ ## File: Configure-DynamicPort.ps1 ## Desc: Configure dynamic port range for TCP and UDP to start at port 49152 ## and to end at the 65536 (16384 ports) ################################################################################ # https://support.microsoft.com/en-us/help/929851/the-default-dynamic-port-range-for-tcp-ip-has-changed-in-windows-vista # The new default start port is 49152, and the new default end port is 65535. # Default port configuration was changed during image generation by Visual Studio Enterprise Installer to: # Protocol tcp Dynamic Port Range # --------------------------------- # Start Port : 1024 # Number of Ports : 64511 Write-Host "Set the dynamic port range to start at port 49152 and to end at the 65536 (16384 ports)" foreach ($ipVersion in @("ipv4", "ipv6")) { foreach ($protocol in @("tcp", "udp")) { $command = "netsh int $ipVersion set dynamicport $protocol start=49152 num=16384" Invoke-Expression $command | Out-Null if ($LASTEXITCODE -ne 0) { Write-Host "Failed to set dynamic port range for $ipVersion $protocol" exit $LASTEXITCODE } } } Invoke-PesterTests -TestFile "WindowsFeatures" -TestName "DynamicPorts" ================================================ FILE: images/windows/scripts/build/Configure-GDIProcessHandleQuota.ps1 ================================================ ################################################################################ ## File: Configure-GDIProcessHandleQuota.ps1 ## Desc: Set the GDIProcessHandleQuota value to 20000 ################################################################################ # https://docs.microsoft.com/en-us/windows/win32/sysinfo/gdi-objects # This value can be set to a number between 256 and 65,536 $defaultValue = 20000 Write-Host "Set the GDIProcessHandleQuota value to $defaultValue" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" -Name GDIProcessHandleQuota -Value $defaultValue Set-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\Windows" -Name GDIProcessHandleQuota -Value $defaultValue Invoke-PesterTests -TestFile "WindowsFeatures" -TestName "GDIProcessHandleQuota" ================================================ FILE: images/windows/scripts/build/Configure-ImageDataFile.ps1 ================================================ ################################################################################ ## File: Configure-ImageDataFile.ps1 ## Desc: Creates a JSON file with information about the image ################################################################################ $os = Get-CimInstance -ClassName Win32_OperatingSystem $caption = $os.Caption $osName = $caption.Substring(0, $caption.LastIndexOf(" ")) $osEdition = $caption.Substring($caption.LastIndexOf(" ") + 1) $osVersion = $os.Version $imageVersion = $env:IMAGE_VERSION $imageVersionComponents = $imageVersion.Split('.') $imageMajorVersion = $imageVersionComponents[0] $imageMinorVersion = $imageVersionComponents[1] $imageDataFile = $env:IMAGEDATA_FILE $githubUrl = "https://github.com/actions/runner-images/blob" if ((Test-IsWin25) -and $env:INSTALL_VS_2026) { $imageLabel = "windows-2025-vs2026" $softwareUrl = "${githubUrl}/win25-vs2026/$imageMajorVersion.$imageMinorVersion/images/windows/Windows2025-VS2026-Readme.md" $releaseUrl = "https://github.com/actions/runner-images/releases/tag/win25-vs2026%2F$imageMajorVersion.$imageMinorVersion" } elseif (Test-IsWin25) { $imageLabel = "windows-2025" $softwareUrl = "${githubUrl}/win25/$imageMajorVersion.$imageMinorVersion/images/windows/Windows2025-Readme.md" $releaseUrl = "https://github.com/actions/runner-images/releases/tag/win25%2F$imageMajorVersion.$imageMinorVersion" } elseif (Test-IsWin22) { $imageLabel = "windows-2022" $softwareUrl = "${githubUrl}/win22/$imageMajorVersion.$imageMinorVersion/images/windows/Windows2022-Readme.md" $releaseUrl = "https://github.com/actions/runner-images/releases/tag/win22%2F$imageMajorVersion.$imageMinorVersion" } else { throw "Invalid platform version is found. Either Windows Server 2022 or 2025 are required" } $json = @" [ { "group": "Operating System", "detail": "${osName}\n${osVersion}\n${osEdition}" }, { "group": "Runner Image", "detail": "Image: ${imageLabel}\nVersion: ${imageVersion}\nIncluded Software: ${softwareUrl}\nImage Release: ${releaseUrl}" } ] "@ $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllText($imageDataFile, $json, $Utf8NoBomEncoding) ================================================ FILE: images/windows/scripts/build/Configure-PowerShell.ps1 ================================================ ################################################################################ ## File: Configure-Powershell.ps1 ## Desc: Manage PowerShell configuration ################################################################################ #region System Write-Host "Setup PowerShellGet" Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force # Specifies the installation policy Set-PSRepository -InstallationPolicy Trusted -Name PSGallery Write-Host 'Warmup PSModuleAnalysisCachePath (speedup first powershell invocation by 20s)' $PSModuleAnalysisCachePath = 'C:\PSModuleAnalysisCachePath\ModuleAnalysisCache' [Environment]::SetEnvironmentVariable('PSModuleAnalysisCachePath', $PSModuleAnalysisCachePath, "Machine") # make variable to be available in the current session ${env:PSModuleAnalysisCachePath} = $PSModuleAnalysisCachePath New-Item -Path $PSModuleAnalysisCachePath -ItemType 'File' -Force | Out-Null #endregion #region User (current user, image generation only) if (-not (Test-Path $profile)) { New-Item $profile -ItemType File -Force } @" if ( -not(Get-Module -ListAvailable -Name PowerHTML)) { Install-Module PowerHTML -Scope CurrentUser } if ( -not(Get-Module -Name PowerHTML)) { Import-Module PowerHTML } "@ | Add-Content -Path $profile -Force #endregion ================================================ FILE: images/windows/scripts/build/Configure-Shell.ps1 ================================================ # Create shells folder $shellPath = "C:\shells" New-Item -Path $shellPath -ItemType Directory | Out-Null # add a wrapper for C:\msys64\usr\bin\bash.exe @' @echo off setlocal IF NOT DEFINED MSYS2_PATH_TYPE set MSYS2_PATH_TYPE=strict IF NOT DEFINED MSYSTEM set MSYSTEM=mingw64 set CHERE_INVOKING=1 C:\msys64\usr\bin\bash.exe -leo pipefail %* '@ | Out-File -FilePath "$shellPath\msys2bash.cmd" -Encoding ascii # gitbash <--> C:\Program Files\Git\bin\bash.exe New-Item -ItemType SymbolicLink -Path "$shellPath\gitbash.exe" -Target "$env:ProgramFiles\Git\bin\bash.exe" | Out-Null # wslbash <--> C:\Windows\System32\bash.exe New-Item -ItemType SymbolicLink -Path "$shellPath\wslbash.exe" -Target "$env:SystemRoot\System32\bash.exe" | Out-Null ================================================ FILE: images/windows/scripts/build/Configure-System.ps1 ================================================ ################################################################################ ## File: Configure-System.ps1 ## Desc: Applies various configuration settings to the final image ################################################################################ # Set default version to 1 for WSL (aka LXSS - Linux Subsystem) # The value should be set in the default user registry hive # https://github.com/actions/runner-images/issues/5760 if (Test-IsWin22) { Write-Host "Setting WSL default version to 1" Mount-RegistryHive ` -FileName "C:\Users\Default\NTUSER.DAT" ` -SubKey "HKLM\DEFAULT" # Create the key if it doesn't exist $keyPath = "DEFAULT\Software\Microsoft\Windows\CurrentVersion\Lxss" if (-not (Test-Path $keyPath)) { Write-Host "Creating $keyPath key" New-Item -Path (Join-Path "HKLM:\" $keyPath) -Force | Out-Null } # Set the DefaultVersion value to 1 $key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($keyPath, $true) $key.SetValue("DefaultVersion", "1", "DWord") $key.Handle.Close() [System.GC]::Collect() Dismount-RegistryHive "HKLM\DEFAULT" } # allow msi to write to temp folder # see https://github.com/actions/runner-images/issues/1704 cmd /c "icacls $env:SystemRoot\Temp /grant Users:f /t /c /q 2>&1" | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to grant Users full control of $env:SystemRoot\Temp" } # Enable inheritance for the entire C:\ drive if (Test-IsWin25) { cmd /c "icacls C:\ /inheritance:e /c /q 2>&1" | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to enable inheritance for C:\ drive" } } # Registry settings $registrySettings = @( @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"; Name = "AUOptions"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"; Name = "NoAutoUpdate"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"; Name = "DoNotConnectToWindowsUpdateInternetLocations"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"; Name = "DisableWindowsUpdateAccess"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Device Metadata"; Name = "PreventDeviceMetadataFromNetwork"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection"; Name = "AllowTelemetry"; Value = 0; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\SQMClient\Windows"; Name = "CEIPEnable"; Value = 0; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppCompat"; Name = "AITEnable"; Value = 0; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppCompat"; Name = "DisableUAR"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\Software\Policies\Microsoft\Windows\DataCollection"; Name = "AllowTelemetry"; Value = 0; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\DataCollection"; Name = "AllowTelemetry"; Value = 0; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\Maintenance"; Name = "MaintenanceDisabled"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\MRT"; Name = "DontOfferThroughWUAU"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\MRT"; Name = "DontReportInfectionInformation"; Value = 1; PropertyType = "DWORD" } @{Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search"; Name = "AllowCortana"; Value = 0; PropertyType = "DWORD" } @{Path = "HKLM:\SYSTEM\CurrentControlSet\Control"; Name = "ServicesPipeTimeout"; Value = 120000; PropertyType = "DWORD" } @{Path = "HKLM:\SYSTEM\CurrentControlSet\Control\WMI\AutoLogger\AutoLogger-Diagtrack-Listener"; Name = "Start"; Value = 0; PropertyType = "DWORD" } @{Path = "HKLM:\SYSTEM\CurrentControlSet\Control\WMI\AutoLogger\SQMLogger"; Name = "Start"; Value = 0; PropertyType = "DWORD" } ) $registrySettings | ForEach-Object { $regPath = $_.Path if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force -ErrorAction Ignore | Out-Null } New-ItemProperty @_ -Force -ErrorAction Ignore } | Out-Null # Disable Template Services / User Services added by Desktop Experience $regUserServicesToDisables = @( "HKLM:\SYSTEM\CurrentControlSet\Services\CDPUserSvc" "HKLM:\SYSTEM\CurrentControlSet\Services\OneSyncSvc" "HKLM:\SYSTEM\CurrentControlSet\Services\PimIndexMaintenanceSvc" "HKLM:\SYSTEM\CurrentControlSet\Services\UnistoreSvc" "HKLM:\SYSTEM\CurrentControlSet\Services\UserDataSvc" ) $regUserServicesToDisables | ForEach-Object { $regPath = $_ if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force -ErrorAction Ignore | Out-Null } New-ItemProperty -Path $regPath -Name "Start" -Value 4 -PropertyType DWORD -Force -ErrorAction Ignore New-ItemProperty -Path $regPath -Name "UserServiceFlags" -Value 0 -PropertyType DWORD -Force -ErrorAction Ignore } | Out-Null Write-Host 'Disable Windows Update Service' Set-ItemProperty -Path HKLM:\System\CurrentControlSet\Services\wuauserv -Name Start -Value 4 -Force # Disabled services $servicesToDisable = @( 'wuauserv' 'DiagTrack' 'dmwappushservice' $(if(-not (Test-IsWin25)){'PcaSvc'}) 'SysMain' 'gupdate' 'gupdatem' $(if(-not (Test-IsWin25)){'StorSvc'}) ) | Get-Service -ErrorAction SilentlyContinue Stop-Service $servicesToDisable $servicesToDisable.WaitForStatus('Stopped', "00:01:00") $servicesToDisable | Set-Service -StartupType Disabled # Disable scheduled tasks $allTasksInTaskPath = @( "\" "\Microsoft\Azure\Security\" "\Microsoft\VisualStudio\" "\Microsoft\VisualStudio\Updates\" "\Microsoft\Windows\Application Experience\" "\Microsoft\Windows\ApplicationData\" "\Microsoft\Windows\Autochk\" "\Microsoft\Windows\Chkdsk\" "\Microsoft\Windows\Customer Experience Improvement Program\" "\Microsoft\Windows\Data Integrity Scan\" "\Microsoft\Windows\Defrag\" "\Microsoft\Windows\Diagnosis\" "\Microsoft\Windows\DiskCleanup\" "\Microsoft\Windows\DiskDiagnostic\" "\Microsoft\Windows\Maintenance\" "\Microsoft\Windows\PI\" "\Microsoft\Windows\Power Efficiency Diagnostics\" "\Microsoft\Windows\Server Manager\" "\Microsoft\Windows\Speech\" "\Microsoft\Windows\UpdateOrchestrator\" "\Microsoft\Windows\Windows Error Reporting\" "\Microsoft\Windows\WindowsUpdate\" "\Microsoft\XblGameSave\" ) $allTasksInTaskPath | ForEach-Object { Get-ScheduledTask -TaskPath $_ -ErrorAction Ignore | Disable-ScheduledTask -ErrorAction Ignore } | Out-Null $disableTaskNames = @( @{TaskPath = "\Microsoft\Windows\.NET Framework\"; TaskName = ".NET Framework NGEN v4.0.30319" } @{TaskPath = "\Microsoft\Windows\.NET Framework\"; TaskName = ".NET Framework NGEN v4.0.30319 64" } @{TaskPath = "\Microsoft\Windows\AppID\"; TaskName = "SmartScreenSpecific" } ) $disableTaskNames | ForEach-Object { Disable-ScheduledTask @PSItem -ErrorAction Ignore } | Out-Null Write-Host "Configure-System.ps1 - completed" ================================================ FILE: images/windows/scripts/build/Configure-SystemEnvironment.ps1 ================================================ ################################################################################ ## File: Configure-SystemEnvironment.ps1 ## Desc: Configures system environment variables ################################################################################ $variables = @{ "ImageVersion" = $env:IMAGE_VERSION "ImageOS" = $env:IMAGE_OS "AGENT_TOOLSDIRECTORY" = $env:AGENT_TOOLSDIRECTORY "RUNNER_TOOL_CACHE" = $env:AGENT_TOOLSDIRECTORY } $variables.GetEnumerator() | ForEach-Object { [Environment]::SetEnvironmentVariable($_.Key, $_.Value, "Machine") } ================================================ FILE: images/windows/scripts/build/Configure-Toolset.ps1 ================================================ ################################################################################ ## File: Configure-Toolset.ps1 ## Team: CI-Build ## Desc: Configure Toolset ################################################################################ $toolEnvConfigs = @{ Python = @{ pathTemplates = @( "{0}" "{0}\Scripts" ) } go = @{ pathTemplates = @( "{0}\bin" ) envVarTemplate = "GOROOT_{0}_{1}_X64" } } $tools = Get-ToolsetContent ` | Select-Object -ExpandProperty toolcache ` | Where-Object { $toolEnvConfigs.Keys -contains $_.name } Write-Host "Configure toolset tools environment..." foreach ($tool in $tools) { $toolEnvConfig = $toolEnvConfigs[$tool.name] if (-not ([string]::IsNullOrEmpty($toolEnvConfig.envVarTemplate))) { foreach ($version in $tool.versions) { Write-Host "Set $($tool.name) $version environment variable..." $foundVersionArchPath = Get-TCToolVersionPath -Name $tool.name -Version $version -Arch $tool.arch $envName = $toolEnvConfig.envVarTemplate -f $version.Split(".") Write-Host "Set $envName to $foundVersionArchPath" [Environment]::SetEnvironmentVariable($envName, $foundVersionArchPath, "Machine") } } if (-not ([string]::IsNullOrEmpty($tool.default))) { Write-Host "Use $($tool.name) $($tool.default) as a system $($tool.name)..." $toolVersionPath = Get-TCToolVersionPath -Name $tool.name -Version $tool.default -Arch $tool.arch foreach ($template in $toolEnvConfig.pathTemplates) { $toolSystemPath = $template -f $toolVersionPath Write-Host "Add $toolSystemPath to system PATH..." Add-MachinePathItem -PathItem $toolSystemPath | Out-Null } if (-not ([string]::IsNullOrEmpty($tool.defaultVariable))) { Write-Host "Set $($tool.name) $($tool.default) $($tool.defaultVariable) environment variable..." [Environment]::SetEnvironmentVariable($tool.defaultVariable, $toolVersionPath, "Machine") } } } Invoke-PesterTests -TestFile "Toolset" ================================================ FILE: images/windows/scripts/build/Configure-User.ps1 ================================================ ################################################################################ ## File: Configure-User.ps1 ## Desc: Performs user part of warm up and moves data to C:\Users\Default ################################################################################ # # more: https://github.com/actions/runner-images-internal/issues/5320 # https://github.com/actions/runner-images/issues/5301#issuecomment-1648292990 # Write-Host "Warmup 'devenv.exe /updateconfiguration'" $vsInstallRoot = (Get-VisualStudioInstance).InstallationPath cmd.exe /c "`"$vsInstallRoot\Common7\IDE\devenv.exe`" /updateconfiguration" if ($LASTEXITCODE -ne 0) { throw "Failed to warmup 'devenv.exe /updateconfiguration'" } # we are fine if some file is locked and cannot be copied Copy-Item ${env:USERPROFILE}\AppData\Local\Microsoft\VisualStudio -Destination c:\users\default\AppData\Local\Microsoft\VisualStudio -Recurse -ErrorAction SilentlyContinue Mount-RegistryHive ` -FileName "C:\Users\Default\NTUSER.DAT" ` -SubKey "HKLM\DEFAULT" reg.exe copy HKCU\Software\Microsoft\VisualStudio HKLM\DEFAULT\Software\Microsoft\VisualStudio /s if ($LASTEXITCODE -ne 0) { throw "Failed to copy HKCU\Software\Microsoft\VisualStudio to HKLM\DEFAULT\Software\Microsoft\VisualStudio" } # TortoiseSVN not installed on Windows 2025 image due to Sysprep issues if (-not (Test-IsWin25)) { # disable TSVNCache.exe $registryKeyPath = 'HKCU:\Software\TortoiseSVN' if (-not(Test-Path -Path $registryKeyPath)) { New-Item -Path $registryKeyPath -ItemType Directory -Force } New-ItemProperty -Path $registryKeyPath -Name CacheType -PropertyType DWORD -Value 0 reg.exe copy HKCU\Software\TortoiseSVN HKLM\DEFAULT\Software\TortoiseSVN /s if ($LASTEXITCODE -ne 0) { throw "Failed to copy HKCU\Software\TortoiseSVN to HKLM\DEFAULT\Software\TortoiseSVN" } } # Accept by default "Send Diagnostic data to Microsoft" consent. if (Test-IsWin25) { $registryKeyPath = 'HKLM:\DEFAULT\SOFTWARE\Microsoft\Windows\CurrentVersion\Privacy' New-ItemProperty -Path $registryKeyPath -Name PrivacyConsentPresentationVersion -PropertyType DWORD -Value 3 | Out-Null New-ItemProperty -Path $registryKeyPath -Name PrivacyConsentSettingsValidMask -PropertyType DWORD -Value 4 | Out-Null New-ItemProperty -Path $registryKeyPath -Name PrivacyConsentSettingsVersion -PropertyType DWORD -Value 5 | Out-Null } Dismount-RegistryHive "HKLM\DEFAULT" # Remove the "installer" (var.install_user) user profile for Windows 2025 image if (Test-IsWin25) { Get-CimInstance -ClassName Win32_UserProfile | where-object {$_.LocalPath -match $env:INSTALL_USER} | Remove-CimInstance -Confirm:$false & net user $env:INSTALL_USER /DELETE } Write-Host "Configure-User.ps1 - completed" ================================================ FILE: images/windows/scripts/build/Configure-WindowsDefender.ps1 ================================================ ################################################################################ ## File: Configure-WindowsDefender.ps1 ## Desc: Disables Windows Defender ################################################################################ Write-Host "Disable Windows Defender..." $avPreference = @( @{DisableArchiveScanning = $true} @{DisableAutoExclusions = $true} @{DisableBehaviorMonitoring = $true} @{DisableBlockAtFirstSeen = $true} @{DisableCatchupFullScan = $true} @{DisableCatchupQuickScan = $true} @{DisableIntrusionPreventionSystem = $true} @{DisableIOAVProtection = $true} @{DisablePrivacyMode = $true} @{DisableScanningNetworkFiles = $true} @{DisableScriptScanning = $true} @{MAPSReporting = 0} @{PUAProtection = 0} @{SignatureDisableUpdateOnStartupWithoutEngine = $true} @{SubmitSamplesConsent = 2} @{ScanAvgCPULoadFactor = 5; ExclusionPath = @("D:\", "C:\")} @{DisableRealtimeMonitoring = $true} @{ScanScheduleDay = 8} ) $avPreference += @( @{EnableControlledFolderAccess = "Disable"} @{EnableNetworkProtection = "Disabled"} ) $avPreference | Foreach-Object { $avParams = $_ Set-MpPreference @avParams } # https://github.com/actions/runner-images/issues/4277 # https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/microsoft-defender-antivirus-compatibility?view=o365-worldwide $atpRegPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection' if (Test-Path $atpRegPath) { Write-Host "Set Microsoft Defender Antivirus to passive mode" Set-ItemProperty -Path $atpRegPath -Name 'ForceDefenderPassiveMode' -Value '1' -Type 'DWORD' } ================================================ FILE: images/windows/scripts/build/Install-AWSTools.ps1 ================================================ ################################################################################ ## File: Install-AWSTools.ps1 ## Desc: Install AWS tools: CLI, Session Manager Plugin, AWS SAM CLI ## Supply chain security: AWS CLI - managed by package manager, Session Manager Plugin for the AWS CLI - missing, AWS SAM CLI - checksum validation ################################################################################ # Install AWS CLI Install-ChocoPackage awscli # Install Session Manager Plugin for the AWS CLI Install-Binary ` -Url "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/windows/SessionManagerPluginSetup.exe" ` -InstallArgs ("/silent", "/install") ` -ExpectedSubject 'CN="Amazon Web Services, Inc.", OU=AWS Systems Manager, O="Amazon Web Services, Inc.", L=Seattle, S=Washington, C=US, SERIALNUMBER=4152954, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US' $env:Path = $env:Path + ";$env:ProgramFiles\Amazon\SessionManagerPlugin\bin" # Install AWS SAM CLI $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "awslabs/aws-sam-cli" ` -Version "latest" ` -UrlMatchPattern "AWS_SAM_CLI_64_PY3.msi" $externalHash = Get-ChecksumFromGithubRelease ` -Repo "awslabs/aws-sam-cli" ` -Version "latest" ` -FileName (Split-Path $downloadUrl -Leaf) ` -HashType "SHA256" Install-Binary ` -Url $downloadUrl ` -ExpectedSHA256Sum $externalHash Invoke-PesterTests -TestFile "CLI.Tools" -TestName "AWS" ================================================ FILE: images/windows/scripts/build/Install-ActionsCache.ps1 ================================================ ################################################################################ ## File: Install-ActionsCache.ps1 ## Desc: Downloads latest release from https://github.com/actions/action-versions ## Maintainer: #actions-runtime and @TingluoHuang ################################################################################ $actionArchiveCache = "C:\actionarchivecache\" if (-not (Test-Path $actionArchiveCache)) { Write-Host "Creating action archive cache folder" New-Item -ItemType Directory -Path $actionArchiveCache | Out-Null } $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "actions/action-versions" ` -Version "latest" ` -Asset "action-versions.zip" Write-Host "Download Latest action-versions archive from $downloadUrl" $actionVersionsArchivePath = Invoke-DownloadWithRetry $downloadUrl Write-Host "Expand action-versions archive" Expand-7ZipArchive -Path $actionVersionsArchivePath -DestinationPath $actionArchiveCache [Environment]::SetEnvironmentVariable("ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE", $actionArchiveCache, "Machine") Invoke-PesterTests -TestFile "ActionArchiveCache" ================================================ FILE: images/windows/scripts/build/Install-AliyunCli.ps1 ================================================ ################################################################################ ## File: Install-AliyunCli.ps1 ## Desc: Install Alibaba Cloud CLI ## Supply chain security: Alibaba Cloud CLI - checksum validation ################################################################################ Write-Host "Download Latest aliyun-cli archive" $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "aliyun/aliyun-cli" ` -Version "latest" ` -UrlMatchPattern "aliyun-cli-windows-*-amd64.zip" $packagePath = Invoke-DownloadWithRetry $downloadUrl #region Supply chain security - Alibaba Cloud CLI $packageName = Split-Path $downloadUrl -Leaf $externalHash = Get-ChecksumFromUrl -Type "SHA256" ` -Url ($downloadUrl -replace $packageName, "SHASUMS256.txt") ` -FileName $packageName Test-FileChecksum $packagePath -ExpectedSHA256Sum $externalHash #endregion Write-Host "Expand aliyun-cli archive" $aliyunPath = "C:\aliyun-cli" New-Item -Path $aliyunPath -ItemType Directory -Force Expand-7ZipArchive -Path $packagePath -DestinationPath $aliyunPath # Add aliyun-cli to path Add-MachinePathItem $aliyunPath Invoke-PesterTests -TestFile "CLI.Tools" -TestName "Aliyun CLI" ================================================ FILE: images/windows/scripts/build/Install-AndroidSDK.ps1 ================================================ ################################################################################ ## File: Install-AndroidSDK.ps1 ## Desc: Install and update Android SDK and tools ## Supply chain security: checksum validation ################################################################################ # Actual Android SDK installation directory $SDKInstallRoot = "C:\Program Files (x86)\Android\android-sdk" # Hardlink to the Android SDK installation directory with no spaces in the path. # ANDROID_NDK* env vars should not contain spaces, otherwise ndk-build.cmd gives an error # https://github.com/actions/runner-images/issues/1122 $SDKRootPath = "C:\Android\android-sdk" #region functions function Install-AndroidSDKPackages { <# .SYNOPSIS This function installs the specified Android SDK packages. .DESCRIPTION The Install-AndroidSDKPackages function takes an array of package names as a parameter and installs each of them using the sdkmanager.bat script. .PARAMETER Packages An array of package names in the format of SDK-style paths to be installed. .EXAMPLE Install-AndroidSDKPackages -Packages "platforms;android-29", "build-tools;29.0.2" This command installs the Android SDK Platform 29 and Build-Tools 29.0.2. #> Param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [AllowNull()] [string[]] $Packages ) # The sdkmanager.bat script is used to install Android SDK packages. $SDKManager = "$SDKRootPath\cmdline-tools\latest\bin\sdkmanager.bat" $errors = @() foreach ($package in $Packages) { & $SDKManager --install "$package" --sdk_root=$SDKRootPath if ($LASTEXITCODE -ne 0) { $errors += "Failed to install package $package with exit code $LASTEXITCODE" } } if ($errors.Count -gt 0) { throw $errors } } #endregion # get packages to install from the toolset $androidToolset = (Get-ToolsetContent).android # Newer version(s) require Java 11 by default # See https://github.com/actions/runner-images/issues/6960 $cmdlineToolsUrl = $androidToolset.commandline_tools_url $cmdlineToolsArchPath = Invoke-DownloadWithRetry $cmdlineToolsUrl Test-FileChecksum $cmdlineToolsArchPath -ExpectedSHA256Sum $androidToolset.hash $cmdlineToolsPath = Join-Path -Path $SDKInstallRoot -ChildPath "cmdline-tools" if (Test-Path "$cmdlineToolsPath\latest") { Write-Host "Removing previous cmdline-tools installation from Visual Studio workload" Remove-Item "$cmdlineToolsPath\latest" -Recurse -Force } Expand-7ZipArchive -Path $cmdlineToolsArchPath -DestinationPath $cmdlineToolsPath # cmdline tools should be installed in ${SDKInstallRoot}\cmdline-tools\latest\bin, but archive contains ${SDKInstallRoot}\cmdline-tools\bin # we need to create the proper folder structure Invoke-ScriptBlockWithRetry -Command { Rename-Item "${SDKInstallRoot}\cmdline-tools\cmdline-tools" "latest" -ErrorAction Stop } # Create hardlink at $SDKRootPath pointing to SDK installation directory in Program Files New-Item -Path (Split-Path $SDKRootPath -Parent) -ItemType Directory -Force New-Item -Path "$SDKRootPath" -ItemType SymbolicLink -Value "$SDKInstallRoot" # Install the standard Android SDK licenses. Currently, there isn't a better way to do this, # so we are base64-encoded a zip of the licenses directory from another installation. # To create this base64 string, create a zip file that contains nothing but a 'licenses' folder, # which folder contains the accepted license files found in 'C:\Program Files (x86)\Android\android-sdk\licenses'. # Then, run this in PowerShell: # $LicensesZipFileName = 'C:\Program Files (x86)\Android\android-sdk\Licenses.zip' # $base64Content = [Convert]::ToBase64String([IO.File]::ReadAllBytes($LicensesZipFileName)) # echo $base64Content # Another possible solution that works in powershell core: # Write-Ouptut "y" | $sdkmanager.bat $licenseContentBase64 = "UEsDBBQAAAAAAKNK11IAAAAAAAAAAAAAAAAJAAAAbGljZW5zZXMvUEsDBAoAAAAAAJ1K11K7n0IrKgAAACoAAAAhAAAAbGljZW5zZXMvYW5kcm9pZC1nb29nbGV0di1saWNlbnNlDQo2MDEwODViOTRjZDc3ZjBiNTRmZjg2NDA2OTU3MDk5ZWJlNzljNGQ2UEsDBAoAAAAAAKBK11LzQumJKgAAACoAAAAkAAAAbGljZW5zZXMvYW5kcm9pZC1zZGstYXJtLWRidC1saWNlbnNlDQo4NTlmMzE3Njk2ZjY3ZWYzZDdmMzBhNTBhNTU2MGU3ODM0YjQzOTAzUEsDBAoAAAAAAKFK11IKSOJFKgAAACoAAAAcAAAAbGljZW5zZXMvYW5kcm9pZC1zZGstbGljZW5zZQ0KMjQzMzNmOGE2M2I2ODI1ZWE5YzU1MTRmODNjMjgyOWIwMDRkMWZlZVBLAwQKAAAAAACiStdSec1a4SoAAAAqAAAAJAAAAGxpY2Vuc2VzL2FuZHJvaWQtc2RrLXByZXZpZXctbGljZW5zZQ0KODQ4MzFiOTQwOTY0NmE5MThlMzA1NzNiYWI0YzljOTEzNDZkOGFiZFBLAwQKAAAAAACiStdSk6vQKCoAAAAqAAAAGwAAAGxpY2Vuc2VzL2dvb2dsZS1nZGstbGljZW5zZQ0KMzNiNmEyYjY0NjA3ZjExYjc1OWYzMjBlZjlkZmY0YWU1YzQ3ZDk3YVBLAwQKAAAAAACiStdSrE3jESoAAAAqAAAAJAAAAGxpY2Vuc2VzL2ludGVsLWFuZHJvaWQtZXh0cmEtbGljZW5zZQ0KZDk3NWY3NTE2OThhNzdiNjYyZjEyNTRkZGJlZWQzOTAxZTk3NmY1YVBLAwQKAAAAAACjStdSkb1vWioAAAAqAAAAJgAAAGxpY2Vuc2VzL21pcHMtYW5kcm9pZC1zeXNpbWFnZS1saWNlbnNlDQplOWFjYWI1YjVmYmI1NjBhNzJjZmFlY2NlODk0Njg5NmZmNmFhYjlkUEsBAj8AFAAAAAAAo0rXUgAAAAAAAAAAAAAAAAkAJAAAAAAAAAAQAAAAAAAAAGxpY2Vuc2VzLwoAIAAAAAAAAQAYACIHOBcRaNcBIgc4FxFo1wHBTVQTEWjXAVBLAQI/AAoAAAAAAJ1K11K7n0IrKgAAACoAAAAhACQAAAAAAAAAIAAAACcAAABsaWNlbnNlcy9hbmRyb2lkLWdvb2dsZXR2LWxpY2Vuc2UKACAAAAAAAAEAGACUEFUTEWjXAZQQVRMRaNcB6XRUExFo1wFQSwECPwAKAAAAAACgStdS80LpiSoAAAAqAAAAJAAkAAAAAAAAACAAAACQAAAAbGljZW5zZXMvYW5kcm9pZC1zZGstYXJtLWRidC1saWNlbnNlCgAgAAAAAAABABgAsEM0FBFo1wGwQzQUEWjXAXb1MxQRaNcBUEsBAj8ACgAAAAAAoUrXUgpI4kUqAAAAKgAAABwAJAAAAAAAAAAgAAAA/AAAAGxpY2Vuc2VzL2FuZHJvaWQtc2RrLWxpY2Vuc2UKACAAAAAAAAEAGAAsMGUVEWjXASwwZRURaNcB5whlFRFo1wFQSwECPwAKAAAAAACiStdSec1a4SoAAAAqAAAAJAAkAAAAAAAAACAAAABgAQAAbGljZW5zZXMvYW5kcm9pZC1zZGstcHJldmlldy1saWNlbnNlCgAgAAAAAAABABgA7s3WFRFo1wHuzdYVEWjXAfGm1hURaNcBUEsBAj8ACgAAAAAAokrXUpOr0CgqAAAAKgAAABsAJAAAAAAAAAAgAAAAzAEAAGxpY2Vuc2VzL2dvb2dsZS1nZGstbGljZW5zZQoAIAAAAAAAAQAYAGRDRxYRaNcBZENHFhFo1wFfHEcWEWjXAVBLAQI/AAoAAAAAAKJK11KsTeMRKgAAACoAAAAkACQAAAAAAAAAIAAAAC8CAABsaWNlbnNlcy9pbnRlbC1hbmRyb2lkLWV4dHJhLWxpY2Vuc2UKACAAAAAAAAEAGADGsq0WEWjXAcayrRYRaNcBxrKtFhFo1wFQSwECPwAKAAAAAACjStdSkb1vWioAAAAqAAAAJgAkAAAAAAAAACAAAACbAgAAbGljZW5zZXMvbWlwcy1hbmRyb2lkLXN5c2ltYWdlLWxpY2Vuc2UKACAAAAAAAAEAGAA4LjgXEWjXATguOBcRaNcBIgc4FxFo1wFQSwUGAAAAAAgACACDAwAACQMAAAAA" $licenseContent = [System.Convert]::FromBase64String($licenseContentBase64) Set-Content -Path "$SDKInstallRoot\android-sdk-licenses.zip" -Value $licenseContent -Encoding Byte Expand-7ZipArchive -Path "$SDKInstallRoot\android-sdk-licenses.zip" -DestinationPath $SDKInstallRoot # Install platform-tools $platformToolsPath = Join-Path -Path $SDKInstallRoot -ChildPath "platform-tools" if (Test-Path $platformToolsPath) { Write-Host "Removing previous platform-tools installation from Visual Studio component" Remove-Item $platformToolsPath -Recurse -Force } Install-AndroidSDKPackages "platform-tools" # Get Android SDK packages list $androidPackages = Get-AndroidPackages -SDKRootPath $SDKRootPath # Install Android platform versions # that are greater than or equal to the minimum version Write-Host "Installing Android SDK packages for platforms..." $platformList = Get-AndroidPlatformPackages ` -SDKRootPath $SDKRootPath ` -minVersion $androidToolset.platform_min_version Install-AndroidSDKPackages $platformList # Install Android build-tools versions # that are greater than or equal to the minimum version Write-Host "Installing Android SDK packages for build tools..." $buildToolsList = Get-AndroidBuildToolPackages ` -SDKRootPath $SDKRootPath ` -minVersion $androidToolset.build_tools_min_version Install-AndroidSDKPackages $buildToolsList # Install Android Emulator Install-AndroidSDKPackages "emulator" # Install extras, add-ons and additional tools Write-Host "Installing Android SDK extras, add-ons and additional tools..." Install-AndroidSDKPackages ($androidToolset.extras | ForEach-Object { "extras;$_" }) Install-AndroidSDKPackages ($androidToolset.addons | ForEach-Object { "add-ons;$_" }) Install-AndroidSDKPackages ($androidToolset.additional_tools) # Install NDKs $ndkMajorVersions = $androidToolset.ndk.versions $ndkDefaultMajorVersion = $androidToolset.ndk.default $ndkLatestMajorVersion = $ndkMajorVersions | Select-Object -Last 1 $androidNDKs = @() foreach ($version in $ndkMajorVersions) { $packageNamePrefix = "ndk;$version" $package = $androidPackages | Where-Object { $_.StartsWith($packageNamePrefix) } | Sort-Object -Unique | Select-Object -Last 1 $androidNDKs += $package } Write-Host "Installing Android SDK packages for NDKs..." Install-AndroidSDKPackages $androidNDKs $ndkLatestVersion = ($androidNDKs | Where-Object { $_ -match "ndk;$ndkLatestMajorVersion" }).Split(';')[1] $ndkDefaultVersion = ($androidNDKs | Where-Object { $_ -match "ndk;$ndkDefaultMajorVersion" }).Split(';')[1] $ndkRoot = "$SDKRootPath\ndk\$ndkDefaultVersion" # Create env variables [Environment]::SetEnvironmentVariable("ANDROID_HOME", $SDKRootPath, "Machine") [Environment]::SetEnvironmentVariable("ANDROID_SDK_ROOT", $SDKRootPath, "Machine") # ANDROID_NDK, ANDROID_NDK_HOME, and ANDROID_NDK_ROOT variables should be set as many customer builds depend on them https://github.com/actions/runner-images/issues/5879 [Environment]::SetEnvironmentVariable("ANDROID_NDK", $ndkRoot, "Machine") [Environment]::SetEnvironmentVariable("ANDROID_NDK_HOME", $ndkRoot, "Machine") [Environment]::SetEnvironmentVariable("ANDROID_NDK_ROOT", $ndkRoot, "Machine") $ndkLatestPath = "$SDKRootPath\ndk\$ndkLatestVersion" if (Test-Path $ndkLatestPath) { [Environment]::SetEnvironmentVariable("ANDROID_NDK_LATEST_HOME", $ndkLatestPath, "Machine") } else { Write-Host "Latest NDK $ndkLatestVersion is not installed at path $ndkLatestPath" exit 1 } Invoke-PesterTests -TestFile "Android" ================================================ FILE: images/windows/scripts/build/Install-Apache.ps1 ================================================ ################################################################################ ## File: Install-Apache.ps1 ## Desc: Install Apache HTTP Server ################################################################################ # Stop w3svc service Stop-Service -Name w3svc # Install latest apache in chocolatey $installDir = "C:\tools" Install-ChocoPackage apache-httpd -ArgumentList "--force", "--params", "/installLocation:$installDir /port:80" # Stop and disable Apache service Stop-Service -Name Apache Set-Service -Name Apache -StartupType Disabled # Start w3svc service Start-Service -Name w3svc # Invoke Pester Tests Invoke-PesterTests -TestFile "Apache" ================================================ FILE: images/windows/scripts/build/Install-AzureCli.ps1 ================================================ ################################################################################ ## File: Install-AzureCli.ps1 ## Desc: Install and warm-up Azure CLI ################################################################################ Write-Host 'Install the latest Azure CLI release' $azureCliConfigPath = 'C:\azureCli' # Store azure-cli cache outside of the provisioning user's profile [Environment]::SetEnvironmentVariable('AZURE_CONFIG_DIR', $azureCliConfigPath, "Machine") $azureCliExtensionPath = Join-Path $env:CommonProgramFiles 'AzureCliExtensionDirectory' New-Item -ItemType 'Directory' -Path $azureCliExtensionPath | Out-Null [Environment]::SetEnvironmentVariable('AZURE_EXTENSION_DIR', $azureCliExtensionPath, "Machine") Install-Binary -Type MSI ` -Url 'https://aka.ms/installazurecliwindowsx64' ` -ExpectedSubject $(Get-MicrosoftPublisher) Update-Environment # Warm-up Azure CLI Write-Host "Warmup 'az'" az --help | Out-Null if ($LASTEXITCODE -ne 0) { throw "Command 'az --help' failed" } Invoke-PesterTests -TestFile 'CLI.Tools' -TestName 'Azure CLI' ================================================ FILE: images/windows/scripts/build/Install-AzureCosmosDbEmulator.ps1 ================================================ #################################################################################### ## File: Install-AzureCosmosDbEmulator.ps1 ## Desc: Install Azure CosmosDb Emulator #################################################################################### Install-Binary -Type MSI ` -Url "https://aka.ms/cosmosdb-emulator" Invoke-PesterTests -TestFile "Tools" -TestName "Azure Cosmos DB Emulator" ================================================ FILE: images/windows/scripts/build/Install-AzureDevOpsCli.ps1 ================================================ ################################################################################ ## File: Install-AzureDevOpsCli.ps1 ## Desc: Install Azure DevOps CLI ################################################################################ $azureDevOpsCliConfigPath = 'C:\azureDevOpsCli' # Store azure-devops-cli cache outside of the provisioning user's profile [Environment]::SetEnvironmentVariable('AZ_DEVOPS_GLOBAL_CONFIG_DIR', $azureDevOpsCliConfigPath, "Machine") $azureDevOpsCliCachePath = Join-Path $azureDevOpsCliConfigPath 'cache' New-Item -ItemType 'Directory' -Path $azureDevOpsCliCachePath | Out-Null [Environment]::SetEnvironmentVariable('AZURE_DEVOPS_CACHE_DIR', $azureDevOpsCliCachePath, "Machine") Update-Environment az extension add -n azure-devops if ($LASTEXITCODE -ne 0) { throw "Command 'az extension add -n azure-devops' failed" } # Warm-up Azure DevOps CLI Write-Host "Warmup 'az-devops'" @('devops', 'pipelines', 'boards', 'repos', 'artifacts') | ForEach-Object { az $_ --help if ($LASTEXITCODE -ne 0) { throw "Command 'az $_ --help' failed" } } # calling az devops login to force it to install `keyring`. Login will actually fail, redirecting error to null Write-Output 'fake token' | az devops login | Out-Null # calling az devops logout to be sure no credentials remain. az devops logout | out-null Invoke-PesterTests -TestFile 'CLI.Tools' -TestName 'Azure DevOps CLI' ================================================ FILE: images/windows/scripts/build/Install-Bazel.ps1 ================================================ ################################################################################ ## File: Install-Bazel.ps1 ## Desc: Install Bazel and Bazelisk (A user-friendly launcher for Bazel) ################################################################################ Install-ChocoPackage bazel npm install -g @bazel/bazelisk if ($LASTEXITCODE -ne 0) { throw "Command 'npm install -g @bazel/bazelisk' failed" } Invoke-PesterTests -TestFile "Tools" -TestName "Bazel" ================================================ FILE: images/windows/scripts/build/Install-Chocolatey.ps1 ================================================ ################################################################################ ## File: Install-Chocolatey.ps1 ## Desc: Install Chocolatey package manager ################################################################################ Write-Host "Set TLS1.2" [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor "Tls12" Write-Host "Install chocolatey" # Add to system PATH Add-MachinePathItem 'C:\ProgramData\Chocolatey\bin' Update-Environment # Verify and run choco installer $installScriptPath = Invoke-DownloadWithRetry 'https://chocolatey.org/install.ps1' Test-FileSignature -Path $installScriptPath -ExpectedSubject 'CN="Chocolatey Software, Inc", O="Chocolatey Software, Inc", L=Topeka, S=Kansas, C=US' Invoke-Expression $installScriptPath # Turn off confirmation choco feature enable -n allowGlobalConfirmation # Initialize environmental variable ChocolateyToolsLocation by invoking choco Get-ToolsLocation function Import-Module "$env:ChocolateyInstall\helpers\chocolateyInstaller.psm1" -Force Get-ToolsLocation ================================================ FILE: images/windows/scripts/build/Install-ChocolateyPackages.ps1 ================================================ ################################################################################ ## File: Install-ChocolateyPackages.ps1 ## Desc: Install common Chocolatey packages ################################################################################ $commonPackages = (Get-ToolsetContent).choco.common_packages foreach ($package in $commonPackages) { Install-ChocoPackage $package.name -Version $package.version -ArgumentList $package.args } Invoke-PesterTests -TestFile "ChocoPackages" ================================================ FILE: images/windows/scripts/build/Install-Chrome.ps1 ================================================ ################################################################################ ## File: Install-Chrome.ps1 ## Desc: Install Google Chrome browser and Chrome WebDriver ################################################################################ # Download and install latest Chrome browser Install-Binary ` -Url 'https://dl.google.com/tag/s/dl/chrome/install/googlechromestandaloneenterprise64.msi' ` -ExpectedSubject 'CN=Google LLC, O=Google LLC, L=Mountain View, S=California, C=US, SERIALNUMBER=3582691, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US' # Prepare firewall rules Write-Host "Adding the firewall rule for Google update blocking..." New-NetFirewallRule -DisplayName "BlockGoogleUpdate" -Direction Outbound -Action Block -Program "C:\Program Files (x86)\Google\Update\GoogleUpdate.exe" $googleServices = Get-Service -Name "GoogleUpdater*" Stop-Service $googleServices $googleServices.WaitForStatus('Stopped', "00:01:00") $googleServices | Set-Service -StartupType Disabled $regGoogleUpdatePath = "HKLM:\SOFTWARE\Policies\Google\Update" $regGoogleUpdateChrome = "HKLM:\SOFTWARE\Policies\Google\Chrome" ($regGoogleUpdatePath, $regGoogleUpdateChrome) | ForEach-Object { New-Item -Path $_ -Force } $regGoogleParameters = @( @{ Name = "AutoUpdateCheckPeriodMinutes"; Value = 00000000}, @{ Name = "UpdateDefault"; Value = 00000000 }, @{ Name = "DisableAutoUpdateChecksCheckboxValue"; Value = 00000001 }, @{ Name = "Update{8A69D345-D564-463C-AFF1-A69D9E530F96}"; Value = 00000000 }, @{ Path = $regGoogleUpdateChrome; Name = "DefaultBrowserSettingEnabled"; Value = 00000000 } ) $regGoogleParameters | ForEach-Object { $arguments = $_ if (-not ($arguments.Path)) { $arguments.Add("Path", $regGoogleUpdatePath) } $arguments.Add("Force", $true) New-ItemProperty @arguments } # Install Chrome WebDriver Write-Host "Install Chrome WebDriver..." $chromeDriverPath = "$($env:SystemDrive)\SeleniumWebDrivers\ChromeDriver" if (-not (Test-Path -Path $chromeDriverPath)) { New-Item -Path $chromeDriverPath -ItemType Directory -Force } Write-Host "Get the Chrome WebDriver download URL..." $registryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths" $chromePath = (Get-ItemProperty "$registryPath\chrome.exe").'(default)' [version] $chromeVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($chromePath).ProductVersion $chromeBuild = "$($chromeVersion.Major).$($chromeVersion.Minor).$($chromeVersion.Build)" $chromeDriverVersionsUrl = "https://googlechromelabs.github.io/chrome-for-testing/latest-patch-versions-per-build-with-downloads.json" Write-Host "Chrome version is $chromeVersion" $chromeDriverVersions = Invoke-RestMethod -Uri $chromeDriverVersionsUrl $chromeDriverVersion = $chromeDriverVersions.builds.$chromeBuild if (-not ($chromeDriverVersion)) { $availableVersions = $chromeDriverVersions.builds | Get-Member | Select-Object -ExpandProperty Name Write-Host "Available chromedriver builds are $availableVersions" throw "Can't determine chromedriver version that matches chrome build $chromeBuild" } $chromeDriverVersion.version | Out-File -FilePath "$chromeDriverPath\versioninfo.txt" -Force; Write-Host "Chrome WebDriver version to install is $($chromeDriverVersion.version)" $chromeDriverZipDownloadUrl = ($chromeDriverVersion.downloads.chromedriver | Where-Object platform -eq "win64").url Write-Host "Download Chrome WebDriver from $chromeDriverZipDownloadUrl..." $chromeDriverArchPath = Invoke-DownloadWithRetry $chromeDriverZipDownloadUrl Write-Host "Expand Chrome WebDriver archive (without using directory names)..." Expand-7ZipArchive -Path $chromeDriverArchPath -DestinationPath $chromeDriverPath -ExtractMethod "e" Write-Host "Setting the environment variables..." [Environment]::SetEnvironmentVariable("ChromeWebDriver", $chromeDriverPath, "Machine") Add-MachinePathItem $chromeDriverPath Update-Environment Invoke-PesterTests -TestFile "Browsers" -TestName "Chrome" ================================================ FILE: images/windows/scripts/build/Install-CodeQLBundle.ps1 ================================================ ################################################################################ ## File: Install-CodeQLBundle.ps1 ## Desc: Install the CodeQL CLI Bundle to the toolcache. ################################################################################ # Retrieve the latest major version of the CodeQL Action to use in the base URL for downloading the bundle. $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/github/codeql-action/releases" # Get the release tags starting with v[0-9] and sort them in descending order, then parse the first one to get the major version. $latestTag = $releases.tag_name | Where-Object { $_ -match '^v[0-9]' } | Sort-Object { [version]($_ -replace '^v','') } -Descending | Select-Object -First 1 if ([string]::IsNullOrEmpty($latestTag)) { Write-Error "Error: Unable to find the latest major version of the CodeQL Action." exit 1 } if ($latestTag -match '^v([0-9]+)') { $codeqlActionLatestMajorVersion = $matches[1] } else { Write-Error "Error: Unable to parse the major version from the latest tag." exit 1 } # Retrieve the CLI version of the latest CodeQL bundle. $defaults = (Invoke-RestMethod "https://raw.githubusercontent.com/github/codeql-action/v$($codeqlActionLatestMajorVersion)/src/defaults.json") $cliVersion = $defaults.cliVersion $tagName = "codeql-bundle-v" + $cliVersion Write-Host "Downloading CodeQL bundle $($cliVersion)..." # Note that this is the all-platforms CodeQL bundle, to support scenarios where customers run # different operating systems within containers. $codeQLBundlePath = Invoke-DownloadWithRetry "https://github.com/github/codeql-action/releases/download/$($tagName)/codeql-bundle-win64.tar.gz" $downloadDirectoryPath = (Get-Item $codeQLBundlePath).Directory.FullName $codeQLToolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath $cliVersion | Join-Path -ChildPath "x64" New-Item -Path $codeQLToolcachePath -ItemType Directory -Force | Out-Null Write-Host "Unpacking the downloaded CodeQL bundle archive..." Expand-7ZipArchive -Path $codeQLBundlePath -DestinationPath $downloadDirectoryPath $unGzipedCodeQLBundlePath = Join-Path $downloadDirectoryPath "codeql-bundle-win64.tar" Expand-7ZipArchive -Path $unGzipedCodeQLBundlePath -DestinationPath $codeQLToolcachePath Write-Host "CodeQL bundle at $($codeQLToolcachePath) contains the following directories:" Get-ChildItem -Path $codeQLToolcachePath -Depth 2 # Touch a file to indicate to the CodeQL Action that this bundle shipped with the toolcache. This is # to support overriding the CodeQL version specified in defaults.json on GitHub Enterprise. New-Item -ItemType file (Join-Path $codeQLToolcachePath -ChildPath "pinned-version") # Touch a file to indicate to the toolcache that setting up CodeQL is complete. New-Item -ItemType file "$codeQLToolcachePath.complete" # Test that the tools have been extracted successfully. Invoke-PesterTests -TestFile "Tools" -TestName "CodeQL Bundle" ================================================ FILE: images/windows/scripts/build/Install-DACFx.ps1 ================================================ #################################################################################### ## File: Install-DACFx.ps1 ## Desc: Install SQL Server® Data-Tier Application Framework (DacFx) for Windows #################################################################################### Install-Binary -Type MSI ` -Url 'https://aka.ms/dacfx-msi' ` -ExpectedSubject $(Get-MicrosoftPublisher) Invoke-PesterTests -TestFile "Tools" -TestName "DACFx" ================================================ FILE: images/windows/scripts/build/Install-Docker.ps1 ================================================ ################################################################################ ## File: Install-Docker.ps1 ## Desc: Install Docker. ## Must be an independent step because it requires a restart before we ## can continue. ################################################################################ Write-Host "Get latest Moby release" $toolsetVersion = (Get-ToolsetContent).docker.components.docker $mobyVersion = (Get-GithubReleasesByVersion -Repo "moby/moby" -Version "${toolsetVersion}").version $dockerceUrl = "https://download.docker.com/win/static/stable/x86_64/" $dockerceBinaries = Invoke-WebRequest -Uri $dockerceUrl -UseBasicParsing Write-Host "Check Moby version $mobyVersion" $mobyRelease = $dockerceBinaries.Links.href -match "${mobyVersion}\.zip" | Select-Object -Last 1 if (-not $mobyRelease) { Write-Host "Release not found for $mobyLatestRelease version" $versions = [regex]::Matches($dockerceBinaries.Links.href, "docker-(\d+\.\d+\.\d+)\.zip") | Sort-Object { [version] $_.Groups[1].Value } $mobyRelease = $versions | Select-Object -ExpandProperty Value -Last 1 Write-Host "Found $mobyRelease" } $mobyReleaseUrl = $dockerceUrl + $mobyRelease Write-Host "Install Moby $mobyRelease..." $mobyArchivePath = Invoke-DownloadWithRetry $mobyReleaseUrl Expand-Archive -Path $mobyArchivePath -DestinationPath $env:TEMP_DIR $dockerPath = "$env:TEMP_DIR\docker\docker.exe" $dockerdPath = "$env:TEMP_DIR\docker\dockerd.exe" Write-Host "Install Docker CE" $instScriptUrl = "https://raw.githubusercontent.com/microsoft/Windows-Containers/Main/helpful_tools/Install-DockerCE/install-docker-ce.ps1" $instScriptPath = Invoke-DownloadWithRetry $instScriptUrl & $instScriptPath -DockerPath $dockerPath -DockerDPath $dockerdPath if ($LastExitCode -ne 0) { Write-Host "Docker installation failed with exit code $LastExitCode" exit $exitCode } # Fix AZ CLI DOCKER_COMMAND_ERROR # cli.azure.cli.command_modules.acr.custom: Could not run 'docker.exe' command. # https://github.com/Azure/azure-cli/issues/18766 New-Item -ItemType SymbolicLink -Path "C:\Windows\SysWOW64\docker.exe" -Target "C:\Windows\System32\docker.exe" if (-not (Test-IsWin25)) { Write-Host "Download docker images" $dockerImages = (Get-ToolsetContent).docker.images foreach ($dockerImage in $dockerImages) { Write-Host "Pulling docker image $dockerImage ..." docker pull $dockerImage if (!$?) { throw "Docker pull failed with a non-zero exit code ($LastExitCode)" } } Invoke-PesterTests -TestFile "Docker" -TestName "DockerImages" } Invoke-PesterTests -TestFile "Docker" -TestName "Docker" ================================================ FILE: images/windows/scripts/build/Install-DockerCompose.ps1 ================================================ ################################################################################ ## File: Install-Docker-Compose.ps1 ## Desc: Install Docker Compose. ################################################################################ Write-Host "Install-Package Docker-Compose v2" $toolsetVersion = (Get-ToolsetContent).docker.components.compose $composeVersion = (Get-GithubReleasesByVersion -Repo "docker/compose" -Version "${toolsetVersion}").version $dockerComposev2Url = "https://github.com/docker/compose/releases/download/v${composeVersion}/docker-compose-windows-x86_64.exe" $cliPluginsDir = "C:\ProgramData\docker\cli-plugins" New-Item -Path $cliPluginsDir -ItemType Directory Invoke-DownloadWithRetry -Url $dockerComposev2Url -Path "$cliPluginsDir\docker-compose.exe" Invoke-PesterTests -TestFile "Docker" -TestName "DockerCompose" ================================================ FILE: images/windows/scripts/build/Install-DockerWinCred.ps1 ================================================ ################################################################################ ## File: Install-Docker-WinCred.ps1 ## Desc: Install Docker credential helper. ## Supply chain security: checksum validation ################################################################################ Write-Host "Install docker-wincred" $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "docker/docker-credential-helpers" ` -Version "latest" ` -UrlMatchPattern "docker-credential-wincred-*amd64.exe" $binaryPath = Invoke-DownloadWithRetry -Url $downloadUrl -Path "C:\Windows\System32\docker-credential-wincred.exe" #region Supply chain security $binaryName = Split-Path $downloadUrl -Leaf $externalHash = Get-ChecksumFromUrl -Type "SHA256" ` -Url ($downloadUrl -replace $binaryName, "checksums.txt") ` -FileName $binaryName Test-FileChecksum -Path $binaryPath -ExpectedSHA256Sum $externalHash #endregion Invoke-PesterTests -TestFile "Docker" -TestName "DockerWinCred" ================================================ FILE: images/windows/scripts/build/Install-DotnetSDK.ps1 ================================================ ################################################################################ ## File: Install-DotnetSDK.ps1 ## Desc: Install all released versions of the dotnet sdk and populate package ## cache. Should run after VS and Node ## Supply chain security: checksum validation ################################################################################ # Set environment variables [Environment]::SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0", "Machine") [Environment]::SetEnvironmentVariable("DOTNET_NOLOGO", "1", "Machine") [Environment]::SetEnvironmentVariable("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1", "Machine") [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor "Tls12" #region "Functions" function Get-SDKVersionsToInstall { param ( [Parameter(Mandatory)] [string] $DotnetVersion ) $releasesJsonUri = "https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/${DotnetVersion}/releases.json" $releasesData = (Invoke-DownloadWithRetry $releasesJsonUri) | Get-Item | Get-Content | ConvertFrom-Json # filtering out the preview/rc releases $releases = $releasesData.'releases' | Where-Object { !$_.'release-version'.Contains('-') } $sdks = @() foreach ($release in $releases) { $sdks += $release.'sdk' $sdks += $release.'sdks' } return $sdks.version ` | Sort-Object { [Version] $_ } -Unique ` | Group-Object { $_.Substring(0, $_.LastIndexOf('.') + 2) } ` | ForEach-Object { $_.Group[-1] } } function Invoke-DotnetWarmup { param ( [Parameter(Mandatory)] [string] $SDKVersion ) # warm up dotnet for first time experience $projectTypes = @('console', 'mstest', 'web', 'mvc', 'webapi') foreach ($template in $projectTypes) { $projectPath = Join-Path -Path "C:\temp" -ChildPath $template New-Item -Path $projectPath -Force -ItemType Directory Push-Location -Path $projectPath & "$env:ProgramFiles\dotnet\dotnet.exe" new globaljson --sdk-version "$SDKVersion" if ($LastExitCode -ne 0) { throw "Dotnet new globaljson failed with exit code $LastExitCode" } & "$env:ProgramFiles\dotnet\dotnet.exe" new $template if ($LastExitCode -ne 0) { throw "Dotnet new $template failed with exit code $LastExitCode" } Pop-Location Remove-Item $projectPath -Force -Recurse } } function Install-DotnetSDK { param ( [Parameter(Mandatory)] [string] $InstallScriptPath, [Parameter(Mandatory)] [Alias('Version')] [string] $SDKVersion, [Parameter(Mandatory)] [string] $DotnetVersion ) if (Test-Path -Path "C:\Program Files\dotnet\sdk\$SDKVersion") { Write-Host "Sdk version $SDKVersion already installed" return } Write-Host "Installing dotnet $SDKVersion" $zipPath = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName()) & $InstallScriptPath -Version $SDKVersion -InstallDir $(Join-Path -Path $env:ProgramFiles -ChildPath 'dotnet') -ZipPath $zipPath -KeepZip # Installer is PowerShell script that doesn't set exit code on failure # If installation failed, tests will fail anyway #region Supply chain security $releasesJsonUri = "https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/${DotnetVersion}/releases.json" $releasesData = (Invoke-DownloadWithRetry $releasesJsonUri) | Get-Item | Get-Content | ConvertFrom-Json $distributorFileHash = $releasesData.releases.sdks.Where({ $_.version -eq $SDKVersion }).files.Where({ $_.name -eq 'dotnet-sdk-win-x64.zip' }).hash Test-FileChecksum $zipPath -ExpectedSHA512Sum $distributorFileHash #endregion } #endregion $dotnetToolset = (Get-ToolsetContent).dotnet # Download installation script. $installScriptPath = Invoke-DownloadWithRetry -Url "https://dot.net/v1/dotnet-install.ps1" # Visual Studio 2022 pre-creates sdk-manifests/8.0.100 folder, causing dotnet-install to skip manifests creation # https://github.com/actions/runner-images/issues/11402 if ((Test-IsWin22) -or (Test-IsWin25)) { $sdkManifestPath = "C:\Program Files\dotnet\sdk-manifests\8.0.100" if (Test-Path $sdkManifestPath) { Move-Item -Path $sdkManifestPath -Destination $env:TEMP_DIR -ErrorAction Stop } } # Install and warm up dotnet foreach ($dotnetVersion in $dotnetToolset.versions) { $sdkVersionsToInstall = Get-SDKVersionsToInstall -DotnetVersion $dotnetVersion # Issue https://github.com/actions/runner-images/issues/13705 # Workaround for broken .NET SDK 10.0.103 - replace it with .NET SDK 10.0.102 $sdkVersionsToInstall = $sdkVersionsToInstall | ForEach-Object { if ($_ -eq "10.0.103") { Write-Host ".NET 10.0.103 detected, replacing with 10.0.102"; "10.0.102" } else { $_ } } foreach ($sdkVersion in $sdkVersionsToInstall) { Install-DotnetSDK -InstallScriptPath $installScriptPath -SDKVersion $sdkVersion -DotnetVersion $dotnetVersion if ($dotnetToolset.warmup) { Invoke-DotnetWarmup -SDKVersion $sdkVersion } } } # Replace manifests inside sdk-manifests/8.0.100 folder with ones from Visual Studio # https://github.com/actions/runner-images/issues/11402 if ((Test-IsWin22) -or (Test-IsWin25)) { if (Test-Path "${env:TEMP_DIR}\8.0.100") { Get-ChildItem -Path "${env:TEMP_DIR}\8.0.100" | ForEach-Object { Remove-Item -Path "$sdkManifestPath\$($_.BaseName)" -Recurse -Force | Out-Null Move-Item -Path $_.FullName -Destination $sdkManifestPath -Force -ErrorAction Stop } } } # Add dotnet to PATH Add-MachinePathItem "C:\Program Files\dotnet" # Remove NuGet Folder $nugetPath = "$env:APPDATA\NuGet" if (Test-Path $nugetPath) { Remove-Item -Path $nugetPath -Force -Recurse } # Generate and copy new NuGet.Config config dotnet nuget list source | Out-Null if ($LastExitCode -ne 0) { throw "Dotnet nuget list source failed with exit code $LastExitCode" } Copy-Item -Path $nugetPath -Destination "C:\Users\Default\AppData\Roaming" -Force -Recurse # Install dotnet tools Write-Host "Installing dotnet tools" Add-DefaultPathItem "%USERPROFILE%\.dotnet\tools" foreach ($dotnetTool in $dotnetToolset.tools) { dotnet tool install $($dotnetTool.name) --tool-path "C:\Users\Default\.dotnet\tools" --add-source "https://api.nuget.org/v3/index.json" | Out-Null if ($LastExitCode -ne 0) { throw "Dotnet tool install failed with exit code $LastExitCode" } } Invoke-PesterTests -TestFile "DotnetSDK" ================================================ FILE: images/windows/scripts/build/Install-EdgeDriver.ps1 ================================================ ################################################################################ ## File: Install-EdgeDriver.ps1 ## Desc: Install Edge WebDriver and configure Microsoft Edge ################################################################################ # Disable Edge auto-updates Rename-Item -Path "C:\Program Files (x86)\Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exe" -NewName "Disabled_MicrosoftEdgeUpdate.exe" -ErrorAction Stop Write-Host "Get the Microsoft Edge WebDriver version..." $edgeBinaryPath = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe").'(default)' [version] $edgeVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($edgeBinaryPath).ProductVersion $edgeDriverPath = "$($env:SystemDrive)\SeleniumWebDrivers\EdgeDriver" if (-not (Test-Path -Path $edgeDriverPath)) { New-Item -Path $edgeDriverPath -ItemType Directory -Force } $versionInfoUrl = "https://msedgedriver.microsoft.com/LATEST_RELEASE_$($edgeVersion.Major)_WINDOWS" $versionInfoFile = Invoke-DownloadWithRetry -Url $versionInfoUrl -Path "$edgeDriverPath\versioninfo.txt" $latestVersion = Get-Content -Path $versionInfoFile Write-Host "Download Microsoft Edge WebDriver..." $downloadUrl = "https://msedgedriver.microsoft.com/$latestVersion/edgedriver_win64.zip" $archivePath = Invoke-DownloadWithRetry $downloadUrl Write-Host "Expand Microsoft Edge WebDriver archive..." Expand-7ZipArchive -Path $archivePath -DestinationPath $edgeDriverPath #Validate the EdgeDriver signature Test-FileSignature -Path "$edgeDriverPath\msedgedriver.exe" -ExpectedSubject $(Get-MicrosoftPublisher) Write-Host "Setting the environment variables..." [Environment]::SetEnvironmentVariable("EdgeWebDriver", $EdgeDriverPath, "Machine") Add-MachinePathItem "$edgeDriverPath\" Invoke-PesterTests -TestFile "Browsers" -TestName "Edge" ================================================ FILE: images/windows/scripts/build/Install-Firefox.ps1 ================================================ ################################################################################ ## File: Install-Firefox.ps1 ## Desc: Install Mozilla Firefox browser and Gecko WebDriver ## Supply chain security: Firefox browser - checksum validation ################################################################################ # Install and configure Firefox browser Write-Host "Get the latest Firefox version..." $versionsManifest = Invoke-RestMethod "https://product-details.mozilla.org/1.0/firefox_versions.json" Write-Host "Install Firefox browser..." $installerUrl = "https://download.mozilla.org/?product=firefox-$($versionsManifest.LATEST_FIREFOX_VERSION)&os=win64&lang=en-US" $hashUrl = "https://archive.mozilla.org/pub/firefox/releases/$($versionsManifest.LATEST_FIREFOX_VERSION)/SHA256SUMS" $externalHash = Get-ChecksumFromUrl -Type "SHA256" ` -Url $hashUrl ` -FileName "win64/en-US/Firefox Setup*exe" Install-Binary -Type EXE ` -Url $installerUrl ` -InstallArgs @("/silent", "/install") ` -ExpectedSHA256Sum $externalHash Write-Host "Disable autoupdate..." $firefoxDirectoryPath = Join-Path $env:ProgramFiles "Mozilla Firefox" New-Item -path $firefoxDirectoryPath -Name 'mozilla.cfg' -Value '// pref("browser.shell.checkDefaultBrowser", false); pref("app.update.enabled", false);' -ItemType file -force $firefoxPreferencesFolder = Join-Path $firefoxDirectoryPath "defaults\pref" New-Item -path $firefoxPreferencesFolder -Name 'local-settings.js' -Value 'pref("general.config.obscure_value", 0); pref("general.config.filename", "mozilla.cfg");' -ItemType file -force # Download and install Gecko WebDriver Write-Host "Install Gecko WebDriver..." $geckoDriverPath = "$($env:SystemDrive)\SeleniumWebDrivers\GeckoDriver" if (-not (Test-Path -Path $geckoDriverPath)) { New-Item -Path $geckoDriverPath -ItemType Directory -Force } Write-Host "Get the Gecko WebDriver version..." $geckoDriverVersion = (Get-GithubReleasesByVersion -Repo "mozilla/geckodriver" -Version "latest").version $geckoDriverVersion | Out-File -FilePath "$geckoDriverPath\versioninfo.txt" -Force Write-Host "Download Gecko WebDriver WebDriver..." $geckoDriverDownloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "mozilla/geckodriver" ` -Version $geckoDriverVersion ` -UrlMatchPattern "geckodriver-*-win64.zip" $geckoDriverArchPath = Invoke-DownloadWithRetry $geckoDriverDownloadUrl Write-Host "Expand Gecko WebDriver archive..." Expand-7ZipArchive -Path $geckoDriverArchPath -DestinationPath $geckoDriverPath # Validate Gecko WebDriver signature Test-FileSignature -Path "$geckoDriverPath/geckodriver.exe" -ExpectedSubject 'CN=Mozilla Corporation, OU=Firefox Engineering Operations, O=Mozilla Corporation, L=San Francisco, S=California, C=US' Write-Host "Setting the environment variables..." Add-MachinePathItem -PathItem $geckoDriverPath [Environment]::SetEnvironmentVariable("GeckoWebDriver", $geckoDriverPath, "Machine") Invoke-PesterTests -TestFile "Browsers" -TestName "Firefox" ================================================ FILE: images/windows/scripts/build/Install-Git.ps1 ================================================ ################################################################################ ## File: Install-Git.ps1 ## Desc: Install Git for Windows ## Supply chain security: Git - checksum validation, Hub CLI - managed by package manager ################################################################################ # Install the latest version of Git for Windows $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "git-for-windows/git" ` -Version "latest" ` -UrlMatchPattern "Git-*-64-bit.exe" $externalHash = Get-ChecksumFromGithubRelease ` -Repo "git-for-windows/git" ` -Version "latest" ` -FileName (Split-Path $downloadUrl -Leaf) ` -HashType "SHA256" Install-Binary ` -Url $downloadUrl ` -InstallArgs @(` "/VERYSILENT", ` "/NORESTART", ` "/NOCANCEL", ` "/SP-", ` "/CLOSEAPPLICATIONS", ` "/RESTARTAPPLICATIONS", ` "/o:PathOption=CmdTools", ` "/o:BashTerminalOption=ConHost", ` "/o:EnableSymlinks=Enabled", ` "/COMPONENTS=gitlfs") ` -ExpectedSHA256Sum $externalHash Update-Environment git config --system --add safe.directory "*" if ($LASTEXITCODE -ne 0) { Write-Error "Failed to configure safe.directory for Git with exit code $LASTEXITCODE" } # Disable GCM machine-wide [Environment]::SetEnvironmentVariable("GCM_INTERACTIVE", "Never", "Machine") # Add to PATH Add-MachinePathItem "C:\Program Files\Git\bin" # Add well-known SSH host keys to ssh_known_hosts # Write content to the file used by OpenSSH and the version includes with Git # for Windows $windowsSSHKnownHosts = "C:\ProgramData\ssh\ssh_known_hosts" $gitSSHKnownHosts = "C:\Program Files\Git\etc\ssh\ssh_known_hosts" $sshKeyScan = "C:\Program Files\Git\usr\bin\ssh-keyscan" $githubHostKeys = &$sshKeyScan -t rsa,ecdsa,ed25519 github.com $azureHostKeys = &$sshKeyscan -t rsa ssh.dev.azure.com Set-Content -Encoding ASCII -Path $windowsSSHKnownHosts, $gitSSHKnownHosts -Value $githubHostKeys Add-Content -Encoding ASCII -Path $windowsSSHKnownHosts, $gitSSHKnownHosts -Value $azureHostKeys Invoke-PesterTests -TestFile "Git" ================================================ FILE: images/windows/scripts/build/Install-GitHub-CLI.ps1 ================================================ ################################################################################ ## File: Install-GitHub-CLI.ps1 ## Desc: Install GitHub CLI ## Supply chain security: GitHub CLI - checksum validation ################################################################################ Write-Host "Get the latest gh version..." $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "cli/cli" ` -Version "latest" ` -UrlMatchPattern "gh_*_windows_amd64.msi" $checksumsUrl = Resolve-GithubReleaseAssetUrl ` -Repo "cli/cli" ` -Version "latest" ` -UrlMatchPattern "gh_*_checksums.txt" $externalHash = Get-ChecksumFromUrl -Type "SHA256" ` -Url $checksumsUrl ` -FileName (Split-Path $downloadUrl -Leaf) Install-Binary ` -Url $downloadUrl ` -ExpectedSHA256Sum $externalHash Add-MachinePathItem "C:\Program Files (x86)\GitHub CLI" Invoke-PesterTests -TestFile "CLI.Tools" -TestName "GitHub CLI" ================================================ FILE: images/windows/scripts/build/Install-Haskell.ps1 ================================================ ################################################################################ ## File: Install-Haskell.ps1 ## Desc: Install Haskell for Windows ################################################################################ # install minimal ghcup, utilizing pre-installed msys2 at C:\msys64 Write-Host 'Installing ghcup...' $msysPath = "C:\msys64" $ghcupPrefix = "C:\" $cabalDir = "C:\cabal" $ghcupDownloadURL = "https://downloads.haskell.org/~ghcup/x86_64-mingw64-ghcup.exe" # If you want to install a specific version of ghcup, uncomment the following lines # $ghver = "0.1.19.4" # $ghcupDownloadURL = "https://downloads.haskell.org/~ghcup/${ghver}/x86_64-mingw64-ghcup-${ghver}.exe" # Other option is to download ghcup from GitHub releases: # https://github.com/haskell/ghcup-hs/releases/latest New-Item -Path "$ghcupPrefix\ghcup" -ItemType 'directory' -ErrorAction SilentlyContinue | Out-Null New-Item -Path "$ghcupPrefix\ghcup\bin" -ItemType 'directory' -ErrorAction SilentlyContinue | Out-Null Invoke-DownloadWithRetry -Url $ghcupDownloadURL -Path "$ghcupPrefix\ghcup\bin\ghcup.exe" [Environment]::SetEnvironmentVariable("GHCUP_INSTALL_BASE_PREFIX", $ghcupPrefix, "Machine") [Environment]::SetEnvironmentVariable("GHCUP_MSYS2", $msysPath, "Machine") [Environment]::SetEnvironmentVariable("CABAL_DIR", $cabalDir, "Machine") Add-MachinePathItem "$ghcupPrefix\ghcup\bin" Add-MachinePathItem "$cabalDir\bin" Update-Environment # Get 1 or 3 latest versions of GHC depending on the OS version If (Test-IsWin25) { $numberOfVersions = 1 } else { $numberOfVersions = 3 } $versions = ghcup list -t ghc -r | Where-Object { $_ -notlike "prerelease" } $versionsOutput = [version[]]($versions | ForEach-Object { $_.Split(' ')[1]; }) $latestMajorMinor = $versionsOutput | Group-Object { $_.ToString(2) } | Sort-Object { [Version] $_.Name } | Select-Object -last $numberOfVersions $versionsList = $latestMajorMinor | ForEach-Object { $_.Group | Select-Object -Last 1 } | Sort-Object # The latest version will be installed as a default foreach ($version in $versionsList) { Write-Host "Installing ghc $version..." ghcup install ghc $version if ($LastExitCode -ne 0) { throw "GHC installation failed with exit code $LastExitCode" } ghcup set ghc $version if ($LastExitCode -ne 0) { throw "Setting GHC version failed with exit code $LastExitCode" } } # Add default version of GHC to path $defaultGhcVersion = $versionsList | Select-Object -Last 1 ghcup set ghc $defaultGhcVersion if ($LastExitCode -ne 0) { throw "Setting default GHC version failed with exit code $LastExitCode" } Write-Host 'Installing cabal...' ghcup install cabal latest if ($LastExitCode -ne 0) { throw "Cabal installation failed with exit code $LastExitCode" } Invoke-PesterTests -TestFile 'Haskell' ================================================ FILE: images/windows/scripts/build/Install-IEWebDriver.ps1 ================================================ ################################################################################ ## File: Install-IEWebDriver.ps1 ## Desc: Install IE Web Driver ################################################################################ $seleniumMajorVersion = (Get-ToolsetContent).selenium.version $ieDriverUrl = Resolve-GithubReleaseAssetUrl ` -Repo "SeleniumHQ/selenium" ` -Version "$seleniumMajorVersion.*" ` -Asset "IEDriverServer_x64_*.zip" # Download IE selenium driver Write-Host "Selenium IEDriverServer download and install..." $driverZipFile = Invoke-DownloadWithRetry $ieDriverUrl $ieDriverPath = "C:\SeleniumWebDrivers\IEDriver" if (-not (Test-Path -Path $ieDriverPath)) { New-Item -Path $ieDriverPath -ItemType Directory -Force | Out-Null } Expand-7ZipArchive -Path $driverZipFile -DestinationPath $ieDriverPath Remove-Item $driverZipFile Write-Host "Get the IEDriver version..." (Get-Item "$ieDriverPath\IEDriverServer.exe").VersionInfo.FileVersion | Out-File -FilePath "$ieDriverPath\versioninfo.txt" Write-Host "Setting the IEWebDriver environment variables" [Environment]::SetEnvironmentVariable("IEWebDriver", $ieDriverPath, "Machine") Invoke-PesterTests -TestFile "Browsers" -TestName "Internet Explorer" ================================================ FILE: images/windows/scripts/build/Install-JavaTools.ps1 ================================================ ################################################################################ ## File: Install-JavaTools.ps1 ## Desc: Install various JDKs and java tools ## Supply chain security: JDK - checksum validation ################################################################################ function Set-JavaPath { param ( [string] $Version, [string] $Architecture = "x64", [switch] $Default ) $javaPathPattern = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath "Java_Temurin-Hotspot_jdk/${Version}*/${Architecture}" $javaPath = (Get-Item -Path $javaPathPattern).FullName if ([string]::IsNullOrEmpty($javaPath)) { Write-Host "Not found path to Java '${Version}'" exit 1 } Write-Host "Set 'JAVA_HOME_${Version}_X64' environmental variable as $javaPath" [Environment]::SetEnvironmentVariable("JAVA_HOME_${Version}_X64", $javaPath, "Machine") if ($Default) { # Clean up any other Java folders from PATH to make sure that they won't conflict with each other $currentPath = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") $pathSegments = $currentPath.Split(';') $newPathSegments = @() foreach ($pathSegment in $pathSegments) { if ($pathSegment -notlike '*java*') { $newPathSegments += $pathSegment } } $newPath = [string]::Join(';', $newPathSegments) $newPath = $javaPath + '\bin;' + $newPath Write-Host "Add $javaPath\bin to PATH" [Environment]::SetEnvironmentVariable("PATH", $newPath, "Machine") Write-Host "Set JAVA_HOME environmental variable as $javaPath" [Environment]::SetEnvironmentVariable("JAVA_HOME", $javaPath, "Machine") } } function Install-JavaJDK { param( [string] $JDKVersion, [string] $Architecture = "x64" ) # Get Java version from api $assetUrl = Invoke-RestMethod -Uri "https://api.adoptium.net/v3/assets/latest/${JDKVersion}/hotspot?architecture=${Architecture}&os=windows" -Headers @{"Accept" = "application/json"} $asset = $assetUrl | Where-Object { $_.binary.os -eq "windows" ` -and $_.binary.architecture -eq $Architecture ` -and $_.binary.image_type -eq "jdk" } # Download and extract java binaries to temporary folder $downloadUrl = $asset.binary.package.link $archivePath = Invoke-DownloadWithRetry $downloadUrl Test-FileChecksum $archivePath -ExpectedSHA256Sum $asset.binary.package.checksum # We have to replace '+' sign in the version to '-' due to the issue with incorrect path in Android builds https://github.com/actions/runner-images/issues/3014 $fullJavaVersion = $asset.version.semver -replace '\+', '-' # Remove 'LTS' suffix from the version if present $fullJavaVersion = $fullJavaVersion -replace '\.LTS$', '' # Create directories in toolcache path $javaToolcachePath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath "Java_Temurin-Hotspot_jdk" $javaVersionPath = Join-Path -Path $javaToolcachePath -ChildPath $fullJavaVersion $javaArchPath = Join-Path -Path $javaVersionPath -ChildPath $Architecture if (-not (Test-Path $javaToolcachePath)) { Write-Host "Creating Temurin-Hotspot toolcache folder" New-Item -ItemType Directory -Path $javaToolcachePath | Out-Null } Write-Host "Creating Java '${fullJavaVersion}' folder in '${javaVersionPath}'" New-Item -ItemType Directory -Path $javaVersionPath -Force | Out-Null # Complete the installation by extracting Java binaries to toolcache and creating the complete file Expand-7ZipArchive -Path $archivePath -DestinationPath $javaVersionPath Invoke-ScriptBlockWithRetry -Command { Get-ChildItem -Path $javaVersionPath | Rename-Item -NewName $javaArchPath -ErrorAction Stop } New-Item -ItemType File -Path $javaVersionPath -Name "$Architecture.complete" | Out-Null } $toolsetJava = (Get-ToolsetContent).java $defaultVersion = $toolsetJava.default $jdkVersionsToInstall = $toolsetJava.versions foreach ($jdkVersionToInstall in $jdkVersionsToInstall) { $isDefaultVersion = $jdkVersionToInstall -eq $defaultVersion Install-JavaJDK -JDKVersion $jdkVersionToInstall if ($isDefaultVersion) { Set-JavaPath -Version $jdkVersionToInstall -Default } else { Set-JavaPath -Version $jdkVersionToInstall } } # Install Java tools # Force chocolatey to ignore dependencies on Ant and Maven or else they will download the Oracle JDK Install-ChocoPackage ant -ArgumentList "--ignore-dependencies" $toolsetMavenVersion = (Get-ToolsetContent).maven.version $versionToInstall = Resolve-ChocoPackageVersion -PackageName "maven" -TargetVersion $toolsetMavenVersion Install-ChocoPackage maven -ArgumentList "--version=$versionToInstall" Install-ChocoPackage gradle # Add maven env variables to Machine [string] $m2Path = ([Environment]::GetEnvironmentVariable("PATH", "Machine")).Split(";") -match "maven" $m2RepoPath = 'C:\ProgramData\m2' New-Item -Path $m2RepoPath -ItemType Directory -Force | Out-Null [Environment]::SetEnvironmentVariable("M2", $m2Path, "Machine") [Environment]::SetEnvironmentVariable("M2_REPO", $m2RepoPath, "Machine") [Environment]::SetEnvironmentVariable("MAVEN_OPTS", "-Xms256m", "Machine") # Download cobertura jars $uri = 'https://repo1.maven.org/maven2/net/sourceforge/cobertura/cobertura/2.1.1/cobertura-2.1.1-bin.zip' $sha256sum = '79479DDE416B082F38ECD1F2F7C6DEBD4D0C2249AF80FD046D1CE05D628F2EC6' $coberturaPath = "C:\cobertura-2.1.1" $archivePath = Invoke-DownloadWithRetry $uri Test-FileChecksum $archivePath -ExpectedSHA256Sum $sha256sum Expand-7ZipArchive -Path $archivePath -DestinationPath "C:\" [Environment]::SetEnvironmentVariable("COBERTURA_HOME", $coberturaPath, "Machine") Invoke-PesterTests -TestFile "Java" ================================================ FILE: images/windows/scripts/build/Install-Kotlin.ps1 ================================================ ################################################################################ ## File: Install-Kotlin.ps1 ## Desc: Install Kotlin ## Supply chain security: Kotlin - checksum validation ################################################################################ # Install Kotlin $kotlinVersion = (Get-ToolsetContent).kotlin.version $kotlinDownloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "JetBrains/kotlin" ` -Version "$kotlinVersion" ` -Asset "kotlin-compiler-*.zip" $kotlinArchivePath = Invoke-DownloadWithRetry $kotlinDownloadUrl #region Supply chain security $externalHash = Get-Content $(Invoke-DownloadWithRetry "$kotlinDownloadUrl.sha256") Test-FileChecksum $kotlinArchivePath -ExpectedSHA256Sum $externalHash #endregion Write-Host "Expand Kotlin archive" $kotlinPath = "C:\tools" Expand-7ZipArchive -Path $kotlinArchivePath -DestinationPath $kotlinPath # Add to PATH Add-MachinePathItem "$kotlinPath\kotlinc\bin" Invoke-PesterTests -TestFile "Tools" -TestName "Kotlin" ================================================ FILE: images/windows/scripts/build/Install-KubernetesTools.ps1 ================================================ ################################################################################ ## File: Install-KubernetesTools.ps1 ## Desc: Install tools for K8s. ## Supply chain security: GitHub Kind - checksum validation, Kubectl, Helm, Minikube - by package manager ################################################################################ Write-Host "Install Kind" # Choco installation can't be used because it depends on docker-desktop $targetDir = "C:\ProgramData\kind" New-Item -Path $targetDir -ItemType Directory -Force | Out-Null $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "kubernetes-sigs/kind" ` -Version "latest" ` -UrlMatchPattern "kind-windows-amd64" $packagePath = Invoke-DownloadWithRetry -Url $downloadUrl -Path "$targetDir\kind.exe" #region Supply chain security - Kind $externalHash = Get-ChecksumFromUrl -Type "SHA256" ` -Url "$downloadUrl.sha256sum" ` -FileName (Split-Path $downloadUrl -Leaf) Test-FileChecksum $packagePath -ExpectedSHA256Sum $externalHash #endregion Add-MachinePathItem $targetDir Write-Host "Install Kubectl" Install-ChocoPackage kubernetes-cli Write-Host "Install Helm" Install-ChocoPackage kubernetes-helm Write-Host "Install Minikube" Install-ChocoPackage minikube Invoke-PesterTests -TestFile "Tools" -TestName "KubernetesTools" ================================================ FILE: images/windows/scripts/build/Install-LLVM.ps1 ================================================ ################################################################################ ## File: Install-LLVM.ps1 ## Desc: Install the latest stable version of llvm and clang compilers ################################################################################ $llvmVersion = (Get-ToolsetContent).llvm.version $latestChocoVersion = Resolve-ChocoPackageVersion -PackageName "llvm" -TargetVersion $llvmVersion Install-ChocoPackage llvm -ArgumentList '--version', $latestChocoVersion Invoke-PesterTests -TestFile "LLVM" ================================================ FILE: images/windows/scripts/build/Install-Mercurial.ps1 ================================================ ################################################################################ ## File: Install-Mercurial.ps1 ## Desc: Install Mercurial ################################################################################ Install-ChocoPackage hg -ArgumentList "--version", "6.3.1" Add-MachinePathItem "${env:ProgramFiles}\Mercurial\" Update-Environment Invoke-PesterTests -TestFile "Tools" -TestName "Mercurial" ================================================ FILE: images/windows/scripts/build/Install-Mingw64.ps1 ================================================ ################################################################################ ## File: Install-Mingw64.ps1 ## Desc: Install GNU tools for Windows ################################################################################ # Install version specified in the toolset $version = (Get-ToolsetContent).mingw.version $runtime = (Get-ToolsetContent).mingw.runtime $("mingw32", "mingw64") | ForEach-Object { if ($_ -eq "mingw32") { $arch = "i686" $threads = "posix" $exceptions = "dwarf" } elseif ($_ -eq "mingw64") { $arch = "x86_64" $threads = "posix" $exceptions = "seh" } else { throw "Unknown architecture $_" } $url = Resolve-GithubReleaseAssetUrl ` -Repo "niXman/mingw-builds-binaries" ` -Version "$version" ` -Asset "$arch-*-release-$threads-$exceptions-$runtime-*.7z" $packagePath = Invoke-DownloadWithRetry $url Expand-7ZipArchive -Path $packagePath -DestinationPath "C:\" # Make a copy of mingw-make.exe to make.exe, which is a more discoverable name # and so the same command line can be used on Windows as on macOS and Linux $path = "C:\$_\bin\mingw32-make.exe" | Get-Item Copy-Item -Path $path -Destination (Join-Path $path.Directory 'make.exe') } Add-MachinePathItem "C:\mingw64\bin" Invoke-PesterTests -TestFile "Tools" -TestName "Mingw64" ================================================ FILE: images/windows/scripts/build/Install-Miniconda.ps1 ================================================ ################################################################################ ## File: Install-Miniconda.ps1 ## Desc: Install the latest version of Miniconda and set $env:CONDA ## Supply chain security: checksum validation ################################################################################ $condaDestination = "C:\Miniconda" $installerName = "Miniconda3-latest-Windows-x86_64.exe" #region Supply chain security $distributorFileHash = $null $checksums = (ConvertFrom-HTML -Uri 'https://repo.anaconda.com/miniconda/').SelectNodes('//html/body/table/tr') foreach ($node in $checksums) { if ($node.ChildNodes[1].InnerText -eq $installerName) { $distributorFileHash = $node.ChildNodes[7].InnerText } } if ($null -eq $distributorFileHash) { throw "Unable to find checksum for $installerName in https://repo.anaconda.com/miniconda/" } #endregion Install-Binary ` -Url "https://repo.anaconda.com/miniconda/${installerName}" ` -InstallArgs @("/S", "/AddToPath=0", "/RegisterPython=0", "/D=$condaDestination") ` -ExpectedSHA256Sum $distributorFileHash [Environment]::SetEnvironmentVariable("CONDA", $condaDestination, "Machine") Invoke-PesterTests -TestFile "Miniconda" ================================================ FILE: images/windows/scripts/build/Install-MongoDB.ps1 ================================================ #################################################################################### ## File: Install-MongoDB.ps1 ## Desc: Install MongoDB #################################################################################### # Install mongodb package $toolsetContent = Get-ToolsetContent $toolsetVersion = $toolsetContent.mongodb.version $getMongoReleases = Invoke-WebRequest -Uri "mongodb.com/docs/v$toolsetVersion/release-notes/$toolsetVersion/" -UseBasicParsing $targetReleases = $getMongoReleases.Links.href | Where-Object { $_ -like "#$toolsetVersion*---*" } $minorVersions = @() foreach ($release in $targetReleases) { if ($release -notlike "*upcoming*") { $pattern = '\d+\.\d+\.\d+' $version = $release | Select-String -Pattern $pattern -AllMatches | ForEach-Object { $_.Matches } | ForEach-Object { $_.Value } $minorVersions += $version } } $latestVersion = $minorVersions[0] Install-Binary ` -Url "https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-$latestVersion-signed.msi" ` -ExtraInstallArgs @('TARGETDIR=C:\PROGRA~1\MongoDB ADDLOCAL=ALL') ` -ExpectedSubject 'CN="MONGODB, INC.", O="MONGODB, INC.", L=New York, S=New York, C=US' # Add mongodb to the PATH $mongoPath = (Get-CimInstance Win32_Service -Filter "Name LIKE 'mongodb'").PathName $mongoBin = Split-Path -Path $mongoPath.split('"')[1] Add-MachinePathItem "$mongoBin" # Wait for mongodb service running $mongodbService = Get-Service "mongodb" $mongodbService.WaitForStatus('Running', '00:01:00') # Stop and disable mongodb service Stop-Service $mongodbService $mongodbService | Set-Service -StartupType Disabled # Install mongodb shell for mongodb $mongoshVersion = (Get-GithubReleasesByVersion -Repo "mongodb-js/mongosh" -Version "latest").version $mongoshDownloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "mongodb-js/mongosh" ` -Version $mongoshVersion ` -UrlMatchPattern "mongosh-*-x64.msi" Install-Binary -Type MSI ` -Url $mongoshDownloadUrl ` -ExtraInstallArgs @('ALLUSERS=1') ` -ExpectedSubject 'CN="MongoDB, Inc.", O="MongoDB, Inc.", L=New York, S=New York, C=US' Invoke-PesterTests -TestFile "Databases" -TestName "MongoDB" ================================================ FILE: images/windows/scripts/build/Install-Msys2.ps1 ================================================ ################################################################################ ## File: Install-Msys2.ps1 ## Desc: Install Msys2 and 64 & 32 bit gcc, cmake, & llvm ################################################################################ # References # https://github.com/msys2/MINGW-packages/blob/master/azure-pipelines.yml # https://packages.msys2.org/group/ $logPrefix = "`n" + ("-" * 40) + "`n---" $origPath = $env:PATH function Install-Msys2 { # We can't use Resolve-GithubReleaseAssetUrl function here # because msys2-installer releases don't have a consistent versioning scheme $assets = (Invoke-RestMethod -Uri "https://api.github.com/repos/msys2/msys2-installer/releases/latest").assets $downloadUri = ($assets | Where-Object { $_.name -match "^msys2-x86_64" -and $_.name.EndsWith(".exe") }).browser_download_url $installerName = Split-Path $downloadUri -Leaf # Download the latest msys2 x86_64, filename includes release date Write-Host "Download msys2 installer $installerName" $installerPath = Invoke-DownloadWithRetry $downloadUri Write-Host "Starting msys2 installation" & $installerPath in --confirm-command --accept-messages --root C:/msys64 if ($LastExitCode -ne 0) { throw "MSYS2 installation failed with exit code $LastExitCode" } Remove-Item $installerPath } function Install-Msys2Packages { param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [string[]]$Packages ) if (-not $Packages) { return } Write-Host "$logPrefix Install msys2 packages" pacman.exe -S --noconfirm --needed --noprogressbar $Packages if ($LastExitCode -ne 0) { throw "MSYS2 packages installation failed with exit code $LastExitCode" } taskkill /f /fi "MODULES eq msys-2.0.dll" Write-Host "$logPrefix Remove p7zip/7z package due to conflicts" pacman.exe -R --noconfirm --noprogressbar p7zip if ($LastExitCode -ne 0) { throw "Removal of p7zip/7z package failed with exit code $LastExitCode" } } function Install-MingwPackages { param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [object[]] $Packages ) if (-not $Packages) { return } Write-Host "$logPrefix Install mingw packages" $archs = $Packages.arch foreach ($arch in $archs) { Write-Host "Installing $arch packages" $archPackages = $toolsetContent.mingw | Where-Object { $_.arch -eq $arch } $runtimePackages = $archPackages.runtime_packages.name | ForEach-Object { "${arch}-$_" } $additionalPackages = $archPackages.additional_packages | ForEach-Object { "${arch}-$_" } $packagesToInstall = $runtimePackages + $additionalPackages Write-Host "The following packages will be installed: $packagesToInstall" pacman.exe -S --noconfirm --needed --noprogressbar $packagesToInstall if ($LastExitCode -ne 0) { throw "Installation of $arch packages failed with exit code $LastExitCode" } } # clean all packages to decrease image size Write-Host "$logPrefix Clean packages" pacman.exe -Scc --noconfirm if ($LastExitCode -ne 0) { throw "Cleaning of packages failed with exit code $LastExitCode" } $pkgs = pacman.exe -Q if ($LastExitCode -ne 0) { throw "Listing of packages failed with exit code $LastExitCode" } foreach ($arch in $archs) { Write-Host "$logPrefix Installed $arch packages" $pkgs | Select-String -Pattern "^${arch}-" } } Install-Msys2 # Add msys2 bin tools folders to PATH temporary $env:PATH = "C:\msys64\mingw64\bin;C:\msys64\usr\bin;$origPath" Write-Host "$logPrefix pacman --noconfirm -Syyuu" pacman.exe -Syyuu --noconfirm if ($LastExitCode -ne 0) { throw "Updating of packages failed with exit code $LastExitCode" } taskkill /f /fi "MODULES eq msys-2.0.dll" Write-Host "$logPrefix pacman --noconfirm -Syuu (2nd pass)" pacman.exe -Syuu --noconfirm if ($LastExitCode -ne 0) { throw "Second pass updating of packages failed with exit code $LastExitCode" } taskkill /f /fi "MODULES eq msys-2.0.dll" $toolsetContent = (Get-ToolsetContent).MsysPackages Install-Msys2Packages -Packages $toolsetContent.msys2 Install-MingwPackages -Packages $toolsetContent.mingw $env:PATH = $origPath Write-Host "`nMSYS2 installation completed" Invoke-PesterTests -TestFile "MSYS2" ================================================ FILE: images/windows/scripts/build/Install-MysqlCli.ps1 ================================================ ################################################################################ ## File: Install-MysqlCli.ps1 ## Desc: Install Mysql CLI ## Supply chain security: checksum validation (visual c++ redistributable package) ################################################################################ # Installing visual c++ redistributable package. Install-Binary ` -Url 'https://download.microsoft.com/download/0/5/6/056dcda9-d667-4e27-8001-8a0c6971d6b1/vcredist_x64.exe' ` -InstallArgs @("/install", "/quiet", "/norestart") ` -ExpectedSHA256Sum '20E2645B7CD5873B1FA3462B99A665AC8D6E14AAE83DED9D875FEA35FFDD7D7E' # Downloading mysql [version] $mysqlVersion = (Get-ToolsetContent).mysql.version $mysqlVersionMajorMinor = $mysqlVersion.ToString(2) if ($mysqlVersion.Build -lt 0) { if ($mysqlVersionMajorMinor -eq "5.7") { $downloadsPageUrl = "https://downloads.mysql.com/archives/community/" } else { $downloadsPageUrl = "https://dev.mysql.com/downloads/mysql/${mysqlVersionMajorMinor}.html" } $mysqlVersion = Invoke-RestMethod -Uri $downloadsPageUrl -Headers @{ 'User-Agent' = 'curl/8.4.0' } ` | Select-String -Pattern "${mysqlVersionMajorMinor}\.\d+" ` | ForEach-Object { $_.Matches.Value } } $mysqlVersionFull = $mysqlVersion.ToString() $mysqlVersionUrl = "https://cdn.mysql.com/Downloads/MySQL-${mysqlVersionMajorMinor}/mysql-${mysqlVersionFull}-winx64.msi" Install-Binary ` -Url $mysqlVersionUrl ` -ExpectedSubject 'CN="Oracle America, Inc.", O="Oracle America, Inc.", L=Redwood City, S=California, C=US, SERIALNUMBER=2101822, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US' # Adding mysql in system environment path $mysqlPath = $(Get-ChildItem -Path "C:\PROGRA~1\MySQL" -Directory)[0].FullName Add-MachinePathItem "${mysqlPath}\bin" Invoke-PesterTests -TestFile "Databases" -TestName "MySQL" ================================================ FILE: images/windows/scripts/build/Install-NSIS.ps1 ================================================ ################################################################################ ## File: Install-NSIS.ps1 ## Desc: Install NSIS ## Supply chain security: NSIS - managed by package manager ################################################################################ $nsisVersion = (Get-ToolsetContent).nsis.version Install-ChocoPackage nsis -ArgumentList "--version", "$nsisVersion" Add-MachinePathItem "${env:ProgramFiles(x86)}\NSIS\" Update-Environment Invoke-PesterTests -TestFile "Tools" -TestName "NSIS" ================================================ FILE: images/windows/scripts/build/Install-NativeImages.ps1 ================================================ ################################################################################ ## File: Install-NativeImages.ps1 ## Desc: Generate and install native images for .NET assemblies ################################################################################ Write-Host "NGen: install Microsoft.PowerShell.Utility.Activities..." & $env:SystemRoot\Microsoft.NET\Framework64\v4.0.30319\ngen.exe install "Microsoft.PowerShell.Utility.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" | Out-Null if ($LASTEXITCODE -ne 0) { throw "Installation of Microsoft.PowerShell.Utility.Activities failed with exit code $LASTEXITCODE" } Write-Host "NGen: update x64 native images..." & $env:SystemRoot\Microsoft.NET\Framework64\v4.0.30319\ngen.exe update | Out-Null if ($LASTEXITCODE -ne 0) { throw "Update of x64 native images failed with exit code $LASTEXITCODE" } Write-Host "NGen: update x86 native images..." & $env:SystemRoot\Microsoft.NET\Framework\v4.0.30319\ngen.exe update | Out-Null if ($LASTEXITCODE -ne 0) { throw "Update of x86 native images failed with exit code $LASTEXITCODE" } ================================================ FILE: images/windows/scripts/build/Install-Nginx.ps1 ================================================ ################################################################################ ## File: Install-Nginx.ps1 ## Desc: Install Nginx ################################################################################ # Stop w3svc service Stop-Service -Name w3svc # Install latest nginx in chocolatey $installDir = "C:\tools" Install-ChocoPackage nginx -ArgumentList "--force", "--params", "/installLocation:$installDir /port:80" # Stop and disable Nginx service Stop-Service -Name nginx Set-Service -Name nginx -StartupType Disabled # Start w3svc service Start-Service -Name w3svc # Invoke Pester Tests Invoke-PesterTests -TestFile "Nginx" ================================================ FILE: images/windows/scripts/build/Install-NodeJS.ps1 ================================================ ################################################################################ ## File: Install-NodeJS.ps1 ## Desc: Install nodejs-lts and other common node tools. ## Must run after python is configured ################################################################################ $prefixPath = 'C:\npm\prefix' $cachePath = 'C:\npm\cache' New-Item -Path $prefixPath -Force -ItemType Directory New-Item -Path $cachePath -Force -ItemType Directory $defaultVersion = (Get-ToolsetContent).node.default $nodeVersion = (Get-GithubReleasesByVersion -Repo "nodejs/node" -Version "${defaultVersion}").version | Select-Object -First 1 $downloadUrl = "https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-x64.msi" $packageName = Split-Path $downloadUrl -Leaf $externalHash = Get-ChecksumFromUrl -Type "SHA256" ` -Url ($downloadUrl -replace $packageName, "SHASUMS256.txt") ` -FileName $packageName Install-Binary -Type MSI ` -Url $downloadUrl ` -ExtraInstallArgs @('ADDLOCAL=ALL') ` -ExpectedSHA256Sum $externalHash Add-MachinePathItem $prefixPath Update-Environment [Environment]::SetEnvironmentVariable("npm_config_prefix", $prefixPath, "Machine") $env:npm_config_prefix = $prefixPath npm config set cache $cachePath --global npm config set registry https://registry.npmjs.org/ $globalNpmPackages = (Get-ToolsetContent).npm.global_packages $globalNpmPackages | ForEach-Object { npm install -g $_.name } Invoke-PesterTests -TestFile "Node" ================================================ FILE: images/windows/scripts/build/Install-OpenSSL.ps1 ================================================ ################################################################################ ## File: Install-OpenSSL.ps1 ## Desc: Install win64-openssl. ## Supply chain security: checksum validation ################################################################################ $arch = 'INTEL' $bits = '64' $light = $false $installerType = "exe" $version = (Get-ToolsetContent).openssl.version $installDir = "$env:ProgramFiles\OpenSSL" # Fetch available installers list $jsonUrl = 'https://raw.githubusercontent.com/slproweb/opensslhashes/master/win32_openssl_hashes.json' $installersAvailable = (Invoke-RestMethod $jsonUrl).files $installerNames = $installersAvailable | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name $installerUrl = $null $installerHash = $null foreach ($key in $installerNames) { $installer = $installersAvailable.$key if (($installer.light -eq $light) -and ($installer.arch -eq $arch) -and ($installer.bits -eq $bits) -and ($installer.installer -eq $installerType) -and ($installer.basever -like $version)) { $installerUrl = $installer.url $installerHash = $installer.sha512 } } if ($null -eq $installerUrl) { throw "Installer not found for version $version" } Install-Binary ` -Url $installerUrl ` -InstallArgs @('/silent', '/sp-', '/suppressmsgboxes','/tasks="copytobin"', "/DIR=`"$installDir`"") ` -ExpectedSHA512Sum $installerHash # Update PATH Add-MachinePathItem "$installDir\bin" Update-Environment Invoke-PesterTests -TestFile "Tools" -TestName "OpenSSL" ================================================ FILE: images/windows/scripts/build/Install-PHP.ps1 ================================================ ################################################################################ ## File: Install-PHP.ps1 ## Desc: Install PHP ################################################################################ # Install latest PHP in chocolatey $installDir = "c:\tools\php" $phpMajorMinor = (Get-ToolsetContent).php.version $phpVersionToInstall = Resolve-ChocoPackageVersion -PackageName "php" -TargetVersion $phpMajorMinor Install-ChocoPackage php -ArgumentList "--params", "/InstallDir:$installDir", "--version=$phpVersionToInstall" # Install latest Composer in chocolatey Install-ChocoPackage composer -ArgumentList "--install-args", "/DEV=$installDir /PHP=$installDir" # update path to extensions and enable curl and mbstring extensions, and enable php openssl extensions. ((Get-Content -path $installDir\php.ini -Raw) -replace ';extension=curl','extension=curl' -replace ';extension=mbstring','extension=mbstring' -replace ';extension_dir = "ext"','extension_dir = "ext"' -replace ';extension=openssl','extension=openssl') | Set-Content -Path $installDir\php.ini # Set the PHPROOT environment variable. [Environment]::SetEnvironmentVariable("PHPROOT", $installDir, "Machine") # Invoke Pester Tests Invoke-PesterTests -TestFile "PHP" ================================================ FILE: images/windows/scripts/build/Install-Pipx.ps1 ================================================ ################################################################################ ## File: Install-Pipx.ps1 ## Desc: Install pipx and pipx packages ################################################################################ Write-Host "Installing pipx..." $env:PIPX_BIN_DIR = "${env:ProgramFiles(x86)}\pipx_bin" $env:PIPX_HOME = "${env:ProgramFiles(x86)}\pipx" pip install pipx if ($LASTEXITCODE -ne 0) { throw "pipx installation failed with exit code $LASTEXITCODE" } Add-MachinePathItem "${env:PIPX_BIN_DIR}" [Environment]::SetEnvironmentVariable("PIPX_BIN_DIR", $env:PIPX_BIN_DIR, "Machine") [Environment]::SetEnvironmentVariable("PIPX_HOME", $env:PIPX_HOME, "Machine") Invoke-PesterTests -TestFile "Tools" -TestName "Pipx" Write-Host "Installing pipx packages..." $pipxToolset = (Get-ToolsetContent).pipx foreach ($tool in $pipxToolset) { if ($tool.python) { $pythonPath = (Get-Item -Path "${env:AGENT_TOOLSDIRECTORY}\Python\${tool.python}.*\x64\python-${tool.python}*").FullName Write-Host "Install ${tool.package} into python ${tool.python}" pipx install $tool.package --python $pythonPath } else { Write-Host "Install ${tool.package} into default python" pipx install $tool.package } if ($LASTEXITCODE -ne 0) { throw "Package ${tool.package} installation failed with exit code $LASTEXITCODE" } } Invoke-PesterTests -TestFile "PipxPackages" ================================================ FILE: images/windows/scripts/build/Install-PostgreSQL.ps1 ================================================ ################################################################################ ## File: Install-PostgreSQL.ps1 ## Desc: Install PostgreSQL ################################################################################ # Define user and password for PostgreSQL database $pgUser = "postgres" $pgPwd = "root" # Save current value of ErrorActionPreference and set it to Stop $errorActionOldValue = $ErrorActionPreference # Prepare environment variable for validation [Environment]::SetEnvironmentVariable("PGUSER", $pgUser, "Machine") [Environment]::SetEnvironmentVariable("PGPASSWORD", $pgPwd, "Machine") $toolsetVersion = (Get-ToolsetContent).postgresql.version if ($null -ne ($toolsetVersion | Select-String -Pattern '\d+\.\d+\.\d+')) { $majorVersion = ([version]$toolsetVersion).Major $minorVersion = ([version]$toolsetVersion).Minor $patchVersion = ([version]$toolsetVersion).Build $installerUrl = "https://get.enterprisedb.com/postgresql/postgresql-$majorVersion.$minorVersion-$patchVersion-windows-x64.exe" } else { # Define latest available version to install based on version specified in the toolset $getPostgreReleases = Invoke-WebRequest -Uri "https://git.postgresql.org/gitweb/?p=postgresql.git;a=tags" -UseBasicParsing # Getting all links matched to the pattern (e.g.a=log;h=refs/tags/REL_14) $targetReleases = $getPostgreReleases.Links.href | Where-Object { $_ -match "a=log;h=refs/tags/REL_$toolsetVersion" } [Int32] $outNumber = $null $minorVersions = @() foreach ($release in $targetReleases) { $version = $release.split('/')[-1] # Checking if the latest symbol of the release version is actually a number. If yes, add to $minorVersions array if ([Int32]::TryParse($($version.Split('_')[-1]), [ref] $outNumber)) { $minorVersions += $outNumber } } # Sorting and getting the last one $targetMinorVersions = ($minorVersions | Sort-Object)[-1] # In order to get rid of error messages (we know we will have them), force ErrorAction to SilentlyContinue $ErrorActionPreference = 'SilentlyContinue' # Install latest PostgreSQL # Starting from number 9 and going down, check if the installer is available. If yes, break the loop. # If an installer with $targetMinorVersions is not to be found, the $targetMinorVersions will be decreased by 1 $increment = 9 do { $url = "https://get.enterprisedb.com/postgresql/postgresql-$toolsetVersion.$targetMinorVersions-$increment-windows-x64.exe" $checkAccess = [System.Net.WebRequest]::Create($url) $response = $null $response = $checkAccess.GetResponse() if ($response) { $installerUrl = $response.ResponseUri.OriginalString } elseif (!$response -and ($increment -eq 0)) { $increment = 9 $targetMinorVersions-- } else { $increment-- } } while (!$response) } # Return the previous value of ErrorAction and invoke Install-Binary function $ErrorActionPreference = $errorActionOldValue # Define new data directory for PostgreSQL and create it if ($installerUrl -match 'postgresql-(\d+)') { $pgMajorVersion = $matches[1] } $pgData = "C:\PostgreSQL\$pgMajorVersion\data" if (-Not (Test-Path -Path $pgData)) { New-Item -ItemType Directory -Path $pgData | Out-Null } # Define silent install arguments for PostgreSQL $installerArgs = @( "--install_runtimes 0", "--superpassword root", "--enable_acledit 1", "--unattendedmodeui none", "--mode unattended", "--datadir `"$pgData`"" ) Install-Binary ` -Url $installerUrl ` -InstallArgs $installerArgs ` -ExpectedSubject 'CN=EnterpriseDB Corporation, O=EnterpriseDB Corporation, L=Wilmington, S=Delaware, C=US' ` -InstallerLogPath "$env:TEMP\**\install-postgresql.log" # Get Path to pg_ctl.exe $pgPath = (Get-CimInstance Win32_Service -Filter "Name LIKE 'postgresql-%'").PathName # Parse output of command above to obtain pure path $pgBin = Split-Path -Path $pgPath.split('"')[1] $pgRoot = Split-Path $pgBin -Parent # Validate PostgreSQL installation $pgReadyPath = Join-Path $pgBin "pg_isready.exe" $pgReady = Start-Process -FilePath $pgReadyPath -Wait -PassThru $exitCode = $pgReady.ExitCode if ($exitCode -ne 0) { Write-Host -Object "PostgreSQL is not ready. Exitcode: $exitCode" exit $exitCode } # Added PostgreSQL environment variable [Environment]::SetEnvironmentVariable("PGBIN", $pgBin, "Machine") [Environment]::SetEnvironmentVariable("PGROOT", $pgRoot, "Machine") [Environment]::SetEnvironmentVariable("PGDATA", $pgData, "Machine") # Stop and disable PostgreSQL service $pgService = Get-Service -Name postgresql* Stop-Service $pgService $pgService | Set-Service -StartupType Disabled Invoke-PesterTests -TestFile "Databases" -TestName "PostgreSQL" ================================================ FILE: images/windows/scripts/build/Install-PowerShellModules.ps1 ================================================ ################################################################################ ## File: Install-PowershellModules.ps1 ## Desc: Install common PowerShell modules ################################################################################ # Set TLS1.2 [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor "Tls12" # Install PowerShell modules $modules = (Get-ToolsetContent).powershellModules foreach ($module in $modules) { $moduleName = $module.name Write-Host "Installing ${moduleName} module" if ($module.versions) { foreach ($version in $module.versions) { Write-Host " - $version" Install-Module -Name $moduleName -RequiredVersion $version -Scope AllUsers -SkipPublisherCheck -Force } } else { Install-Module -Name $moduleName -Scope AllUsers -SkipPublisherCheck -Force } } Import-Module Pester Invoke-PesterTests -TestFile "PowerShellModules" -TestName "PowerShellModules" ================================================ FILE: images/windows/scripts/build/Install-PowershellAzModules.ps1 ================================================ ################################################################################ ## File: Install-PowershellAzModules.ps1 ## Desc: Install PowerShell modules used by AzureFileCopy@4, AzureFileCopy@5, AzurePowerShell@4, AzurePowerShell@5 tasks ## Supply chain security: package manager ################################################################################ # The correct Modules need to be saved in C:\Modules $installPSModulePath = "C:\\Modules" if (-not (Test-Path -LiteralPath $installPSModulePath)) { Write-Host "Creating ${installPSModulePath} folder to store PowerShell Azure modules..." New-Item -Path $installPSModulePath -ItemType Directory | Out-Null } # Get modules content from toolset $modules = (Get-ToolsetContent).azureModules $psModuleMachinePath = "" foreach ($module in $modules) { $moduleName = $module.name Write-Host "Installing ${moduleName} to the ${installPSModulePath} path..." foreach ($version in $module.versions) { $modulePath = Join-Path -Path $installPSModulePath -ChildPath "${moduleName}_${version}" Write-Host " - $version [$modulePath]" $psModuleMachinePath += "$modulePath;" Save-Module -Path $modulePath -Name $moduleName -RequiredVersion $version -Force -ErrorAction Stop } } # Add modules to the PSModulePath $psModuleMachinePath += $env:PSModulePath [Environment]::SetEnvironmentVariable("PSModulePath", $psModuleMachinePath, "Machine") Invoke-PesterTests -TestFile "PowerShellAzModules" -TestName "AzureModules" ================================================ FILE: images/windows/scripts/build/Install-PowershellCore.ps1 ================================================ ################################################################################ ## File: Install-PowershellCore.ps1 ## Desc: Install PowerShell Core ## Supply chain security: checksum validation ################################################################################ $ErrorActionPreference = "Stop" $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) New-Item -ItemType Directory -Path $tempDir -Force -ErrorAction SilentlyContinue | Out-Null try { $originalValue = [Net.ServicePointManager]::SecurityProtocol [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $metadata = Invoke-RestMethod https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/metadata.json $pwshMajorMinor = (Get-ToolsetContent).pwsh.version $releases = $metadata.LTSReleaseTag -replace '^v' foreach ($release in $releases) { if ($release -like "${pwshMajorMinor}*") { $downloadUrl = "https://github.com/PowerShell/PowerShell/releases/download/v${release}/PowerShell-${release}-win-x64.msi" break } } $installerName = Split-Path $downloadUrl -Leaf $externalHash = Get-ChecksumFromUrl -Type "SHA256" ` -Url ($downloadUrl -replace $installerName, "hashes.sha256") ` -FileName $installerName Install-Binary -Url $downloadUrl -ExpectedSHA256Sum $externalHash } finally { # Restore original value [Net.ServicePointManager]::SecurityProtocol = $originalValue Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } # about_update_notifications # While the update check happens during the first session in a given 24-hour period, for performance reasons, # the notification will only be shown on the start of subsequent sessions. # Also for performance reasons, the check will not start until at least 3 seconds after the session begins. [Environment]::SetEnvironmentVariable("POWERSHELL_UPDATECHECK", "Off", "Machine") Invoke-PesterTests -TestFile "Tools" -TestName "PowerShell Core" ================================================ FILE: images/windows/scripts/build/Install-PyPy.ps1 ================================================ ################################################################################ ## File: Install-PyPy.ps1 ## Desc: Install PyPy ## Supply chain security: checksum validation ################################################################################ function Install-PyPy { param( [String] $PackagePath, [String] $Architecture ) # Create PyPy toolcache folder $pypyToolcachePath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath "PyPy" if (-not (Test-Path $pypyToolcachePath)) { Write-Host "Create PyPy toolcache folder" New-Item -ItemType Directory -Path $pypyToolcachePath | Out-Null } # Expand archive with binaries $packageName = [IO.Path]::GetFileNameWithoutExtension((Split-Path -Path $packagePath -Leaf)) $tempFolder = Join-Path -Path $pypyToolcachePath -ChildPath $packageName Expand-7ZipArchive -Path $packagePath -DestinationPath $pypyToolcachePath # Get Python version from binaries $pypyApp = Get-ChildItem -Path "$tempFolder\pypy*.exe" | Where-Object Name -match "pypy(\d+)?.exe" | Select-Object -First 1 $pythonVersion = & $pypyApp -c "import sys;print('{}.{}.{}'.format(sys.version_info[0],sys.version_info[1],sys.version_info[2]))" $pypyFullVersion = & $pypyApp -c "import sys;print('{}.{}.{}'.format(*sys.pypy_version_info[0:3]))" Write-Host "Put '$pypyFullVersion' to PYPY_VERSION file" New-Item -Path "$tempFolder\PYPY_VERSION" -Value $pypyFullVersion | Out-Null if ($pythonVersion) { Write-Host "Installing PyPy $pythonVersion" $pypyVersionPath = Join-Path -Path $pypyToolcachePath -ChildPath $pythonVersion $pypyArchPath = Join-Path -Path $pypyVersionPath -ChildPath $architecture Write-Host "Create PyPy '${pythonVersion}' folder in '${pypyVersionPath}'" New-Item -ItemType Directory -Path $pypyVersionPath -Force | Out-Null Write-Host "Move PyPy '${pythonVersion}' files to '${pypyArchPath}'" Invoke-ScriptBlockWithRetry -Command { Move-Item -Path $tempFolder -Destination $pypyArchPath -ErrorAction Stop | Out-Null } Write-Host "Install PyPy '${pythonVersion}' in '${pypyArchPath}'" if (Test-Path "$pypyArchPath\python.exe") { cmd.exe /c "cd /d $pypyArchPath && python.exe -m ensurepip && python.exe -m pip install --upgrade pip" } else { $pypyName = $pypyApp.Name cmd.exe /c "cd /d $pypyArchPath && mklink python.exe $pypyName && python.exe -m ensurepip && python.exe -m pip install --upgrade pip" } # Create pip.exe if missing $pipPath = Join-Path -Path $pypyArchPath -ChildPath "Scripts/pip.exe" if (-not (Test-Path $pipPath)) { $pip3Path = Join-Path -Path $pypyArchPath -ChildPath "Scripts/pip3.exe" Copy-Item -Path $pip3Path -Destination $pipPath } if ($LASTEXITCODE -ne 0) { throw "PyPy installation failed with exit code $LASTEXITCODE" } Write-Host "Create complete file" New-Item -ItemType File -Path $pypyVersionPath -Name "$architecture.complete" | Out-Null } else { throw "PyPy application is not found. Failed to expand '$packagePath' archive" } } # Get PyPy content from toolset $toolsetVersions = Get-ToolsetContent | Select-Object -ExpandProperty toolcache | Where-Object Name -eq "PyPy" # Get PyPy releases $pypyVersions = Invoke-RestMethod https://downloads.python.org/pypy/versions.json # required for html parsing $checksums = (Invoke-RestMethod -Uri 'https://www.pypy.org/checksums.html' | ConvertFrom-HTML).SelectNodes('//*[@id="content"]/article/div/pre') Write-Host "Start PyPy installation" foreach ($toolsetVersion in $toolsetVersions.versions) { # Query latest PyPy version $latestMajorPyPyVersion = $pypyVersions | Where-Object { $_.python_version.StartsWith("$toolsetVersion") -and $_.stable -eq $true } | Select-Object -ExpandProperty files -First 1 | Where-Object platform -like "win*" if (-not $latestMajorPyPyVersion) { throw "Failed to query PyPy version '$toolsetVersion'" } $filename = $latestMajorPyPyVersion.filename Write-Host "Found PyPy '$filename' package" $tempPyPyPackagePath = Invoke-DownloadWithRetry $latestMajorPyPyVersion.download_url #region Supply chain security $distributorFileHash = $null foreach ($node in $checksums) { if ($node.InnerText -ilike "*${filename}*") { $distributorFileHash = $node.InnerText.ToString().Split("`n").Where({ $_ -ilike "*${filename}*" }).Split(' ')[0] } } Test-FileChecksum $tempPyPyPackagePath -ExpectedSHA256Sum $distributorFileHash #endregion Install-PyPy -PackagePath $tempPyPyPackagePath -Architecture $toolsetVersions.arch } ================================================ FILE: images/windows/scripts/build/Install-R.ps1 ================================================ ################################################################################ ## File: Install-R.ps1 ## Desc: Install R for Windows ################################################################################ Install-ChocoPackage R.Project Install-ChocoPackage rtools $rscriptPath = Resolve-Path "C:\Program Files\R\*\bin\x64" Add-MachinePathItem $rscriptPath Invoke-PesterTests -TestFile "Tools" -TestName "R" ================================================ FILE: images/windows/scripts/build/Install-RootCA.ps1 ================================================ ################################################################################ ## File: Install-RootCA.ps1 ## Desc: Install Root CA certificates ################################################################################ # https://www.sysadmins.lv/blog-en/how-to-retrieve-certificate-purposes-property-with-cryptoapi-and-powershell.aspx # https://www.sysadmins.lv/blog-en/dump-authroot-and-disallowed-certificates-with-powershell.aspx # https://www.sysadmins.lv/blog-en/constraining-extended-key-usages-in-microsoft-windows.aspx function Add-ExtendedCertType { $signature = @" [DllImport("Crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool CertGetCertificateContextProperty( IntPtr pCertContext, uint dwPropId, Byte[] pvData, ref uint pcbData ); [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CertSetCertificateContextProperty( IntPtr pCertContext, int dwPropId, uint dwFlags, IntPtr pvData ); "@ Add-Type -MemberDefinition $signature -Namespace PKI -Name Cert } function Get-CertificatesWithoutPropId { # List installed certificates $certs = Get-ChildItem -Path "Cert:\LocalMachine\Root" Write-Host "Certificates without CERT_NOT_BEFORE_FILETIME_PROP_ID property" $certsWithoutPropId = @{} $certs | ForEach-Object -Process { $certHandle = $_.Handle $isPropertySet = [PKI.Cert]::CertGetCertificateContextProperty( $certHandle, $CERT_NOT_BEFORE_FILETIME_PROP_ID, $null, [ref] $null ) if (-not $isPropertySet) { Write-Host "Subject: $($_.Subject)" $certsWithoutPropId[$_.Thumbprint] = $null } } $certsWithoutPropId } function Import-SSTFromWU { # Serialized Certificate Store File $sstFile = "$env:TEMP\roots.sst" # Generate SST from Windows Update $result = Invoke-ScriptBlockWithRetry -RetryCount 5 -RetryIntervalSeconds 10 -Command { $r = certutil.exe -generateSSTFromWU $sstFile if ($LASTEXITCODE -ne 0) { throw "failed to generate $sstFile sst file`n$o" } return $r } if ($LASTEXITCODE -ne 0) { Write-Host "[Error]: failed to generate $sstFile sst file`n$result" exit $LASTEXITCODE } $result = certutil.exe -dump $sstFile if ($LASTEXITCODE -ne 0) { Write-Host "[Error]: failed to dump $sstFile sst file`n$result" exit $LASTEXITCODE } Import-Certificate -FilePath $sstFile -CertStoreLocation Cert:\LocalMachine\Root } function Clear-CertificatesPropId { param( [hashtable] $CertsWithoutPropId ) # List installed certificates $certs = Get-ChildItem -Path Cert:\LocalMachine\Root # Clear property CERT_NOT_BEFORE_FILETIME_PROP_ID $certs | ForEach-Object -Process { $thumbprint = $_.Thumbprint if ($certsWithoutPropId.ContainsKey($thumbprint)) { $subject = $_.Subject $certHandle = $_.Handle $result = [PKI.Cert]::CertSetCertificateContextProperty( $certHandle, $CERT_NOT_BEFORE_FILETIME_PROP_ID, 0, [System.IntPtr]::Zero ) if ($result) { Write-Host "[Success] Clear CERT_NOT_BEFORE_FILETIME_PROP_ID property $subject" } else { Write-Host "[Fail] Clear CERT_NOT_BEFORE_FILETIME_PROP_ID property $subject" } } } } function Disable-RootAutoUpdate { Write-Host "Disable auto root update mechanism" $regPath = "HKLM:\Software\Policies\Microsoft\SystemCertificates\AuthRoot" $regKey = "DisableRootAutoUpdate" # Create the registry key if it doesn't exist if (-not (Test-Path $regPath)) { Write-Verbose "Creating $regPath" New-Item $regPath | Out-Null } Set-ItemProperty $regPath -Name $regKey -Type DWord -Value 1 } # Property to remove $CERT_NOT_BEFORE_FILETIME_PROP_ID = 126 # Add extended cert type Add-ExtendedCertType # Get certificates without property CERT_NOT_BEFORE_FILETIME_PROP_ID $certsWithoutPropId = Get-CertificatesWithoutPropId # Download and install the latest version of root ca list Import-SSTFromWU # Clear property CERT_NOT_BEFORE_FILETIME_PROP_ID if ($certsWithoutPropId.Count -gt 0) { Clear-CertificatesPropId -CertsWithoutPropId $certsWithoutPropId } else { Write-Host "Nothing to clear" } # Disable auto root update mechanism Disable-RootAutoUpdate ================================================ FILE: images/windows/scripts/build/Install-Ruby.ps1 ================================================ ################################################################################ ## File: Install-Ruby.ps1 ## Desc: Install Ruby using the RubyInstaller2 package and set the default Ruby version ################################################################################ # Most of this logic is from # https://github.com/ruby/setup-ruby/blob/master/windows.js function Install-Ruby { param( [String] $PackagePath, [String] $Architecture = "x64" ) # Create Ruby toolcache folder $rubyToolcachePath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath "Ruby" if (-not (Test-Path $rubyToolcachePath)) { Write-Host "Creating Ruby toolcache folder" New-Item -ItemType Directory -Path $rubyToolcachePath | Out-Null } $packageName = [IO.Path]::GetFileNameWithoutExtension((Split-Path -Path $PackagePath -Leaf)) Write-Host "Expanding Ruby archive $packageName" $tempFolder = Join-Path -Path $rubyToolcachePath -ChildPath $packageName Expand-7ZipArchive -Path $PackagePath -DestinationPath $rubyToolcachePath # Get Ruby version from binaries $rubyVersion = & "$tempFolder\bin\ruby.exe" -e "print RUBY_VERSION" if (($LASTEXITCODE -ne 0) -or (-not $rubyVersion)) { throw "Unable to determine Ruby version. Exit code: $LASTEXITCODE, output: '$rubyVersion'" } Write-Host "Ruby version is $rubyVersion" $rubyVersionPath = Join-Path -Path $rubyToolcachePath -ChildPath $rubyVersion $rubyArchPath = Join-Path -Path $rubyVersionPath -ChildPath $Architecture Write-Host "Creating Ruby '${rubyVersion}' folder in '${rubyVersionPath}'" New-Item -ItemType Directory -Path $rubyVersionPath -Force | Out-Null Write-Host "Moving Ruby '${rubyVersion}' files to '${rubyArchPath}'" Invoke-ScriptBlockWithRetry -Command { Move-Item -Path $tempFolder -Destination $rubyArchPath -ErrorAction Stop | Out-Null } Write-Host "Removing Ruby '${rubyVersion}' documentation '${rubyArchPath}\share\doc' folder" Remove-Item -Path "${rubyArchPath}\share\doc" -Force -Recurse -ErrorAction Ignore Write-Host "Creating complete file for Ruby $rubyVersion $Architecture" New-Item -ItemType File -Path $rubyVersionPath -Name "$Architecture.complete" | Out-Null } function Set-DefaultRubyVersion { param( [Parameter(Mandatory = $true)] [version] $Version, [Alias("Arch")] [string] $Architecture = "x64" ) $rubyPath = Join-Path $env:AGENT_TOOLSDIRECTORY "/Ruby/${Version}*/${Architecture}/bin" $rubyDir = (Get-Item -Path $rubyPath).FullName Write-Host "Use Ruby ${Version} as a system Ruby" Add-MachinePathItem -PathItem $rubyDir | Out-Null } # Install Ruby $rubyTools = (Get-ToolsetContent).toolcache | Where-Object { $_.name -eq "Ruby" } $rubyToolVersions = $rubyTools.versions Write-Host "Starting installation Ruby..." foreach ($rubyVersion in $rubyToolVersions) { Write-Host "Starting Ruby $rubyVersion installation" $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "oneclick/rubyinstaller2" ` -Version "$rubyVersion*" ` -UrlMatchPattern "*-x64.7z" $packagePath = Invoke-DownloadWithRetry $downloadUrl Install-Ruby -PackagePath $packagePath } Set-DefaultRubyVersion -Version $rubyTools.default -Arch $rubyTools.arch ================================================ FILE: images/windows/scripts/build/Install-Rust.ps1 ================================================ ################################################################################ ## File: Install-Rust.ps1 ## Desc: Install Rust for Windows ## Supply chain security: checksum validation for bootstrap, managed by rustup for workloads ################################################################################ # Rust Env $env:RUSTUP_HOME = "C:\Users\Default\.rustup" $env:CARGO_HOME = "C:\Users\Default\.cargo" # Download the latest rustup-init.exe for Windows x64 # See https://rustup.rs/# $rustupPath = Invoke-DownloadWithRetry "https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe" #region Supply chain security $distributorFileHash = (Invoke-RestMethod -Uri 'https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe.sha256').Trim() Test-FileChecksum $rustupPath -ExpectedSHA256Sum $distributorFileHash #endregion # Install Rust by running rustup-init.exe (disabling the confirmation prompt with -y) & $rustupPath -y --default-toolchain=stable --profile=minimal if ($LASTEXITCODE -ne 0) { throw "Rust installation failed with exit code $LASTEXITCODE" } # Add %USERPROFILE%\.cargo\bin to USER PATH Add-DefaultPathItem "%USERPROFILE%\.cargo\bin" # Add Rust binaries to the path $env:Path += ";$env:CARGO_HOME\bin" # Add i686 target for building 32-bit binaries rustup target add i686-pc-windows-msvc # Add target for building mingw-w64 binaries rustup target add x86_64-pc-windows-gnu # Install common tools rustup component add rustfmt clippy if ($LASTEXITCODE -ne 0) { throw "Rust component installation failed with exit code $LASTEXITCODE" } if (-not (Test-IsWin25)) { cargo install --locked bindgen-cli cbindgen cargo-audit cargo-outdated if ($LASTEXITCODE -ne 0) { throw "Rust tools installation failed with exit code $LASTEXITCODE" } # Cleanup Cargo crates cache Remove-Item "${env:CARGO_HOME}\registry\*" -Recurse -Force } Invoke-PesterTests -TestFile "Rust" ================================================ FILE: images/windows/scripts/build/Install-SQLOLEDBDriver.ps1 ================================================ ################################################################################ ## File: Install-SQLOLEDBDriver.ps1 ## Desc: Install OLE DB Driver for SQL Server ################################################################################ # Install OLE DB Driver 18 Install-Binary -Type MSI ` -Url "https://go.microsoft.com/fwlink/?linkid=2242656" ` -ExtraInstallArgs @("ADDLOCAL=ALL", "IACCEPTMSOLEDBSQLLICENSETERMS=YES") ` -ExpectedSubject $(Get-MicrosoftPublisher) # Install OLE DB Driver 19 Install-Binary -Type MSI ` -Url "https://go.microsoft.com/fwlink/?linkid=2318101" ` -ExtraInstallArgs @("ADDLOCAL=ALL", "IACCEPTMSOLEDBSQLLICENSETERMS=YES") ` -ExpectedSubject $(Get-MicrosoftPublisher) ================================================ FILE: images/windows/scripts/build/Install-SQLPowerShellTools.ps1 ================================================ ################################################################################ ## File: Install-SQLPowerShellTools.ps1 ## Desc: Install SQL PowerShell tool ################################################################################ $baseUrl = "https://download.microsoft.com/download/B/1/7/B1783FE9-717B-4F78-A39A-A2E27E3D679D/ENU/x64" # install required MSIs Install-Binary ` -Url "${baseUrl}/SQLSysClrTypes.msi" ` -ExpectedSubject $(Get-MicrosoftPublisher) Install-Binary ` -Url "${baseUrl}/SharedManagementObjects.msi" ` -ExpectedSubject $(Get-MicrosoftPublisher) Install-Binary ` -Url "${baseUrl}/PowerShellTools.msi" ` -ExpectedSubject $(Get-MicrosoftPublisher) ================================================ FILE: images/windows/scripts/build/Install-Sbt.ps1 ================================================ ################################################################################ ## File: Install-Sbt.ps1 ## Desc: Install sbt for Windows ################################################################################ # Install the latest version of sbt. # See https://chocolatey.org/packages/sbt Install-ChocoPackage sbt $env:SBT_HOME="${env:ProgramFiles(x86)}\sbt" # Add sbt binaries to the path Add-MachinePathItem "$env:SBT_HOME\bin" Invoke-PesterTests -TestFile "Tools" -TestName "Sbt" ================================================ FILE: images/windows/scripts/build/Install-Selenium.ps1 ================================================ ################################################################################ ## File: Install-Selenium.ps1 ## Desc: Install Selenium Server standalone ################################################################################ # Create Selenium directory $seleniumDirectory = "C:\selenium\" New-Item -ItemType directory -Path $seleniumDirectory # Download Selenium $seleniumMajorVersion = (Get-ToolsetContent).selenium.version $seleniumDownloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "SeleniumHQ/selenium" ` -Version "$seleniumMajorVersion.*" ` -Asset "selenium-server-*.jar" ` -AllowMultipleMatches $seleniumBinPath = Join-Path $seleniumDirectory "selenium-server.jar" Invoke-DownloadWithRetry -Url $seleniumDownloadUrl -Path $seleniumBinPath # Create an empty file to retrieve Selenium version $seleniumFullVersion = $seleniumDownloadUrl.Split("-")[1].Split("/")[0] New-Item -Path $seleniumDirectory -Name "selenium-server-$seleniumFullVersion" # Add SELENIUM_JAR_PATH environment variable [Environment]::SetEnvironmentVariable("SELENIUM_JAR_PATH", $seleniumBinPath, "Machine") Invoke-PesterTests -TestFile "Browsers" -TestName "Selenium" ================================================ FILE: images/windows/scripts/build/Install-ServiceFabricSDK.ps1 ================================================ ################################################################################ ## File: Install-ServiceFabricSDK.ps1 ## Desc: Install webpicmd and then the service fabric sdk ## must be install after Visual Studio ## Supply chain security: checksum validation ################################################################################ # Creating 'Installer' cache folder if it doesn't exist New-Item -Path 'C:\Windows\Installer' -ItemType Directory -Force # Get Service Fabric components versions $runtimeVersion = (Get-ToolsetContent).serviceFabric.runtime.version $sdkVersion = (Get-ToolsetContent).serviceFabric.sdk.version $urlBase = "https://download.microsoft.com/download/b/8/a/b8a2fb98-0ec1-41e5-be98-9d8b5abf7856" # Install Service Fabric Runtime for Windows Install-Binary ` -Url "${urlBase}/MicrosoftServiceFabric.${runtimeVersion}.exe" ` -InstallArgs @("/accepteula ", "/quiet", "/force") ` -ExpectedSHA256Sum (Get-ToolsetContent).serviceFabric.runtime.checksum # Install Service Fabric SDK Install-Binary ` -Url "${urlBase}/MicrosoftServiceFabricSDK.${sdkVersion}.msi" ` -ExpectedSHA256Sum (Get-ToolsetContent).serviceFabric.sdk.checksum Invoke-PesterTests -TestFile "Tools" -TestName "ServiceFabricSDK" ================================================ FILE: images/windows/scripts/build/Install-Stack.ps1 ================================================ ################################################################################ ## File: Install-Stack.ps1 ## Desc: Install Stack for Windows ## Supply chain security: Stack - checksum validation ################################################################################ Write-Host "Get the latest Stack version..." $version = (Get-GithubReleasesByVersion -Repo "commercialhaskell/stack" -Version "latest" -WithAssetsOnly).version $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "commercialhaskell/stack" ` -Version $version ` -UrlMatchPattern "stack-*-windows-x86_64.zip" Write-Host "Download stack archive" $stackToolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "stack\$version" $destinationPath = Join-Path $stackToolcachePath "x64" $stackArchivePath = Invoke-DownloadWithRetry $downloadUrl #region Supply chain security - Stack $externalHash = Get-ChecksumFromUrl -Type "SHA256" ` -Url "$downloadUrl.sha256" ` -FileName (Split-Path $downloadUrl -Leaf) Test-FileChecksum $stackArchivePath -ExpectedSHA256Sum $externalHash #endregion Write-Host "Expand stack archive" Expand-7ZipArchive -Path $stackArchivePath -DestinationPath $destinationPath New-Item -Name "x64.complete" -Path $stackToolcachePath Add-MachinePathItem -PathItem $destinationPath Invoke-PesterTests -TestFile "Tools" -TestName "Stack" ================================================ FILE: images/windows/scripts/build/Install-Toolset.ps1 ================================================ ################################################################################ ## File: Install-Toolset.ps1 ## Team: CI-Build ## Desc: Install toolset ################################################################################ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 Function Install-Asset { param( [Parameter(Mandatory=$true)] [object] $ReleaseAsset ) $releaseAssetName = [System.IO.Path]::GetFileNameWithoutExtension($ReleaseAsset.filename) $assetFolderPath = Join-Path $env:TEMP_DIR $releaseAssetName $assetArchivePath = Invoke-DownloadWithRetry $ReleaseAsset.download_url Write-Host "Extract $($ReleaseAsset.filename) content..." if ($assetArchivePath.EndsWith(".tar.gz")) { $assetTarPath = $assetArchivePath.TrimEnd(".tar.gz") Expand-7ZipArchive -Path $assetArchivePath -DestinationPath $assetTarPath Expand-7ZipArchive -Path $assetTarPath -DestinationPath $assetFolderPath } else { Expand-7ZipArchive -Path $assetArchivePath -DestinationPath $assetFolderPath } Write-Host "Invoke installation script..." Push-Location -Path $assetFolderPath Invoke-Expression .\setup.ps1 Pop-Location } # Get toolcache content from toolset $toolsToInstall = @("Python", "Node", "Go") $tools = Get-ToolsetContent | Select-Object -ExpandProperty toolcache | Where-Object { $toolsToInstall -contains $_.Name } foreach ($tool in $tools) { # Get versions manifest for current tool # Invoke-RestMethod doesn't support retry in PowerShell 5.1 $assets = Invoke-ScriptBlockWithRetry -Command { Invoke-RestMethod $tool.url } # Get github release asset for each version foreach ($toolVersion in $tool.versions) { $asset = $assets ` | Where-Object version -like $toolVersion ` | Select-Object -ExpandProperty files ` | Where-Object { ($_.platform -eq $tool.platform) -and ($_.arch -eq $tool.arch) -and ($_.toolset -eq $tool.toolset) } ` | Select-Object -First 1 if (-not $asset) { throw "Asset for $($tool.name) $toolVersion $($tool.arch) not found in versions manifest" } Write-Host "Installing $($tool.name) $toolVersion $($tool.arch)..." Install-Asset -ReleaseAsset $asset } } ================================================ FILE: images/windows/scripts/build/Install-TortoiseSvn.ps1 ================================================ ################################################################################ ## File: Install-TortoiseSvn.ps1 ## Desc: Install TortoiseSvn ################################################################################ Install-ChocoPackage tortoisesvn ================================================ FILE: images/windows/scripts/build/Install-VSExtensions.ps1 ================================================ ################################################################################### ## File: Install-VSExtensions.ps1 ## Desc: Install the Visual Studio Extensions from toolset.json ################################################################################### $toolset = Get-ToolsetContent $vsixPackagesList = $toolset.visualStudio.vsix if (-not $vsixPackagesList) { Write-Host "No extensions to install" exit 0 } $vsixPackagesList | ForEach-Object { # Retrieve cdn endpoint to avoid HTTP error 429 # https://github.com/actions/runner-images/issues/3074 $vsixPackage = Get-VsixInfoFromMarketplace $_ Write-Host "Installing $vsixPackage" if ($vsixPackage.FileName.EndsWith(".vsix")) { Install-VSIXFromUrl $vsixPackage.DownloadUri } else { Install-Binary ` -Url $vsixPackage.DownloadUri ` -InstallArgs @('/install', '/quiet', '/norestart') } } Invoke-PesterTests -TestFile "Vsix" ================================================ FILE: images/windows/scripts/build/Install-Vcpkg.ps1 ================================================ ################################################################################ ## File: Install-Vcpkg.ps1 ## Desc: Install vcpkg ################################################################################ $Uri = 'https://github.com/Microsoft/vcpkg.git' $InstallDir = 'C:\vcpkg' $VcpkgExecPath = 'vcpkg.exe' git clone $Uri $InstallDir -q # Build and integrate vcpkg Invoke-Expression "$InstallDir\bootstrap-vcpkg.bat" if ($LASTEXITCODE -ne 0) { throw "vcpkg bootstrap failed with exit code $LASTEXITCODE" } Invoke-Expression "$InstallDir\$VcpkgExecPath integrate install" if ($LASTEXITCODE -ne 0) { throw "vcpkg integration failed with exit code $LASTEXITCODE" } # Add vcpkg to system environment Add-MachinePathItem $InstallDir [Environment]::SetEnvironmentVariable("VCPKG_INSTALLATION_ROOT", $InstallDir, "Machine") Update-Environment Invoke-PesterTests -TestFile "Tools" -TestName "Vcpkg" ================================================ FILE: images/windows/scripts/build/Install-VisualStudio.ps1 ================================================ ################################################################################ ## File: Install-VisualStudio.ps1 ## Desc: Install Visual Studio ################################################################################ $vsToolset = (Get-ToolsetContent).visualStudio # Install Visual Studio for Windows 22 and 25 with InstallChannel Install-VisualStudio ` -Version $vsToolset.subversion ` -Edition $vsToolset.edition ` -Channel $vsToolset.channel ` -InstallChannelUri $vsToolset.installChannelUri ` -RequiredComponents $vsToolset.workloads ` -ExtraArgs "--allWorkloads --includeRecommended --remove Component.CPython3.x64" # Find the version of VS installed for this instance # Only supports a single instance $vsProgramData = Get-Item -Path "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances" $instanceFolders = Get-ChildItem -Path $vsProgramData.FullName if ($instanceFolders -is [array]) { throw "More than one instance installed" } # Updating content of MachineState.json file to disable autoupdate of VSIX extensions $vsInstallRoot = (Get-VisualStudioInstance).InstallationPath $newContent = '{"Extensions":[{"Key":"1e906ff5-9da8-4091-a299-5c253c55fdc9","Value":{"ShouldAutoUpdate":false}},{"Key":"Microsoft.VisualStudio.Web.AzureFunctions","Value":{"ShouldAutoUpdate":false}}],"ShouldAutoUpdate":false,"ShouldCheckForUpdates":false}' Set-Content -Path "$vsInstallRoot\Common7\IDE\Extensions\MachineState.json" -Value $newContent if (Test-IsWin22) { # Install Windows 10 SDK version 10.0.17763 Install-Binary -Type EXE ` -Url 'https://go.microsoft.com/fwlink/p/?LinkID=2033908' ` -InstallArgs @("/q", "/norestart", "/ceip off", "/features OptionId.UWPManaged OptionId.UWPCPP OptionId.UWPLocalized OptionId.DesktopCPPx86 OptionId.DesktopCPPx64 OptionId.DesktopCPParm64") ` -ExpectedSubject $(Get-MicrosoftPublisher) } # Install Windows 11 SDK version 10.0.26100 Install-Binary -Type EXE ` -Url 'https://go.microsoft.com/fwlink/?linkid=2349110' ` -InstallArgs @("/q", "/norestart", "/ceip off", "/features OptionId.UWPManaged OptionId.UWPCPP OptionId.UWPLocalized OptionId.DesktopCPPx86 OptionId.DesktopCPPx64 OptionId.DesktopCPParm64") ` -ExpectedSubject $(Get-MicrosoftPublisher) # Enable Windows Desktop Debuggers (cdb.exe) on Windows Server 2025 if (Test-IsWin25) { $installerEntry = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* ` | Where-Object { $_.DisplayName -match "Windows Software Development Kit" } ` | Sort-Object DisplayVersion -Descending | Select-Object -First 1 if ($installerEntry -and $installerEntry.BundleCachePath) { Install-Binary -Type EXE ` -LocalPath $installerEntry.BundleCachePath ` -InstallArgs @("/features", "OptionId.WindowsDesktopDebuggers", "/q", "/norestart") ` -ExpectedSubject $(Get-MicrosoftPublisher) } } Invoke-PesterTests -TestFile "VisualStudio" ================================================ FILE: images/windows/scripts/build/Install-WDK.ps1 ================================================ ################################################################################ ## File: Install-WDK.ps1 ## Desc: Install the Windows Driver Kit ################################################################################ # Requires Windows SDK with the same version number as the WDK if (Test-IsWin22) { # SDK is available through Visual Studio $wdkUrl = "https://go.microsoft.com/fwlink/?linkid=2324617" } else { throw "Invalid version of Visual Studio is found. Windows Server 2022 is required" } # Install all features without showing the GUI using wdksetup.exe Install-Binary -Type EXE ` -Url $wdkUrl ` -InstallArgs @("/features", "+", "/quiet") ` -ExpectedSubject $(Get-MicrosoftPublisher) Invoke-PesterTests -TestFile "WDK" ================================================ FILE: images/windows/scripts/build/Install-WSL2.ps1 ================================================ Write-Host "Install WSL2" $version = (Get-GithubReleasesByVersion -Repo "microsoft/WSL" -Version "latest").version $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "microsoft/WSL" ` -Version $version ` -UrlMatchPattern "wsl.*.x64.msi" Install-Binary -Type MSI ` -Url $downloadUrl Write-Host "Performing wsl --install --no-distribution" wsl.exe --install --no-distribution Invoke-PesterTests -TestFile "WindowsFeatures" -TestName "WSL2" ================================================ FILE: images/windows/scripts/build/Install-WebPlatformInstaller.ps1 ================================================ ################################################################################ ## File: Install-WebPI.ps1 ## Desc: Install WebPlatformInstaller ################################################################################ Install-Binary -Type MSI ` -Url 'https://go.microsoft.com/fwlink/?LinkId=287166' ` -ExpectedSubject $(Get-MicrosoftPublisher) Invoke-PesterTests -TestFile "Tools" -TestName "WebPlatformInstaller" ================================================ FILE: images/windows/scripts/build/Install-WinAppDriver.ps1 ================================================ #################################################################################### ## File: Install-WinAppDriver.ps1 ## Desc: Install Windows Application Driver (WinAppDriver) #################################################################################### [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "microsoft/WinAppDriver" ` -Version "latest" ` -UrlMatchPattern "WindowsApplicationDriver_*.msi" Install-Binary ` -Url $downloadUrl ` -ExpectedSubject $(Get-MicrosoftPublisher) Invoke-PesterTests -TestFile "WinAppDriver" -TestName "WinAppDriver" ================================================ FILE: images/windows/scripts/build/Install-WindowsFeatures.ps1 ================================================ #################################################################################### ## File: Install-WindowsFeatures.ps1 ## Desc: Install Windows Features #################################################################################### $windowsFeatures = (Get-ToolsetContent).windowsFeatures foreach ($feature in $windowsFeatures) { if ($feature.optionalFeature) { Write-Host "Activating Windows Optional Feature '$($feature.name)'..." Enable-WindowsOptionalFeature -Online -FeatureName $feature.name -NoRestart $resultSuccess = $? } else { Write-Host "Activating Windows Feature '$($feature.name)'..." $arguments = @{ Name = $feature.name IncludeAllSubFeature = [System.Convert]::ToBoolean($feature.includeAllSubFeatures) IncludeManagementTools = [System.Convert]::ToBoolean($feature.includeManagementTools) } $result = Install-WindowsFeature @arguments $resultSuccess = $result.Success } if ($resultSuccess) { Write-Host "Windows Feature '$($feature.name)' was activated successfully" } else { throw "Failed to activate Windows Feature '$($feature.name)'" } } # it improves Android emulator launch on Windows Server # https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/manage/manage-hyper-v-scheduler-types bcdedit /set hypervisorschedulertype root if ($LASTEXITCODE -ne 0) { throw "Failed to set hypervisorschedulertype to root" } ================================================ FILE: images/windows/scripts/build/Install-WindowsUpdates.ps1 ================================================ ################################################################################ ## File: Install-WindowsUpdates.ps1 ## Desc: Install Windows Updates. ## Should be run at end, just before SoftwareReport and Finalize-VM.ps1. ################################################################################ function Install-WindowsUpdates { Write-Host "Starting wuauserv" Start-Service -Name wuauserv -PassThru | Out-Host # Temporarily exclude Windows update KB5034439 since it throws an error. # The known issue (https://support.microsoft.com/en-au/topic/kb5034439-windows-recovery-environment-update-for-azure-stack-hci-version-22h2-and-windows-server-2022-january-9-2024-6f9d26e6-784c-4503-a3c6-0beedda443ca) Write-Host "Getting list of available windows updates" Get-WindowsUpdate -MicrosoftUpdate -NotKBArticleID "KB5034439" -OutVariable updates | Out-Host if ( -not $updates ) { Write-Host "There are no windows updates to install" return } Write-Host "Installing windows updates" Get-WindowsUpdate -MicrosoftUpdate -NotKBArticleID "KB5034439" -AcceptAll -Install -IgnoreUserInput -IgnoreReboot | Out-Host Write-Host "Validating windows updates installation" # Get-WUHistory doesn't support Windows Server 2022 $notFailedUpdateNames = Get-WindowsUpdateStates | Where-Object { $_.State -in ("Installed", "Running") } | Select-Object -ExpandProperty Title # We ignore Microsoft Defender Antivirus updates; Azure service updates AV automatically $failedUpdates = $updates[0] | Where-Object Title -notmatch "Microsoft Defender Antivirus" | Where-Object { -not ($notFailedUpdateNames -match $_.KB) } if ( $failedUpdates ) { throw "Windows updates failed to install: $($failedUpdates.KB)" } } Install-WindowsUpdates # Create complete windows update file New-Item -Path $env:windir -Name WindowsUpdateDone.txt -ItemType File | Out-Null ================================================ FILE: images/windows/scripts/build/Install-WindowsUpdatesAfterReboot.ps1 ================================================ ################################################################################ ## File: Install-WindowsUpdatesAfterReboot.ps1 ## Desc: Waits for Windows Updates to finish installing after reboot ################################################################################ Invoke-ScriptBlockWithRetry -RetryCount 10 -RetryIntervalSeconds 120 -Command { $inProgress = Get-WindowsUpdateStates | Where-Object State -eq "Running" | Where-Object Title -notmatch "Microsoft Defender Antivirus" if ( $inProgress ) { $title = $inProgress.Title -join "`n" throw "Windows updates are still installing: $title" } } ================================================ FILE: images/windows/scripts/build/Install-Wix.ps1 ================================================ ################################################################################ ## File: Install-Wix.ps1 ## Desc: Install WIX. ################################################################################ Install-ChocoPackage wixtoolset -ArgumentList "--force" Update-Environment $currentPath = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") $newPath = $currentPath + ";$(Join-Path -Path $env:WIX -ChildPath "bin")" [Environment]::SetEnvironmentVariable("PATH", $newPath, "Machine") Update-Environment Invoke-PesterTests -TestFile "Wix" ================================================ FILE: images/windows/scripts/build/Install-Zstd.ps1 ================================================ ################################################################################ ## File: Install-Zstd.ps1 ## Desc: Install zstd ################################################################################ $downloadUrl = Resolve-GithubReleaseAssetUrl ` -Repo "facebook/zstd" ` -Version "latest" ` -UrlMatchPattern "zstd-*-win64.zip" $zstdArchivePath = Invoke-DownloadWithRetry $downloadUrl $zstdName = [IO.Path]::GetFileNameWithoutExtension($zstdArchivePath) $toolPath = "C:\tools" $zstdPath = Join-Path $toolPath zstd $filesInArchive = 7z l $zstdArchivePath | Out-String if ($filesInArchive.Contains($zstdName)) { Expand-7ZipArchive -Path $zstdArchivePath -DestinationPath $toolPath Invoke-ScriptBlockWithRetry -Command { Move-Item -Path "${zstdPath}*" -Destination $zstdPath -ErrorAction Stop } } else { Expand-7ZipArchive -Path $zstdArchivePath -DestinationPath $zstdPath } # Add zstd-win64 to PATH Add-MachinePathItem $zstdPath Invoke-PesterTests -TestFile "Tools" -TestName "Zstd" ================================================ FILE: images/windows/scripts/build/Invoke-Cleanup.ps1 ================================================ ################################################################################ ## File: Invoke-Cleanup.ps1 ## Desc: Cleanup WinSxS, temp, cache and compress some directories ################################################################################ Write-Host "Cleanup WinSxS" dism.exe /online /Cleanup-Image /StartComponentCleanup /ResetBase if ($LASTEXITCODE -ne 0) { throw "Failed to cleanup WinSxS" } Write-Host "Clean up various directories" @( "$env:SystemDrive\Recovery", "$env:SystemRoot\logs", "$env:SystemRoot\winsxs\manifestcache", "$env:SystemRoot\Temp", "$env:SystemDrive\Users\$env:INSTALL_USER\AppData\Local\Temp", "$env:TEMP", "$env:AZURE_CONFIG_DIR\logs", "$env:AZURE_CONFIG_DIR\commands", "$env:AZURE_CONFIG_DIR\telemetry" ) | ForEach-Object { if (Test-Path $_) { Write-Host "Removing $_" cmd /c "takeown /d Y /R /f $_ 2>&1" | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to take ownership of $_" } cmd /c "icacls $_ /grant:r administrators:f /t /c /q 2>&1" | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to grant administrators full control of $_" } Remove-Item $_ -Recurse -Force -ErrorAction SilentlyContinue | Out-Null } } # Remove AllUsersAllHosts profile Remove-Item $profile.AllUsersAllHosts -Force -ErrorAction SilentlyContinue | Out-Null # Clean yarn and npm cache cmd /c "yarn cache clean 2>&1" | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to clean yarn cache" } cmd /c "npm cache clean --force 2>&1" | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to clean npm cache" } if (Test-IsWin25) { $directoriesToCompact = @( "C:\Program Files (x86)\Android", "C:\Program Files\dotnet", "$env:SystemRoot\assembly", "$env:SystemRoot\WinSxS" ) Write-Host "Starting Image slimming process" $start = get-date $ErrorActionPreviousValue = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' foreach ($directory in $directoriesToCompact) { Write-Host "Compressing '$directory' directory" $compressionResult = & compact /s:"$directory" /c /a /i /EXE:LZX * $compressionResult | Select-Object -Last 3 } $ErrorActionPreference = $ErrorActionPreviousValue $finish = get-date $time = "$(($finish - $start).Minutes):$(($finish - $start).Seconds)" Write-Host "The process took a total of $time (in minutes:seconds)" } ================================================ FILE: images/windows/scripts/build/Post-Build-Validation.ps1 ================================================ ################################################################################ ## File: post-build-validation.sh ## Desc: Validate different aspects of the image after build ################################################################################ Write-Host "Test Microsoft Defender not set up using 'sc query sense'" $response = sc query sense foreach ($item in $response) { if ($item -match "STATE") { $state = $item.Split(":")[1].Trim() if ($state -notmatch "RUNNING") { Write-Host "MDE is not running" } else { Write-Host "MDE is running" exit 1 } } } Write-Host "Test Microsoft Defender not set up by checking for the MDE extension" if (Test-Path -Path "C:\Packages\Plugins\Microsoft.Azure.AzureDefenderForServers.MDE.Windows") { Write-Error "MDE extension detected, MDE is more likely installed on the system" exit 1 } else { Write-Host "MDE is not setup on the system" } ================================================ FILE: images/windows/scripts/docs-gen/Generate-SoftwareReport.ps1 ================================================ using module ./software-report-base/SoftwareReport.psm1 using module ./software-report-base/SoftwareReport.Nodes.psm1 $global:ErrorActionPreference = "Stop" $global:ProgressPreference = "SilentlyContinue" $ErrorView = "NormalView" Set-StrictMode -Version Latest Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Android.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Browsers.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.CachedTools.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Common.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Databases.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Helpers.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Tools.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Java.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.WebServers.psm1") -DisableNameChecking Import-Module (Join-Path $PSScriptRoot "SoftwareReport.VisualStudio.psm1") -DisableNameChecking # Software report $softwareReport = [SoftwareReport]::new($(Build-OSInfoSection)) $optionalFeatures = $softwareReport.Root.AddHeader("Windows features") $optionalFeatures.AddToolVersion("Windows Subsystem for Linux (WSLv1):", "Enabled") if (Test-IsWin25) { $optionalFeatures.AddToolVersion("Windows Subsystem for Linux (Default, WSLv2):", $(Get-WSL2Version)) } $installedSoftware = $softwareReport.Root.AddHeader("Installed Software") # Language and Runtime $languageAndRuntime = $installedSoftware.AddHeader("Language and Runtime") $languageAndRuntime.AddToolVersion("Bash", $(Get-BashVersion)) $languageAndRuntime.AddToolVersion("Go", $(Get-GoVersion)) $languageAndRuntime.AddToolVersion("Julia", $(Get-JuliaVersion)) $languageAndRuntime.AddToolVersion("Kotlin", $(Get-KotlinVersion)) $languageAndRuntime.AddToolVersion("LLVM", $(Get-LLVMVersion)) $languageAndRuntime.AddToolVersion("Node", $(Get-NodeVersion)) $languageAndRuntime.AddToolVersion("Perl", $(Get-PerlVersion)) $languageAndRuntime.AddToolVersion("PHP", $(Get-PHPVersion)) $languageAndRuntime.AddToolVersion("Python", $(Get-PythonVersion)) $languageAndRuntime.AddToolVersion("Ruby", $(Get-RubyVersion)) # Package Management $packageManagement = $installedSoftware.AddHeader("Package Management") $packageManagement.AddToolVersion("Chocolatey", $(Get-ChocoVersion)) $packageManagement.AddToolVersion("Composer", $(Get-ComposerVersion)) $packageManagement.AddToolVersion("Helm", $(Get-HelmVersion)) $packageManagement.AddToolVersion("Miniconda", $(Get-CondaVersion)) $packageManagement.AddToolVersion("NPM", $(Get-NPMVersion)) $packageManagement.AddToolVersion("NuGet", $(Get-NugetVersion)) $packageManagement.AddToolVersion("pip", $(Get-PipVersion)) $packageManagement.AddToolVersion("Pipx", $(Get-PipxVersion)) $packageManagement.AddToolVersion("RubyGems", $(Get-RubyGemsVersion)) $packageManagement.AddToolVersion("Vcpkg", $(Get-VcpkgVersion)) $packageManagement.AddToolVersion("Yarn", $(Get-YarnVersion)) $packageManagement.AddHeader("Environment variables").AddTable($(Build-PackageManagementEnvironmentTable)) # Project Management $projectManagement = $installedSoftware.AddHeader("Project Management") $projectManagement.AddToolVersion("Ant", $(Get-AntVersion)) $projectManagement.AddToolVersion("Gradle", $(Get-GradleVersion)) $projectManagement.AddToolVersion("Maven", $(Get-MavenVersion)) $projectManagement.AddToolVersion("sbt", $(Get-SbtVersion)) # Tools $tools = $installedSoftware.AddHeader("Tools") $tools.AddToolVersion("7zip", $(Get-7zipVersion)) $tools.AddToolVersion("aria2", $(Get-Aria2Version)) $tools.AddToolVersion("azcopy", $(Get-AzCopyVersion)) $tools.AddToolVersion("Bazel", $(Get-BazelVersion)) $tools.AddToolVersion("Bazelisk", $(Get-BazeliskVersion)) $tools.AddToolVersion("Bicep", $(Get-BicepVersion)) $tools.AddToolVersion("Cabal", $(Get-CabalVersion)) $tools.AddToolVersion("CMake", $(Get-CMakeVersion)) $tools.AddToolVersion("CodeQL Action Bundle", $(Get-CodeQLBundleVersion)) $tools.AddToolVersion("Docker", $(Get-DockerVersion)) $tools.AddToolVersion("Docker Compose v2", $(Get-DockerComposeVersionV2)) $tools.AddToolVersion("Docker-wincred", $(Get-DockerWincredVersion)) $tools.AddToolVersion("ghc", $(Get-GHCVersion)) $tools.AddToolVersion("Git", $(Get-GitVersion)) $tools.AddToolVersion("Git LFS", $(Get-GitLFSVersion)) $tools.AddToolVersion("ImageMagick", $(Get-ImageMagickVersion)) $tools.AddToolVersion("InnoSetup", $(Get-InnoSetupVersion)) $tools.AddToolVersion("jq", $(Get-JQVersion)) $tools.AddToolVersion("Kind", $(Get-KindVersion)) $tools.AddToolVersion("Kubectl", $(Get-KubectlVersion)) if (-not (Test-IsWin25)) { $tools.AddToolVersion("Mercurial", $(Get-MercurialVersion)) } $tools.AddToolVersion("gcc", $(Get-GCCVersion)) $tools.AddToolVersion("gdb", $(Get-GDBVersion)) $tools.AddToolVersion("GNU Binutils", $(Get-GNUBinutilsVersion)) $tools.AddToolVersion("Newman", $(Get-NewmanVersion)) if (-not (Test-IsWin25)) { $tools.AddToolVersion("NSIS", $(Get-NSISVersion)) } $tools.AddToolVersion("OpenSSL", $(Get-OpenSSLVersion)) $tools.AddToolVersion("Packer", $(Get-PackerVersion)) $tools.AddToolVersion("Pulumi", $(Get-PulumiVersion)) $tools.AddToolVersion("R", $(Get-RVersion)) $tools.AddToolVersion("Service Fabric SDK", $(Get-ServiceFabricSDKVersion)) $tools.AddToolVersion("Stack", $(Get-StackVersion)) if (-not (Test-IsWin25)) { $tools.AddToolVersion("Subversion (SVN)", $(Get-SVNVersion)) } $tools.AddToolVersion("Swig", $(Get-SwigVersion)) $tools.AddToolVersion("VSWhere", $(Get-VSWhereVersion)) $tools.AddToolVersion("WinAppDriver", $(Get-WinAppDriver)) $tools.AddToolVersion("WiX Toolset", $(Get-WixVersion)) $tools.AddToolVersion("yamllint", $(Get-YAMLLintVersion)) $tools.AddToolVersion("zstd", $(Get-ZstdVersion)) $tools.AddToolVersion("Ninja", $(Get-NinjaVersion)) # CLI Tools $cliTools = $installedSoftware.AddHeader("CLI Tools") if (-not (Test-IsWin25)) { $cliTools.AddToolVersion("Alibaba Cloud CLI", $(Get-AlibabaCLIVersion)) } $cliTools.AddToolVersion("AWS CLI", $(Get-AWSCLIVersion)) $cliTools.AddToolVersion("AWS SAM CLI", $(Get-AWSSAMVersion)) $cliTools.AddToolVersion("AWS Session Manager CLI", $(Get-AWSSessionManagerVersion)) $cliTools.AddToolVersion("Azure CLI", $(Get-AzureCLIVersion)) $cliTools.AddToolVersion("Azure DevOps CLI extension", $(Get-AzureDevopsExtVersion)) $cliTools.AddToolVersion("GitHub CLI", $(Get-GHVersion)) # Rust Tools Initialize-RustEnvironment $rustTools = $installedSoftware.AddHeader("Rust Tools") $rustTools.AddToolVersion("Cargo", $(Get-RustCargoVersion)) $rustTools.AddToolVersion("Rust", $(Get-RustVersion)) $rustTools.AddToolVersion("Rustdoc", $(Get-RustdocVersion)) $rustTools.AddToolVersion("Rustup", $(Get-RustupVersion)) $rustToolsPackages = $rustTools.AddHeader("Packages") if (-not (Test-IsWin25)) { $rustToolsPackages.AddToolVersion("bindgen", $(Get-BindgenVersion)) $rustToolsPackages.AddToolVersion("cargo-audit", $(Get-CargoAuditVersion)) $rustToolsPackages.AddToolVersion("cargo-outdated", $(Get-CargoOutdatedVersion)) $rustToolsPackages.AddToolVersion("cbindgen", $(Get-CbindgenVersion)) } $rustToolsPackages.AddToolVersion("Clippy", $(Get-RustClippyVersion)) $rustToolsPackages.AddToolVersion("Rustfmt", $(Get-RustfmtVersion)) # Browsers and Drivers $browsersAndWebdrivers = $installedSoftware.AddHeader("Browsers and Drivers") $browsersAndWebdrivers.AddNodes($(Build-BrowserSection)) $browsersAndWebdrivers.AddHeader("Environment variables").AddTable($(Build-BrowserWebdriversEnvironmentTable)) # Java $installedSoftware.AddHeader("Java").AddTable($(Get-JavaVersions)) # Shells $installedSoftware.AddHeader("Shells").AddTable($(Get-ShellTarget)) # MSYS2 $msys2 = $installedSoftware.AddHeader("MSYS2") $msys2.AddToolVersion("Pacman", $(Get-PacmanVersion)) $notes = @' Location: C:\msys64 Note: MSYS2 is pre-installed on image but not added to PATH. '@ $msys2.AddHeader("Notes").AddNote($notes) # Cached Tools $installedSoftware.AddHeader("Cached Tools").AddNodes($(Build-CachedToolsSection)) # Databases $databases = $installedSoftware.AddHeader("Databases") $databases.AddHeader("PostgreSQL").AddTable($(Get-PostgreSQLTable)) $databases.AddHeader("MongoDB").AddTable($(Get-MongoDBTable)) # Database tools $databaseTools = $installedSoftware.AddHeader("Database tools") $databaseTools.AddToolVersion("Azure CosmosDb Emulator", $(Get-AzCosmosDBEmulatorVersion)) $databaseTools.AddToolVersion("DacFx", $(Get-DacFxVersion)) $databaseTools.AddToolVersion("MySQL", $(Get-MySQLVersion)) $databaseTools.AddToolVersion("SQL OLEDB Driver 18", $(Get-SQLOLEDBDriver18Version)) $databaseTools.AddToolVersion("SQL OLEDB Driver 19", $(Get-SQLOLEDBDriver19Version)) $databaseTools.AddToolVersion("SQLPS", $(Get-SQLPSVersion)) $databaseTools.AddToolVersion("MongoDB Shell (mongosh)", $(Get-MongoshVersion)) # Web Servers $installedSoftware.AddHeader("Web Servers").AddTable($(Build-WebServersSection)) # Visual Studio $vsTable = Get-VisualStudioVersion $visualStudio = $installedSoftware.AddHeader($vsTable.Name) $visualStudio.AddTable($vsTable) $workloads = $visualStudio.AddHeader("Workloads, components and extensions") $workloads.AddTable((Get-VisualStudioComponents) + (Get-VisualStudioExtensions)) $msVisualCpp = $visualStudio.AddHeader("Microsoft Visual C++") $msVisualCpp.AddTable($(Get-VisualCPPComponents)) $visualStudio.AddToolVersionsList("Installed Windows SDKs", $(Get-WindowsSDKs).Versions, '^.+') # .NET Core Tools $netCoreTools = $installedSoftware.AddHeader(".NET Core Tools") $netCoreTools.AddToolVersionsListInline(".NET Core SDK", $(Get-DotnetSdks).Versions, '^\d+\.\d+\.\d{3}') $netCoreTools.AddToolVersionsListInline(".NET Framework", $(Get-DotnetFrameworkVersions), '^.+') Get-DotnetRuntimes | ForEach-Object { $netCoreTools.AddToolVersionsListInline($_.Runtime, $_.Versions, '^.+') } $netCoreTools.AddNodes($(Get-DotnetTools)) # PowerShell Tools $psTools = $installedSoftware.AddHeader("PowerShell Tools") $psTools.AddToolVersion("PowerShell", $(Get-PowershellCoreVersion)) $psModules = $psTools.AddHeader("Powershell Modules") $psModules.AddNodes($(Get-PowerShellModules)) # Android $android = $installedSoftware.AddHeader("Android") $android.AddTable($(Build-AndroidTable)) $android.AddHeader("Environment variables").AddTable($(Build-AndroidEnvironmentTable)) # Cached Docker images if (-not (Test-IsWin25)) { $installedSoftware.AddHeader("Cached Docker images").AddTable($(Get-CachedDockerImagesTableData)) } # Generate reports $softwareReport.ToJson() | Out-File -FilePath "C:\software-report.json" -Encoding UTF8NoBOM $softwareReport.ToMarkdown() | Out-File -FilePath "C:\software-report.md" -Encoding UTF8NoBOM ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.Android.psm1 ================================================ Import-Module (Join-Path $PSScriptRoot "SoftwareReport.Helpers.psm1") -DisableNameChecking function Split-TableRowByColumns { param( [string] $Row ) return $Row.Split("|") | ForEach-Object { $_.trim() } } function Build-AndroidTable { $packageInfo = Get-AndroidInstalledPackages return @( @{ "Package" = "Android Command Line Tools" "Version" = Get-AndroidCommandLineToolsVersion }, @{ "Package" = "Android Emulator" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android Emulator" }, @{ "Package" = "Android SDK Build-tools" "Version" = Get-AndroidBuildToolVersions -PackageInfo $packageInfo }, @{ "Package" = "Android SDK Platforms" "Version" = Get-AndroidPlatformVersions -PackageInfo $packageInfo }, @{ "Package" = "Android SDK Platform-Tools" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android SDK Platform-Tools" }, @{ "Package" = "Android Support Repository" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Android Support Repository" }, @{ "Package" = "CMake" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "cmake" }, @{ "Package" = "Google APIs" "Version" = Get-AndroidGoogleAPIsVersions -PackageInfo $packageInfo }, @{ "Package" = "Google Play services" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Google Play services" }, @{ "Package" = "Google Repository" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "Google Repository" }, @{ "Package" = "NDK" "Version" = Get-AndroidNdkVersions -PackageInfo $packageInfo }, @{ "Package" = "SDK Patch Applier v4" "Version" = Get-AndroidPackageVersions -PackageInfo $packageInfo -MatchedString "SDK Patch Applier v4" } ) | Where-Object { $_.Version } | ForEach-Object { [PSCustomObject] @{ "Package Name" = $_.Package "Version" = $_.Version } } } function Get-AndroidPackageVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo, [Parameter(Mandatory)] [object] $MatchedString ) $versions = $packageInfo | Where-Object { $_ -Match $MatchedString } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[1] } return ($versions -Join "
") } function Get-AndroidPlatformVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $packageInfo | Where-Object { $_ -Match "Android SDK Platform " } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ $revision = $packageInfoParts[1] $version = $packageInfoParts[0].split(";")[1] return "$version (rev $revision)" } [array]::Reverse($versions) return ($versions -Join "
") } function Get-AndroidCommandLineToolsVersion { $commandLineTools = (Join-Path $env:ANDROID_HOME "cmdline-tools\latest\bin\sdkmanager.bat") (cmd /c "$commandLineTools --version 2>NUL" | Out-String).Trim() -match "(?^(\d+\.){1,}\d+$)" | Out-Null $commandLineToolsVersion = $Matches.Version return $commandLineToolsVersion } function Get-AndroidBuildToolVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $packageInfo | Where-Object { $_ -Match "Android SDK Build-Tools" } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[1] } $groupVersions = @() $versions | ForEach-Object { $majorVersion = $_.Split(".")[0] $groupVersions += $versions | Where-Object { $_.StartsWith($majorVersion) } | Join-String -Separator " " } return ($groupVersions | Sort-Object -Descending -Unique | Join-String -Separator "
") } function Get-AndroidGoogleAPIsVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $versions = $packageInfo | Where-Object { $_ -Match "addon-google_apis" } | ForEach-Object { $packageInfoParts = Split-TableRowByColumns $_ return $packageInfoParts[0].split(";")[1] } return ($versions -Join "
") } function Get-AndroidNdkVersions { param ( [Parameter(Mandatory)] [object] $PackageInfo ) $ndkDefaultFullVersion = Get-ChildItem $env:ANDROID_NDK_HOME -Name $versions = $packageInfo | Where-Object { $_ -Match "ndk;" } | ForEach-Object { $version = (Split-TableRowByColumns $_)[1] if ($version -eq $ndkDefaultFullVersion) { $version += " (default)" } $version } return ($versions -Join "
") } function Build-AndroidEnvironmentTable { $androidVersions = Get-Item env:ANDROID_* $shoulddResolveLink = 'ANDROID_NDK', 'ANDROID_NDK_HOME', 'ANDROID_NDK_ROOT', 'ANDROID_NDK_LATEST_HOME' return $androidVersions | Sort-Object -Property Name | ForEach-Object { [PSCustomObject] @{ "Name" = $_.Name "Value" = if ($shoulddResolveLink.Contains($_.Name )) { Get-PathWithLink($_.Value) } else {$_.Value} } } } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.Browsers.psm1 ================================================ $browsers = @{ chrome = @{ Name="Google Chrome"; File="chrome.exe" }; edge = @{ Name="Microsoft Edge"; File="msedge.exe" }; firefox = @{ Name="Mozilla Firefox"; File="firefox.exe" } } $webDrivers = @{ chrome = @{ Name="Chrome Driver"; Path="C:\SeleniumWebDrivers\ChromeDriver" }; edge = @{ Name="Microsoft Edge Driver"; Path="C:\SeleniumWebDrivers\EdgeDriver" }; firefox = @{ Name="Gecko Driver"; Path="C:\SeleniumWebDrivers\GeckoDriver" }; iexplorer = @{ Name="IE Driver"; Path="C:\SeleniumWebDrivers\IEDriver" } } function Build-BrowserSection { return @( $(Get-BrowserVersion -Browser "chrome"), $(Get-SeleniumWebDriverVersion -Driver "chrome"), $(Get-BrowserVersion -Browser "edge"), $(Get-SeleniumWebDriverVersion -Driver "edge"), $(Get-BrowserVersion -Browser "firefox"), $(Get-SeleniumWebDriverVersion -Driver "firefox"), $(Get-SeleniumWebDriverVersion -Driver "iexplorer"), $(Get-SeleniumVersion) ) } function Get-BrowserVersion { param( [string] $Browser ) $browserName = $browsers.$Browser.Name $browserFile = $browsers.$Browser.File $registryKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\$browserFile" $browserVersion = (Get-Item (Get-ItemProperty $registryKey)."(Default)").VersionInfo.FileVersion return [ToolVersionNode]::new($browserName, $browserVersion) } function Get-SeleniumWebDriverVersion { param( [string] $Driver ) $driverName = $webDrivers.$Driver.Name $driverPath = $webDrivers.$Driver.Path $versionFileName = "versioninfo.txt"; $webDriverVersion = Get-Content -Path "$driverPath\$versionFileName" return [ToolVersionNode]::new($driverName, $webDriverVersion) } function Get-SeleniumVersion { $seleniumBinaryName = "selenium-server" $fullSeleniumVersion = (Get-ChildItem "C:\selenium\${seleniumBinaryName}-*").Name -replace "${seleniumBinaryName}-" return [ToolVersionNode]::new("Selenium server", $fullSeleniumVersion) } function Build-BrowserWebdriversEnvironmentTable { return @( @{ "Name" = "CHROMEWEBDRIVER" "Value" = $env:CHROMEWEBDRIVER }, @{ "Name" = "EDGEWEBDRIVER" "Value" = $env:EDGEWEBDRIVER }, @{ "Name" = "GECKOWEBDRIVER" "Value" = $env:GECKOWEBDRIVER }, @{ "Name" = "SELENIUM_JAR_PATH" "Value" = $env:SELENIUM_JAR_PATH } ) | ForEach-Object { [PSCustomObject] @{ "Name" = $_.Name "Value" = $_.Value } } } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.CachedTools.psm1 ================================================ function Get-ToolcacheGoVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "Go" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version] $_ } } function Get-ToolcacheNodeVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "Node" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version] $_ } } function Get-ToolcachePythonVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "Python" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version] $_ } } function Get-ToolcacheRubyVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "Ruby" return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version] $_ } } function Get-ToolcachePyPyVersions { $toolcachePath = Join-Path $env:AGENT_TOOLSDIRECTORY "PyPy" Get-ChildItem -Path $toolcachePath -Name | Sort-Object { [Version] $_ } | ForEach-Object { $pypyRootPath = Join-Path $toolcachePath $_ "x86" [string] $pypyVersionOutput = & "$pypyRootPath\python.exe" -c "import sys;print(sys.version)" $pypyVersionOutput -match "^([\d\.]+) \(.+\) \[PyPy ([\d\.]+\S*) .+]$" | Out-Null return "{0} [PyPy {1}]" -f $Matches[1], $Matches[2] } } function Build-CachedToolsSection { return @( [ToolVersionsListNode]::new("Go", $(Get-ToolcacheGoVersions), '^\d+\.\d+', 'List'), [ToolVersionsListNode]::new("Node.js", $(Get-ToolcacheNodeVersions), '^\d+', 'List'), [ToolVersionsListNode]::new("Python", $(Get-ToolcachePythonVersions), '^\d+\.\d+', 'List'), [ToolVersionsListNode]::new("PyPy", $(Get-ToolcachePyPyVersions), '^\d+\.\d+', 'List'), [ToolVersionsListNode]::new("Ruby", $(Get-ToolcacheRubyVersions), '^\d+\.\d+', 'List') ) } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.Common.psm1 ================================================ function Initialize-RustEnvironment { $env:RUSTUP_HOME = "C:\Users\Default\.rustup" $env:CARGO_HOME = "C:\Users\Default\.cargo" $env:Path += ";$env:CARGO_HOME\bin" } function Get-OSName { return (Get-CimInstance -ClassName Win32_OperatingSystem).Caption | Get-StringPart -Part 1,2,3 } function Get-OSVersion { $OSVersion = (Get-CimInstance -ClassName Win32_OperatingSystem).Version $OSBuild = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' UBR).UBR return "$OSVersion Build $OSBuild" } function Build-OSInfoSection { $osInfoNode = [HeaderNode]::new($(Get-OSName)) $osInfoNode.AddToolVersion("OS Version:", $(Get-OSVersion)) $osInfoNode.AddToolVersion("Image Version:", $env:IMAGE_VERSION) return $osInfoNode } function Get-BashVersion { bash --% -c 'echo ${BASH_VERSION}' } function Get-RustVersion { rustc --version | Get-StringPart -Part 1 } function Get-RustupVersion { cmd /c "rustup --version 2>NUL" | Get-StringPart -Part 1 } function Get-RustCargoVersion { cargo --version | Get-StringPart -Part 1 } function Get-RustdocVersion { rustdoc --version | Get-StringPart -Part 1 } function Get-RustfmtVersion { rustfmt --version | Get-StringPart -Part 1 | Get-StringPart -Part 0 -Delimiter ('-') } function Get-RustClippyVersion { cargo clippy --version | Get-StringPart -Part 1 } function Get-BindgenVersion { bindgen --version | Get-StringPart -Part 1 } function Get-CbindgenVersion { cbindgen --version | Get-StringPart -Part 1 } function Get-CargoAuditVersion { cargo-audit --version | Get-StringPart -Part 1 } function Get-CargoOutdatedVersion { cargo outdated --version | Get-StringPart -Part 1 } function Get-PythonVersion { python --version | Get-StringPart -Part 1 } function Get-PowershellCoreVersion { pwsh --version | Get-StringPart -Part 1 } function Get-RubyVersion { ruby --version | Get-StringPart -Part 1 } function Get-GoVersion { go version | Get-StringPart -Part 2 | Get-StringPart -Part 1 -Delimiter ('o') } function Get-KotlinVersion { cmd /c "kotlinc -version 2>&1" | Get-StringPart -Part 2 } function Get-PHPVersion { php --version | Out-String | Get-StringPart -Part 1 } function Get-JuliaVersion { julia --version | Get-StringPart -Part 2 } function Get-LLVMVersion { (clang --version) -match "clang" | Get-StringPart -Part 2 } function Get-PerlVersion { ($(perl --version) | Out-String) -match "\(v(?\d+\.\d+\.\d+)\)" | Out-Null $perlVersion = $Matches.Version return $perlVersion } function Get-NodeVersion { node --version | Get-StringPart -Part 0 -Delimiter ('v') } function Get-ChocoVersion { choco --version } function Get-VcpkgVersion { $commitId = git -C "C:\vcpkg" rev-parse --short HEAD return "(build from commit $commitId)" } function Get-NPMVersion { npm -version } function Get-YarnVersion { yarn -version } function Get-RubyGemsVersion { gem --version } function Get-HelmVersion { ($(helm version --short) | Out-String) -match "v(?\d+\.\d+\.\d+)" | Out-Null $helmVersion = $Matches.Version return $helmVersion } function Get-PipVersion { (pip --version) -match "pip" | Get-StringPart -Part 1, 4, 5 } function Get-CondaVersion { $condaVersion = ((& "$env:CONDA\Scripts\conda.exe" --version) -replace "^conda").Trim() return "$condaVersion (pre-installed on the image but not added to PATH)" } function Get-ComposerVersion { composer --version | Get-StringPart -Part 2 } function Get-NugetVersion { (nuget help) -match "Nuget Version" | Get-StringPart -Part 2 } function Get-AntVersion { ant -version | Get-StringPart -Part 3 } function Get-MavenVersion { (mvn -version) -match "Apache Maven" | Get-StringPart -Part 2 } function Get-GradleVersion { ($(gradle -version) | Out-String) -match "Gradle (?\d+\.\d+)" | Out-Null $gradleVersion = $Matches.Version return $gradleVersion } function Get-SbtVersion { sbt --script-version } function Get-DotnetSdks { $sdksRawList = dotnet --list-sdks $sdkVersions = $sdksRawList | Foreach-Object { $_.Split()[0] } $sdkPath = $sdksRawList[0].Split(' ', 2)[1] -replace '\[|]' [PSCustomObject]@{ Versions = $sdkVersions Path = $sdkPath } } function Get-DotnetTools { $env:Path += ";C:\Users\Default\.dotnet\tools" $dotnetTools = (Get-ToolsetContent).dotnet.tools $toolsList = @() foreach ($dotnetTool in $dotnetTools) { $version = Invoke-Expression $dotnetTool.getversion $toolsList += [ToolVersionNode]::new($dotnetTool.name, $version) } return $toolsList } function Get-DotnetRuntimes { $runtimesRawList = dotnet --list-runtimes $runtimesRawList | Group-Object { $_.Split()[0] } | ForEach-Object { $runtimeName = $_.Name $runtimeVersions = $_.Group | Foreach-Object { $_.split()[1] } $runtimePath = $_.Group[0].Split(' ', 3)[2] -replace '\[|]' [PSCustomObject]@{ "Runtime" = $runtimeName "Versions" = $runtimeVersions "Path" = $runtimePath } } } function Get-DotnetFrameworkVersions { $path = "${env:ProgramFiles(x86)}\Microsoft SDKs\Windows\*\*\NETFX * Tools" return Get-ChildItem -Path $path -Directory | ForEach-Object { $_.Name | Get-StringPart -Part 1 } } function Get-PowerShellAzureModules { [Array] $result = @() $defaultAzureModuleVersion = "12.5.0" [Array] $azInstalledModules = Get-ChildItem -Path "C:\Modules\az_*" -Directory | ForEach-Object { $_.Name.Split("_")[1] } if ($azInstalledModules.Count -gt 0) { $result += [ToolVersionsListNode]::new("Az", $($azInstalledModules), '^\d+\.\d+', "Inline") } [Array] $azureInstalledModules = Get-ChildItem -Path "C:\Modules\azure_*" -Directory | ForEach-Object { $_.Name.Split("_")[1] } | ForEach-Object { if ($_ -eq $defaultAzureModuleVersion) { "$($_) (Default)" } else { $_ } } if ($azureInstalledModules.Count -gt 0) { $result += [ToolVersionsListNode]::new("Azure", $($azureInstalledModules), '^\d+\.\d+', "Inline") } [Array] $azurermInstalledModules = Get-ChildItem -Path "C:\Modules\azurerm_*" -Directory | ForEach-Object { $_.Name.Split("_")[1] } | ForEach-Object { if ($_ -eq $defaultAzureModuleVersion) { "$($_) (Default)" } else { $_ } } if ($azurermInstalledModules.Count -gt 0) { $result += [ToolVersionsListNode]::new("AzureRM", $($azurermInstalledModules), '^\d+\.\d+', "Inline") } return $result } function Get-PowerShellModules { [Array] $result = @() $result += Get-PowerShellAzureModules $result += (Get-ToolsetContent).powershellModules.name | Sort-Object | ForEach-Object { $moduleName = $_ $moduleVersions = Get-Module -Name $moduleName -ListAvailable | Select-Object -ExpandProperty Version | Sort-Object -Unique return [ToolVersionsListNode]::new($moduleName, $moduleVersions, '^\d+', "Inline") } return $result } function Get-CachedDockerImagesTableData { $allImages = docker images --digests --format "*{{.Repository}}:{{.Tag}}|{{.Digest}} |{{.CreatedAt}}" if (-not $allImages) { return $null } $allImages.Split("*") | Where-Object { $_ } | ForEach-Object { $parts = $_.Split("|") [PSCustomObject] @{ "Repository:Tag" = $parts[0] "Digest" = $parts[1] "Created" = $parts[2].split(' ')[0] } } | Sort-Object -Property "Repository:Tag" } function Get-ShellTarget { return Get-ChildItem C:\shells -File | Select-Object Name, @{n = "Target"; e = { if ($_.Name -eq "msys2bash.cmd") { "C:\msys64\usr\bin\bash.exe" } else { @($_.Target)[0] } } } | Sort-Object Name } function Get-PacmanVersion { $msys2BinDir = "C:\msys64\usr\bin" $pacmanPath = Join-Path $msys2BinDir "pacman.exe" $rawVersion = & $pacmanPath --version $rawVersion.Split([System.Environment]::NewLine)[1] -match "\d+\.\d+(\.\d+)?" | Out-Null $pacmanVersion = $matches[0] return $pacmanVersion } function Get-YAMLLintVersion { yamllint --version | Get-StringPart -Part 1 } function Get-PipxVersion { pipx --version } function Build-PackageManagementEnvironmentTable { return @( [PSCustomObject] @{ "Name" = "VCPKG_INSTALLATION_ROOT" "Value" = $env:VCPKG_INSTALLATION_ROOT }, [PSCustomObject] @{ "Name" = "CONDA" "Value" = $env:CONDA } ) } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.Databases.psm1 ================================================ function Get-PostgreSQLTable { $pgService = Get-CimInstance Win32_Service -Filter "Name LIKE 'postgresql-%'" $pgPath = $pgService.PathName $pgRoot = $pgPath.split('"')[1].replace("\bin\pg_ctl.exe", "") $env:Path += ";${env:PGBIN}" $pgVersion = (postgres --version).split()[2].Trim() return @( [PSCustomObject]@{ Property = "ServiceName"; Value = $pgService.Name }, [PSCustomObject]@{ Property = "Version"; Value = $pgVersion }, [PSCustomObject]@{ Property = "ServiceStatus"; Value = $pgService.State }, [PSCustomObject]@{ Property = "ServiceStartType"; Value = $pgService.StartMode }, [PSCustomObject]@{ Property = "EnvironmentVariables"; Value = "`PGBIN=$env:PGBIN`
`PGDATA=$env:PGDATA`
`PGROOT=$env:PGROOT` " }, [PSCustomObject]@{ Property = "Path"; Value = $pgRoot }, [PSCustomObject]@{ Property = "UserName"; Value = $env:PGUSER }, [PSCustomObject]@{ Property = "Password"; Value = $env:PGPASSWORD } ) } function Get-MongoDBTable { $name = "MongoDB" $command = "mongod" $mongoService = Get-Service -Name $name $mongoVersion = (Get-Command -Name $command).Version.ToString() return [PSCustomObject]@{ Version = $mongoVersion ServiceName = $name ServiceStatus = $mongoService.Status ServiceStartType = $mongoService.StartType } } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.Helpers.psm1 ================================================ function Get-LinkTarget { param ( [string] $inputPath ) $link = Get-Item $inputPath | Select-Object -ExpandProperty Target if ($link) { return " -> $link" } return "" } function Get-PathWithLink { param ( [string] $inputPath ) $link = Get-LinkTarget($inputPath) return "${inputPath}${link}" } function Get-StringPart { param ( [Parameter(ValueFromPipeline)] [string] $toolOutput, [string] $Delimiter = " ", [int[]] $Part ) $parts = $toolOutput.Split($Delimiter, [System.StringSplitOptions]::RemoveEmptyEntries) $selectedParts = $parts[$Part] return [string]::Join($Delimiter, $selectedParts) } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.Java.psm1 ================================================ function Get-JavaVersions { $defaultJavaPath = $env:JAVA_HOME $javaVersions = Get-Item env:JAVA_HOME_*_X64 $sortRules = @{ Expression = { [Int32] $_.Name.Split("_")[2] } Descending = $false } return $javaVersions | Sort-Object $sortRules | ForEach-Object { $javaPath = $_.Value # Take semver from the java path # The path contains '-' sign in the version number instead of '+' due to the following issue, need to substitute it back https://github.com/actions/runner-images/issues/3014 $versionInPath = (Split-Path $javaPath) -replace "\w:\\.*\\" $version = $versionInPath -replace '-', '+' $defaultPostfix = ($javaPath -eq $defaultJavaPath) ? " (default)" : "" [PSCustomObject] @{ "Version" = $version + $defaultPostfix "Environment Variable" = $_.Name } } } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.Tools.psm1 ================================================ function Get-Aria2Version { (aria2c -v | Out-String) -match "(?(\d+\.){1,}\d+)" | Out-Null $aria2Version = $Matches.Version return $aria2Version } function Get-AzCosmosDBEmulatorVersion { $regKey = gci HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\* | gp | ? { $_.DisplayName -eq 'Azure Cosmos DB Emulator' } $installDir = $regKey.InstallLocation $exeFilePath = Join-Path $installDir 'CosmosDB.Emulator.exe' $version = (Get-Item $exeFilePath).VersionInfo.FileVersion return $version } function Get-BazelVersion { ((cmd /c "bazel --version 2>&1") | Out-String) -match "bazel (?\d+\.\d+\.\d+)" | Out-Null $bazelVersion = $Matches.Version return $bazelVersion } function Get-BazeliskVersion { ((cmd /c "bazelisk version 2>&1") | Out-String) -match "Bazelisk version: v(?\d+\.\d+\.\d+)" | Out-Null $bazeliskVersion = $Matches.Version return $bazeliskVersion } function Get-BicepVersion { (bicep --version | Out-String) -match "bicep cli version (?\d+\.\d+\.\d+)" | Out-Null $bicepVersion = $Matches.Version return $bicepVersion } function Get-RVersion { ($(cmd /c "Rscript --version 2>&1") | Out-String) -match "Rscript .* version (?\d+\.\d+\.\d+)" | Out-Null $rVersion = $Matches.Version return $rVersion } function Get-CMakeVersion { ($(cmake -version) | Out-String) -match "cmake version (?\d+\.\d+\.\d+)" | Out-Null $cmakeVersion = $Matches.Version return $cmakeVersion } function Get-CodeQLBundleVersion { $CodeQLVersionsWildcard = Join-Path $env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath "*" $CodeQLVersionPath = Get-ChildItem $CodeQLVersionsWildcard | Select-Object -First 1 -Expand FullName $CodeQLPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "codeql.exe" $CodeQLVersion = & $CodeQLPath version --quiet return $CodeQLVersion } function Get-DockerVersion { $dockerVersion = $(docker version --format "{{.Server.Version}}") return $dockerVersion } function Get-DockerComposeVersionV2 { $dockerComposeVersion = docker compose version --short return $dockerComposeVersion } function Get-DockerWincredVersion { $dockerCredVersion = docker-credential-wincred version | Get-StringPart -Part 2 | Get-StringPart -Part 0 -Delimiter "v" return $dockerCredVersion } function Get-GitVersion { $gitVersion = git --version | Get-StringPart -Part -1 return $gitVersion } function Get-GitLFSVersion { $(git-lfs version) -match "git-lfs\/(?\d+\.\d+\.\d+)" | Out-Null $gitLfsVersion = $Matches.Version return $gitLfsVersion } function Get-InnoSetupVersion { $innoSetupVersion = $(choco list innosetup) | Select-String -Pattern "InnoSetup" return ($innoSetupVersion -replace "^InnoSetup").Trim() } function Get-JQVersion { $jqVersion = ($(jq --version) -Split "jq-")[1] return $jqVersion } function Get-KubectlVersion { $kubectlVersion = (kubectl version --client --output=json | ConvertFrom-Json).clientVersion.gitVersion.Replace('v', '') return $kubectlVersion } function Get-KindVersion { $(kind version) -match "kind v(?\d+\.\d+\.\d+)" | Out-Null $kindVersion = $Matches.Version return $kindVersion } function Get-GCCVersion { (gcc --version | Select-String -Pattern "gcc.exe") -match "(?\d+\.\d+\.\d+)" | Out-Null $mingwVersion = $Matches.Version return $mingwVersion } function Get-GDBVersion { (gdb --version | Select-String -Pattern "GNU gdb") -match "(?\d+\.\d+)" | Out-Null $mingwVersion = $Matches.Version return $mingwVersion } function Get-GNUBinutilsVersion { (ld --version | Select-String -Pattern "GNU Binutils") -match "(?\d+\.\d+)" | Out-Null $mingwVersion = $Matches.Version return $mingwVersion } function Get-MySQLVersion { $mysqlCommand = Get-Command -Name "mysql" $mysqlVersion = $mysqlCommand.Version.ToString() return $mysqlVersion } function Get-SQLOLEDBDriver18Version { $SQLOLEDBDriverVersion = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSOLEDBSQL' InstalledVersion).InstalledVersion return $SQLOLEDBDriverVersion } function Get-SQLOLEDBDriver19Version { $SQLOLEDBDriverVersion = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSOLEDBSQL19' InstalledVersion).InstalledVersion return $SQLOLEDBDriverVersion } function Get-MercurialVersion { ($(hg --version) | Out-String) -match "version (?\d+\.\d+\.?\d*)" | Out-Null $mercurialVersion = $Matches.Version return $mercurialVersion } function Get-NSISVersion { $nsisVersion = & "c:\Program Files (x86)\NSIS\makensis.exe" "/Version" return $nsisVersion.TrimStart("v") } function Get-OpenSSLVersion { $(openssl version) -match "OpenSSL (?\d+\.\d+\.\d+\w?) " | Out-Null $opensslVersion = $Matches.Version return $opensslVersion } function Get-PackerVersion { $packerVersion = (packer --version | Select-String "^Packer").Line.Replace('v','') | Get-StringPart -Part 1 return $packerVersion } function Get-PulumiVersion { return (pulumi version).TrimStart("v") } function Get-SQLPSVersion { $module = Get-Module -Name SQLPS -ListAvailable $version = $module.Version return $version } function Get-SVNVersion { $svnVersion = $(svn --version --quiet) return $svnVersion } function Get-VSWhereVersion { ($(Get-Command -Name vswhere).FileVersionInfo.ProductVersion) -match "(?\d+\.\d+\.\d+)" | Out-Null $vswhereVersion = $Matches.Version return $vswhereVersion } function Get-WinAppDriver { $winAppDriverVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe").FileVersion return $winAppDriverVersion } function Get-WixVersion { $regKey = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" $installedApplications = Get-ItemProperty -Path $regKey $wixToolsetVersion = ($installedApplications | Where-Object { $_.BundleCachePath -imatch ".*\\WiX\d*\.exe$" } | Select-Object -First 1).DisplayName return ($wixToolsetVersion -replace "^WiX Toolset v").Trim() } function Get-ZstdVersion { $(zstd --version) -match "v(?\d+\.\d+\.\d+)" | Out-Null $zstdVersion = $Matches.Version return $zstdVersion } function Get-AzureCLIVersion { $azureCLIVersion = $(az version) | ConvertFrom-Json | Foreach{ $_."azure-cli" } return $azureCLIVersion } function Get-AzCopyVersion { return ($(azcopy --version) -replace "^azcopy version").Trim() } function Get-AzureDevopsExtVersion { $azureDevExtVersion = (az version | ConvertFrom-Json | ForEach-Object { $_."extensions" })."azure-devops" return $azureDevExtVersion } function Get-AWSCLIVersion { $(aws --version) -match "aws-cli\/(?\d+\.\d+\.\d+)" | Out-Null $awscliVersion = $Matches.Version return $awscliVersion } function Get-AWSSAMVersion { $(sam --version) -match "version (?\d+\.\d+\.\d+)" | Out-Null $awssamVersion = $Matches.Version return $awssamVersion } function Get-AWSSessionManagerVersion { $awsSessionManagerVersion = $(session-manager-plugin --version) return $awsSessionManagerVersion } function Get-AlibabaCLIVersion { $alicliVersion = $(aliyun version) return $alicliVersion } function Get-7zipVersion { (7z | Out-String) -match "7-Zip (?\d+\.\d+\.?\d*)" | Out-Null $version = $Matches.Version return $version } function Get-GHCVersion { ((ghc --version) | Out-String) -match "version (?\d+\.\d+\.\d+)" | Out-Null $ghcVersion = $Matches.Version return $ghcVersion } function Get-CabalVersion { ((cabal --version) | Out-String) -match "version (?\d+\.\d+\.\d+\.\d+)" | Out-Null $cabalVersion = $Matches.Version return $cabalVersion } function Get-StackVersion { ((stack --version --quiet) | Out-String) -match "Version (?\d+\.\d+\.\d+)," | Out-Null $stackVersion = $Matches.Version return $stackVersion } function Get-ServiceFabricSDKVersion { $serviceFabricSDKVersion = Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Service Fabric\' -Name FabricVersion return $serviceFabricSDKVersion } function Get-NewmanVersion { return $(newman --version) } function Get-GHVersion { ($(gh --version) | Select-String -Pattern "gh version") -match "gh version (?\d+\.\d+\.\d+)" | Out-Null $ghVersion = $Matches.Version return $ghVersion } function Get-VisualCPPComponents { $regKeys = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" ) $vcpp = Get-ItemProperty -Path $regKeys | Where-Object DisplayName -like "Microsoft Visual C++*" $vcpp | Sort-Object DisplayName, DisplayVersion | ForEach-Object { $isMatch = $_.DisplayName -match "^(?Microsoft Visual C\+\+ \d{4})\s+(?\w{3})\s+(?.+)\s+-" if ($isMatch) { $name = '{0} {1}' -f $matches["Name"], $matches["Ext"] $arch = $matches["Arch"].ToLower() $version = $_.DisplayVersion [PSCustomObject]@{ Name = $name Architecture = $arch Version = $version } } } } function Get-DacFxVersion { $dacfxversion = & "$env:ProgramFiles\Microsoft SQL Server\170\DAC\bin\sqlpackage.exe" /version return $dacfxversion } function Get-SwigVersion { (swig -version | Out-String) -match "version (?\d+\.\d+\.\d+)" | Out-Null $swigVersion = $Matches.Version return $swigVersion } function Get-ImageMagickVersion { (magick -version | Select-String -Pattern "Version") -match "(?\d+\.\d+\.\d+-\d+)" | Out-Null $magickVersion = $Matches.Version return $magickVersion } function Get-MongoshVersion { return $(mongosh --version) } function Get-WSL2Version { return $((Get-AppxPackage -Name "MicrosoftCorporationII.WindowsSubsystemForLinux").version) } function Get-NinjaVersion { return $(ninja --version) } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.VisualStudio.psm1 ================================================ function Get-VisualStudioVersion { $vsInstance = Get-VisualStudioInstance [PSCustomObject]@{ Name = $vsInstance.DisplayName Version = $vsInstance.InstallationVersion Path = $vsInstance.InstallationPath } } function Get-SDKVersion { $regKey = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" $installedApplications = Get-ItemProperty -Path $regKey ($installedApplications | Where-Object { $_.DisplayName -eq 'Windows SDK' } | Select-Object -First 1).DisplayVersion } function Get-WDKVersion { $regKey = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" $installedApplications = Get-ItemProperty -Path $regKey ($installedApplications | Where-Object { $_.DisplayName -eq 'Windows Driver Kit' } | Select-Object -First 1).DisplayVersion } function Get-VisualStudioExtensions { $vsPackages = (Get-VisualStudioInstance).Packages # Additional vsixs $toolset = Get-ToolsetContent $vsixPackagesList = $toolset.visualStudio.vsix if ($vsixPackagesList) { $vsixs = $vsixPackagesList | ForEach-Object { $vsixPackage = Get-VsixInfoFromMarketplace $_ $vsixVersion = ($vsPackages | Where-Object { $_.Id -match $vsixPackage.VsixId -and $_.type -eq 'vsix' }).Version @{ Package = $vsixPackage.ExtensionName Version = $vsixVersion } } } # SDK $sdkVersion = Get-SDKVersion $sdkPackages = @( @{Package = 'Windows Software Development Kit'; Version = $sdkVersion } ) # WDK if (-not (Test-IsWin25)) { $wdkVersion = Get-WDKVersion $wdkPackages = @( @{Package = 'Windows Driver Kit'; Version = $wdkVersion } ) } # WDK extension $wdkExtensionVersion = Get-VSExtensionVersion -packageName 'Microsoft.Windows.DriverKit' $wdkExtensions = @( @{Package = 'Windows Driver Kit Visual Studio Extension'; Version = $wdkExtensionVersion } ) $extensions = @( $vsixs $sdkPackages $wdkPackages $wdkExtensions ) $extensions | Foreach-Object { [PSCustomObject] $_ } | Select-Object Package, Version | Sort-Object Package } function Get-WindowsSDKs { $path = "${env:ProgramFiles(x86)}\Windows Kits\10\Extension SDKs\WindowsDesktop" return [PSCustomObject]@{ Path = $path Versions = $(Get-ChildItem $path).Name } } ================================================ FILE: images/windows/scripts/docs-gen/SoftwareReport.WebServers.psm1 ================================================ function Get-ApachePath { return Join-Path "C:\tools\" (Get-Item C:\tools\apache*).Name } function Get-NginxPath { return Join-Path "C:\tools\" (Get-Item C:\tools\nginx*).Name } function Get-ApacheVersion { $apacheBinPath = Join-Path (Get-ApachePath) "\bin\httpd" (. $apacheBinPath -V | Select-String -Pattern "Apache/") -match "Apache/(?\d+\.\d+\.\d+)" | Out-Null return $Matches.Version } function Get-NginxVersion { $nginxBinPath = Join-Path (Get-NginxPath) "nginx" (cmd /c "$nginxBinPath -v 2>&1") -match "nginx/(?\d+\.\d+\.\d+)" | Out-Null return $Matches.Version } function Get-ApacheSection { $name = "Apache" $apachePort = "80" $apacheService = Get-Service -Name $name $apacheVersion = Get-ApacheVersion $apacheConfigFile = Join-Path (Get-ApachePath) "\conf\httpd.conf" return [PSCustomObject]@{ Name = $name Version = $apacheVersion ConfigFile = $apacheConfigFile ServiceName = $apacheService.Name ServiceStatus = $apacheService.Status ListenPort = $apachePort } } function Get-NginxSection { $name = "Nginx" $nginxPort = "80" $nginxService = Get-Service -Name $name $nginxVersion = Get-NginxVersion $nginxConfigFile = Join-Path (Get-NginxPath) "\conf\nginx.conf" return [PSCustomObject]@{ Name = $name Version = $nginxVersion ConfigFile = $nginxConfigFile ServiceName = $nginxService.Name ServiceStatus = $nginxService.Status ListenPort = $nginxPort } } function Build-WebServersSection { return @( (Get-ApacheSection), (Get-NginxSection) ) } ================================================ FILE: images/windows/scripts/helpers/AndroidHelpers.ps1 ================================================ function Get-AndroidPackages { <# .SYNOPSIS This function returns a list of available Android packages. .DESCRIPTION The Get-AndroidPackages function checks if a list of packages is already available in a file. If not, it uses the sdkmanager.bat script to generate a list of available packages and saves it to a file. It then returns the content of this file. .PARAMETER SDKRootPath The root path of the Android SDK installation. If not specified, the function uses the ANDROID_HOME environment variable. .EXAMPLE Get-AndroidPackages -SDKRootPath "C:\Android\SDK" This command returns a list of available Android packages for the specified SDK root path. .NOTES This function requires the Android SDK to be installed and the sdkmanager.bat script to be accessible. #> Param ( [string] $SDKRootPath ) if (-not $SDKRootPath) { $SDKRootPath = $env:ANDROID_HOME } $packagesListFile = "$SDKRootPath\packages-list.txt" $sdkManager = "$SDKRootPath\cmdline-tools\latest\bin\sdkmanager.bat" if (-Not (Test-Path -Path $packagesListFile -PathType Leaf)) { (cmd /c "$sdkManager --list --verbose 2>&1") | Where-Object { $_ -Match "^[^\s]" } | Where-Object { $_ -NotMatch "^(Loading |Info: Parsing |---|\[=+|Installed |Available )" } | Where-Object { $_ -NotMatch "^[^;]*$" } | Out-File -FilePath $packagesListFile } return Get-Content $packagesListFile } function Get-AndroidPlatformPackages { <# .SYNOPSIS This function returns a list of available Android platform packages. .DESCRIPTION The Get-AndroidPlatformPackages function uses the Get-AndroidPackages function to get a list of available packages and filters it to return only platform packages. .PARAMETER SDKRootPath The root path of the Android SDK installation. If not specified, the function uses the ANDROID_HOME environment variable. .PARAMETER minimumVersion The minimum version of the platform packages to include in the result. Default is 0. .EXAMPLE Get-AndroidPlatformPackages -SDKRootPath "C:\Android\SDK" -minimumVersion 29 This command returns a list of available Android platform packages for the specified SDK root path with a minimum version of 29. .NOTES This function requires the Android SDK to be installed and the sdkmanager.bat script to be accessible. #> Param ( [string] $SDKRootPath, [Alias("minVersion")] [int] $minimumVersion = 0 ) if (-not $SDKRootPath) { $SDKRootPath = $env:ANDROID_HOME } return (Get-AndroidPackages -SDKRootPath $SDKRootPath) ` | Where-Object { "$_".StartsWith("platforms;") } ` | Where-Object { ($_.Split("-")[1] -as [int]) -ge $minimumVersion } ` | Sort-Object -Unique } function Get-AndroidBuildToolPackages { <# .SYNOPSIS This function returns a list of available Android build tool packages. .DESCRIPTION The Get-AndroidBuildToolPackages function uses the Get-AndroidPackages function to get a list of available packages and filters it to return only build tool packages. .PARAMETER SDKRootPath The root path of the Android SDK installation. If not specified, the function uses the ANDROID_HOME environment variable. .PARAMETER minimumVersion The minimum version of the build tool packages to include in the result. Default is 0.0.0. .EXAMPLE Get-AndroidBuildToolPackages -SDKRootPath "C:\Android\SDK" -minimumVersion "30.0.2" This command returns a list of available Android build tool packages for the specified SDK root path with a minimum version of 30.0.2. .NOTES This function requires the Android SDK to be installed and the sdkmanager.bat script to be accessible. #> Param ( [string] $SDKRootPath, [Alias("minVersion")] [version] $minimumVersion = "0.0.0" ) if (-not $SDKRootPath) { $SDKRootPath = $env:ANDROID_HOME } return (Get-AndroidPackages -SDKRootPath $SDKRootPath) ` | Where-Object { "$_".StartsWith("build-tools;") } ` | Where-Object { ($_.Split(";")[1] -as [version]) -ge $minimumVersion } ` | Sort-Object -Unique } function Get-AndroidInstalledPackages { <# .SYNOPSIS Retrieves a list of installed Android packages. .DESCRIPTION This function retrieves a list of installed Android packages using the specified SDK root path. .PARAMETER SDKRootPath The root path of the Android SDK. If not specified, the function uses the ANDROID_HOME environment variable. .EXAMPLE Get-AndroidInstalledPackages -SDKRootPath "C:\Android\SDK" Retrieves a list of installed Android packages using the specified SDK root path. .NOTES This function requires the Android SDK to be installed and the SDK root path to be provided. #> Param ( [string] $SDKRootPath ) if (-not $SDKRootPath) { $SDKRootPath = $env:ANDROID_HOME } $sdkManager = "$SDKRootPath\cmdline-tools\latest\bin\sdkmanager.bat" return (cmd /c "$sdkManager --list_installed 2>&1") -notmatch "Warning" } ================================================ FILE: images/windows/scripts/helpers/ChocoHelpers.ps1 ================================================ function Install-ChocoPackage { <# .SYNOPSIS A function to install a Chocolatey package with retries. .DESCRIPTION This function attempts to install a specified Chocolatey package. If the installation fails, it retries a specified number of times. .PARAMETER PackageName The name of the Chocolatey package to install. .PARAMETER ArgumentList An array of arguments to pass to the choco install command. .PARAMETER RetryCount The number of times to retry the installation if it fails. Default is 5. .PARAMETER Version The version of the package to install. .EXAMPLE Install-ChocoPackage -PackageName "git" -Version "2.39.2" -RetryCount 3 #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $PackageName, [string[]] $ArgumentList, [string] $Version, [int] $RetryCount = 5 ) process { $count = 1 while ($true) { Write-Host "Running [#$count]: choco install $packageName -y $argumentList" if ($Version) { choco install $packageName --version $Version -y @ArgumentList --no-progress --require-checksums } else { choco install $packageName -y @ArgumentList --no-progress --require-checksums } $pkg = choco list --localonly $packageName --exact --all --limitoutput if ($pkg) { Write-Host "Package installed: $pkg" break } else { $count++ if ($count -ge $retryCount) { Write-Host "Could not install $packageName after $count attempts" exit 1 } Start-Sleep -Seconds 30 } } } } function Resolve-ChocoPackageVersion { <# .SYNOPSIS Resolves the latest version of a Chocolatey package. .DESCRIPTION This function takes a package name and a target version as input and returns the latest version of the package that is greater than or equal to the target version. .PARAMETER PackageName The name of the Chocolatey package. .PARAMETER TargetVersion The target version of the package. .EXAMPLE Resolve-ChocoPackageVersion -PackageName "example-package" -TargetVersion "1.0.0" Returns the latest version of the "example-package" that is greater than or equal to "1.0.0". #> param( [Parameter(Mandatory)] [string] $PackageName, [Parameter(Mandatory)] [string] $TargetVersion ) $searchResult = choco search $PackageName --exact --all-versions --approved-only --limit-output | ConvertFrom-CSV -Delimiter '|' -Header 'Name', 'Version' $latestVersion = $searchResult.Version | Where-Object { $_ -Like "$TargetVersion.*" -or $_ -eq $TargetVersion } | Sort-Object { [version] $_ } | Select-Object -Last 1 return $latestVersion } ================================================ FILE: images/windows/scripts/helpers/ImageHelpers.psd1 ================================================ @{ # Script module or binary module file associated with this manifest. RootModule = 'ImageHelpers.psm1' # Version number of this module. ModuleVersion = '0.0.1' # Supported PSEditions # CompatiblePSEditions = @() # ID used to uniquely identify this module GUID = 'c9334909-16a1-48f1-a94a-c7baf1b961d9' # Description of the functionality provided by this module Description = 'Helper functions for creating vsts images' # Minimum version of the Windows PowerShell engine required by this module # PowerShellVersion = '' # Name of the Windows PowerShell host required by this module # PowerShellHostName = '' # Minimum version of the Windows PowerShell host required by this module # PowerShellHostVersion = '' # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # DotNetFrameworkVersion = '' # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # CLRVersion = '' # Processor architecture (None, X86, Amd64) required by this module # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module # RequiredModules = @() # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() # Script files (.ps1) that are run in the caller's environment prior to importing this module. # ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module # FormatsToProcess = @() # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = '*' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. AliasesToExport = '*' # DSC resources to export from this module # DscResourcesToExport = @() # List of all modules packaged with this module # ModuleList = @() # List of all files packaged with this module # FileList = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. # Tags = @() # A URL to the license for this module. # LicenseUri = '' # A URL to the main website for this project. # ProjectUri = '' # A URL to an icon representing this module. # IconUri = '' # ReleaseNotes of this module # ReleaseNotes = '' } # End of PSData hashtable } # End of PrivateData hashtable # HelpInfo URI of this module # HelpInfoURI = '' # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' } ================================================ FILE: images/windows/scripts/helpers/ImageHelpers.psm1 ================================================ [CmdletBinding()] param() . $PSScriptRoot\AndroidHelpers.ps1 Export-ModuleMember -Function @( 'Get-AndroidPackages' 'Get-AndroidPlatformPackages' 'Get-AndroidBuildToolPackages' 'Get-AndroidInstalledPackages' ) . $PSScriptRoot\ChocoHelpers.ps1 Export-ModuleMember -Function @( 'Install-ChocoPackage' 'Resolve-ChocoPackageVersion' ) . $PSScriptRoot\InstallHelpers.ps1 Export-ModuleMember -Function @( 'Install-Binary' 'Invoke-DownloadWithRetry' 'Get-MicrosoftPublisher' 'Get-ToolsetContent' 'Get-TCToolPath' 'Get-TCToolVersionPath' 'Test-IsWin25' 'Test-IsWin22' 'Expand-7ZipArchive' 'Get-WindowsUpdateStates' 'Invoke-ScriptBlockWithRetry' 'Get-GithubReleasesByVersion' 'Resolve-GithubReleaseAssetUrl' 'Get-ChecksumFromGithubRelease' 'Get-ChecksumFromUrl' 'Test-FileChecksum' 'Test-FileSignature' 'Update-Environment' ) . $PSScriptRoot\PathHelpers.ps1 Export-ModuleMember -Function @( 'Mount-RegistryHive' 'Dismount-RegistryHive' 'Add-MachinePathItem' 'Add-DefaultPathItem' ) . $PSScriptRoot\VisualStudioHelpers.ps1 Export-ModuleMember -Function @( 'Install-VisualStudio' 'Get-VisualStudioInstance' 'Get-VisualStudioComponents' 'Get-VsixInfoFromMarketplace' 'Install-VSIXFromFile' 'Install-VSIXFromUrl' 'Get-VSExtensionVersion' ) ================================================ FILE: images/windows/scripts/helpers/InstallHelpers.ps1 ================================================ function Install-Binary { <# .SYNOPSIS A function to install binaries from either a URL or a local path. .DESCRIPTION This function downloads and installs .exe or .msi binaries from a specified URL or a local path. It also supports checking the binary's signature and SHA256/SHA512 sum before installation. .PARAMETER Url The URL from which the binary will be downloaded. This parameter is required if LocalPath is not specified. .PARAMETER LocalPath The local path of the binary to be installed. This parameter is required if Url is not specified. .PARAMETER Type The type of the binary to be installed. Valid values are "MSI" and "EXE". If not specified, the type is inferred from the file extension. .PARAMETER InstallArgs The list of arguments that will be passed to the installer. Cannot be used together with ExtraInstallArgs. .PARAMETER ExtraInstallArgs Additional arguments that will be passed to the installer. Cannot be used together with InstallArgs. .PARAMETER ExpectedSubject The expected signature subject of the binary. If specified, the binary's signature is checked before installation. .PARAMETER ExpectedSHA256Sum The expected SHA256 sum of the binary. If specified, the binary's SHA256 sum is checked before installation. .PARAMETER ExpectedSHA512Sum The expected SHA512 sum of the binary. If specified, the binary's SHA512 sum is checked before installation. .PARAMETER InstallerLogPath The path to the log file which is produced when the installation fails. This can be used for debugging purposes. This is only displayed when the installation fails. .EXAMPLE Install-Binary -Url "https://go.microsoft.com/fwlink/p/?linkid=2083338" -Type EXE -InstallArgs ("/features", "+", "/quiet") -ExpectedSubject "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" #> Param ( [Parameter(Mandatory, ParameterSetName = "Url")] [String] $Url, [Parameter(Mandatory, ParameterSetName = "LocalPath")] [String] $LocalPath, [ValidateSet("MSI", "EXE")] [String] $Type, [String[]] $InstallArgs, [String[]] $ExtraInstallArgs, [String] $ExpectedSubject, [String] $ExpectedSHA256Sum, [String] $ExpectedSHA512Sum, [String] $InstallerLogPath ) if ($PSCmdlet.ParameterSetName -eq "LocalPath") { if (-not (Test-Path -Path $LocalPath)) { throw "LocalPath parameter is specified, but the file does not exist." } if (-not $Type) { $Type = ([System.IO.Path]::GetExtension($LocalPath)).Replace(".", "").ToUpper() if ($Type -ne "MSI" -and $Type -ne "EXE") { throw "LocalPath parameter is specified, but the file extension is not .msi or .exe. Please specify the Type parameter." } } $filePath = $LocalPath } else { if (-not $Type) { $Type = ([System.IO.Path]::GetExtension($Url)).Replace(".", "").ToUpper() if ($Type -ne "MSI" -and $Type -ne "EXE") { throw "Cannot determine the file type from the URL. Please specify the Type parameter." } $fileName = [System.IO.Path]::GetFileName($Url) } else { $fileName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName()) + ".$Type".ToLower() } $filePath = Invoke-DownloadWithRetry -Url $Url -Path "${env:TEMP_DIR}\$fileName" } if ($PSBoundParameters.ContainsKey('ExpectedSubject')) { if ($ExpectedSubject) { Test-FileSignature -Path $filePath -ExpectedSubject $ExpectedSubject } else { throw "ExpectedSubject parameter is specified, but no value is provided." } } if ($ExpectedSHA256Sum) { Test-FileChecksum $filePath -ExpectedSHA256Sum $ExpectedSHA256Sum } if ($ExpectedSHA512Sum) { Test-FileChecksum $filePath -ExpectedSHA512Sum $ExpectedSHA512Sum } if ($ExtraInstallArgs -and $InstallArgs) { throw "InstallArgs and ExtraInstallArgs parameters cannot be used together." } if ($Type -eq "MSI") { # MSI binaries should be installed via msiexec.exe if ($ExtraInstallArgs) { $InstallArgs = @('/i', $filePath, '/qn', '/norestart') + $ExtraInstallArgs } elseif (-not $InstallArgs) { Write-Host "No arguments provided for MSI binary. Using default arguments: /i, /qn, /norestart" $InstallArgs = @('/i', $filePath, '/qn', '/norestart') } $filePath = "msiexec.exe" } else { # EXE binaries should be started directly if ($ExtraInstallArgs) { $InstallArgs = $ExtraInstallArgs } } $installStartTime = Get-Date Write-Host "Starting Install $Name..." try { $process = Start-Process -FilePath $filePath -ArgumentList $InstallArgs -Wait -PassThru $exitCode = $process.ExitCode $installCompleteTime = [math]::Round(($(Get-Date) - $installStartTime).TotalSeconds, 2) if ($exitCode -eq 0) { Write-Host "Installation successful in $installCompleteTime seconds" } elseif ($exitCode -eq 3010) { Write-Host "Installation successful in $installCompleteTime seconds. Reboot is required." } else { Write-Host "Installation process returned unexpected exit code: $exitCode" Write-Host "Time elapsed: $installCompleteTime seconds" if ($InstallerLogPath) { Write-Host "Searching for logs maching $InstallerLogPath pattern" Get-ChildItem -Recurse -Path $InstallerLogPath | ForEach-Object { Write-Output "Found Installer Log: $InstallerLogPath" Write-Output "File content:" Get-Content -Path $_.FullName } } exit $exitCode } } catch { $installCompleteTime = [math]::Round(($(Get-Date) - $installStartTime).TotalSeconds, 2) Write-Host "Installation failed in $installCompleteTime seconds" } } function Invoke-DownloadWithRetry { <# .SYNOPSIS Downloads a file from a given URL with retry functionality. .DESCRIPTION The Invoke-DownloadWithRetry function downloads a file from the specified URL to the specified path. It includes retry functionality in case the download fails. .PARAMETER Url The URL of the file to download. .PARAMETER Path The path where the downloaded file will be saved. If not provided, a temporary path will be used. .EXAMPLE Invoke-DownloadWithRetry -Url "https://example.com/file.zip" -Path "C:\Downloads\file.zip" Downloads the file from the specified URL and saves it to the specified path. .EXAMPLE Invoke-DownloadWithRetry -Url "https://example.com/file.zip" Downloads the file from the specified URL and saves it to a temporary path. .OUTPUTS The path where the downloaded file is saved. #> Param ( [Parameter(Mandatory)] [string] $Url, [Alias("Destination")] [string] $Path ) if (-not $Path) { $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join '' $re = "[{0}]" -f [RegEx]::Escape($invalidChars) $fileName = [IO.Path]::GetFileName($Url) -replace $re if ([String]::IsNullOrEmpty($fileName)) { $fileName = [System.IO.Path]::GetRandomFileName() } $Path = Join-Path -Path "${env:TEMP_DIR}" -ChildPath $fileName } Write-Host "Downloading package from $Url to $Path..." $interval = 30 $downloadStartTime = Get-Date for ($retries = 20; $retries -gt 0; $retries--) { try { $attemptStartTime = Get-Date $ProgressPreference = 'SilentlyContinue' Invoke-WebRequest -Uri $Url -OutFile $Path -UseBasicParsing $attemptSeconds = [math]::Round(($(Get-Date) - $attemptStartTime).TotalSeconds, 2) Write-Host "Package downloaded in $attemptSeconds seconds" break } catch { $attemptSeconds = [math]::Round(($(Get-Date) - $attemptStartTime).TotalSeconds, 2) Write-Warning "Package download failed in $attemptSeconds seconds" Write-Warning $_.Exception.Message if ($_.Exception.InnerException.Response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) { Write-Warning "Request returned 404 Not Found. Aborting download." $retries = 0 } } if ($retries -eq 0) { $totalSeconds = [math]::Round(($(Get-Date) - $downloadStartTime).TotalSeconds, 2) throw "Package download failed after $totalSeconds seconds" } Write-Warning "Waiting $interval seconds before retrying (retries left: $retries)..." Start-Sleep -Seconds $interval } return $Path } function Get-ToolsetContent { <# .SYNOPSIS Retrieves the content of the toolset.json file. .DESCRIPTION This function reads the toolset.json file in path provided by IMAGE_FOLDER environment variable and returns the content as a PowerShell object. #> $toolsetPath = Join-Path $env:IMAGE_FOLDER "toolset.json" $toolsetJson = Get-Content -Path $toolsetPath -Raw ConvertFrom-Json -InputObject $toolsetJson } function Get-TCToolPath { <# .SYNOPSIS This function returns the full path of a tool in the tool cache. .DESCRIPTION The Get-TCToolPath function takes a tool name as a parameter and returns the full path of the tool in the tool cache. It uses the AGENT_TOOLSDIRECTORY environment variable to determine the root path of the tool cache. .PARAMETER ToolName The name of the tool for which the path is to be returned. .EXAMPLE Get-TCToolPath -ToolName "Tool1" This command returns the full path of "Tool1" in the tool cache. #> Param ( [string] $ToolName ) $toolcacheRootPath = Resolve-Path $env:AGENT_TOOLSDIRECTORY return Join-Path $toolcacheRootPath $ToolName } function Get-TCToolVersionPath { <# .SYNOPSIS This function returns the full path of a specific version of a tool in the tool cache. .DESCRIPTION The Get-TCToolVersionPath function takes a tool name, version, and architecture as parameters and returns the full path of the specified version of the tool in the tool cache. It uses the Get-TCToolPath function to get the root path of the tool. .PARAMETER Name The name of the tool for which the path is to be returned. .PARAMETER Version The version of the tool for which the path is to be returned. If the version number is less than 3 parts, a wildcard is added. .PARAMETER Arch The architecture of the tool for which the path is to be returned. Defaults to "x64". .EXAMPLE Get-TCToolVersionPath -Name "Tool1" -Version "1.0" -Arch "x86" This command returns the full path of version "1.0" of "Tool1" for "x86" architecture in the tool cache. #> Param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [string] $Version, [string] $Arch = "x64" ) $toolPath = Get-TCToolPath -ToolName $Name # Add wildcard if missing if ($Version.Split(".").Length -lt 3) { $Version += ".*" } $versionPath = Join-Path $toolPath $Version # Take latest installed version in case if toolset version contains wildcards $foundVersion = Get-Item $versionPath ` | Sort-Object -Property { [version] $_.name } -Descending ` | Select-Object -First 1 if (-not $foundVersion) { return $null } return Join-Path $foundVersion $Arch } function Test-IsWin25 { <# .SYNOPSIS Checks if the current Windows operating system is Windows Server 2025. .DESCRIPTION This function uses the Get-CimInstance cmdlet to retrieve information about the current Windows operating system. It then checks if the Caption property of the Win32_OperatingSystem class contains the string "2025", indicating that the operating system is Windows Server 2025. .OUTPUTS Returns $true if the current Windows operating system is Windows Server 2025. Otherwise, returns $false. #> (Get-CimInstance -ClassName Win32_OperatingSystem).Caption -match "2025" } function Test-IsWin22 { <# .SYNOPSIS Checks if the current Windows operating system is Windows Server 2022. .DESCRIPTION This function uses the Get-CimInstance cmdlet to retrieve information about the current Windows operating system. It then checks if the Caption property of the Win32_OperatingSystem class contains the string "2022", indicating that the operating system is Windows Server 2022. .OUTPUTS Returns $true if the current Windows operating system is Windows Server 2022. Otherwise, returns $false. #> (Get-CimInstance -ClassName Win32_OperatingSystem).Caption -match "2022" } function Expand-7ZipArchive { <# .SYNOPSIS Extracts files from a 7-Zip archive. .DESCRIPTION This function uses the 7z.exe command-line tool to extract files from an archive. The archive path, destination path, and extract method are specified as parameters. .PARAMETER Path The path to the archive. .PARAMETER DestinationPath The path to the directory where the files will be extracted. .PARAMETER ExtractMethod The method used to extract the files. Valid values are "x" (extract with full paths) and "e" (extract without paths). .EXAMPLE Expand-7ZipArchive -Path "C:\archive.7z" -DestinationPath "C:\extracted" -ExtractMethod "x" Extracts files from the "C:\archive.7z" archive to the "C:\extracted" directory keeping the full paths. #> Param ( [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $true)] [string] $DestinationPath, [ValidateSet("x", "e")] [char] $ExtractMethod = "x" ) Write-Host "Expand archive '$PATH' to '$DestinationPath' directory" 7z.exe $ExtractMethod "$Path" -o"$DestinationPath" -y | Out-Null if ($LASTEXITCODE -ne 0) { Write-Host "There is an error during expanding '$Path' to '$DestinationPath' directory" exit 1 } } function Get-WindowsUpdateStates { <# .SYNOPSIS Retrieves the status of Windows updates. .DESCRIPTION The Get-WindowsUpdateStates function checks the Windows Event Log for specific event IDs related to Windows updates and returns a custom PowerShell object with the state and title of each update. .PARAMETER None This function does not take any parameters. .OUTPUTS PSCustomObject. This function returns a collection of custom PowerShell objects. Each object has two properties: - State: A string that represents the state of the update. Possible values are "Installed", "Failed", and "Running". - Title: A string that represents the title of the update. .NOTES Event IDs used: - 19: Installation Successful: Windows successfully installed the following update - 20: Installation Failure: Windows failed to install the following update with error - 43: Installation Started: Windows has started installing the following update #> $completedUpdates = @{} $filter = @{ LogName = "System" Id = 19, 20, 43 ProviderName = "Microsoft-Windows-WindowsUpdateClient" } $events = Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue | Sort-Object Id foreach ( $event in $events ) { switch ( $event.Id ) { 19 { $state = "Installed" $title = $event.Properties[0].Value $completedUpdates[$title] = $state break } 20 { $state = "Failed" $title = $event.Properties[1].Value if (-not $completedUpdates.ContainsKey($title)) { $completedUpdates[$title] = $state } break } 43 { $state = "Running" $title = $event.Properties[0].Value break } } # Skip Running update event if it was already completed if ( ($state -eq "Running") -and $completedUpdates.ContainsKey($title) ) { continue } # Skip Failed update event if it was already successfully installed if ( ($state -eq "Failed") -and $completedUpdates[$title] -eq "Installed" ) { continue } [PSCustomObject]@{ State = $state Title = $title } } } function Invoke-ScriptBlockWithRetry { <# .SYNOPSIS Executes a script block with retry logic. .DESCRIPTION The Invoke-ScriptBlockWithRetry function executes a specified script block with retry logic. It allows you to specify the number of retries and the interval between retries. .PARAMETER Command The script block to be executed. .PARAMETER RetryCount The number of times to retry executing the script block. The default value is 10. .PARAMETER RetryIntervalSeconds The interval in seconds between each retry. The default value is 5. .EXAMPLE Invoke-ScriptBlockWithRetry -Command { Get-Process } -RetryCount 3 -RetryIntervalSeconds 10 This example executes the script block { Get-Process } with 3 retries and a 10-second interval between each retry. #> param ( [scriptblock] $Command, [int] $RetryCount = 10, [int] $RetryIntervalSeconds = 5 ) while ($RetryCount -gt 0) { try { & $Command return } catch { Write-Host "There is an error encountered:`n $_" $RetryCount-- if ($RetryCount -eq 0) { exit 1 } Write-Host "Waiting $RetryIntervalSeconds seconds before retrying. Retries left: $RetryCount" Start-Sleep -Seconds $RetryIntervalSeconds } } } function Get-GithubReleasesByVersion { <# .SYNOPSIS Retrieves GitHub releases for a specified repository based on version. .DESCRIPTION The function retrieves GitHub releases for a specified repository based on the version provided. It supports filtering by version, allowing for the retrieval of specific releases or the latest release. The function utilizes the GitHub REST API to fetch the releases and caches the results to improve performance and reduce the number of API calls. .PARAMETER Repository The name of the GitHub repository in the format "owner/repo". .PARAMETER Version The version of the release to retrieve. It can be a specific version number, "latest" to retrieve the latest release, or a wildcard pattern to match multiple versions. .PARAMETER AllowPrerelease Specifies whether to include prerelease versions in the results. By default, prerelease versions are excluded. .PARAMETER WithAssetsOnly Specifies whether to exclude releases without assets. By default, releases without assets are included. .EXAMPLE Get-GithubReleasesByVersion -Repository "Microsoft/PowerShell" -Version "7.2.0" Retrieves the GitHub releases for the "Microsoft/PowerShell" repository with the version "7.2.0". .EXAMPLE Get-GithubReleasesByVersion -Repository "Microsoft/PowerShell" -Version "latest" Retrieves the latest GitHub release for the "Microsoft/PowerShell" repository. .EXAMPLE Get-GithubReleasesByVersion -Repository "Microsoft/PowerShell" -Version "7.*" Retrieves all GitHub releases for the "Microsoft/PowerShell" repository with versions starting with "7.". #> param ( [Parameter(Mandatory = $true)] [Alias("Repo")] [string] $Repository, [string] $Version = "*", [switch] $AllowPrerelease, [switch] $WithAssetsOnly ) $localCacheFile = Join-Path ${env:TEMP_DIR} "github-releases_$($Repository -replace "/", "_").json" if (Test-Path $localCacheFile) { $releases = Get-Content $localCacheFile | ConvertFrom-Json Write-Debug "Found cached releases for ${Repository} in local file" Write-Debug "Release count: $($releases.Count)" } else { $releases = @() $page = 1 $pageSize = 100 do { $releasesPage = Invoke-RestMethod -Uri "https://api.github.com/repos/${Repository}/releases?per_page=${pageSize}&page=${page}" $releases += $releasesPage $page++ } while ($releasesPage.Count -eq $pageSize) Write-Debug "Found $($releases.Count) releases for ${Repository}" Write-Debug "Caching releases for ${Repository} in local file" $releases | ConvertTo-Json -Depth 10 | Set-Content $localCacheFile } if (-not $releases) { throw "Failed to get releases from ${Repository}" } if ($WithAssetsOnly) { $releases = $releases.Where{ $_.assets } } if (-not $AllowPrerelease) { $releases = $releases.Where{ $_.prerelease -eq $false } } Write-Debug "Found $($releases.Count) releases with assets for ${Repository}" # Parse version from tag name and put it to parameter Version foreach ($release in $releases) { $release | Add-Member -MemberType NoteProperty -Name version -Value ( $release.tag_name | Select-String -Pattern "\d+.\d+.\d+" | ForEach-Object { $_.Matches.Value } ) } # Sort releases by version, then by tag name parts if version is the same $releases = $releases | Sort-Object -Descending { [version] $_.version }, { $cleanTagName = $_.tag_name -replace '^v', '' $parts = $cleanTagName -split '[.\-]' $parsedParts = $parts | ForEach-Object { if ($_ -match '^\d+$') { [int]$_ } else { $_ } } $parsedParts } # Select releases matching version if ($Version -eq "latest") { $matchingReleases = $releases | Select-Object -First 1 } elseif ($Version.Contains("*")) { $matchingReleases = $releases | Where-Object { $_.version -like "$Version" } } else { $matchingReleases = $releases | Where-Object { $_.version -eq "$Version" } } if (-not $matchingReleases) { throw "Failed to get releases from ${Repository} matching version `"${Version}`".`nAvailable versions: $($availableVersions -join ", ")" } Write-Debug "Found $($matchingReleases.Count) releases matching version ${Version} for ${Repository}" return $matchingReleases } function Resolve-GithubReleaseAssetUrl { <# .SYNOPSIS Resolves the download URL for a specific asset in a GitHub release. .DESCRIPTION This function retrieves the download URL for a specific asset in a GitHub release. It takes the repository name, version, and a URL match pattern as input parameters. It searches for releases that match the specified version and then looks for a download URL that matches the provided pattern. If a matching URL is found, it returns the URL. If no matching URL is found, an exception is thrown. .PARAMETER Repository The name of the GitHub repository in the format "owner/repo". .PARAMETER Version The version of the release to retrieve. It can be a specific version number, "latest" to retrieve the latest release, or a wildcard pattern to match multiple versions. .PARAMETER AllowPrerelease Specifies whether to include prerelease versions in the results. By default, prerelease versions are excluded. .PARAMETER UrlMatchPattern The pattern to match against the download URLs of the release assets. Wildcards (*) can be used to match any characters. .PARAMETER AllowMultipleMatches Specifies whether to choose one of multiple assets matching the pattern or consider this behavior to be erroneous. By default, multiple matches are not considered normal behavior and result in an error. .EXAMPLE Resolve-GithubReleaseAssetUrl -Repository "myrepo" -Version "1.0" -UrlMatchPattern "*.zip" Retrieves the download URL for the asset in the "myrepo" repository with version "1.0" and a file extension of ".zip". #> param ( [Parameter(Mandatory = $true)] [Alias("Repo")] [string] $Repository, [string] $Version = "*", [switch] $AllowPrerelease, [Parameter(Mandatory = $true)] [Alias("Pattern", "File", "Asset")] [string] $UrlMatchPattern, [switch] $AllowMultipleMatches = $false ) $matchingReleases = Get-GithubReleasesByVersion ` -Repository $Repository ` -AllowPrerelease:$AllowPrerelease ` -Version $Version ` -WithAssetsOnly # Add wildcard to the beginning of the pattern if it's not there if ($UrlMatchPattern.Substring(0, 2) -ne "*/") { $UrlMatchPattern = "*/$UrlMatchPattern" } # Loop over releases until we find a download url matching the pattern foreach ($release in $matchingReleases) { $matchedVersion = $release.version $matchedUrl = ([string[]] $release.assets.browser_download_url) -like $UrlMatchPattern if ($matchedUrl) { break } } if (-not $matchedUrl) { Write-Debug "Found no download urls matching pattern ${UrlMatchPattern}" Write-Debug "Available download urls:`n$($matchingReleases.assets.browser_download_url -join "`n")" throw "No assets found in ${Repository} matching version `"${Version}`" and pattern `"${UrlMatchPattern}`"" } # If multiple urls match the pattern, sort them and take the last one # Will only work with simple number series of no more than nine in a row. if ($matchedUrl.Count -gt 1) { if ($AllowMultipleMatches) { Write-Debug "Found multiple download urls matching pattern ${UrlMatchPattern}:`n$($matchedUrl -join "`n")" Write-Host "Performing sorting of urls to find the most recent version matching the pattern" $matchedUrl = $matchedUrl | Sort-Object -Descending $matchedUrl = $matchedUrl[0] } else { throw "Found multiple assets in ${Repository} matching version `"${Version}`" and pattern `"${UrlMatchPattern}`".`nAvailable assets:`n$($matchedUrl -join "`n")" } } Write-Host "Found download url for ${Repository} version ${matchedVersion}: ${matchedUrl}" return ($matchedUrl -as [string]) } function Get-ChecksumFromGithubRelease { <# .SYNOPSIS Retrieves the hash value of a specific file from a GitHub release body. .DESCRIPTION The Get-ChecksumFromGithubRelease function retrieves the hash value (SHA256 or SHA512) of a specific file from a GitHub release. It searches for the file in the release body and returns the hash value if found. .PARAMETER Repository The name of the GitHub repository in the format "owner/repo". .PARAMETER Version The version of the release to inspect. It can be a specific version number, "latest" to retrieve the latest release, or a wildcard pattern to match multiple versions. .PARAMETER AllowPrerelease Specifies whether to include prerelease versions in the results. By default, prerelease versions are excluded. .PARAMETER FileName The name of the file to retrieve the hash value for. .PARAMETER HashType The type of hash value to retrieve. Valid values are "SHA256" and "SHA512". .EXAMPLE Get-ChecksumFromGithubRelease -Repository "MyRepo" -FileName "myfile.txt" -HashType "SHA256" Retrieves the SHA256 hash value of "myfile.txt" from the latest release of the "MyRepo" repository. .EXAMPLE Get-ChecksumFromGithubRelease -Repository "MyRepo" -Version "1.0" -FileName "myfile.txt" -HashType "SHA512" Retrieves the SHA512 hash value of "myfile.txt" from the release version "1.0" of the "MyRepo" repository. #> param ( [Parameter(Mandatory = $true)] [Alias("Repo")] [string] $Repository, [string] $Version = "*", [switch] $AllowPrerelease, [Parameter(Mandatory = $true)] [Alias("File", "Asset")] [string] $FileName, [Parameter(Mandatory = $false)] [ValidateSet("SHA256", "SHA512")] [string] $HashType ) $matchingReleases = Get-GithubReleasesByVersion ` -Repository $Repository ` -AllowPrerelease:$AllowPrerelease ` -Version $Version ` -WithAssetsOnly foreach ($release in $matchingReleases) { $matchedVersion = $release.version $matchedBody = $release.body $matchedLine = $matchedBody.Split("`n") | Where-Object { $_ -like "*$FileName*" } if ($matchedLine.Count -gt 1) { throw "Found multiple lines matching file name '${FileName}' in body of release ${matchedVersion}." } elseif ($matchedLine.Count -ne 0) { break } } if (-not $matchedLine) { throw "File name '${FileName}' not found in release body." } Write-Debug "Found line matching file name '${FileName}' in body of release ${matchedVersion}:`n${matchedLine}" if ($HashType -eq "SHA256") { $pattern = "[A-Fa-f0-9]{64}" } elseif ($HashType -eq "SHA512") { $pattern = "[A-Fa-f0-9]{128}" } else { throw "Unknown hash type: ${HashType}" } $hash = $matchedLine | Select-String -Pattern $pattern | ForEach-Object { $_.Matches.Value } if ([string]::IsNullOrEmpty($hash)) { throw "Found '${FileName}' in body of release ${matchedVersion}, but failed to get hash from it.`nLine: ${matchedLine}" } Write-Host "Found hash for ${FileName} in release ${matchedVersion}: $hash" return $hash } function Get-ChecksumFromUrl { <# .SYNOPSIS Retrieves the checksum hash for a file from a given URL. .DESCRIPTION The Get-ChecksumFromUrl function retrieves the checksum hash for a specified file from a given URL. It supports SHA256 and SHA512 hash types. .PARAMETER Url The URL of the checksum file. .PARAMETER FileName The name of the file to retrieve the checksum hash for. .PARAMETER HashType The type of hash to retrieve. Valid values are "SHA256" and "SHA512". .EXAMPLE Get-ChecksumFromUrl -Url "https://example.com/checksums.txt" -FileName "file.txt" -HashType "SHA256" Retrieves the SHA256 checksum hash for the file "file.txt" from the URL "https://example.com/checksums.txt". #> param ( [Parameter(Mandatory = $true)] [string] $Url, [Parameter(Mandatory = $true)] [Alias("File", "Asset")] [string] $FileName, [Parameter(Mandatory = $false)] [ValidateSet("SHA256", "SHA512")] [Alias("Type")] [string] $HashType ) $tempFile = Join-Path -Path $env:TEMP_DIR -ChildPath ([System.IO.Path]::GetRandomFileName()) $checksums = (Invoke-DownloadWithRetry -Url $Url -Path $tempFile | Get-Item | Get-Content) -as [string[]] Remove-Item -Path $tempFile $matchedLine = $checksums | Where-Object { $_ -like "*$FileName*" } if ($matchedLine.Count -gt 1) { throw "Found multiple lines matching file name '${FileName}' in checksum file." } elseif ($matchedLine.Count -eq 0) { throw "File name '${FileName}' not found in checksum file." } if ($HashType -eq "SHA256") { $pattern = "[A-Fa-f0-9]{64}" } elseif ($HashType -eq "SHA512") { $pattern = "[A-Fa-f0-9]{128}" } else { throw "Unknown hash type: ${HashType}" } Write-Debug "Found line matching file name '${FileName}' in checksum file:`n${matchedLine}" $hash = $matchedLine | Select-String -Pattern $pattern | ForEach-Object { $_.Matches.Value } if ([string]::IsNullOrEmpty($hash)) { throw "Found '${FileName}' in checksum file, but failed to get hash from it.`nLine: ${matchedLine}" } Write-Host "Found hash for ${FileName} in checksum file: $hash" return $hash } function Test-FileChecksum { <# .SYNOPSIS Verifies the checksum of a file. .DESCRIPTION The Test-FileChecksum function verifies the SHA256 or SHA512 checksum of a file against an expected value. If the checksum does not match the expected value, the function throws an error. .PARAMETER Path The path to the file for which to verify the checksum. .PARAMETER ExpectedSHA256Sum The expected SHA256 checksum. If this parameter is provided, the function will calculate the SHA256 checksum of the file and compare it to this value. .PARAMETER ExpectedSHA512Sum The expected SHA512 checksum. If this parameter is provided, the function will calculate the SHA512 checksum of the file and compare it to this value. .EXAMPLE Test-FileChecksum -Path "C:\temp\file.txt" -ExpectedSHA256Sum "ABC123" Verifies that the SHA256 checksum of the file at C:\temp\file.txt is ABC123. .EXAMPLE Test-FileChecksum -Path "C:\temp\file.txt" -ExpectedSHA512Sum "DEF456" Verifies that the SHA512 checksum of the file at C:\temp\file.txt is DEF456. #> param ( [Parameter(Mandatory = $true, Position = 0)] [string] $Path, [Parameter(Mandatory = $false)] [String] $ExpectedSHA256Sum, [Parameter(Mandatory = $false)] [String] $ExpectedSHA512Sum ) Write-Verbose "Performing checksum verification" if ($ExpectedSHA256Sum -and $ExpectedSHA512Sum) { throw "Only one of the ExpectedSHA256Sum and ExpectedSHA512Sum parameters can be provided" } if (-not (Test-Path $Path)) { throw "File not found: $Path" } if ($ExpectedSHA256Sum) { $fileHash = (Get-FileHash -Path $Path -Algorithm SHA256).Hash $expectedHash = $ExpectedSHA256Sum } if ($ExpectedSHA512Sum) { $fileHash = (Get-FileHash -Path $Path -Algorithm SHA512).Hash $expectedHash = $ExpectedSHA512Sum } if ($fileHash -ne $expectedHash) { throw "Checksum verification failed: expected $expectedHash, got $fileHash" } else { Write-Verbose "Checksum verification passed" } } function Test-FileSignature { <# .SYNOPSIS Tests the file signature of a given file. .DESCRIPTION The Test-FileSignature function checks the signature of a file against the expected subject. It uses the Get-AuthenticodeSignature cmdlet to retrieve the signature information of the file. If the signature status is not valid or the subject of the signing certificate does not match the expected subject, an exception is thrown. .PARAMETER Path Specifies the path of the file to test. .PARAMETER ExpectedSubject Specifies the expected subject to match against the file's signature. .EXAMPLE Test-FileSignature -Path "C:\Path\To\File.exe" -ExpectedSubject "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" This example tests the signature of the file "C:\Path\To\File.exe" against the expected subject "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US". #> param( [Parameter(Mandatory = $true, Position = 0)] [string] $Path, [Parameter(Mandatory = $true, Position = 1)] [string] $ExpectedSubject ) $signature = Get-AuthenticodeSignature $Path if ($signature.Status -ne "Valid") { throw "Signature status is not valid. Status: $($signature.Status)" } if ($signature.SignerCertificate.EnhancedKeyUsageList.FriendlyName -notcontains "Code Signing") { throw "Certificate is not for code signing. Key usage: $($signature.SignerCertificate.EnhancedKeyUsageList)" } if ($signature.SignerCertificate.Subject -ne $ExpectedSubject) { throw "Certificate subject does not match. Subject: $($signature.SignerCertificate.Subject)" } Write-Output "Signature for $Path is valid" } function Update-Environment { <# .SYNOPSIS Updates the environment variables by reading values from the registry. .DESCRIPTION This function updates current environment by reading values from the registry. It is useful when you need to update the environment variables without restarting the current session. .NOTES The function requires administrative privileges to modify the system registry. #> $locations = @( 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'HKCU:\Environment' ) # Update PATH variable $pathItems = $locations | ForEach-Object { (Get-Item $_).GetValue('PATH').Split(';') } | Select-Object -Unique $env:PATH = $pathItems -join ';' # Update other variables $locations | ForEach-Object { $key = Get-Item $_ foreach ($name in $key.GetValueNames()) { $value = $key.GetValue($name) if (-not ($name -ieq 'PATH')) { Set-Item -Path Env:$name -Value $value } } } } function Get-MicrosoftPublisher { <# .SYNOPSIS Returns well-known subject for the Microsoft signing certificate #> return "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" } ================================================ FILE: images/windows/scripts/helpers/PathHelpers.ps1 ================================================ function Mount-RegistryHive { <# .SYNOPSIS Mounts a registry hive from a file. .DESCRIPTION The Mount-RegistryHive function loads a registry hive from a specified file into a specified subkey. .PARAMETER FileName The path to the file from which to load the registry hive. .PARAMETER SubKey The registry subkey into which to load the hive. .EXAMPLE Mount-RegistryHive -FileName "C:\Path\To\HiveFile.hiv" -SubKey "HKLM\SubKey" #> param( [Parameter(Mandatory = $true)] [string] $FileName, [Parameter(Mandatory = $true)] [string] $SubKey ) Write-Host "Loading the file $FileName to the Key $SubKey" if (Test-Path $SubKey.Replace("\", ":")) { Write-Warning "The key $SubKey is already loaded" return } $result = reg load $SubKey $FileName *>&1 if ($LASTEXITCODE -ne 0) { throw "Failed to load file $FileName to the key ${SubKey}: $result" } } function Dismount-RegistryHive { <# .SYNOPSIS Dismounts a registry hive. .DESCRIPTION The Dismount-RegistryHive function unloads a registry hive from a specified subkey. .PARAMETER SubKey The registry subkey from which to unload the hive. .EXAMPLE Dismount-RegistryHive -SubKey "HKLM\SubKey" #> param( [Parameter(Mandatory = $true)] [string] $SubKey ) Write-Host "Unloading the hive $SubKey" if (-not (Test-Path $SubKey.Replace("\", ":"))) { return } $result = reg unload $SubKey *>&1 if ($LASTEXITCODE -ne 0) { Write-Host "Failed to unload hive: $result" exit 1 } } function Add-MachinePathItem { <# .SYNOPSIS Adds a new item to the machine-level PATH environment variable. .DESCRIPTION The Add-MachinePathItem function adds a new item to the machine-level PATH environment variable. It takes a string parameter, $PathItem, which represents the new item to be added to the PATH. .PARAMETER PathItem Specifies the new item to be added to the machine-level PATH environment variable. .EXAMPLE Add-MachinePathItem -PathItem "C:\Program Files\MyApp" This example adds "C:\Program Files\MyApp" to the machine-level PATH environment variable. #> param( [Parameter(Mandatory = $true)] [string] $PathItem ) $currentPath = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") $newPath = $PathItem + ';' + $currentPath [Environment]::SetEnvironmentVariable("PATH", $newPath, "Machine") } function Add-DefaultPathItem { <# .SYNOPSIS Adds a path item to the default user profile path. .DESCRIPTION This function adds a specified path item to the default user profile path. It mounts the NTUSER.DAT file of the default user to the registry, retrieves the current value of the "Path" environment variable, appends the new path item to it, and updates the registry with the modified value. .PARAMETER PathItem The path item to be added to the default user profile path. .EXAMPLE Add-DefaultPathItem -PathItem "C:\Program Files\MyApp" This example adds "C:\Program Files\MyApp" to the default user profile path. .NOTES This function requires administrative privileges to modify the Windows registry. #> param( [Parameter(Mandatory = $true)] [string] $PathItem ) Mount-RegistryHive ` -FileName "C:\Users\Default\NTUSER.DAT" ` -SubKey "HKLM\DEFAULT" $key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("DEFAULT\Environment", $true) $currentValue = $key.GetValue("Path", "", "DoNotExpandEnvironmentNames") $updatedValue = $PathItem + ';' + $currentValue $key.SetValue("Path", $updatedValue, "ExpandString") $key.Handle.Close() [System.GC]::Collect() Dismount-RegistryHive "HKLM\DEFAULT" } ================================================ FILE: images/windows/scripts/helpers/VisualStudioHelpers.ps1 ================================================ Function Install-VisualStudio { <# .SYNOPSIS A helper function to install Visual Studio. .DESCRIPTION Prepare system environment, and install Visual Studio bootstrapper with selected workloads. .PARAMETER Version The version of Visual Studio that will be installed. Required parameter. .PARAMETER Edition The edition of Visual Studio that will be installed. Required parameter. .PARAMETER Channel The channel of Visual Studio that will be installed. Required parameter. .PARAMETER InstallChannelUri The InstallChannelUri of Visual Studio that will be installed. Optional parameter. .PARAMETER RequiredComponents The list of required components. Required parameter. .PARAMETER ExtraArgs The extra arguments to pass to the bootstrapper. Optional parameter. #> Param ( [Parameter(Mandatory)] [String] $Version, [Parameter(Mandatory)] [String] $Edition, [Parameter(Mandatory)] [String] $Channel, [String] $InstallChannelUri = "", [Parameter(Mandatory)] [String[]] $RequiredComponents, [String] $ExtraArgs = "" ) if ($env:INSTALL_VS_2026) { $bootstrapperUrl = "https://aka.ms/vs/postGRO-${Channel}/vs_${Edition}.exe" } else { $bootstrapperUrl = "https://aka.ms/vs/${Version}/postGRO-${Channel}/vs_${Edition}.exe" } $channelUri = "https://aka.ms/vs/${Version}/${Channel}/channel" $channelId = "VisualStudio.${Version}.Release" $productId = "Microsoft.VisualStudio.Product.${Edition}" if (-not [string]::IsNullOrEmpty($InstallChannelUri)) { $installChannelUri = $InstallChannelUri } else { $installChannelUri = $channelUri } Write-Host "Downloading Bootstrapper ..." $bootstrapperFilePath = Invoke-DownloadWithRetry $BootstrapperUrl # Verify that the bootstrapper is signed by Microsoft Test-FileSignature -Path $bootstrapperFilePath -ExpectedSubject $(Get-MicrosoftPublisher) try { $responseData = @{ "installChannelUri" = $installChannelUri "channelUri" = $channelUri "channelId" = $channelId "productId" = $productId "arch" = "x64" "add" = $RequiredComponents | ForEach-Object { "$_;includeRecommended" } } # Create json file with response data $responseDataPath = "$env:TEMP\vs_install_response.json" $responseData | ConvertTo-Json | Out-File -FilePath $responseDataPath $installStartTime = Get-Date Write-Host "Starting Install ..." $bootstrapperArgumentList = ('/c', $bootstrapperFilePath, '--in', $responseDataPath, $ExtraArgs, '--quiet', '--norestart', '--wait', '--nocache' ) Write-Host "Bootstrapper arguments: $bootstrapperArgumentList" $process = Start-Process -FilePath cmd.exe -ArgumentList $bootstrapperArgumentList -Wait -PassThru $exitCode = $process.ExitCode $installCompleteTime = [math]::Round(($(Get-Date) - $installStartTime).TotalSeconds, 2) if ($exitCode -eq 0) { Write-Host "Installation successful in $installCompleteTime seconds" return $exitCode } elseif ($exitCode -eq 3010) { Write-Host "Installation successful in $installCompleteTime seconds. Reboot is required." return $exitCode } else { Write-Host "Non zero exit code returned by the installation process : $exitCode" # Try to download tool to collect logs $collectExeUrl = "https://aka.ms/vscollect.exe" $collectExePath = Invoke-DownloadWithRetry -Url $collectExeUrl # Collect installation logs using the collect.exe tool and check if it is successful & "$collectExePath" if ($LastExitCode -ne 0) { Write-Host "Failed to collect logs using collect.exe tool. Exit code : $LastExitCode" exit $exitCode } # Expand the zip file Expand-Archive -Path "$env:TEMP_DIR\vslogs.zip" -DestinationPath "$env:TEMP_DIR\vslogs" # Print logs $vsLogsPath = "$env:TEMP_DIR\vslogs" $vsLogs = Get-ChildItem -Path $vsLogsPath -Recurse | Where-Object { -not $_.PSIsContainer } | Select-Object -ExpandProperty FullName foreach ($log in $vsLogs) { Write-Host "============================" Write-Host "== Log file : $log " Write-Host "============================" Get-Content -Path $log -ErrorAction Continue } exit $exitCode } } catch { Write-Host "Failed to install Visual Studio; $($_.Exception.Message)" exit -1 } } function Get-VisualStudioInstance { <# .SYNOPSIS Retrieves the Visual Studio instance. .DESCRIPTION This function retrieves the Visual Studio instance using the Get-VSSetupInstance cmdlet. It searches for both regular and preview versions of Visual Studio and returns the first instance found. #> # Use -Prerelease and -All flags to make sure that Preview versions of VS are found correctly $vsInstance = Get-VSSetupInstance -Prerelease -All | Where-Object { $_.DisplayName -match "Visual Studio" } | Select-Object -First 1 $vsInstance | Select-VSSetupInstance -Product * } function Get-VisualStudioComponents { <# .SYNOPSIS Retrieves the Visual Studio components. .DESCRIPTION This function retrieves the Visual Studio components by filtering the packages returned by Get-VisualStudioInstance cmdlet. It filters the packages based on their type, sorts them by Id and Version, and excludes packages with GUID-like Ids. #> (Get-VisualStudioInstance).Packages ` | Where-Object type -in 'Component', 'Workload' ` | Sort-Object Id, Version ` | Select-Object @{n = 'Package'; e = { $_.Id } }, Version ` | Where-Object { $_.Package -notmatch "[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}" } } function Get-VsixInfoFromMarketplace { <# .SYNOPSIS Retrieves information about a Visual Studio extension from the Visual Studio Marketplace. .DESCRIPTION The Get-VsixInfoFromMarketplace function retrieves information about a Visual Studio extension from the Visual Studio Marketplace. It takes the name of the extension as input and returns the extension's name, VsixId, filename, and download URI. .PARAMETER Name The name of the Visual Studio extension. .PARAMETER MarketplaceUri The URI of the Visual Studio Marketplace. Default value is "https://marketplace.visualstudio.com/items?itemName=". .EXAMPLE Get-VsixInfoFromMarketplace -Name "ProBITools.MicrosoftReportProjectsforVisualStudio2022" Retrieves information about the "ProBITools.MicrosoftReportProjectsforVisualStudio2022" extension from the Visual Studio Marketplace. #> Param ( [Parameter(Mandatory)] [Alias("ExtensionMarketPlaceName")] [string] $Name, [string] $MarketplaceUri = "https://marketplace.visualstudio.com/items?itemName=" ) # Invoke-WebRequest doesn't support retry in PowerShell 5.1 $webResponse = Invoke-ScriptBlockWithRetry -RetryCount 20 -RetryIntervalSeconds 30 -Command { Invoke-WebRequest -Uri "${MarketplaceUri}${Name}" -UseBasicParsing } $webResponse -match 'UniqueIdentifierValue":"(?[^"]*)' | Out-Null $extensionName = $Matches.extensionname $webResponse -match 'VsixId":"(?[^"]*)' | Out-Null $vsixId = $Matches.vsixid $webResponse -match 'AssetUri":"(?[^"]*)' | Out-Null $assetUri = $Matches.uri $webResponse -match 'Microsoft\.VisualStudio\.Services\.Payload\.FileName":"(?[^"]*)' | Out-Null $fileName = $Matches.filename switch ($Name) { # ProBITools.MicrosoftReportProjectsforVisualStudio2022 has different URL # https://github.com/actions/runner-images/issues/5340 "ProBITools.MicrosoftReportProjectsforVisualStudio2022" { $assetUri = "https://download.microsoft.com/download/1fd275d8-5163-476b-910b-e2f678b3fdbc" $fileName = "Microsoft.DataTools.ReportingServices.vsix" } "ProBITools.MicrosoftAnalysisServicesModelingProjects2022" { $assetUri = "https://download.microsoft.com/download/7c91cb5c-1e9c-4df7-a053-d2852e22c658" $fileName = "Microsoft.DataTools.AnalysisServices.vsix" } # Starting from version 4.1 SqlServerIntegrationServicesProjects extension is distributed as exe file "SSIS.SqlServerIntegrationServicesProjects" { $fileName = "Microsoft.DataTools.IntegrationServices.exe" } } $downloadUri = $assetUri + "/" + $fileName return [PSCustomObject] @{ "ExtensionName" = $extensionName "VsixId" = $vsixId "FileName" = $fileName "DownloadUri" = $downloadUri } } function Install-VSIXFromFile { <# .SYNOPSIS Installs a Visual Studio Extension (VSIX) from a file. .DESCRIPTION This function installs a Visual Studio Extension (VSIX) from the specified file path. It uses the VSIXInstaller.exe tool provided by Microsoft Visual Studio. .PARAMETER FilePath The path to the VSIX file that needs to be installed. .PARAMETER Retries The number of retries to attempt if the installation fails. Default is 20. .EXAMPLE Install-VSIXFromFile -FilePath "C:\Extensions\MyExtension.vsix" -Retries 10 Installs the VSIX file located at "C:\Extensions\MyExtension.vsix" with 10 retries in case of failure. #> Param ( [Parameter(Mandatory = $true)] [string] $FilePath, [int] $Retries = 20 ) Write-Host "Installing VSIX from $FilePath..." while ($True) { $installerPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\resources\app\ServiceHub\Services\Microsoft.VisualStudio.Setup.Service\VSIXInstaller.exe" try { $process = Start-Process ` -FilePath $installerPath ` -ArgumentList @('/quiet', "`"$FilePath`"") ` -Wait -PassThru } catch { Write-Host "Failed to start VSIXInstaller.exe with error:" $_ exit 1 } $exitCode = $process.ExitCode if ($exitCode -eq 0) { Write-Host "VSIX installed successfully." break } elseif ($exitCode -eq 1001) { Write-Host "VSIX is already installed." break } Write-Host "VSIX installation failed with exit code $exitCode." $Retries-- if ($Retries -eq 0) { Write-Host "VSIX installation failed after $Retries retries." exit 1 } Write-Host "Waiting 10 seconds before retrying. Retries left: $Retries" Start-Sleep -Seconds 10 } } function Install-VSIXFromUrl { <# .SYNOPSIS Installs a Visual Studio extension (VSIX) from a given URL. .DESCRIPTION This function downloads a Visual Studio extension (VSIX) from the specified URL and installs it. .PARAMETER Url The URL of the VSIX file to download and install. .PARAMETER Retries The number of retries to attempt if the download fails. Default is 20. .EXAMPLE Install-VSIXFromUrl -Url "https://example.com/extension.vsix" -Retries 10 Downloads and installs the VSIX file from the specified URL with 10 retries. #> Param ( [Parameter(Mandatory = $true)] [string] $Url, [int] $Retries = 20 ) $filePath = Invoke-DownloadWithRetry $Url Install-VSIXFromFile -FilePath $filePath -Retries $Retries Remove-Item -Force -Confirm:$false $filePath } function Get-VSExtensionVersion { <# .SYNOPSIS Retrieves the version of a Visual Studio extension package. .DESCRIPTION This function retrieves the version of a specified Visual Studio extension package. It searches for the package in the installed instances of Visual Studio and returns the version number. .PARAMETER packageName The name of the extension package. .EXAMPLE Get-VSExtensionVersion -packageName "MyExtensionPackage" Retrieves the version of the extension package named "MyExtensionPackage" for Visual Studio. #> Param ( [Parameter(Mandatory = $true)] [string] $packageName ) $instanceFolders = Get-ChildItem -Path "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances" if ($instanceFolders -is [array]) { Write-Host ($instanceFolders | Out-String) Write-Host ($instanceFolders | Get-ChildItem | Out-String) Write-Host "More than one instance installed" exit 1 } $stateContent = Get-Content -Path (Join-Path $instanceFolders.FullName '\state.packages.json') $state = $stateContent | ConvertFrom-Json $packageVersion = ($state.packages | Where-Object { $_.id -eq $packageName }).version if (-not $packageVersion) { Write-Host "Installed package $packageName for Visual Studio was not found" exit 1 } return $packageVersion } ================================================ FILE: images/windows/scripts/helpers/test/ImageHelpers.Tests.ps1 ================================================ $ModuleManifestName = 'ImageHelpers.psd1' $ModuleManifestPath = "$PSScriptRoot\..\$ModuleManifestName" Describe 'Module Manifest Tests' { It 'Passes Test-ModuleManifest' { Test-ModuleManifest -Path $ModuleManifestPath | Should Not BeNullOrEmpty $? | Should Be $true } } ================================================ FILE: images/windows/scripts/tests/ActionArchiveCache.Tests.ps1 ================================================ Describe "ActionArchiveCache" { Context "Action archive cache directory not empty" { It "C:\actionarchivecache not empty" { (Get-ChildItem -Path "C:\actionarchivecache\*.zip" -Recurse).Count | Should -BeGreaterThan 0 } } Context "Action zipball not empty" { $testCases = Get-ChildItem -Path "C:\actionarchivecache\*.zip" -Recurse | ForEach-Object { @{ ActionZipball = $_.FullName } } It "" -TestCases $testCases { param ([string] $ActionZipball) (Get-Item "$ActionZipball").Length | Should -BeGreaterThan 0 } } } ================================================ FILE: images/windows/scripts/tests/Android.Tests.ps1 ================================================ Describe "Android SDK" { $androidToolset = (Get-ToolsetContent).android $androidInstalledPackages = Get-AndroidInstalledPackages $platformList = Get-AndroidPlatformPackages -minVersion $androidToolset.platform_min_version $platformTestCases = $platformList | ForEach-Object { @{ platformVersion = $_; installedPackages = $androidInstalledPackages } } $buildToolsList = Get-AndroidBuildToolPackages -minVersion $androidToolset.build_tools_min_version $buildToolsTestCases = $buildToolsList | ForEach-Object { @{ buildToolsVersion = $_; installedPackages = $androidInstalledPackages } } $extraPackagesTestCases = $androidToolset.extra_list | ForEach-Object { @{ extraPackage = $_; installedPackages = $androidInstalledPackages } } $addonsTestCases = $androidToolset.addon_list | ForEach-Object { @{ addonPackage = $_; installedPackages = $androidInstalledPackages } } $additionalToolsTestCases = $androidToolset.additional_tools | ForEach-Object { @{ additionalToolVersion = $_; installedPackages = $androidInstalledPackages } } $ndkPackagesTestCases = $androidToolset.ndk.versions | ForEach-Object { @{ ndkPackage = $_; installedPackages = $androidInstalledPackages } } Context "SDKManagers" { $testCases = @( @{ PackageName = "Command-line tools" Sdkmanager = "$env:ANDROID_HOME\cmdline-tools\latest\bin\sdkmanager.bat" } ) It "Sdkmanager from is available" -TestCases $testCases { "$Sdkmanager --list" | Should -ReturnZeroExitCode } } Context "Packages" { It "Platform version is installed" -TestCases $platformTestCases { "$installedPackages" | Should -Match "$platformVersion" } It "Platform build tools is installed" -TestCases $buildToolsTestCases { "$installedPackages" | Should -Match "$buildToolsVersion" } It "Additional tool is installed" -TestCases $additionalToolsTestCases { "$installedPackages" | Should -Match $additionalToolVersion } It "NDK is installed" -TestCases $ndkPackagesTestCases { "$installedPackages" | Should -Match "ndk;$ndkPackage" } } } ================================================ FILE: images/windows/scripts/tests/Apache.Tests.ps1 ================================================ Describe "Apache" { Context "Path" { It "Apache" { $apachePath = Join-Path (Join-Path "C:\tools\" (Get-Item C:\tools\apache*).Name) "\bin\httpd" "$apachePath -V" | Should -ReturnZeroExitCode } } Context "Service" { $apacheService = Get-Service -Name Apache $apacheServiceTests = @{ Name = $apacheService.Name Status = $apacheService.Status StartType = $apacheService.StartType } It " service is stopped" -TestCases $apacheServiceTests { $Status | Should -Be "Stopped" } It " service is disabled" -TestCases $apacheServiceTests { $StartType | Should -Be "Disabled" } } } ================================================ FILE: images/windows/scripts/tests/Browsers.Tests.ps1 ================================================ Describe "Chrome" { Context "WebDriver" { It "ChromeWebDriver environment variable and path exists" { $env:ChromeWebDriver | Should -Not -BeNullOrEmpty $env:ChromeWebDriver | Should -BeExactly "C:\SeleniumWebDrivers\ChromeDriver" $env:ChromeWebDriver | Should -Exist } It "chromedriver.exe is installed" { "$env:ChromeWebDriver\chromedriver.exe --version" | Should -ReturnZeroExitCode } It "versioninfo.txt exists" { "$env:ChromeWebDriver\versioninfo.txt" | Should -Exist } } Context "Browser" { $chromeRegPath = "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe" $chromePath = (Get-ItemProperty $chromeRegPath).'(default)' It "Chrome '' registry path exists" -TestCases @{chromeRegPath = $chromeRegPath} { $chromeRegPath | Should -Exist } It "Chrome VersionInfo registry value exists" -TestCases @{chromePath = $chromePath} { $versionInfo = (Get-Item $chromePath).VersionInfo $versionInfo | Should -Not -BeNullOrEmpty } It "GoogleUpdater services is stopped" { $services = Get-Service -Name "GoogleUpdater*" foreach ($svc in $services) { $svc.Status | Should -BeExactly 'Stopped' } } It "BlockGoogleUpdate firewall rule exists" { Get-NetFirewallRule -DisplayName BlockGoogleUpdate | Should -Not -BeNullOrEmpty } It " is installed" -TestCases @{chromePath = $chromePath} { $chromeName = (Get-Item $chromePath).Name $chromePath | Should -Exist $chromeName | Should -BeExactly "chrome.exe" } It "Chrome and Chrome Driver major versions are the same" -TestCases @{chromePath = $chromePath} { $chromeMajor = (Get-Item $chromePath).VersionInfo.ProductMajorPart $chromeDriverMajor = (Get-Content $env:ChromeWebDriver\versioninfo.txt).Split(".")[0] $chromeMajor | Should -BeExactly $chromeDriverMajor } } } Describe "Edge" { Context "WebDriver" { It "EdgeWebDriver environment variable and path exists" { $env:EdgeWebDriver | Should -Not -BeNullOrEmpty $env:EdgeWebDriver | Should -BeExactly "C:\SeleniumWebDrivers\EdgeDriver" $env:EdgeWebDriver | Should -Exist } It "msedgedriver.exe is installed" { "$env:EdgeWebDriver\msedgedriver.exe --version" | Should -ReturnZeroExitCode } It "versioninfo.txt exists" { "$env:EdgeWebDriver\versioninfo.txt" | Should -Exist } } Context "Browser" { $edgeRegPath = "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe" It "Edge '' registry path exists" -TestCases @{edgeRegPath = $edgeRegPath} { $edgeRegPath | Should -Exist } It "Edge VersionInfo registry value exists" -TestCases @{edgeRegPath = $edgeRegPath} { $versionInfo = (Get-Item (Get-ItemProperty $edgeRegPath).'(Default)').VersionInfo $versionInfo | Should -Not -BeNullOrEmpty } It "msedge.exe is installed" { "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe" | Should -Exist } } } Describe "Firefox" { Context "WebDriver" { It "GeckoWebDriver environment variable and path exists" { $env:GeckoWebDriver | Should -Not -BeNullOrEmpty $env:GeckoWebDriver | Should -BeExactly "C:\SeleniumWebDrivers\GeckoDriver" $env:GeckoWebDriver | Should -Exist } It "geckodriver.exe is installed" { "$env:GeckoWebDriver\geckodriver.exe --version" | Should -ReturnZeroExitCode } It "versioninfo.txt exists" { "$env:GeckoWebDriver\versioninfo.txt" | Should -Exist } } Context "Browser" { $firefoxRegPath = "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\firefox.exe" It "Firefox '' registry path exists" -TestCases @{firefoxRegPath = $firefoxRegPath} { $firefoxRegPath | Should -Exist } It "Firefox VersionInfo registry value exists" -TestCases @{firefoxRegPath = $firefoxRegPath} { $versionInfo = (Get-Item (Get-ItemProperty $firefoxRegPath).'(Default)').VersionInfo $versionInfo | Should -Not -BeNullOrEmpty } It "firefox.exe is installed" { "$env:ProgramFiles\Mozilla Firefox\firefox.exe" | Should -Exist } } } Describe "Internet Explorer" { Context "WebDriver" { It "IEWebDriver environment variable and path exists" { $env:IEWebDriver | Should -Not -BeNullOrEmpty $env:IEWebDriver | Should -BeExactly "C:\SeleniumWebDrivers\IEDriver" $env:IEWebDriver | Should -Exist } It "iedriverserver.exe is installed" { "$env:IEWebDriver\IEDriverServer.exe --version" | Should -ReturnZeroExitCode } It "versioninfo.txt exists" { "$env:IEWebDriver\versioninfo.txt" | Should -Exist } } } Describe "Selenium" { BeforeAll { $seleniumBinPath = "C:\selenium\selenium-server.jar" } It "Selenium server is installed" { $seleniumBinPath | Should -Exist } It "SELENIUM_JAR_PATH environment variable exists" { Get-EnvironmentVariable "SELENIUM_JAR_PATH" | Should -BeExactly "$seleniumBinPath" } } ================================================ FILE: images/windows/scripts/tests/CLI.Tools.Tests.ps1 ================================================ Describe "Azure CLI" { It "Azure CLI" { "az --version" | Should -ReturnZeroExitCode } } Describe "Azure DevOps CLI" { It "az devops" { "az devops -h" | Should -ReturnZeroExitCode } } Describe "Aliyun CLI" -Skip:(Test-IsWin25) { It "Aliyun CLI" { "aliyun version" | Should -ReturnZeroExitCode } } Describe "AWS" { It "AWS CLI" { "aws --version" | Should -ReturnZeroExitCode } It "Session Manager Plugin for the AWS CLI" { @(session-manager-plugin) -Match '\S' | Out-String | Should -Match "plugin was installed successfully" } It "AWS SAM CLI" { "sam --version" | Should -ReturnZeroExitCode } } Describe "GitHub CLI" { It "gh" { "gh --version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/windows/scripts/tests/ChocoPackages.Tests.ps1 ================================================ Describe "7-Zip" { It "7z" { "7z" | Should -ReturnZeroExitCode } } Describe "Aria2" { It "Aria2" { "aria2c --version" | Should -ReturnZeroExitCode } } Describe "AzCopy" { It "AzCopy" { "azcopy --version" | Should -ReturnZeroExitCode } } Describe "Bicep" { It "Bicep" { "bicep --version" | Should -ReturnZeroExitCode } } Describe "InnoSetup" { It "InnoSetup" { (Get-Command -Name iscc).CommandType | Should -BeExactly "Application" } } Describe "Jq" { It "Jq" { "jq -n ." | Should -ReturnZeroExitCode } } Describe "Nuget" { It "Nuget" { "nuget" | Should -ReturnZeroExitCode } } Describe "Packer" { It "Packer" { "packer --version" | Should -ReturnZeroExitCode } } Describe "Perl" { It "Perl" { "perl --version" | Should -ReturnZeroExitCode } } Describe "Pulumi" { It "pulumi" { "pulumi version" | Should -ReturnZeroExitCode } } Describe "Svn" -Skip:(Test-IsWin25) { It "svn" { "svn --version --quiet" | Should -ReturnZeroExitCode } } Describe "Swig" { It "Swig" { "swig -version" | Should -ReturnZeroExitCode } } Describe "VSWhere" { It "vswhere" { "vswhere" | Should -ReturnZeroExitCode } } Describe "Julia" { It "Julia path exists" { "C:\Julia" | Should -Exist } It "Julia" { "julia --version" | Should -ReturnZeroExitCode } } Describe "CMake" { It "cmake" { "cmake --version" | Should -ReturnZeroExitCode } } Describe "ImageMagick" { It "ImageMagick" { "magick -version" | Should -ReturnZeroExitCode } } Describe "Ninja" { BeforeAll { $ninjaProjectPath = $(Join-Path $env:TEMP_DIR "ninjaproject") New-item -Path $ninjaProjectPath -ItemType Directory -Force @' cmake_minimum_required(VERSION 3.10) project(NinjaTest NONE) '@ | Out-File -FilePath "$ninjaProjectPath/CMakeLists.txt" -Encoding utf8 $ninjaProjectBuildPath = $(Join-Path $ninjaProjectPath "build") New-item -Path $ninjaProjectBuildPath -ItemType Directory -Force Set-Location $ninjaProjectBuildPath } It "Make a simple ninja project" { "cmake -GNinja $ninjaProjectPath" | Should -ReturnZeroExitCode } It "build.ninja file should exist" { $buildFilePath = $(Join-Path $ninjaProjectBuildPath "build.ninja") $buildFilePath | Should -Exist } It "Ninja" { "ninja --version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/windows/scripts/tests/Databases.Tests.ps1 ================================================ Describe "MongoDB" { Context "Version" { It "" -TestCases @( @{ ToolName = "mongos" } @{ ToolName = "mongod" } ) { $toolsetVersion = (Get-ToolsetContent).mongodb.version (& $ToolName --version)[2].Split('"')[-2] | Should -BeLike "$toolsetVersion*" } } Context "Service" { $mongoService = Get-Service -Name mongodb -ErrorAction Ignore $mongoServiceTests = @{ Name = $mongoService.Name Status = $mongoService.Status StartType = $mongoService.StartType } It " service is stopped" -TestCases $mongoServiceTests { $Status | Should -Be "Stopped" } It " service is disabled" -TestCases $mongoServiceTests { $StartType | Should -Be "Disabled" } } Context "Shell" { It "mongosh" { "mongosh --version" | Should -ReturnZeroExitCode } } } Describe "PostgreSQL" { $psqlTests = @( @{envVar = "PGROOT"; pgPath = Get-EnvironmentVariable "PGROOT" } @{envVar = "PGBIN"; pgPath = Get-EnvironmentVariable "PGBIN" } @{envVar = "PGDATA"; pgPath = Get-EnvironmentVariable "PGDATA" } ) Context "Environment variable" { It "PGUSER contains postgres" { Get-EnvironmentVariable "PGUSER" | Should -Be "postgres" } It "PGPASSWORD contains root" { Get-EnvironmentVariable "PGPASSWORD" | Should -Be "root" } It " environment variable exists" -TestCases $psqlTests { Get-EnvironmentVariable $envVar | Should -Not -BeNullOrEmpty } } Context "Path" { It " path exists" -TestCases $psqlTests { $pgPath | Should -Exist } } Context "Service" { $psqlService = Get-Service -Name postgresql* $psqlServiceTests = @{ Name = $psqlService.Name Status = $psqlService.Status StartType = $psqlService.StartType } It " service is stopped" -TestCases $psqlServiceTests { $Status | Should -Be "Stopped" } It " service is disabled" -TestCases $psqlServiceTests { $StartType | Should -Be "Disabled" } } Context "PostgreSQL version" { It "PostgreSQL version should correspond to the Major version in the toolset" { $toolsetVersion = (Get-ToolsetContent).postgresql.version.Split(".")[0] # Client version (& $env:PGBIN\psql --version).split()[-1] | Should -BeLike "$toolsetVersion*" # Server version (& $env:PGBIN\pg_config --version).split()[-1] | Should -BeLike "$toolsetVersion*" } } } Describe "MySQL" { It "MySQL CLI" { $MysqlVersion = (Get-ToolsetContent).mysql.version mysql -V | Should -BeLike "*${MysqlVersion}*" } } ================================================ FILE: images/windows/scripts/tests/Docker.Tests.ps1 ================================================ Describe "Docker" { It "docker is installed" { "docker --version" | Should -ReturnZeroExitCode } It "docker service is up" { "docker images" | Should -ReturnZeroExitCode } It "docker symlink" { "C:\Windows\SysWOW64\docker.exe ps" | Should -ReturnZeroExitCode } } Describe "DockerCompose" { It "docker compose v2" { "docker compose version" | Should -ReturnZeroExitCode } } Describe "DockerWinCred" { It "docker-wincred" { "docker-credential-wincred version" | Should -ReturnZeroExitCode } } Describe "DockerImages" -Skip:(Test-IsWin25) { Context "docker images" { $testCases = (Get-ToolsetContent).docker.images | ForEach-Object { @{ ImageName = $_ } } It "" -TestCases $testCases { docker images "$ImageName" --format "{{.Repository}}" | Should -Not -BeNullOrEmpty } } } ================================================ FILE: images/windows/scripts/tests/DotnetSDK.Tests.ps1 ================================================ $dotnetVersions = (Get-ToolsetContent).dotnet.versions $dotnetTools = (Get-ToolsetContent).dotnet.tools Describe "Dotnet SDK and tools" { Context "Default" { It "Default Dotnet SDK is available" { "dotnet --version" | Should -ReturnZeroExitCode } } foreach ($version in $dotnetVersions) { Context "Dotnet $version" { $dotnet = @{ dotnetVersion = $version } It "SDK $version is available" -TestCases $dotnet { (dotnet --list-sdks | Where-Object { $_ -match "${dotnetVersion}\.[0-9]*" }).Count | Should -BeGreaterThan 0 } It "Runtime $version is available" -TestCases $dotnet { (dotnet --list-runtimes | Where-Object { $_ -match "${dotnetVersion}\.[0-9]*" }).Count | Should -BeGreaterThan 0 } } } Context "Dotnet tools" { $env:Path += ";C:\Users\Default\.dotnet\tools" $testCases = $dotnetTools | ForEach-Object { @{ ToolName = $_.name; TestInstance = $_.test }} It " is available" -TestCases $testCases { "$TestInstance" | Should -ReturnZeroExitCode } } } ================================================ FILE: images/windows/scripts/tests/Git.Tests.ps1 ================================================ Describe "Git" { $gitTools = 'bash', 'awk', 'git', 'git-lfs' $gitTestCases = $gitTools | ForEach-Object { @{ toolName = $_ source = [regex]::Escape("$env:ProgramFiles\Git") } } It " is installed" -TestCases $gitTestCases { "$toolName --version" | Should -ReturnZeroExitCode } It " is located in ''" -TestCases $gitTestCases { (Get-Command -Name $toolName).Source | Should -Match $source } It "Git core.symlinks=true option is enabled" { git config core.symlinks | Should -BeExactly true } It "GCM_INTERACTIVE environment variable should be equal Never" { $env:GCM_INTERACTIVE | Should -BeExactly Never } } ================================================ FILE: images/windows/scripts/tests/Haskell.Tests.ps1 ================================================ Describe "Haskell" { $ghcPackagesPath = "c:\ghcup\ghc" [array] $ghcVersionList = Get-ChildItem -Path $ghcPackagesPath -Filter "*" | ForEach-Object { $_.Name.Trim() } $ghcCount = $ghcVersionList.Count $defaultGhcVersion = $ghcVersionList | Sort-Object {[Version] $_} | Select-Object -Last 1 $ghcDefaultCases = @{ defaultGhcVersion = $defaultGhcVersion defaultGhcShortVersion = ([version] $defaultGhcVersion).ToString(3) } $ghcTestCases = $ghcVersionList | ForEach-Object { $ghcVersion = $_ $ghcShortVersion = ([version] $ghcVersion).ToString(3) $binGhcPath = Join-Path $ghcPackagesPath "$ghcShortVersion\bin\ghc.exe" @{ ghcVersion = $ghcVersion ghcShortVersion = $ghcShortVersion binGhcPath = $binGhcPath } } $ghcupEnvExists = @( @{envVar = "GHCUP_INSTALL_BASE_PREFIX"} @{envVar = "GHCUP_MSYS2"} ) If (Test-IsWin25) { $numberOfVersions = 1 } else { $numberOfVersions = 3 } It " environment variable exists" -TestCases $ghcupEnvExists { Test-Path env:\$envVar } It "Accurate $numberOfVersions versions of GHC are installed" -TestCases @{ghcCount = $ghcCount; numberOfVersions = $numberOfVersions} { $ghcCount | Should -BeExactly $numberOfVersions } It "GHC is installed" -TestCases $ghcTestCases { "$binGhcPath --version" | Should -OutputTextMatchingRegex $ghcShortVersion } It "GHC is the default version and should be the latest installed" -TestCases $ghcDefaultCases { "ghc --version" | Should -OutputTextMatchingRegex $defaultGhcShortVersion } It "Cabal is installed" { "cabal --version" | Should -ReturnZeroExitCode } It "cabal folder does not exist" { $env:CABAL_DIR | Should -Not -Exist } It "CABAL_DIR environment variable exists" { Get-EnvironmentVariable CABAL_DIR | Should -BeExactly "C:\cabal" } It "ghcup is installed" { "ghcup --version" | Should -ReturnZeroExitCode } It "ghcup can access msys2" { "ghcup run --mingw-path -- pacman --version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/windows/scripts/tests/Helpers.psm1 ================================================ [CmdletBinding()] param() # Gets value of environment variable by the name function Get-EnvironmentVariable($variable) { return [System.Environment]::GetEnvironmentVariable($variable, "Machine") } function Invoke-PesterTests { <# .SYNOPSIS Runs Pester tests based on the provided test file and test name. .DESCRIPTION The Invoke-PesterTests function runs Pester tests based on the provided test file and test name. It supports filtering tests by name and generating test result output. .PARAMETER TestFile The name of the test file to run. This should be the base name of the test file without the extension. .PARAMETER TestName The name of the specific test to run. If provided, only the test with the matching name will be executed. .EXAMPLE Invoke-PesterTests -TestFile "MyTests" -TestName "Test1" Runs the test named "Test1" from the test file "MyTests.Tests.ps1". .EXAMPLE Invoke-PesterTests -TestFile "*" -TestName "Test2" Runs all tests from all test files and generates the test result output. .NOTES This function requires the Pester module to be installed. #> Param( [Parameter(Mandatory)][string] $TestFile, [string] $TestName ) $testPath = "C:\image\tests\${TestFile}.Tests.ps1" if (-not (Test-Path $testPath)) { throw "Unable to find test file '$TestFile' on '$testPath'." } $configuration = [PesterConfiguration] @{ Run = @{ Path = $testPath; PassThru = $true } Output = @{ Verbosity = "Detailed"; RenderMode = "Plaintext" } } if ($TestName) { $configuration.Filter.FullName = $TestName } if ($TestFile -eq "*") { $configuration.TestResult.Enabled = $true $configuration.TestResult.OutputPath = "C:\image\tests\testResults.xml" } # Update environment variables without reboot Update-Environment # Switch ErrorActionPreference to Stop temporary to make sure that tests will on silent errors too $backupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = "Stop" $results = Invoke-Pester -Configuration $configuration $ErrorActionPreference = $backupErrorActionPreference # Fail in case if no tests are run if (-not ($results -and ($results.FailedCount -eq 0) -and ($results.PassedCount -gt 0))) { $results throw "Test run has failed" } } function ShouldReturnZeroExitCode { <# .SYNOPSIS Implements a custom Should-operator for the Pester framework. .DESCRIPTION This function is used to check if a command has returned a zero exit code. It can be used by registering it using the Add-ShouldOperator function in Pester. .PARAMETER ActualValue The actual value to be checked. .PARAMETER Negate A switch parameter that, when specified, negates the result of the check. .PARAMETER Because An optional string that provides additional context or explanation for the check. .NOTES This function is designed to be used with the Pester framework. .LINK https://pester.dev/docs/assertions/custom-assertions #> Param( [string] $ActualValue, [switch] $Negate, [string] $Because ) $outputLines = (& $env:comspec /c "$ActualValue 2>&1") -as [string[]] $exitCode = $LASTEXITCODE [bool] $succeeded = $exitCode -eq 0 if ($Negate) { $succeeded = -not $succeeded } if (-not $succeeded) { $commandOutputIndent = " " * 4 $commandOutput = ($outputLines | ForEach-Object { "${commandOutputIndent}${_}" }) -join "`n" $failureMessage = "Command '${ActualValue}' has finished with exit code ${exitCode} and output:`n${commandOutput}" } return [PSCustomObject] @{ Succeeded = $succeeded FailureMessage = $failureMessage } } function ShouldOutputTextMatchingRegex { <# .SYNOPSIS Implements a custom Should-operator for the Pester framework. .DESCRIPTION This function is used to check if a command outputs text that matches a regular expression. It can be used by registering it using the Add-ShouldOperator function in Pester. .PARAMETER ActualValue The actual value to be checked. .PARAMETER Negate A switch parameter that, when specified, negates the result of the check. .PARAMETER Because An optional string that provides additional context or explanation for the check. .NOTES This function is designed to be used with the Pester framework. .LINK https://pester.dev/docs/assertions/custom-assertions #> Param( [String] $ActualValue, [String] $RegularExpression, [switch] $Negate ) [string] $output = (& $env:comspec /c "$ActualValue 2>&1") [bool] $succeeded = $output -cmatch $RegularExpression if ($Negate) { $succeeded = -not $succeeded } $failureMessage = '' if (-not $succeeded) { if ($Negate) { $failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to not match '$output', but it did match." } else { $failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to match '$output', but it did not match." } } return [PSCustomObject] @{ Succeeded = $succeeded FailureMessage = $failureMessage } } If (Get-Command -Name Add-ShouldOperator -ErrorAction SilentlyContinue) { Add-ShouldOperator -Name ReturnZeroExitCode -InternalName ShouldReturnZeroExitCode -Test ${function:ShouldReturnZeroExitCode} Add-ShouldOperator -Name OutputTextMatchingRegex -InternalName ShouldOutputTextMatchingRegex -Test ${function:ShouldOutputTextMatchingRegex} } function Get-ModuleVersionAsJob { Param ( [Parameter(Mandatory)] [String] $modulePath, [Parameter(Mandatory)] [String] $moduleName ) # Script block to run commands in separate PowerShell environment $testJob = Start-Job -ScriptBlock { param ( $modulePath, $moduleName ) # No need to suppress AzureRM warnings now, only Az module is considered $env:PsModulePath = "$modulePath;$env:PsModulePath" Import-Module -Name $moduleName (Get-Module -Name $moduleName).Version.ToString() } -ArgumentList $modulePath, $moduleName $testJob | Wait-Job | Receive-Job | Out-File -FilePath "${env:TEMP}\module-version.txt" Remove-Job $testJob } Export-ModuleMember -Function @( 'Get-EnvironmentVariable' 'Invoke-PesterTests' 'Get-ModuleVersionAsJob' ) ================================================ FILE: images/windows/scripts/tests/Java.Tests.ps1 ================================================ Describe "Java" { $toolsetJava = (Get-ToolsetContent).java $defaultVersion = $toolsetJava.default $jdkVersions = $toolsetJava.versions [array] $testCases = $jdkVersions | ForEach-Object { @{Version = $_ } } It "Java is default" -TestCases @(@{ DefaultJavaVersion = $defaultVersion }) { $actualJavaPath = Get-EnvironmentVariable "JAVA_HOME" $expectedJavaPath = Get-EnvironmentVariable "JAVA_HOME_${DefaultJavaVersion}_X64" $actualJavaPath | Should -Not -BeNullOrEmpty $expectedJavaPath | Should -Not -BeNullOrEmpty $actualJavaPath | Should -Be $expectedJavaPath } It "" -TestCases @( @{ ToolName = "java" } @{ ToolName = "mvn" } @{ ToolName = "ant" } @{ ToolName = "gradle" } ) { "$ToolName -version" | Should -ReturnZeroExitCode } It "Java " -TestCases $testCases { $javaVariableValue = Get-EnvironmentVariable "JAVA_HOME_${Version}_X64" $javaVariableValue | Should -Not -BeNullOrEmpty $javaPath = Join-Path $javaVariableValue "bin\java" if ($Version -eq 8) { $Version = "1.${Version}" } $outputPattern = "openjdk version `"${Version}" $outputLines = (& $env:comspec /c "`"$javaPath`" -version 2>&1") -as [string[]] $LASTEXITCODE | Should -Be 0 $outputLines[0] | Should -Match $outputPattern } } ================================================ FILE: images/windows/scripts/tests/LLVM.Tests.ps1 ================================================ Describe "Clang/LLVM" { BeforeAll { $toolsetVersion = (Get-ToolsetContent).llvm.version } It "Clang/LLVM installed and version is correct" { $clangVersion = clang --version $clangVersion[0] | Should -BeLike "*${toolsetVersion}*" } } ================================================ FILE: images/windows/scripts/tests/MSYS2.Tests.ps1 ================================================ BeforeAll { $msys2Dir = "C:\msys64\usr\bin" $originalPath = $env:PATH } Describe "MSYS2 packages" { BeforeEach { $env:PATH = "$msys2Dir;$env:PATH" } It "msys2Dir exists" { $msys2Dir | Should -Exist } $TestCases = @( @{ ToolName = "bash.exe" } ) It " is installed in " -TestCases $TestCases { (Get-Command "$ToolName").Source | Should -BeLike "$msys2Dir*" } It " is avaialable" -TestCases $TestCases { "$ToolName --version" | Should -ReturnZeroExitCode } AfterEach { $env:PATH = $originalPath } } $mingwTypes = (Get-ToolsetContent).MsysPackages.mingw foreach ($mingwType in $mingwTypes) { Describe "$($mingwType.arch) packages" { $tools = $mingwType.runtime_packages $execDir = Join-Path "C:\msys64" $mingwType.exec_dir | Join-Path -ChildPath "bin" foreach ($tool in $tools) { Context "$($tool.name) package" { $executables = $tool.executables | ForEach-Object { @{ ExecName = $_ ExecDir = $execDir } } BeforeEach { $env:PATH = "$execDir;$env:PATH" } It " is installed in " -TestCases $executables { (Get-Command "$ExecName").Source | Should -BeLike "$ExecDir*" } It " is available" -TestCases $executables { "$ExecName --version" | Should -ReturnZeroExitCode } AfterEach { $env:PATH = $originalPath } } } } } ================================================ FILE: images/windows/scripts/tests/Miniconda.Tests.ps1 ================================================ Describe "Miniconda" { It "Miniconda Environment variables is set. " { ${env:CONDA} | Should -Not -BeNullOrEmpty } It "Miniconda $env:CONDA\ " -TestCases @( @{ PathTest = "python.exe" } @{ PathTest = "Scripts\conda.exe" } ) { $condaPath = Join-Path ${env:CONDA} $PathTest $condaPath | Should -Exist "$condaPath --version" | Should -ReturnZeroExitCode } } ================================================ FILE: images/windows/scripts/tests/Nginx.Tests.ps1 ================================================ Describe "Nginx" { Context "Path" { It "Nginx" { $nginxPath = Join-Path (Join-Path "C:\tools\" (Get-Item C:\tools\nginx*).Name) "nginx" "$nginxPath -v" | Should -ReturnZeroExitCode } } Context "Service" { $nginxService = Get-Service -Name nginx $nginxServiceTests = @{ Name = $nginxService.Name Status = $nginxService.Status StartType = $nginxService.StartType } It " service is stopped" -TestCases $nginxServiceTests { $Status | Should -Be "Stopped" } It " service is disabled" -TestCases $nginxServiceTests { $StartType | Should -Be "Disabled" } } } ================================================ FILE: images/windows/scripts/tests/Node.Tests.ps1 ================================================ Describe "Node.JS" { Context "Basic modules"{ It " " -TestCases @( @{ ToolName = "node" } @{ ToolName = "npm" } ) { "$ToolName --version" | Should -ReturnZeroExitCode } } $globalNpmPackages = (Get-ToolsetContent).npm.global_packages $globalNpmPackagesWithTests = $globalNpmPackages | Where-Object { $_.test } | ForEach-Object { @{ Name = $_.name; Test = $_.test } } Context "Global NPM Packages" { It "" -TestCases $globalNpmPackagesWithTests { $Test | Should -ReturnZeroExitCode } } Context "Node.js version" { It "Node.js version should correspond to the version in the toolset" { node --version | Should -BeLike "v$((Get-ToolsetContent).node.default)*" } } } ================================================ FILE: images/windows/scripts/tests/PHP.Tests.ps1 ================================================ Describe "PHP" { It "Check PHP version" { $phpMajorMinor = (Get-ToolsetContent).php.version $phpInstalledVersion = php --version | Select-String -Pattern "PHP $phpMajorMinor" $phpInstalledVersion | Should -BeLike "*${phpMajorMinor}*" } It "Check Composer in the PATH" { "composer --version" | Should -ReturnZeroExitCode } It "PHP Environment variables is set." { ${env:PHPROOT} | Should -Not -BeNullOrEmpty ${env:PHPROOT} | Should -Exist } } ================================================ FILE: images/windows/scripts/tests/PipxPackages.Tests.ps1 ================================================ Describe "PipxPackages" { $pipxToolset = (Get-ToolsetContent).pipx $testCases = $pipxToolset | ForEach-Object { @{package = $_.package; cmd = $_.cmd} } It "" -TestCases $testCases { "$cmd" | Should -ReturnZeroExitCode } } ================================================ FILE: images/windows/scripts/tests/PowerShellAzModules.Tests.ps1 ================================================ Describe "AzureModules" { $modules = (Get-ToolsetContent).azureModules $modulesRootPath = "C:\\Modules" foreach ($module in $modules) { $moduleName = $module.name Context "$moduleName" { foreach ($version in $module.versions) { $modulePath = Join-Path -Path $modulesRootPath -ChildPath "${moduleName}_${version}" $moduleInfo = @{ moduleName = $moduleName; modulePath = $modulePath; expectedVersion = $version } It " exists in modules directory" -TestCases $moduleInfo { $ScriptBlock = { param ($modulePath, $moduleName) Import-Module ImageHelpers Get-ModuleVersionAsJob -modulePath $modulePath -moduleName $moduleName } if ($moduleName -eq "Az"){ Start-Process -FilePath pwsh.exe -ArgumentList "-Command (Invoke-Command -ScriptBlock {$ScriptBlock} -ArgumentList $modulePath, $moduleName)" -Wait -PassThru } else { Start-Process -FilePath powershell.exe -ArgumentList "-Command (Invoke-Command -ScriptBlock {$ScriptBlock} -ArgumentList $modulePath, $moduleName)" -Wait -PassThru } $moduleVersion = Get-Content -Path "$env:TEMP\module-version.txt" Remove-Item -Path "${env:TEMP}\module-version.txt" -Force $moduleVersion | Should -Match $expectedVersion } } } } } ================================================ FILE: images/windows/scripts/tests/PowerShellModules.Tests.ps1 ================================================ Describe "PowerShellModules" { $modules = (Get-ToolsetContent).powershellModules $withoutVersionsModules = $modules | Where-Object {-not $_.versions} | ForEach-Object { @{moduleName = $_.name} } $withVersionsModules = $modules | Where-Object {$_.versions} | ForEach-Object { $moduleName = $_.name $_.versions | ForEach-Object { @{moduleName = $moduleName; expectedVersion = $_} } } It " is installed" -TestCases $withoutVersionsModules { Get-Module -Name $moduleName -ListAvailable | Should -BeTrue } if ($withVersionsModules) { It " with is installed" -TestCases $withVersionsModules { (Get-Module -Name $moduleName -ListAvailable).Version -contains $expectedVersion | Should -BeTrue } } } ================================================ FILE: images/windows/scripts/tests/RunAll-Tests.ps1 ================================================ Invoke-PesterTests "*" ================================================ FILE: images/windows/scripts/tests/Rust.Tests.ps1 ================================================ Describe "Rust" { BeforeAll { $env:RUSTUP_HOME = "C:\Users\Default\.rustup" $env:CARGO_HOME = "C:\Users\Default\.cargo" $env:Path += ";$env:CARGO_HOME\bin" } if (Test-IsWin25) { $rustTools = @( @{ToolName = "rustup"; binPath = "C:\Users\Default\.cargo\bin\rustup.exe"} @{ToolName = "rustc"; binPath = "C:\Users\Default\.cargo\bin\rustc.exe"} @{ToolName = "cargo"; binPath = "C:\Users\Default\.cargo\bin\cargo.exe"} ) } else { $rustTools = @( @{ToolName = "rustup"; binPath = "C:\Users\Default\.cargo\bin\rustup.exe"} @{ToolName = "rustc"; binPath = "C:\Users\Default\.cargo\bin\rustc.exe"} @{ToolName = "bindgen.exe"; binPath = "C:\Users\Default\.cargo\bin\bindgen.exe"} @{ToolName = "cbindgen.exe"; binPath = "C:\Users\Default\.cargo\bin\cbindgen.exe"} @{ToolName = "cargo"; binPath = "C:\Users\Default\.cargo\bin\cargo.exe"} @{ToolName = "cargo audit"; binPath = "C:\Users\Default\.cargo\bin\cargo-audit.exe"} @{ToolName = "cargo outdated"; binPath = "C:\Users\Default\.cargo\bin\cargo-outdated.exe"} ) } $rustEnvNotExists = @( @{envVar = "RUSTUP_HOME"} @{envVar = "CARGO_HOME"} ) It "C:\Users\Default\.rustup and C:\Users\Default\.cargo folders exist" { "C:\Users\Default\.rustup", "C:\Users\Default\.cargo" | Should -Exist } It " environment variable does not exist" -TestCases $rustEnvNotExists { [Environment]::GetEnvironmentVariables("Machine").ContainsKey($envVar) | Should -BeFalse } It " is installed to the '' folder" -TestCases $rustTools { "$ToolName --version" | Should -ReturnZeroExitCode $binPath | Should -Exist } } ================================================ FILE: images/windows/scripts/tests/SSDTExtensions.Tests.ps1 ================================================ Describe "SSDTExtensions" { #These extensions don't have any proper name in the state.packages.json file, only id is available, which can be found on extension marketplace download page It "Extension SSDT" { $version = Get-VSExtensionVersion -packageName "SSDT" $version | Should -Not -BeNullOrEmpty } } ================================================ FILE: images/windows/scripts/tests/Shell.Tests.ps1 ================================================ Describe "Shell" { $shellTestCases = @( @{Name = "C:\shells\gitbash.exe"; Target = "$env:ProgramFiles\Git\bin\bash.exe"}, @{Name = "C:\shells\msys2bash.cmd"; Target = $null} @{Name = "C:\shells\wslbash.exe"; Target = "$env:SystemRoot\System32\bash.exe"} ) It " target to " -TestCases $shellTestCases { (Get-Item $Name).Target | Should -BeExactly $Target } } ================================================ FILE: images/windows/scripts/tests/Tools.Tests.ps1 ================================================ Describe "Azure Cosmos DB Emulator" { $cosmosDbEmulatorRegKey = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | Get-ItemProperty | Where-Object { $_.DisplayName -eq 'Azure Cosmos DB Emulator' } $installDir = $cosmosDbEmulatorRegKey.InstallLocation It "Azure Cosmos DB Emulator install location registry key exists" -TestCases @{installDir = $installDir} { $installDir | Should -Not -BeNullOrEmpty } It "Azure Cosmos DB Emulator exe file exists" -TestCases @{installDir = $installDir} { $exeFilePath = Join-Path $installDir 'CosmosDB.Emulator.exe' $exeFilePath | Should -Exist } } Describe "Bazel" { It "" -TestCases @( @{ ToolName = "bazel" } @{ ToolName = "bazelisk" } ) { "$ToolName --version"| Should -ReturnZeroExitCode } } Describe "CodeQL Bundle" { It "Single distribution installed" { $CodeQLVersionsWildcard = Join-Path $env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath "*" $CodeQLVersionPath = Get-ChildItem $CodeQLVersionsWildcard | Should -HaveCount 1 } It "Contains CodeQL executable" { $CodeQLVersionsWildcard = Join-Path $env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath "*" $CodeQLVersionPath = Get-ChildItem $CodeQLVersionsWildcard | Sort-Object -Descending | Select-Object -First 1 -Expand FullName $CodeQLPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "codeql.exe" "$CodeQLPath version --quiet" | Should -ReturnZeroExitCode } It "Contains CodeQL packs" { $CodeQLVersionsWildcard = Join-Path $env:AGENT_TOOLSDIRECTORY -ChildPath "CodeQL" | Join-Path -ChildPath "*" $CodeQLVersionPath = Get-ChildItem $CodeQLVersionsWildcard | Sort-Object -Descending | Select-Object -First 1 -Expand FullName $CodeQLPacksPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "qlpacks" $CodeQLPacksPath | Should -Exist } } Describe "R" { It "Rscript" { "Rscript --version" | Should -ReturnZeroExitCode } } Describe "DACFx" { It "DACFx" { (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*).DisplayName -Contains "Microsoft SQL Server Data-Tier Application Framework" | Should -BeTrue $sqlPackagePath = 'C:\Program Files\Microsoft SQL Server\170\DAC\bin\SqlPackage.exe' "${sqlPackagePath}" | Should -Exist } } Describe "Mercurial" -Skip:(Test-IsWin25) { It "Mercurial" { "hg --version" | Should -ReturnZeroExitCode } } Describe "KubernetesTools" { It "Kind" { "kind version" | Should -ReturnZeroExitCode } It "kubectl" { "kubectl version --client=true" | Should -ReturnZeroExitCode } It "Helm" { "helm version --short" | Should -ReturnZeroExitCode } It "minikube" { "minikube version --short" | Should -ReturnZeroExitCode } } Describe "Mingw64" { It "" -TestCases @( @{ ToolName = "gcc" } @{ ToolName = "g++" } @{ ToolName = "make" } ) { "$ToolName --version" | Should -ReturnZeroExitCode } } Describe "NET48" { It "NET48" { Get-ChildItem -Path "${env:ProgramFiles(x86)}\Microsoft SDKs\Windows\*\*\NETFX 4.8 Tools" -Directory | Should -HaveCount 1 } } Describe "NSIS" -Skip:(Test-IsWin25) { It "NSIS" { "makensis /VERSION" | Should -ReturnZeroExitCode } } Describe "PowerShell Core" { It "pwsh" { "pwsh --version" | Should -ReturnZeroExitCode } It "Execute 2+2 command" { pwsh -Command "2+2" | Should -BeExactly 4 } } Describe "Sbt" { It "sbt" { "sbt --version" | Should -ReturnZeroExitCode } } Describe "ServiceFabricSDK" { It "PowerShell Module" { # Ignore PowerShell version check if running in PowerShell Core # https://github.com/microsoft/service-fabric/issues/1343 if ($PSVersionTable.PSEdition -eq 'Core') { Get-Module -Name ServiceFabric -SkipEditionCheck -ListAvailable | Should -Not -BeNullOrEmpty } else { Get-Module -Name ServiceFabric -ListAvailable | Should -Not -BeNullOrEmpty } } It "ServiceFabricSDK version" { Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Service Fabric\' -Name FabricVersion | Should -Not -BeNullOrEmpty } } Describe "Stack" { It "Stack" { "stack --version" | Should -ReturnZeroExitCode } } Describe "Vcpkg" { It "vcpkg" { "vcpkg version" | Should -ReturnZeroExitCode } It "env variable VCPKG_INSTALLATION_ROOT is set" { $env:VCPKG_INSTALLATION_ROOT | Should -Not -BeNullOrEmpty } It "VCPKG_INSTALLATION_ROOT directory" { $env:VCPKG_INSTALLATION_ROOT | Should -Exist } } Describe "WebPlatformInstaller" { It "WebPlatformInstaller" { "WebPICMD" | Should -ReturnZeroExitCode } } Describe "Zstd" { It "zstd" { "zstd -V" | Should -ReturnZeroExitCode } } Describe "Pipx" { It "Pipx" { "pipx --version" | Should -ReturnZeroExitCode } } Describe "Kotlin" { $kotlinPackages = @("kapt", "kotlin", "kotlinc", "kotlinc-jvm") It " is available" -TestCases ($kotlinPackages | ForEach-Object { @{ toolName = $_ } }) { "$toolName -version" | Should -ReturnZeroExitCode } It "kotlinc-js is available" { "kotlinc-js -help" | Should -ReturnZeroExitCode } } Describe "SQL OLEDB Driver" { It "SQL OLEDB Driver 18" { "HKLM:\SOFTWARE\Microsoft\MSOLEDBSQL" | Should -Exist } It "SQL OLEDB Driver 19" { "HKLM:\SOFTWARE\Microsoft\MSOLEDBSQL19" | Should -Exist } } Describe "OpenSSL" { It "OpenSSL Version" { $OpenSSLVersion = (Get-ToolsetContent).openssl.version openssl version | Should -BeLike "* ${OpenSSLVersion}*" } It "OpenSSL Path" { (Get-Command openssl).Source -eq (Join-Path ${env:ProgramFiles} 'OpenSSL\bin\openssl.exe') | Should -Be $true } It "OpenSSL Full package" { Join-Path ${env:ProgramFiles} 'OpenSSL\include' | Should -Exist } It "OpenSSL DLLs not in System32" { Get-ChildItem -Path "$env:SystemRoot\System32" -Filter "libcrypto-*.dll" -File -ErrorAction SilentlyContinue | Should -BeNullOrEmpty Get-ChildItem -Path "$env:SystemRoot\System32" -Filter "libssl-*.dll" -File -ErrorAction SilentlyContinue | Should -BeNullOrEmpty } } ================================================ FILE: images/windows/scripts/tests/Toolset.Tests.ps1 ================================================ $toolsExecutables = @{ Python = @( @{ Binary = "python.exe"; Arguments = "--version" }, @{ Binary = "Scripts\pip.exe"; Arguments = "--version" } ) PyPy = @( @{ Binary = "python.exe"; Arguments = "--version" }, @{ Binary = "Scripts\pip.exe"; Arguments = "--version" } ) Node = @( @{ Binary = "node.exe"; Arguments = "--version" }, @{ Binary = "npm"; Arguments = "--version" } ) Go = @( @{ Binary = "bin\go.exe"; Arguments = "version" } ) Ruby = @( @{ Binary = "bin\ruby.exe"; Arguments = "--version" } ) } function Get-ToolExecutables { Param ([String] $Name) if ($toolsExecutables.ContainsKey($Name)) { $toolsExecutables[$Name] } else { @() } } function Test-Binaries { Param ( [String] $Name, [String] $Version, [String] $Arch, [Array] $ToolExecs ) $testCases = $ToolExecs | ForEach-Object { @{ Name = $Name; Version = $Version; Arch = $Arch; Binary = $_.Binary; Arguments = $_.Arguments } } It " " -TestCases $testCases { $binaryFullPath = Join-Path (Get-TCToolVersionPath -Name $Name -Version $Version -Arch $Arch) $Binary "$binaryFullPath $Arguments" | Should -ReturnZeroExitCode } } function Test-DefaultVersion { Param ( [String] $Name, [String] $ExpectedVersion, [Array] $ToolExecs ) $binaryName = [IO.Path]::GetFileNameWithoutExtension($ToolExecs[0].Binary) $testCase = @{ Binary = $binaryName; Arguments = $ToolExecs[0].Arguments; ExpectedVersion = $ExpectedVersion } It " is default version" -TestCases $testCase { $outputLines = (& $env:comspec /c "$Binary $Arguments 2>&1") -as [string[]] $LASTEXITCODE | Should -Be 0 $outputLines | Should -Match $ExpectedVersion } It "default version is located in tool-cache" -TestCases $testCase { $binaryFullPath = (Get-Command $Binary).Path $toolcacheDirectory = Get-TCToolPath -ToolName $Name $binaryFullPath | Should -Match ([Regex]::Escape($toolcacheDirectory)) } } $tools = Get-ToolsetContent | Select-Object -ExpandProperty toolcache foreach ($tool in $tools) { Describe "$($tool.name) [$($tool.arch)]" { $toolExecs = Get-ToolExecutables -Name $tool.name foreach ($version in $tool.versions) { Context "$version" { $toolInfo = @{ Name = $tool.name; Version = $version; Arch = $tool.arch } It "tool-cache directory exists" -TestCases $toolInfo { $toolFullPath = Get-TCToolVersionPath -Name $Name -Version $Version -Arch $Arch $toolFullPath | Should -Exist } if ($toolExecs) { Test-Binaries -Name $tool.name -Version $version -Arch $tool.arch -ToolExecs $toolExecs } } } if ($tool.default -and $toolExecs) { Context "Default" { Test-DefaultVersion -Name $tool.name -ExpectedVersion $tool.default -ToolExecs $toolExecs } } } } ================================================ FILE: images/windows/scripts/tests/VisualStudio.Tests.ps1 ================================================ Describe "Visual Studio" { Context "Basic" { It "Catalog.json" { $instanceFolder = Get-Item "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances\*" | Select-Object -First 1 Join-Path $instanceFolder.FullName "catalog.json" | Should -Exist } It "Devenv.exe" { $vsInstallRoot = (Get-VisualStudioInstance).InstallationPath $devenvexePath = "${vsInstallRoot}\Common7\IDE\devenv.exe" $devenvexePath | Should -Exist } } Context "Visual Studio components" { $expectedComponents = Get-ToolsetContent | Select-Object -ExpandProperty visualStudio | Select-Object -ExpandProperty workloads $testCases = $expectedComponents | ForEach-Object { @{ComponentName = $_} } BeforeAll { $installedComponents = Get-VisualStudioComponents | Select-Object -ExpandProperty Package } It "" -TestCases $testCases { $installedComponents | Should -Contain $ComponentName } } } Describe "Windows 10 SDK" -Skip:(Test-IsWin25) { It "Verifies 17763 SDK is installed" { "${env:ProgramFiles(x86)}\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.17763.0\UAP.props" | Should -Exist } } Describe "Windows 11 SDK" { It "Verifies 26100 SDK is installed" { "${env:ProgramFiles(x86)}\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.26100.0\UAP.props" | Should -Exist } } ================================================ FILE: images/windows/scripts/tests/Vsix.Tests.ps1 ================================================ Describe "Vsix" { $toolset = Get-ToolsetContent $requiredVsixPackages = $toolset.visualStudio.vsix $allPackages = (Get-VisualStudioInstance).Packages $testCases = $requiredVsixPackages | ForEach-Object { $vsixPackage = Get-VsixInfoFromMarketplace $_ @{ VsixName = $vsixPackage.ExtensionName VsixId = $vsixPackage.VsixId AllPackages = $allPackages } } if ($testCases.Count -gt 0) { It "Extension is installed" -TestCases $testCases { $objVsix = $AllPackages | Where-Object { $_.id -eq $VsixId } $objVsix | Should -Not -BeNullOrEmpty } } } ================================================ FILE: images/windows/scripts/tests/WDK.Tests.ps1 ================================================ Describe "WDK" -Skip:(Test-IsWin25) { It "WDK exists" { $regKey = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" $installedApplications = Get-ItemProperty -Path $regKey $WDKVersion = $installedApplications | Where-Object DisplayName -eq 'Windows Driver Kit' | Select-Object -First 1 -ExpandProperty DisplayVersion $WDKVersion | Should -Not -BeNullOrEmpty } It "Windows Driver Kit VSIX extension" { $version = Get-VSExtensionVersion -packageName "Microsoft.Windows.DriverKit" $version | Should -Not -BeNullOrEmpty } } ================================================ FILE: images/windows/scripts/tests/WinAppDriver.Tests.ps1 ================================================ Describe "WinAppDriver" { It "WinAppDriver directory exists" { Test-Path -Path "${env:ProgramFiles(x86)}\Windows Application Driver" | Should -BeTrue } } Describe "Developer Mode" { It "Developer Mode is enabled" { $path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"; Get-ItemProperty -Path $path | Select-Object -ExpandProperty "AllowDevelopmentWithoutDevLicense" | Should -Be 1 } } ================================================ FILE: images/windows/scripts/tests/WindowsFeatures.Tests.ps1 ================================================ Describe "WindowsFeatures" { $windowsFeatures = (Get-ToolsetContent).windowsFeatures $testCases = $windowsFeatures | ForEach-Object { @{ Name = $_.name; OptionalFeature = $_.optionalFeature } } It "Windows Feature is installed" -TestCases $testCases { if ($OptionalFeature) { (Get-WindowsOptionalFeature -Online -FeatureName $Name).State | Should -Be "Enabled" } else { (Get-WindowsFeature -Name $Name).InstallState | Should -Be "Installed" } } it "Check WSL is on path" { (Get-Command -Name 'wsl') | Should -BeTrue } it "Check WLAN service is stopped" { (Get-Service -Name wlansvc).Status | Should -Be "Stopped" } } Describe "DiskSpace" { It "The image has enough disk space" { $diskInfo = Get-PSDrive -Name C $totalSpaceGB = [math]::Floor(($diskInfo.Used + $diskInfo.Free) / 1GB) $freeSpaceGB = [math]::Floor($diskInfo.Free / 1GB) Write-Host " [i] Disk size: ${totalSpaceGB} GB; Free space: ${freeSpaceGB} GB" $freeSpaceGB | Should -BeGreaterOrEqual 18 } } Describe "DynamicPorts" { It "Test TCP dynamicport start=49152 num=16384" { $tcpPorts = Get-NetTCPSetting | Where-Object { $_.SettingName -ne "Automatic" } | Where-Object { $_.DynamicPortRangeStartPort -ne 49152 -or $_.DynamicPortRangeNumberOfPorts -ne 16384 } $tcpPorts | Should -BeNullOrEmpty } It "Test UDP dynamicport start=49152 num=16384" { $udpPorts = Get-NetUDPSetting | Where-Object { $_.DynamicPortRangeStartPort -ne 49152 -or $_.DynamicPortRangeNumberOfPorts -ne 16384 } $udpPorts | Should -BeNullOrEmpty } } Describe "GDIProcessHandleQuota" { It "The GDIProcessHandleQuota value is 20000" { $regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" $regPath32 = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\Windows" (Get-ItemProperty $regPath).GDIProcessHandleQuota | Should -BeExactly 20000 (Get-ItemProperty $regPath32).GDIProcessHandleQuota | Should -BeExactly 20000 } } Describe "Test Signed Drivers" { It "bcdedit testsigning should be Yes" { "$(bcdedit)" | Should -Match "testsigning\s+Yes" } } Describe "Windows Updates" { It "WindowsUpdateDone.txt should exist" { "$env:windir\WindowsUpdateDone.txt" | Should -Exist } $testCases = Get-WindowsUpdateStates | Sort-Object Title | ForEach-Object { @{ Title = $_.Title State = $_.State } } It "" -TestCases $testCases { $expect = "Installed" if ( $Title -match "Microsoft Defender Antivirus" ) { $expect = "Installed", "Failed", "Running" } $State | Should -BeIn $expect } } Describe "WSL2" -Skip:(Test-IsWin22) { It "WSL status should return zero exit code" { "wsl --status" | Should -ReturnZeroExitCode } } ================================================ FILE: images/windows/scripts/tests/Wix.Tests.ps1 ================================================ Describe "Wix" { BeforeAll { $regKey = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" $installedApplications = Get-ItemProperty -Path $regKey $version = ($installedApplications | Where-Object { $_.BundleCachePath -imatch ".*\\WiX\d*\.exe$" } | Select-Object -First 1).DisplayName } It "Wix Toolset version from registry" { $version | Should -Not -BeNullOrEmpty } It "Wix variable exists" { $env:WIX | Should -Not -BeNullOrEmpty } It "Wix binaries folder exists" { Test-Path -Path $(Join-Path -Path $env:WIX -ChildPath "bin") | Should -Be $true } It "Wix binaries folder is in PATH" { $testPath = Join-Path -Path $env:WIX -ChildPath "bin" $env:PATH -split ";" | Should -Contain "$testPath" } } ================================================ FILE: images/windows/templates/build.windows-2022.pkr.hcl ================================================ build { sources = ["source.azure-arm.image"] name = "windows-2022" provisioner "powershell" { inline = [ "New-Item -Path ${var.image_folder} -ItemType Directory -Force", "New-Item -Path ${var.temp_dir} -ItemType Directory -Force" ] } provisioner "file" { destination = "${var.image_folder}\\" sources = [ "${path.root}/../assets", "${path.root}/../scripts", "${path.root}/../toolsets" ] } provisioner "file" { destination = "${var.image_folder}\\scripts\\docs-gen\\" source = "${path.root}/../../../helpers/software-report-base" } provisioner "powershell" { inline = [ "Move-Item '${var.image_folder}\\assets\\post-gen' 'C:\\post-generation'", "Remove-Item -Recurse '${var.image_folder}\\assets'", "Move-Item '${var.image_folder}\\scripts\\docs-gen' '${var.image_folder}\\SoftwareReport'", "Move-Item '${var.image_folder}\\scripts\\helpers' '${var.helper_script_folder}\\ImageHelpers'", "New-Item -Type Directory -Path '${var.helper_script_folder}\\TestsHelpers\\'", "Move-Item '${var.image_folder}\\scripts\\tests\\Helpers.psm1' '${var.helper_script_folder}\\TestsHelpers\\TestsHelpers.psm1'", "Move-Item '${var.image_folder}\\scripts\\tests' '${var.image_folder}\\tests'", "Remove-Item -Recurse '${var.image_folder}\\scripts'", "Move-Item '${var.image_folder}\\toolsets\\toolset-2022.json' '${var.image_folder}\\toolset.json'", "Remove-Item -Recurse '${var.image_folder}\\toolsets'" ] } provisioner "windows-shell" { inline = [ "net user ${var.install_user} ${var.install_password} /add /passwordchg:no /passwordreq:yes /active:yes /Y", "net localgroup Administrators ${var.install_user} /add", "winrm set winrm/config/service/auth @{Basic=\"true\"}", "winrm get winrm/config/service/auth" ] } provisioner "powershell" { inline = ["if (-not ((net localgroup Administrators) -contains '${var.install_user}')) { exit 1 }"] } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" inline = ["bcdedit.exe /set TESTSIGNING ON"] } provisioner "powershell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGE_OS=${var.image_os}", "AGENT_TOOLSDIRECTORY=${var.agent_tools_directory}", "IMAGEDATA_FILE=${var.imagedata_file}", "IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] execution_policy = "unrestricted" scripts = [ "${path.root}/../scripts/build/Configure-WindowsDefender.ps1", "${path.root}/../scripts/build/Configure-PowerShell.ps1", "${path.root}/../scripts/build/Install-PowerShellModules.ps1", "${path.root}/../scripts/build/Install-WindowsFeatures.ps1", "${path.root}/../scripts/build/Install-Chocolatey.ps1", "${path.root}/../scripts/build/Configure-BaseImage.ps1", "${path.root}/../scripts/build/Configure-ImageDataFile.ps1", "${path.root}/../scripts/build/Configure-SystemEnvironment.ps1", "${path.root}/../scripts/build/Configure-DotnetSecureChannel.ps1" ] } provisioner "windows-restart" { check_registry = true restart_check_command = "powershell -command \"& {while ( (Get-WindowsOptionalFeature -Online -FeatureName Containers -ErrorAction SilentlyContinue).State -ne 'Enabled' ) { Start-Sleep 30; Write-Output 'InProgress' }}\"" restart_timeout = "10m" } provisioner "powershell" { inline = ["Set-Service -Name wlansvc -StartupType Manual", "if ($(Get-Service -Name wlansvc).Status -eq 'Running') { Stop-Service -Name wlansvc}"] } provisioner "powershell" { environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-Docker.ps1", "${path.root}/../scripts/build/Install-DockerWinCred.ps1", "${path.root}/../scripts/build/Install-DockerCompose.ps1", "${path.root}/../scripts/build/Install-PowershellCore.ps1", "${path.root}/../scripts/build/Install-WebPlatformInstaller.ps1", "${path.root}/../scripts/build/Install-TortoiseSvn.ps1" ] } provisioner "windows-restart" { restart_timeout = "30m" } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-VisualStudio.ps1", "${path.root}/../scripts/build/Install-KubernetesTools.ps1" ] valid_exit_codes = [0, 3010] } provisioner "windows-restart" { check_registry = true restart_timeout = "10m" } provisioner "powershell" { pause_before = "2m0s" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-Wix.ps1", "${path.root}/../scripts/build/Install-WDK.ps1", "${path.root}/../scripts/build/Install-VSExtensions.ps1", "${path.root}/../scripts/build/Install-AzureCli.ps1", "${path.root}/../scripts/build/Install-AzureDevOpsCli.ps1", "${path.root}/../scripts/build/Install-ChocolateyPackages.ps1", "${path.root}/../scripts/build/Install-JavaTools.ps1", "${path.root}/../scripts/build/Install-Kotlin.ps1", "${path.root}/../scripts/build/Install-OpenSSL.ps1" ] } provisioner "powershell" { execution_policy = "remotesigned" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = ["${path.root}/../scripts/build/Install-ServiceFabricSDK.ps1"] } provisioner "windows-restart" { restart_timeout = "10m" } provisioner "windows-shell" { inline = ["wmic product where \"name like '%%microsoft azure powershell%%'\" call uninstall /nointeractive"] } provisioner "powershell" { environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-ActionsCache.ps1", "${path.root}/../scripts/build/Install-Ruby.ps1", "${path.root}/../scripts/build/Install-PyPy.ps1", "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1", "${path.root}/../scripts/build/Install-NodeJS.ps1", "${path.root}/../scripts/build/Install-AndroidSDK.ps1", "${path.root}/../scripts/build/Install-PowershellAzModules.ps1", "${path.root}/../scripts/build/Install-Pipx.ps1", "${path.root}/../scripts/build/Install-Git.ps1", "${path.root}/../scripts/build/Install-GitHub-CLI.ps1", "${path.root}/../scripts/build/Install-PHP.ps1", "${path.root}/../scripts/build/Install-Rust.ps1", "${path.root}/../scripts/build/Install-Sbt.ps1", "${path.root}/../scripts/build/Install-Chrome.ps1", "${path.root}/../scripts/build/Install-EdgeDriver.ps1", "${path.root}/../scripts/build/Install-Firefox.ps1", "${path.root}/../scripts/build/Install-Selenium.ps1", "${path.root}/../scripts/build/Install-IEWebDriver.ps1", "${path.root}/../scripts/build/Install-Apache.ps1", "${path.root}/../scripts/build/Install-Nginx.ps1", "${path.root}/../scripts/build/Install-Msys2.ps1", "${path.root}/../scripts/build/Install-WinAppDriver.ps1", "${path.root}/../scripts/build/Install-R.ps1", "${path.root}/../scripts/build/Install-AWSTools.ps1", "${path.root}/../scripts/build/Install-DACFx.ps1", "${path.root}/../scripts/build/Install-MysqlCli.ps1", "${path.root}/../scripts/build/Install-SQLPowerShellTools.ps1", "${path.root}/../scripts/build/Install-SQLOLEDBDriver.ps1", "${path.root}/../scripts/build/Install-DotnetSDK.ps1", "${path.root}/../scripts/build/Install-Mingw64.ps1", "${path.root}/../scripts/build/Install-Haskell.ps1", "${path.root}/../scripts/build/Install-Stack.ps1", "${path.root}/../scripts/build/Install-Miniconda.ps1", "${path.root}/../scripts/build/Install-AzureCosmosDbEmulator.ps1", "${path.root}/../scripts/build/Install-Mercurial.ps1", "${path.root}/../scripts/build/Install-Zstd.ps1", "${path.root}/../scripts/build/Install-NSIS.ps1", "${path.root}/../scripts/build/Install-Vcpkg.ps1", "${path.root}/../scripts/build/Install-PostgreSQL.ps1", "${path.root}/../scripts/build/Install-Bazel.ps1", "${path.root}/../scripts/build/Install-AliyunCli.ps1", "${path.root}/../scripts/build/Install-RootCA.ps1", "${path.root}/../scripts/build/Install-MongoDB.ps1", "${path.root}/../scripts/build/Install-CodeQLBundle.ps1", "${path.root}/../scripts/build/Configure-Diagnostics.ps1" ] } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-WindowsUpdates.ps1", "${path.root}/../scripts/build/Configure-DynamicPort.ps1", "${path.root}/../scripts/build/Configure-GDIProcessHandleQuota.ps1", "${path.root}/../scripts/build/Configure-Shell.ps1", "${path.root}/../scripts/build/Configure-DeveloperMode.ps1", "${path.root}/../scripts/build/Install-LLVM.ps1" ] } provisioner "windows-restart" { check_registry = true restart_check_command = "powershell -command \"& {if ((-not (Get-Process TiWorker.exe -ErrorAction SilentlyContinue)) -and (-not [System.Environment]::HasShutdownStarted) ) { Write-Output 'Restart complete' }}\"" restart_timeout = "30m" } provisioner "powershell" { pause_before = "2m0s" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-WindowsUpdatesAfterReboot.ps1", "${path.root}/../scripts/build/Invoke-Cleanup.ps1", "${path.root}/../scripts/tests/RunAll-Tests.ps1" ] } provisioner "powershell" { inline = ["if (-not (Test-Path ${var.image_folder}\\tests\\testResults.xml)) { throw '${var.image_folder}\\tests\\testResults.xml not found' }"] } provisioner "powershell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGE_FOLDER=${var.image_folder}"] inline = ["pwsh -File '${var.image_folder}\\SoftwareReport\\Generate-SoftwareReport.ps1'"] } provisioner "powershell" { inline = ["if (-not (Test-Path C:\\software-report.md)) { throw 'C:\\software-report.md not found' }", "if (-not (Test-Path C:\\software-report.json)) { throw 'C:\\software-report.json not found' }"] } provisioner "file" { destination = "${path.root}/../Windows2022-Readme.md" direction = "download" source = "C:\\software-report.md" } provisioner "file" { destination = "${path.root}/../software-report.json" direction = "download" source = "C:\\software-report.json" } provisioner "powershell" { environment_vars = ["INSTALL_USER=${var.install_user}"] scripts = [ "${path.root}/../scripts/build/Install-NativeImages.ps1", "${path.root}/../scripts/build/Configure-System.ps1", "${path.root}/../scripts/build/Configure-User.ps1", "${path.root}/../scripts/build/Post-Build-Validation.ps1" ] skip_clean = true } provisioner "windows-restart" { restart_timeout = "10m" } provisioner "powershell" { inline = [ "if( Test-Path $env:SystemRoot\\System32\\Sysprep\\unattend.xml ){ rm $env:SystemRoot\\System32\\Sysprep\\unattend.xml -Force}", "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /mode:vm /quiet /quit", "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10 } else { break } }" ] } } ================================================ FILE: images/windows/templates/build.windows-2025-vs2026.pkr.hcl ================================================ build { sources = ["source.azure-arm.image"] name = "windows-2025-vs2026" provisioner "powershell" { inline = [ "New-Item -Path ${var.image_folder} -ItemType Directory -Force", "New-Item -Path ${var.temp_dir} -ItemType Directory -Force" ] } provisioner "file" { destination = "${var.image_folder}\\" sources = [ "${path.root}/../assets", "${path.root}/../scripts", "${path.root}/../toolsets" ] } provisioner "file" { destination = "${var.image_folder}\\scripts\\docs-gen\\" source = "${path.root}/../../../helpers/software-report-base" } provisioner "powershell" { inline = [ "Move-Item '${var.image_folder}\\assets\\post-gen' 'C:\\post-generation'", "Remove-Item -Recurse '${var.image_folder}\\assets'", "Move-Item '${var.image_folder}\\scripts\\docs-gen' '${var.image_folder}\\SoftwareReport'", "Move-Item '${var.image_folder}\\scripts\\helpers' '${var.helper_script_folder}\\ImageHelpers'", "New-Item -Type Directory -Path '${var.helper_script_folder}\\TestsHelpers\\'", "Move-Item '${var.image_folder}\\scripts\\tests\\Helpers.psm1' '${var.helper_script_folder}\\TestsHelpers\\TestsHelpers.psm1'", "Move-Item '${var.image_folder}\\scripts\\tests' '${var.image_folder}\\tests'", "Remove-Item -Recurse '${var.image_folder}\\scripts'", "Move-Item '${var.image_folder}\\toolsets\\toolset-2025-vs2026.json' '${var.image_folder}\\toolset.json'", "Remove-Item -Recurse '${var.image_folder}\\toolsets'" ] } provisioner "windows-shell" { inline = [ "net user ${var.install_user} ${var.install_password} /add /passwordchg:no /passwordreq:yes /active:yes /Y", "net localgroup Administrators ${var.install_user} /add", "winrm set winrm/config/service/auth @{Basic=\"true\"}", "winrm get winrm/config/service/auth" ] } provisioner "powershell" { inline = ["if (-not ((net localgroup Administrators) -contains '${var.install_user}')) { exit 1 }"] } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" inline = ["bcdedit.exe /set TESTSIGNING ON"] } provisioner "powershell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGE_OS=${var.image_os}", "AGENT_TOOLSDIRECTORY=${var.agent_tools_directory}", "IMAGEDATA_FILE=${var.imagedata_file}", "IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}", "INSTALL_VS_2026=true"] execution_policy = "unrestricted" scripts = [ "${path.root}/../scripts/build/Configure-WindowsDefender.ps1", "${path.root}/../scripts/build/Configure-PowerShell.ps1", "${path.root}/../scripts/build/Install-PowerShellModules.ps1", "${path.root}/../scripts/build/Install-WSL2.ps1", "${path.root}/../scripts/build/Install-WindowsFeatures.ps1", "${path.root}/../scripts/build/Install-Chocolatey.ps1", "${path.root}/../scripts/build/Configure-BaseImage.ps1", "${path.root}/../scripts/build/Configure-ImageDataFile.ps1", "${path.root}/../scripts/build/Configure-SystemEnvironment.ps1", "${path.root}/../scripts/build/Configure-DotnetSecureChannel.ps1" ] } provisioner "windows-restart" { check_registry = true restart_check_command = "powershell -command \"& {while ( (Get-WindowsOptionalFeature -Online -FeatureName Containers -ErrorAction SilentlyContinue).State -ne 'Enabled' ) { Start-Sleep 30; Write-Output 'InProgress' }}\"" restart_timeout = "10m" } provisioner "powershell" { inline = ["Set-Service -Name wlansvc -StartupType Manual", "if ($(Get-Service -Name wlansvc).Status -eq 'Running') { Stop-Service -Name wlansvc}"] } provisioner "powershell" { environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-Docker.ps1", "${path.root}/../scripts/build/Install-DockerWinCred.ps1", "${path.root}/../scripts/build/Install-DockerCompose.ps1", "${path.root}/../scripts/build/Install-PowershellCore.ps1", "${path.root}/../scripts/build/Install-WebPlatformInstaller.ps1" ] } provisioner "windows-restart" { restart_timeout = "30m" } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}", "INSTALL_VS_2026=true"] scripts = [ "${path.root}/../scripts/build/Install-VisualStudio.ps1", "${path.root}/../scripts/build/Install-KubernetesTools.ps1" ] valid_exit_codes = [0, 3010] } provisioner "windows-restart" { check_registry = true restart_timeout = "10m" } provisioner "powershell" { pause_before = "2m0s" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-Wix.ps1", "${path.root}/../scripts/build/Install-VSExtensions.ps1", "${path.root}/../scripts/build/Install-AzureCli.ps1", "${path.root}/../scripts/build/Install-AzureDevOpsCli.ps1", "${path.root}/../scripts/build/Install-ChocolateyPackages.ps1", "${path.root}/../scripts/build/Install-JavaTools.ps1", "${path.root}/../scripts/build/Install-Kotlin.ps1", "${path.root}/../scripts/build/Install-OpenSSL.ps1" ] } provisioner "powershell" { execution_policy = "remotesigned" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = ["${path.root}/../scripts/build/Install-ServiceFabricSDK.ps1"] } provisioner "windows-restart" { restart_timeout = "10m" } provisioner "powershell" { environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-ActionsCache.ps1", "${path.root}/../scripts/build/Install-Ruby.ps1", "${path.root}/../scripts/build/Install-PyPy.ps1", "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1", "${path.root}/../scripts/build/Install-NodeJS.ps1", "${path.root}/../scripts/build/Install-AndroidSDK.ps1", "${path.root}/../scripts/build/Install-PowershellAzModules.ps1", "${path.root}/../scripts/build/Install-Pipx.ps1", "${path.root}/../scripts/build/Install-Git.ps1", "${path.root}/../scripts/build/Install-GitHub-CLI.ps1", "${path.root}/../scripts/build/Install-PHP.ps1", "${path.root}/../scripts/build/Install-Rust.ps1", "${path.root}/../scripts/build/Install-Sbt.ps1", "${path.root}/../scripts/build/Install-Chrome.ps1", "${path.root}/../scripts/build/Install-EdgeDriver.ps1", "${path.root}/../scripts/build/Install-Firefox.ps1", "${path.root}/../scripts/build/Install-Selenium.ps1", "${path.root}/../scripts/build/Install-IEWebDriver.ps1", "${path.root}/../scripts/build/Install-Apache.ps1", "${path.root}/../scripts/build/Install-Nginx.ps1", "${path.root}/../scripts/build/Install-Msys2.ps1", "${path.root}/../scripts/build/Install-WinAppDriver.ps1", "${path.root}/../scripts/build/Install-R.ps1", "${path.root}/../scripts/build/Install-AWSTools.ps1", "${path.root}/../scripts/build/Install-DACFx.ps1", "${path.root}/../scripts/build/Install-MysqlCli.ps1", "${path.root}/../scripts/build/Install-SQLPowerShellTools.ps1", "${path.root}/../scripts/build/Install-SQLOLEDBDriver.ps1", "${path.root}/../scripts/build/Install-DotnetSDK.ps1", "${path.root}/../scripts/build/Install-Mingw64.ps1", "${path.root}/../scripts/build/Install-Haskell.ps1", "${path.root}/../scripts/build/Install-Stack.ps1", "${path.root}/../scripts/build/Install-Miniconda.ps1", "${path.root}/../scripts/build/Install-AzureCosmosDbEmulator.ps1", "${path.root}/../scripts/build/Install-Zstd.ps1", "${path.root}/../scripts/build/Install-Vcpkg.ps1", "${path.root}/../scripts/build/Install-Bazel.ps1", "${path.root}/../scripts/build/Install-RootCA.ps1", "${path.root}/../scripts/build/Install-MongoDB.ps1", "${path.root}/../scripts/build/Install-CodeQLBundle.ps1", "${path.root}/../scripts/build/Configure-Diagnostics.ps1" ] } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-PostgreSQL.ps1", "${path.root}/../scripts/build/Install-WindowsUpdates.ps1", "${path.root}/../scripts/build/Configure-DynamicPort.ps1", "${path.root}/../scripts/build/Configure-GDIProcessHandleQuota.ps1", "${path.root}/../scripts/build/Configure-Shell.ps1", "${path.root}/../scripts/build/Configure-DeveloperMode.ps1", "${path.root}/../scripts/build/Install-LLVM.ps1" ] } provisioner "windows-restart" { check_registry = true restart_check_command = "powershell -command \"& {if ((-not (Get-Process TiWorker.exe -ErrorAction SilentlyContinue)) -and (-not [System.Environment]::HasShutdownStarted) ) { Write-Output 'Restart complete' }}\"" restart_timeout = "30m" } provisioner "powershell" { pause_before = "2m0s" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-WindowsUpdatesAfterReboot.ps1", "${path.root}/../scripts/build/Invoke-Cleanup.ps1", "${path.root}/../scripts/tests/RunAll-Tests.ps1" ] } provisioner "powershell" { inline = ["if (-not (Test-Path ${var.image_folder}\\tests\\testResults.xml)) { throw '${var.image_folder}\\tests\\testResults.xml not found' }"] } provisioner "powershell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGE_FOLDER=${var.image_folder}"] inline = ["pwsh -File '${var.image_folder}\\SoftwareReport\\Generate-SoftwareReport.ps1'"] } provisioner "powershell" { inline = ["if (-not (Test-Path C:\\software-report.md)) { throw 'C:\\software-report.md not found' }", "if (-not (Test-Path C:\\software-report.json)) { throw 'C:\\software-report.json not found' }"] } provisioner "file" { destination = "${path.root}/../Windows2025-VS2026-Readme.md" direction = "download" source = "C:\\software-report.md" } provisioner "file" { destination = "${path.root}/../software-report.json" direction = "download" source = "C:\\software-report.json" } provisioner "powershell" { environment_vars = ["INSTALL_USER=${var.install_user}"] scripts = [ "${path.root}/../scripts/build/Install-NativeImages.ps1", "${path.root}/../scripts/build/Configure-System.ps1", "${path.root}/../scripts/build/Configure-User.ps1", "${path.root}/../scripts/build/Post-Build-Validation.ps1" ] skip_clean = true } provisioner "windows-restart" { restart_timeout = "10m" } provisioner "powershell" { inline = [ "if( Test-Path $env:SystemRoot\\System32\\Sysprep\\unattend.xml ){ rm $env:SystemRoot\\System32\\Sysprep\\unattend.xml -Force}", "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /mode:vm /quiet /quit", "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10 } else { break } }" ] } } ================================================ FILE: images/windows/templates/build.windows-2025.pkr.hcl ================================================ build { sources = ["source.azure-arm.image"] name = "windows-2025" provisioner "powershell" { inline = [ "New-Item -Path ${var.image_folder} -ItemType Directory -Force", "New-Item -Path ${var.temp_dir} -ItemType Directory -Force" ] } provisioner "file" { destination = "${var.image_folder}\\" sources = [ "${path.root}/../assets", "${path.root}/../scripts", "${path.root}/../toolsets" ] } provisioner "file" { destination = "${var.image_folder}\\scripts\\docs-gen\\" source = "${path.root}/../../../helpers/software-report-base" } provisioner "powershell" { inline = [ "Move-Item '${var.image_folder}\\assets\\post-gen' 'C:\\post-generation'", "Remove-Item -Recurse '${var.image_folder}\\assets'", "Move-Item '${var.image_folder}\\scripts\\docs-gen' '${var.image_folder}\\SoftwareReport'", "Move-Item '${var.image_folder}\\scripts\\helpers' '${var.helper_script_folder}\\ImageHelpers'", "New-Item -Type Directory -Path '${var.helper_script_folder}\\TestsHelpers\\'", "Move-Item '${var.image_folder}\\scripts\\tests\\Helpers.psm1' '${var.helper_script_folder}\\TestsHelpers\\TestsHelpers.psm1'", "Move-Item '${var.image_folder}\\scripts\\tests' '${var.image_folder}\\tests'", "Remove-Item -Recurse '${var.image_folder}\\scripts'", "Move-Item '${var.image_folder}\\toolsets\\toolset-2025.json' '${var.image_folder}\\toolset.json'", "Remove-Item -Recurse '${var.image_folder}\\toolsets'" ] } provisioner "windows-shell" { inline = [ "net user ${var.install_user} ${var.install_password} /add /passwordchg:no /passwordreq:yes /active:yes /Y", "net localgroup Administrators ${var.install_user} /add", "winrm set winrm/config/service/auth @{Basic=\"true\"}", "winrm get winrm/config/service/auth" ] } provisioner "powershell" { inline = ["if (-not ((net localgroup Administrators) -contains '${var.install_user}')) { exit 1 }"] } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" inline = ["bcdedit.exe /set TESTSIGNING ON"] } provisioner "powershell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGE_OS=${var.image_os}", "AGENT_TOOLSDIRECTORY=${var.agent_tools_directory}", "IMAGEDATA_FILE=${var.imagedata_file}", "IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] execution_policy = "unrestricted" scripts = [ "${path.root}/../scripts/build/Configure-WindowsDefender.ps1", "${path.root}/../scripts/build/Configure-PowerShell.ps1", "${path.root}/../scripts/build/Install-PowerShellModules.ps1", "${path.root}/../scripts/build/Install-WSL2.ps1", "${path.root}/../scripts/build/Install-WindowsFeatures.ps1", "${path.root}/../scripts/build/Install-Chocolatey.ps1", "${path.root}/../scripts/build/Configure-BaseImage.ps1", "${path.root}/../scripts/build/Configure-ImageDataFile.ps1", "${path.root}/../scripts/build/Configure-SystemEnvironment.ps1", "${path.root}/../scripts/build/Configure-DotnetSecureChannel.ps1" ] } provisioner "windows-restart" { check_registry = true restart_check_command = "powershell -command \"& {while ( (Get-WindowsOptionalFeature -Online -FeatureName Containers -ErrorAction SilentlyContinue).State -ne 'Enabled' ) { Start-Sleep 30; Write-Output 'InProgress' }}\"" restart_timeout = "10m" } provisioner "powershell" { inline = ["Set-Service -Name wlansvc -StartupType Manual", "if ($(Get-Service -Name wlansvc).Status -eq 'Running') { Stop-Service -Name wlansvc}"] } provisioner "powershell" { environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-Docker.ps1", "${path.root}/../scripts/build/Install-DockerWinCred.ps1", "${path.root}/../scripts/build/Install-DockerCompose.ps1", "${path.root}/../scripts/build/Install-PowershellCore.ps1", "${path.root}/../scripts/build/Install-WebPlatformInstaller.ps1" ] } provisioner "windows-restart" { restart_timeout = "30m" } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-VisualStudio.ps1", "${path.root}/../scripts/build/Install-KubernetesTools.ps1" ] valid_exit_codes = [0, 3010] } provisioner "windows-restart" { check_registry = true restart_timeout = "10m" } provisioner "powershell" { pause_before = "2m0s" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-Wix.ps1", "${path.root}/../scripts/build/Install-VSExtensions.ps1", "${path.root}/../scripts/build/Install-AzureCli.ps1", "${path.root}/../scripts/build/Install-AzureDevOpsCli.ps1", "${path.root}/../scripts/build/Install-ChocolateyPackages.ps1", "${path.root}/../scripts/build/Install-JavaTools.ps1", "${path.root}/../scripts/build/Install-Kotlin.ps1", "${path.root}/../scripts/build/Install-OpenSSL.ps1" ] } provisioner "powershell" { execution_policy = "remotesigned" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = ["${path.root}/../scripts/build/Install-ServiceFabricSDK.ps1"] } provisioner "windows-restart" { restart_timeout = "10m" } provisioner "powershell" { environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-ActionsCache.ps1", "${path.root}/../scripts/build/Install-Ruby.ps1", "${path.root}/../scripts/build/Install-PyPy.ps1", "${path.root}/../scripts/build/Install-Toolset.ps1", "${path.root}/../scripts/build/Configure-Toolset.ps1", "${path.root}/../scripts/build/Install-NodeJS.ps1", "${path.root}/../scripts/build/Install-AndroidSDK.ps1", "${path.root}/../scripts/build/Install-PowershellAzModules.ps1", "${path.root}/../scripts/build/Install-Pipx.ps1", "${path.root}/../scripts/build/Install-Git.ps1", "${path.root}/../scripts/build/Install-GitHub-CLI.ps1", "${path.root}/../scripts/build/Install-PHP.ps1", "${path.root}/../scripts/build/Install-Rust.ps1", "${path.root}/../scripts/build/Install-Sbt.ps1", "${path.root}/../scripts/build/Install-Chrome.ps1", "${path.root}/../scripts/build/Install-EdgeDriver.ps1", "${path.root}/../scripts/build/Install-Firefox.ps1", "${path.root}/../scripts/build/Install-Selenium.ps1", "${path.root}/../scripts/build/Install-IEWebDriver.ps1", "${path.root}/../scripts/build/Install-Apache.ps1", "${path.root}/../scripts/build/Install-Nginx.ps1", "${path.root}/../scripts/build/Install-Msys2.ps1", "${path.root}/../scripts/build/Install-WinAppDriver.ps1", "${path.root}/../scripts/build/Install-R.ps1", "${path.root}/../scripts/build/Install-AWSTools.ps1", "${path.root}/../scripts/build/Install-DACFx.ps1", "${path.root}/../scripts/build/Install-MysqlCli.ps1", "${path.root}/../scripts/build/Install-SQLPowerShellTools.ps1", "${path.root}/../scripts/build/Install-SQLOLEDBDriver.ps1", "${path.root}/../scripts/build/Install-DotnetSDK.ps1", "${path.root}/../scripts/build/Install-Mingw64.ps1", "${path.root}/../scripts/build/Install-Haskell.ps1", "${path.root}/../scripts/build/Install-Stack.ps1", "${path.root}/../scripts/build/Install-Miniconda.ps1", "${path.root}/../scripts/build/Install-AzureCosmosDbEmulator.ps1", "${path.root}/../scripts/build/Install-Zstd.ps1", "${path.root}/../scripts/build/Install-Vcpkg.ps1", "${path.root}/../scripts/build/Install-Bazel.ps1", "${path.root}/../scripts/build/Install-RootCA.ps1", "${path.root}/../scripts/build/Install-MongoDB.ps1", "${path.root}/../scripts/build/Install-CodeQLBundle.ps1", "${path.root}/../scripts/build/Configure-Diagnostics.ps1" ] } provisioner "powershell" { elevated_password = "${var.install_password}" elevated_user = "${var.install_user}" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-PostgreSQL.ps1", "${path.root}/../scripts/build/Install-WindowsUpdates.ps1", "${path.root}/../scripts/build/Configure-DynamicPort.ps1", "${path.root}/../scripts/build/Configure-GDIProcessHandleQuota.ps1", "${path.root}/../scripts/build/Configure-Shell.ps1", "${path.root}/../scripts/build/Configure-DeveloperMode.ps1", "${path.root}/../scripts/build/Install-LLVM.ps1" ] } provisioner "windows-restart" { check_registry = true restart_check_command = "powershell -command \"& {if ((-not (Get-Process TiWorker.exe -ErrorAction SilentlyContinue)) -and (-not [System.Environment]::HasShutdownStarted) ) { Write-Output 'Restart complete' }}\"" restart_timeout = "30m" } provisioner "powershell" { pause_before = "2m0s" environment_vars = ["IMAGE_FOLDER=${var.image_folder}", "TEMP_DIR=${var.temp_dir}"] scripts = [ "${path.root}/../scripts/build/Install-WindowsUpdatesAfterReboot.ps1", "${path.root}/../scripts/build/Invoke-Cleanup.ps1", "${path.root}/../scripts/tests/RunAll-Tests.ps1" ] } provisioner "powershell" { inline = ["if (-not (Test-Path ${var.image_folder}\\tests\\testResults.xml)) { throw '${var.image_folder}\\tests\\testResults.xml not found' }"] } provisioner "powershell" { environment_vars = ["IMAGE_VERSION=${var.image_version}", "IMAGE_FOLDER=${var.image_folder}"] inline = ["pwsh -File '${var.image_folder}\\SoftwareReport\\Generate-SoftwareReport.ps1'"] } provisioner "powershell" { inline = ["if (-not (Test-Path C:\\software-report.md)) { throw 'C:\\software-report.md not found' }", "if (-not (Test-Path C:\\software-report.json)) { throw 'C:\\software-report.json not found' }"] } provisioner "file" { destination = "${path.root}/../Windows2025-Readme.md" direction = "download" source = "C:\\software-report.md" } provisioner "file" { destination = "${path.root}/../software-report.json" direction = "download" source = "C:\\software-report.json" } provisioner "powershell" { environment_vars = ["INSTALL_USER=${var.install_user}"] scripts = [ "${path.root}/../scripts/build/Install-NativeImages.ps1", "${path.root}/../scripts/build/Configure-System.ps1", "${path.root}/../scripts/build/Configure-User.ps1", "${path.root}/../scripts/build/Post-Build-Validation.ps1" ] skip_clean = true } provisioner "windows-restart" { restart_timeout = "10m" } provisioner "powershell" { inline = [ "if( Test-Path $env:SystemRoot\\System32\\Sysprep\\unattend.xml ){ rm $env:SystemRoot\\System32\\Sysprep\\unattend.xml -Force}", "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /mode:vm /quiet /quit", "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10 } else { break } }" ] } } ================================================ FILE: images/windows/templates/locals.windows.pkr.hcl ================================================ locals { image_properties_map = { "win22" = { source_image_marketplace_sku = "MicrosoftWindowsServer:WindowsServer:2022-Datacenter-g2" os_disk_size_gb = 256 }, "win25" = { source_image_marketplace_sku = "MicrosoftWindowsServer:WindowsServer:2025-Datacenter-g2" os_disk_size_gb = 150 }, "win25-vs2026" = { source_image_marketplace_sku = "MicrosoftWindowsServer:WindowsServer:2025-Datacenter-g2" os_disk_size_gb = 150 } } source_image_marketplace_sku = local.image_properties_map[var.image_os].source_image_marketplace_sku os_disk_size_gb = coalesce(var.os_disk_size_gb, local.image_properties_map[var.image_os].os_disk_size_gb) } ================================================ FILE: images/windows/templates/source.windows.pkr.hcl ================================================ source "azure-arm" "image" { client_cert_path = var.client_cert_path client_id = var.client_id client_secret = var.client_secret object_id = var.object_id oidc_request_token = var.oidc_request_token oidc_request_url = var.oidc_request_url subscription_id = var.subscription_id tenant_id = var.tenant_id use_azure_cli_auth = var.use_azure_cli_auth allowed_inbound_ip_addresses = var.allowed_inbound_ip_addresses build_key_vault_name = var.build_key_vault_name build_key_vault_secret_name = var.build_key_vault_secret_name build_resource_group_name = var.build_resource_group_name communicator = "winrm" image_publisher = split(":", local.source_image_marketplace_sku)[0] image_offer = split(":", local.source_image_marketplace_sku)[1] image_sku = split(":", local.source_image_marketplace_sku)[2] image_version = var.source_image_version location = var.location managed_image_name = var.managed_image_name managed_image_resource_group_name = var.managed_image_resource_group_name managed_image_storage_account_type = var.managed_image_storage_account_type os_disk_size_gb = local.os_disk_size_gb os_type = var.image_os_type private_virtual_network_with_public_ip = var.private_virtual_network_with_public_ip temp_resource_group_name = var.temp_resource_group_name virtual_network_name = var.virtual_network_name virtual_network_resource_group_name = var.virtual_network_resource_group_name virtual_network_subnet_name = var.virtual_network_subnet_name vm_size = var.vm_size winrm_expiration_time = var.winrm_expiration_time winrm_insecure = "true" winrm_use_ssl = "true" winrm_username = var.winrm_username shared_image_gallery_destination { subscription = var.subscription_id gallery_name = var.gallery_name resource_group = var.gallery_resource_group_name image_name = var.gallery_image_name image_version = var.gallery_image_version storage_account_type = var.gallery_storage_account_type } dynamic "azure_tag" { for_each = var.azure_tags content { name = azure_tag.key value = azure_tag.value } } } ================================================ FILE: images/windows/templates/variable.windows.pkr.hcl ================================================ // Authentication related variables variable "client_cert_path" { type = string default = "${env("ARM_CLIENT_CERT_PATH")}" } variable "client_id" { type = string default = "${env("ARM_CLIENT_ID")}" } variable "client_secret" { type = string default = "${env("ARM_CLIENT_SECRET")}" sensitive = true } variable "object_id" { type = string default = "${env("ARM_OBJECT_ID")}" } variable "oidc_request_token" { type = string default = "" } variable "oidc_request_url" { type = string default = "" } variable "subscription_id" { type = string default = "${env("ARM_SUBSCRIPTION_ID")}" } variable "tenant_id" { type = string default = "${env("ARM_TENANT_ID")}" } variable "use_azure_cli_auth" { type = bool default = false } // Azure environment related variables variable "allowed_inbound_ip_addresses" { type = list(string) default = [] } variable "azure_tags" { type = map(string) default = {} } variable "build_key_vault_name" { type = string default = "${env("BUILD_KEY_VAULT_NAME")}" } variable "build_key_vault_secret_name" { type = string default = "${env("BUILD_KEY_VAULT_SECRET_NAME")}" } variable "build_resource_group_name" { type = string default = "${env("BUILD_RG_NAME")}" } variable "gallery_image_name" { type = string default = "${env("GALLERY_IMAGE_NAME")}" } variable "gallery_image_version" { type = string default = "${env("GALLERY_IMAGE_VERSION")}" } variable "gallery_name" { type = string default = "${env("GALLERY_NAME")}" } variable "gallery_resource_group_name" { type = string default = "${env("GALLERY_RG_NAME")}" } variable "gallery_storage_account_type" { type = string default = "${env("GALLERY_STORAGE_ACCOUNT_TYPE")}" } variable "image_os_type" { type = string default = "Windows" } variable "location" { type = string default = "" } variable "managed_image_name" { type = string default = "" } variable "managed_image_resource_group_name" { type = string default = "${env("ARM_RESOURCE_GROUP")}" } variable "managed_image_storage_account_type" { type = string default = "Premium_LRS" } variable "private_virtual_network_with_public_ip" { type = bool default = false } variable "os_disk_size_gb" { type = number default = null } variable "source_image_version" { type = string default = "latest" } variable "temp_resource_group_name" { type = string default = "${env("TEMP_RESOURCE_GROUP_NAME")}" } variable "virtual_network_name" { type = string default = "${env("VNET_NAME")}" } variable "virtual_network_resource_group_name" { type = string default = "${env("VNET_RESOURCE_GROUP")}" } variable "virtual_network_subnet_name" { type = string default = "${env("VNET_SUBNET")}" } variable "vm_size" { type = string default = "Standard_F8s_v2" } variable "winrm_expiration_time" { // A time duration with which to set the WinRM certificate to expire type = string // Also applies to key vault secret expiration time default = "1440h" } variable "winrm_username" { // The username used to connect to the VM via WinRM type = string // Also applies to the username used to create the VM default = "packer" } // Image related variables variable "agent_tools_directory" { type = string default = "C:\\hostedtoolcache\\windows" } variable "helper_script_folder" { type = string default = "C:\\Program Files\\WindowsPowerShell\\Modules\\" } variable "image_folder" { type = string default = "C:\\image" } variable "image_os" { type = string default = "" } variable "image_version" { type = string default = "dev" } variable "imagedata_file" { type = string default = "C:\\imagedata.json" } variable "install_password" { type = string default = "" sensitive = true } variable "install_user" { type = string default = "installer" } variable "temp_dir" { type = string default = "D:\\temp" } ================================================ FILE: images/windows/toolsets/toolset-2022.json ================================================ { "toolcache": [ { "name": "Ruby", "arch": "x64", "platform" : "win32", "versions": [ "3.2", "3.3", "3.4", "4.0" ], "default": "3.3" }, { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "3.10.*", "3.11.*", "3.12.*", "3.13.*", "3.14.*" ], "default": "3.12.*" }, { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "arch": "x86", "platform" : "win32", "versions": [ "3.10.*", "3.11.*", "3.12.*", "3.13.*" ] }, { "name": "PyPy", "arch": "x86", "platform" : "win64", "versions": [ "2.7", "3.7", "3.8", "3.9", "3.10" ] }, { "name": "node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "20.*", "22.*", "24.*" ] }, { "name": "go", "url" : "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ], "default": "1.24.*" } ], "powershellModules": [ { "name": "DockerMsftProvider" }, { "name": "MarkdownPS" }, { "name": "Pester" }, { "name": "PowerShellGet" }, { "name": "PSScriptAnalyzer" }, { "name": "PSWindowsUpdate" }, { "name": "SqlServer", "versions": [ "22.3.0" ] }, { "name": "VSSetup" }, { "name": "Microsoft.Graph" }, {"name": "AWSPowershell"} ], "azureModules": [ { "name": "az", "versions": [ "14.6.0" ] } ], "java": { "default": "8", "versions": [ "8", "11", "17", "21", "25"] }, "android": { "commandline_tools_url": "https://dl.google.com/android/repository/commandlinetools-win-9123335_latest.zip", "hash": "8A90E6A3DEB2FA13229B2E335EFD07687DCC8A55A3C544DA9F40B41404993E7D", "platform_min_version": "34", "build_tools_min_version": "34.0.0", "extras": [ "android;m2repository", "google;m2repository", "google;google_play_services" ], "addons": [], "additional_tools": [ "cmake;3.22.1", "cmake;3.31.5", "cmake;4.1.2" ], "ndk": { "default": "27", "versions": [ "27", "28", "29" ] } }, "mingw": { "version": "14.*", "runtime": "ucrt" }, "MsysPackages": { "msys2": [], "mingw": [] }, "windowsFeatures": [ { "name": "Containers" }, { "name": "Microsoft-Windows-Subsystem-Linux", "optionalFeature": true }, { "name": "VirtualMachinePlatform", "optionalFeature": true }, { "name": "NET-Framework-45-Features", "includeAllSubFeatures": true }, { "name": "Client-ProjFS", "optionalFeature": true }, { "name": "NET-Framework-Features", "includeAllSubFeatures": true }, { "name": "Hyper-V", "includeAllSubFeatures": true }, { "name": "HypervisorPlatform", "optionalFeature": true }, { "name": "Hyper-V-PowerShell" }, { "name": "Wireless-Networking" } ], "visualStudio": { "version" : "2022", "subversion" : "17", "edition" : "Enterprise", "channel": "release", "installChannelUri": "", "workloads": [ "Component.Dotfuscator", "Component.Linux.CMake", "Component.UnityEngine.x64", "Component.Unreal.Android", "Component.Xamarin", "Microsoft.Component.VC.Runtime.UCRTSDK", "Microsoft.Net.Component.4.7.2.SDK", "Microsoft.Net.Component.4.7.TargetingPack", "Microsoft.Net.Component.4.7.2.TargetingPack", "Microsoft.Net.Component.4.8.1.SDK", "Microsoft.Net.Component.4.8.1.TargetingPack", "Microsoft.VisualStudio.Component.AspNet45", "Microsoft.VisualStudio.Component.Azure.ServiceFabric.Tools", "Microsoft.VisualStudio.Component.Debugger.JustInTime", "Microsoft.VisualStudio.Component.EntityFramework", "Microsoft.VisualStudio.Component.DslTools", "Microsoft.VisualStudio.Component.LinqToSql", "Microsoft.VisualStudio.Component.SQL.SSDT", "Microsoft.VisualStudio.Component.Sharepoint.Tools", "Microsoft.VisualStudio.Component.PortableLibrary", "Microsoft.VisualStudio.Component.TeamOffice", "Microsoft.VisualStudio.Component.TestTools.CodedUITest", "Microsoft.VisualStudio.Component.TestTools.WebLoadTest", "Microsoft.VisualStudio.Component.UWP.VC.ARM64", "Microsoft.VisualStudio.Component.UWP.VC.ARM64EC", "Microsoft.VisualStudio.Component.VC.CLI.Support", "Microsoft.VisualStudio.Component.VC.CMake.Project", "Microsoft.VisualStudio.Component.VC.DiagnosticTools", "Microsoft.VisualStudio.Component.VC.Llvm.Clang", "Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset", "Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest", "Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest", "Microsoft.VisualStudio.Component.VC.Tools.ARM", "Microsoft.VisualStudio.Component.VC.Tools.ARM64", "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "Microsoft.VisualStudio.Component.VC.Modules.x86.x64", "Microsoft.VisualStudio.Component.VC.Redist.MSM", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM.Spectre", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM64EC.Spectre", "Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre", "Microsoft.VisualStudio.Component.VC.MFC.ARM", "Microsoft.VisualStudio.Component.VC.MFC.ARM.Spectre", "Microsoft.VisualStudio.Component.VC.MFC.ARM64", "Microsoft.VisualStudio.Component.VC.MFC.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.ATLMFC", "Microsoft.VisualStudio.Component.VC.ATLMFC.Spectre", "Microsoft.VisualStudio.Component.VC.ATL", "Microsoft.VisualStudio.Component.VC.ATL.Spectre", "Microsoft.VisualStudio.Component.VC.ATL.ARM", "Microsoft.VisualStudio.Component.VC.ATL.ARM.Spectre", "Microsoft.VisualStudio.Component.VC.ATL.ARM64", "Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.ASAN", "Microsoft.VisualStudio.Component.Windows10SDK.19041", "Microsoft.VisualStudio.Component.Windows11SDK.22621", "Microsoft.VisualStudio.Component.Windows11SDK.26100", "Microsoft.VisualStudio.Component.Workflow", "Microsoft.VisualStudio.ComponentGroup.Azure.CloudServices", "Microsoft.VisualStudio.ComponentGroup.Azure.ResourceManager.Tools", "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang", "Microsoft.VisualStudio.ComponentGroup.UWP.VC.v142", "Microsoft.VisualStudio.ComponentGroup.Web.CloudTools", "Microsoft.VisualStudio.Workload.Azure", "Microsoft.VisualStudio.Workload.Data", "Microsoft.VisualStudio.Workload.ManagedDesktop", "Microsoft.VisualStudio.Workload.ManagedGame", "Microsoft.VisualStudio.Workload.NativeCrossPlat", "Microsoft.VisualStudio.Workload.NativeDesktop", "Microsoft.VisualStudio.Workload.NativeGame", "Microsoft.VisualStudio.Workload.NativeMobile", "Microsoft.VisualStudio.Workload.NetCrossPlat", "Microsoft.VisualStudio.Workload.NetWeb", "Microsoft.VisualStudio.Workload.Node", "Microsoft.VisualStudio.Workload.Office", "Microsoft.VisualStudio.Workload.Python", "Microsoft.VisualStudio.Workload.Universal", "Microsoft.VisualStudio.Workload.VisualStudioExtension", "Component.MDD.Linux", "Component.Microsoft.Windows.DriverKit", "wasm.tools", "Microsoft.Component.MSBuild" ], "vsix": [ "SSIS.MicrosoftDataToolsIntegrationServices", "VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects", "WixToolset.WixToolsetVisualStudio2022Extension", "ProBITools.MicrosoftReportProjectsforVisualStudio2022", "ProBITools.MicrosoftAnalysisServicesModelingProjects2022" ] }, "docker": { "images": [ "mcr.microsoft.com/windows/servercore:ltsc2022", "mcr.microsoft.com/windows/nanoserver:ltsc2022", "mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2022", "mcr.microsoft.com/dotnet/framework/runtime:4.8-windowsservercore-ltsc2022", "mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2022" ], "components": { "docker": "29.1.5", "compose": "2.40.3" } }, "pipx": [ { "package": "yamllint", "cmd": "yamllint --version" } ], "selenium": { "version": "4" }, "npm": { "global_packages": [ { "name": "yarn", "test": "yarn --version" }, { "name": "newman", "test": "newman --version" }, { "name": "lerna", "test": "lerna --version" }, { "name": "gulp-cli", "test": "gulp --version" }, { "name": "grunt-cli", "test": "grunt --version" } ] }, "serviceFabric": { "runtime": { "version": "10.1.2493.9590", "checksum": "09C63A971BACDE338282C73B3C9174BED9AAD53E1D3A1B73D44515852C9C00CF" }, "sdk": { "version": "7.1.2493", "checksum": "0CB1084156C75CF5075EA91ABA330CF10B58648B8E036C9C2F286805263C497F" } }, "dotnet": { "versions": [ "8.0", "9.0", "10.0" ], "tools": [ { "name": "nbgv", "test": "nbgv --version", "getversion": "nbgv --version" } ], "warmup": false }, "choco": { "common_packages": [ { "name": "7zip.install" }, { "name": "aria2" }, { "name": "azcopy10" }, { "name": "Bicep" }, { "name": "innosetup" }, { "name": "jq" }, { "name": "NuGet.CommandLine" }, { "name": "packer" }, { "name": "strawberryperl" , "args": [ "--version", "5.32.1.1" ] }, { "name": "pulumi" }, { "name": "swig" }, { "name": "vswhere" }, { "name": "julia", "args": [ "--ia", "/DIR=C:\\Julia" ] }, { "name": "cmake.install", "version": "3.31.6", "args": [ "--installargs", "ADD_CMAKE_TO_PATH=\"System\"" ] }, { "name": "imagemagick" }, { "name": "ninja" } ] }, "node": { "default": "20.*" }, "maven": { "version": "3.9" }, "mysql": { "version": "8.0" }, "mongodb": { "version": "7.0" }, "nsis": { "version": "3.10" }, "llvm": { "version": "20" }, "php": { "version": "8.5" }, "postgresql": { "version": "14" }, "kotlin": { "version": "latest" }, "openssl": { "version": "3.*" }, "pwsh": { "version": "7.4" } } ================================================ FILE: images/windows/toolsets/toolset-2025-vs2026.json ================================================ { "toolcache": [ { "name": "Ruby", "arch": "x64", "platform" : "win32", "versions": [ "3.2", "3.3", "3.4", "4.0" ], "default": "3.3" }, { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "3.10.*", "3.11.*", "3.12.*", "3.13.*", "3.14.*" ], "default": "3.12.*" }, { "name": "PyPy", "arch": "x86", "platform" : "win64", "versions": [ "3.9", "3.10" ] }, { "name": "node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "20.*", "22.*", "24.*" ] }, { "name": "go", "url" : "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ], "default": "1.24.*" } ], "powershellModules": [ { "name": "DockerMsftProvider" }, { "name": "MarkdownPS" }, { "name": "Pester" }, { "name": "PowerShellGet" }, { "name": "PSScriptAnalyzer" }, { "name": "PSWindowsUpdate" }, { "name": "SqlServer", "versions": [ "22.3.0" ] }, { "name": "VSSetup" }, { "name": "Microsoft.Graph" }, {"name": "AWSPowershell"} ], "azureModules": [ { "name": "az", "versions": [ "14.6.0" ] } ], "java": { "default": "17", "versions": [ "8", "11", "17", "21", "25"] }, "android": { "commandline_tools_url": "https://dl.google.com/android/repository/commandlinetools-win-13114758_latest.zip", "hash": "98B565CB657B012DAE6794CEFC0F66AE1EFB4690C699B78A614B4A6A3505B003", "platform_min_version": "34", "build_tools_min_version": "34.0.0", "extras": [ "android;m2repository", "google;m2repository", "google;google_play_services" ], "addons": [], "additional_tools": [ "cmake;3.30.5", "cmake;3.31.5", "cmake;4.1.2" ], "ndk": { "default": "27", "versions": [ "27", "28", "29" ] } }, "mingw": { "version": "15.*", "runtime": "ucrt" }, "MsysPackages": { "msys2": [], "mingw": [] }, "windowsFeatures": [ { "name": "Containers" }, { "name": "Microsoft-Windows-Subsystem-Linux", "optionalFeature": true }, { "name": "VirtualMachinePlatform", "optionalFeature": true }, { "name": "NET-Framework-45-Features", "includeAllSubFeatures": true }, { "name": "Client-ProjFS", "optionalFeature": true }, { "name": "NET-Framework-Features", "includeAllSubFeatures": true }, { "name": "Hyper-V", "includeAllSubFeatures": true }, { "name": "HypervisorPlatform", "optionalFeature": true }, { "name": "Hyper-V-PowerShell" }, { "name": "Wireless-Networking" } ], "visualStudio": { "version" : "2026", "subversion" : "18", "edition" : "Enterprise", "channel": "stable", "installChannelUri": "", "workloads": [ "Component.Linux.CMake", "Component.UnityEngine.x64", "Microsoft.Component.VC.Runtime.UCRTSDK", "Microsoft.Net.Component.4.8.1.SDK", "Microsoft.Net.Component.4.8.1.TargetingPack", "Microsoft.VisualStudio.Component.AspNet45", "Microsoft.VisualStudio.Component.Debugger.JustInTime", "Microsoft.VisualStudio.Component.EntityFramework", "Microsoft.VisualStudio.Component.DslTools", "Microsoft.VisualStudio.Component.LinqToSql", "Microsoft.VisualStudio.Component.SQL.SSDT", "Microsoft.VisualStudio.Component.Sharepoint.Tools", "Microsoft.VisualStudio.Component.PortableLibrary", "Microsoft.VisualStudio.Component.TeamOffice", "Microsoft.VisualStudio.Component.UWP.VC.ARM64", "Microsoft.VisualStudio.Component.UWP.VC.ARM64EC", "Microsoft.VisualStudio.Component.VC.CLI.Support", "Microsoft.VisualStudio.Component.VC.CMake.Project", "Microsoft.VisualStudio.Component.VC.DiagnosticTools", "Microsoft.VisualStudio.Component.VC.Llvm.Clang", "Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset", "Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest", "Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest", "Microsoft.VisualStudio.Component.VC.Tools.ARM64", "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "Microsoft.VisualStudio.Component.VC.Redist.MSM", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM64EC.Spectre", "Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre", "Microsoft.VisualStudio.Component.VC.MFC.ARM64", "Microsoft.VisualStudio.Component.VC.MFC.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.ATLMFC", "Microsoft.VisualStudio.Component.VC.ATLMFC.Spectre", "Microsoft.VisualStudio.Component.VC.ATL", "Microsoft.VisualStudio.Component.VC.ATL.Spectre", "Microsoft.VisualStudio.Component.VC.ATL.ARM64", "Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.ASAN", "Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64", "Microsoft.VisualStudio.Component.Windows11SDK.26100", "Microsoft.VisualStudio.Component.Workflow", "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang", "Microsoft.VisualStudio.ComponentGroup.UWP.VC.v142", "Microsoft.VisualStudio.ComponentGroup.Web.CloudTools", "Microsoft.VisualStudio.Workload.Azure", "Microsoft.VisualStudio.Workload.Data", "Microsoft.VisualStudio.Workload.ManagedDesktop", "Microsoft.VisualStudio.Workload.ManagedGame", "Microsoft.VisualStudio.Workload.NativeCrossPlat", "Microsoft.VisualStudio.Workload.NativeDesktop", "Microsoft.VisualStudio.Workload.NativeGame", "Microsoft.VisualStudio.Workload.NativeMobile", "Microsoft.VisualStudio.Workload.NetCrossPlat", "Microsoft.VisualStudio.Workload.NetWeb", "Microsoft.VisualStudio.Workload.Node", "Microsoft.VisualStudio.Workload.Office", "Microsoft.VisualStudio.Workload.Python", "Microsoft.VisualStudio.Workload.Universal", "Microsoft.VisualStudio.Workload.VisualStudioExtension", "Component.MDD.Linux", "Component.Microsoft.Windows.DriverKit", "wasm.tools", "Microsoft.Component.MSBuild" ], "vsix": [ "SSIS.MicrosoftDataToolsIntegrationServices", "VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects", "WixToolset.WixToolsetVisualStudio2022Extension", "ProBITools.MicrosoftReportProjectsforVisualStudio2022", "ProBITools.MicrosoftAnalysisServicesModelingProjects2022" ] }, "docker": { "images": [], "components": { "docker": "29.1.5", "compose": "2.40.3" } }, "pipx": [ { "package": "yamllint", "cmd": "yamllint --version" } ], "selenium": { "version": "4" }, "npm": { "global_packages": [ { "name": "yarn", "test": "yarn --version" }, { "name": "newman", "test": "newman --version" }, { "name": "lerna", "test": "lerna --version" }, { "name": "gulp-cli", "test": "gulp --version" }, { "name": "grunt-cli", "test": "grunt --version" } ] }, "serviceFabric": { "runtime": { "version": "10.1.2493.9590", "checksum": "09C63A971BACDE338282C73B3C9174BED9AAD53E1D3A1B73D44515852C9C00CF" }, "sdk": { "version": "7.1.2493", "checksum": "0CB1084156C75CF5075EA91ABA330CF10B58648B8E036C9C2F286805263C497F" } }, "dotnet": { "versions": [ "8.0", "9.0", "10.0" ], "tools": [ { "name": "nbgv", "test": "nbgv --version", "getversion": "nbgv --version" } ], "warmup": false }, "choco": { "common_packages": [ { "name": "7zip.install" }, { "name": "aria2" }, { "name": "azcopy10" }, { "name": "Bicep" }, { "name": "innosetup" }, { "name": "jq" }, { "name": "NuGet.CommandLine" }, { "name": "packer" }, { "name": "pulumi" }, { "name": "swig" }, { "name": "vswhere" }, { "name": "julia", "args": [ "--ia", "/DIR=C:\\Julia" ] }, { "name": "cmake.install", "args": [ "--installargs", "ADD_CMAKE_TO_PATH=\"System\"" ] }, { "name": "strawberryperl" , "args": [ "--version", "5.42.0.1" ] }, { "name": "imagemagick" }, { "name": "ninja" } ] }, "node": { "default": "22.*" }, "maven": { "version": "3.9" }, "mysql": { "version": "8.0" }, "mongodb": { "version": "7.0" }, "llvm": { "version": "20" }, "php": { "version": "8.5" }, "postgresql": { "version": "17" }, "kotlin": { "version": "latest" }, "openssl": { "version": "3.*" }, "pwsh": { "version": "7.4" } } ================================================ FILE: images/windows/toolsets/toolset-2025.json ================================================ { "toolcache": [ { "name": "Ruby", "arch": "x64", "platform" : "win32", "versions": [ "3.2", "3.3", "3.4", "4.0" ], "default": "3.3" }, { "name": "Python", "url" : "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "3.10.*", "3.11.*", "3.12.*", "3.13.*", "3.14.*" ], "default": "3.12.*" }, { "name": "PyPy", "arch": "x86", "platform" : "win64", "versions": [ "3.9", "3.10" ] }, { "name": "node", "url" : "https://raw.githubusercontent.com/actions/node-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "20.*", "22.*", "24.*" ] }, { "name": "go", "url" : "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json", "arch": "x64", "platform" : "win32", "versions": [ "1.22.*", "1.23.*", "1.24.*", "1.25.*" ], "default": "1.24.*" } ], "powershellModules": [ { "name": "DockerMsftProvider" }, { "name": "MarkdownPS" }, { "name": "Pester" }, { "name": "PowerShellGet" }, { "name": "PSScriptAnalyzer" }, { "name": "PSWindowsUpdate" }, { "name": "SqlServer", "versions": [ "22.3.0" ] }, { "name": "VSSetup" }, { "name": "Microsoft.Graph" }, {"name": "AWSPowershell"} ], "azureModules": [ { "name": "az", "versions": [ "14.6.0" ] } ], "java": { "default": "17", "versions": [ "8", "11", "17", "21", "25"] }, "android": { "commandline_tools_url": "https://dl.google.com/android/repository/commandlinetools-win-12266719_latest.zip", "hash": "F9088C04A44F1F37A8A3A228A7663E11AE9445FA07529C96CEF38ACB985A88F3", "platform_min_version": "34", "build_tools_min_version": "34.0.0", "extras": [ "android;m2repository", "google;m2repository", "google;google_play_services" ], "addons": [], "additional_tools": [ "cmake;3.30.5", "cmake;3.31.5", "cmake;4.1.2" ], "ndk": { "default": "27", "versions": [ "27", "28", "29" ] } }, "mingw": { "version": "15.*", "runtime": "ucrt" }, "MsysPackages": { "msys2": [], "mingw": [] }, "windowsFeatures": [ { "name": "Containers" }, { "name": "Microsoft-Windows-Subsystem-Linux", "optionalFeature": true }, { "name": "VirtualMachinePlatform", "optionalFeature": true }, { "name": "NET-Framework-45-Features", "includeAllSubFeatures": true }, { "name": "Client-ProjFS", "optionalFeature": true }, { "name": "NET-Framework-Features", "includeAllSubFeatures": true }, { "name": "Hyper-V", "includeAllSubFeatures": true }, { "name": "HypervisorPlatform", "optionalFeature": true }, { "name": "Hyper-V-PowerShell" }, { "name": "Wireless-Networking" } ], "visualStudio": { "version" : "2022", "subversion" : "17", "edition" : "Enterprise", "channel": "release", "installChannelUri": "", "workloads": [ "Component.Dotfuscator", "Component.Linux.CMake", "Component.UnityEngine.x64", "Microsoft.Component.VC.Runtime.UCRTSDK", "Microsoft.Net.Component.4.8.1.SDK", "Microsoft.Net.Component.4.8.1.TargetingPack", "Microsoft.VisualStudio.Component.AspNet45", "Microsoft.VisualStudio.Component.Azure.ServiceFabric.Tools", "Microsoft.VisualStudio.Component.Debugger.JustInTime", "Microsoft.VisualStudio.Component.EntityFramework", "Microsoft.VisualStudio.Component.DslTools", "Microsoft.VisualStudio.Component.LinqToSql", "Microsoft.VisualStudio.Component.SQL.SSDT", "Microsoft.VisualStudio.Component.Sharepoint.Tools", "Microsoft.VisualStudio.Component.PortableLibrary", "Microsoft.VisualStudio.Component.TeamOffice", "Microsoft.VisualStudio.Component.TestTools.CodedUITest", "Microsoft.VisualStudio.Component.TestTools.WebLoadTest", "Microsoft.VisualStudio.Component.UWP.VC.ARM64", "Microsoft.VisualStudio.Component.UWP.VC.ARM64EC", "Microsoft.VisualStudio.Component.VC.CLI.Support", "Microsoft.VisualStudio.Component.VC.CMake.Project", "Microsoft.VisualStudio.Component.VC.DiagnosticTools", "Microsoft.VisualStudio.Component.VC.Llvm.Clang", "Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset", "Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest", "Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest", "Microsoft.VisualStudio.Component.VC.Tools.ARM", "Microsoft.VisualStudio.Component.VC.Tools.ARM64", "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "Microsoft.VisualStudio.Component.VC.Modules.x86.x64", "Microsoft.VisualStudio.Component.VC.Redist.MSM", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM.Spectre", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM64EC.Spectre", "Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre", "Microsoft.VisualStudio.Component.VC.MFC.ARM", "Microsoft.VisualStudio.Component.VC.MFC.ARM.Spectre", "Microsoft.VisualStudio.Component.VC.MFC.ARM64", "Microsoft.VisualStudio.Component.VC.MFC.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.ATLMFC", "Microsoft.VisualStudio.Component.VC.ATLMFC.Spectre", "Microsoft.VisualStudio.Component.VC.ATL", "Microsoft.VisualStudio.Component.VC.ATL.Spectre", "Microsoft.VisualStudio.Component.VC.ATL.ARM", "Microsoft.VisualStudio.Component.VC.ATL.ARM.Spectre", "Microsoft.VisualStudio.Component.VC.ATL.ARM64", "Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre", "Microsoft.VisualStudio.Component.VC.ASAN", "Microsoft.VisualStudio.Component.Windows11SDK.26100", "Microsoft.VisualStudio.Component.Workflow", "Microsoft.VisualStudio.ComponentGroup.Azure.CloudServices", "Microsoft.VisualStudio.ComponentGroup.Azure.ResourceManager.Tools", "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang", "Microsoft.VisualStudio.ComponentGroup.UWP.VC.v142", "Microsoft.VisualStudio.ComponentGroup.Web.CloudTools", "Microsoft.VisualStudio.Workload.Azure", "Microsoft.VisualStudio.Workload.Data", "Microsoft.VisualStudio.Workload.ManagedDesktop", "Microsoft.VisualStudio.Workload.ManagedGame", "Microsoft.VisualStudio.Workload.NativeCrossPlat", "Microsoft.VisualStudio.Workload.NativeDesktop", "Microsoft.VisualStudio.Workload.NativeGame", "Microsoft.VisualStudio.Workload.NativeMobile", "Microsoft.VisualStudio.Workload.NetCrossPlat", "Microsoft.VisualStudio.Workload.NetWeb", "Microsoft.VisualStudio.Workload.Node", "Microsoft.VisualStudio.Workload.Office", "Microsoft.VisualStudio.Workload.Python", "Microsoft.VisualStudio.Workload.Universal", "Microsoft.VisualStudio.Workload.VisualStudioExtension", "Component.MDD.Linux", "Component.Microsoft.Windows.DriverKit", "wasm.tools", "Microsoft.Component.MSBuild" ], "vsix": [ "SSIS.MicrosoftDataToolsIntegrationServices", "VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects", "WixToolset.WixToolsetVisualStudio2022Extension", "ProBITools.MicrosoftReportProjectsforVisualStudio2022", "ProBITools.MicrosoftAnalysisServicesModelingProjects2022" ] }, "docker": { "images": [], "components": { "docker": "29.1.5", "compose": "2.40.3" } }, "pipx": [ { "package": "yamllint", "cmd": "yamllint --version" } ], "selenium": { "version": "4" }, "npm": { "global_packages": [ { "name": "yarn", "test": "yarn --version" }, { "name": "newman", "test": "newman --version" }, { "name": "lerna", "test": "lerna --version" }, { "name": "gulp-cli", "test": "gulp --version" }, { "name": "grunt-cli", "test": "grunt --version" } ] }, "serviceFabric": { "runtime": { "version": "10.1.2493.9590", "checksum": "09C63A971BACDE338282C73B3C9174BED9AAD53E1D3A1B73D44515852C9C00CF" }, "sdk": { "version": "7.1.2493", "checksum": "0CB1084156C75CF5075EA91ABA330CF10B58648B8E036C9C2F286805263C497F" } }, "dotnet": { "versions": [ "8.0", "9.0", "10.0" ], "tools": [ { "name": "nbgv", "test": "nbgv --version", "getversion": "nbgv --version" } ], "warmup": false }, "choco": { "common_packages": [ { "name": "7zip.install" }, { "name": "aria2" }, { "name": "azcopy10" }, { "name": "Bicep" }, { "name": "innosetup" }, { "name": "jq" }, { "name": "NuGet.CommandLine" }, { "name": "packer" }, { "name": "pulumi" }, { "name": "swig" }, { "name": "vswhere" }, { "name": "julia", "args": [ "--ia", "/DIR=C:\\Julia" ] }, { "name": "cmake.install", "version": "3.31.6", "args": [ "--installargs", "ADD_CMAKE_TO_PATH=\"System\"" ] }, { "name": "strawberryperl" , "args": [ "--version", "5.42.0.1" ] }, { "name": "imagemagick" }, { "name": "ninja" } ] }, "node": { "default": "22.*" }, "maven": { "version": "3.9" }, "mysql": { "version": "8.0" }, "mongodb": { "version": "7.0" }, "llvm": { "version": "20" }, "php": { "version": "8.5" }, "postgresql": { "version": "17" }, "kotlin": { "version": "latest" }, "openssl": { "version": "3.*" }, "pwsh": { "version": "7.4" } } ================================================ FILE: images.CI/credscan-exclusions.json ================================================ { "tool": "Credential Scanner", "suppressions": [ { "placeholder": "P@ssword!!", "_justification": "Password used by SQL Express. It is required to interact with database." } ] } ================================================ FILE: images.CI/linux-and-win/build-image.ps1 ================================================ param( [String] [Parameter (Mandatory=$true)] $TemplatePath, [String] [Parameter (Mandatory=$true)] $BuildTemplateName, [String] [Parameter (Mandatory=$true)] $ClientId, [String] [Parameter (Mandatory=$false)] $ClientSecret, [String] [Parameter (Mandatory=$true)] $Location, [String] [Parameter (Mandatory=$true)] $ImageName, [String] [Parameter (Mandatory=$true)] $ImageResourceGroupName, [String] [Parameter (Mandatory=$true)] $TempResourceGroupName, [String] [Parameter (Mandatory=$true)] $SubscriptionId, [String] [Parameter (Mandatory=$true)] $TenantId, [String] [Parameter (Mandatory=$true)] $ImageOS, # e.g. "ubuntu22", "ubuntu24" or "win22", "win25" [String] [Parameter (Mandatory=$false)] $UseAzureCliAuth = "false", [String] [Parameter (Mandatory=$false)] $PluginVersion = "2.3.3", [String] [Parameter (Mandatory=$false)] $VirtualNetworkName, [String] [Parameter (Mandatory=$false)] $VirtualNetworkRG, [String] [Parameter (Mandatory=$false)] $VirtualNetworkSubnet, [String] [Parameter (Mandatory=$false)] $AllowedInboundIpAddresses = "[]", [hashtable] [Parameter (Mandatory=$false)] $Tags = @{} ) if (-not (Test-Path $TemplatePath)) { Write-Error "'-TemplatePath' parameter is not valid. You have to specify correct Template Path" exit 1 } $buildName = $($BuildTemplateName).Split(".")[1] $InstallPassword = [System.GUID]::NewGuid().ToString().ToUpper() $SensitiveData = @( 'OSType', 'StorageAccountLocation', 'OSDiskUri', 'OSDiskUriReadOnlySas', 'TemplateUri', 'TemplateUriReadOnlySas', ': ->' ) $azure_tags = $Tags | ConvertTo-Json -Compress Write-Host "Show Packer Version" packer --version Write-Host "Download packer plugins" packer plugins install github.com/hashicorp/azure $pluginVersion Write-Host "Validate packer template" packer validate -syntax-only -only "$buildName*" $TemplatePath Write-Host "Build $buildName VM" packer build -only "$buildName*" ` -var "client_id=$ClientId" ` -var "client_secret=$ClientSecret" ` -var "install_password=$InstallPassword" ` -var "location=$Location" ` -var "image_os=$ImageOS" ` -var "managed_image_name=$ImageName" ` -var "managed_image_resource_group_name=$ImageResourceGroupName" ` -var "subscription_id=$SubscriptionId" ` -var "temp_resource_group_name=$TempResourceGroupName" ` -var "tenant_id=$TenantId" ` -var "virtual_network_name=$VirtualNetworkName" ` -var "virtual_network_resource_group_name=$VirtualNetworkRG" ` -var "virtual_network_subnet_name=$VirtualNetworkSubnet" ` -var "allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" ` -var "use_azure_cli_auth=$UseAzureCliAuth" ` -var "azure_tags=$azure_tags" ` -color=false ` $TemplatePath ` | Where-Object { #Filter sensitive data from Packer logs $currentString = $_ $sensitiveString = $SensitiveData | Where-Object { $currentString -match $_ } $sensitiveString -eq $null } ================================================ FILE: images.CI/linux-and-win/cleanup.ps1 ================================================ param( [Parameter (Mandatory=$true)] [string] $TempResourceGroupName ) $groupExist = az group exists --name $TempResourceGroupName if ($groupExist -eq "true") { Write-Host "Found a match, deleting temporary files" az group delete --name $TempResourceGroupName --yes | Out-Null Write-Host "Temporary group was deleted successfully" } else { Write-Host "No temporary groups found" } ================================================ FILE: images.CI/linux-and-win/create-release.ps1 ================================================ param( [Parameter (Mandatory)] [UInt32] $BuildId, [Parameter (Mandatory)] [string] $Organization, [Parameter (Mandatory)] [string] $Project, [Parameter (Mandatory)] [string] $ImageType, [Parameter (Mandatory)] [string] $ManagedImageName, [Parameter (Mandatory)] [string] $DefinitionId, [Parameter (Mandatory)] [string] $AccessToken ) $Body = @{ definitionId = $DefinitionId variables = @{ ImageBuildId = @{ value = $BuildId } ImageType = @{ value = $ImageType } ManagedImageName = @{ value = $ManagedImageName } } isDraft = "false" } | ConvertTo-Json -Depth 3 $URL = "https://vsrm.dev.azure.com/$Organization/$Project/_apis/release/releases?api-version=5.1" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("'':${AccessToken}")) $headers = @{ Authorization = "Basic ${base64AuthInfo}" } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 $NewRelease = Invoke-RestMethod $URL -Body $Body -Method "POST" -Headers $headers -ContentType "application/json" Write-Host "Created release: $($NewRelease._links.web.href)" ================================================ FILE: images.CI/measure-provisioners-duration.ps1 ================================================ param( [Parameter(Mandatory=$true)] [string]$PackerLogPath, [string]$PrefixToPathTrim, [int]$PrintTopNLongest = 25 ) $DateTimeRegex = "(\d+\/\d+\/\d+ \d+:\d+:\d+)" $TelemetryLineRegex = "\[INFO\] \(telemetry\)" $StartProvisionerRegex = "^${DateTimeRegex} ${TelemetryLineRegex} Starting provisioner (.+)$" $EndProvisionerRegex = "^${DateTimeRegex} ${TelemetryLineRegex} ending (.+)$" $ShellScriptSubItemRegex = "${DateTimeRegex} ui: ==> .+: Provisioning with \w+ script: (.+)" $DownloadUploadSubItemRegex = "${DateTimeRegex} ui: ==> .+: (Downloading .+|Uploading .+)" function Start-ProvisionerItem { param([string]$ProvisionerType, [string]$StartTime) return @{ ProvisionerType = $ProvisionerType StartTime = [DateTime]::Parse($StartTime) SubItems = @() } } function End-ProvisionerItem { param([object]$Provisioner, [string]$EndTime) $Provisioner.EndTime = [DateTime]::Parse($EndTime) $Provisioner.Duration = New-TimeSpan -Start $Provisioner.StartTime -End $Provisioner.EndTime } function Add-ProvisionerSubItem { param([object]$Provisioner, [string]$Command, [string]$DateTime) $lastItem = $Provisioner.SubItems | Select-Object -Last 1 if ($lastItem) { $lastItem.EndTime = [DateTime]::Parse($DateTime) $lastItem.Duration = New-TimeSpan -Start $lastItem.StartTime -End $lastItem.EndTime } if ($Command) { if ($PrefixToPathTrim) { $Command = $Command.Replace($PrefixToPathTrim, ".") } $Provisioner.SubItems += @{ Command = $Command StartTime = [DateTime]::Parse($DateTime) } } } function Invoke-TryFindProvisionerSubItem { param([object]$Provisioner, [string] $Line) if ($Provisioner.ProvisionerType -in "powershell", "shell", "windows-shell") { if ($Line -match $ShellScriptSubItemRegex) { Add-ProvisionerSubItem -Provisioner $Provisioner -Command $Matches[2] -DateTime $Matches[1] } } elseif ($Provisioner.ProvisionerType -eq "file") { if ($Line -match $DownloadUploadSubItemRegex) { Add-ProvisionerSubItem -Provisioner $Provisioner -Command $Matches[2] -DateTime $Matches[1] } } } function Assert-StartProvisioner { param([object]$Provisioner, [string]$ProvisionerType) if ($null -ne $Provisioner) { throw "New provisioner '$ProvisionerType' has been started but previous '$($Provisioner.ProvisionerType)' was not finished yet" } } function Assert-EndProvisioner { param([object]$Provisioner, [string]$ProvisionerType) if (($null -ne $Provisioner) -and ($Provisioner.ProvisionerType -ne $ProvisionerType)) { throw "Expected end of '$($Provisioner.ProvisionerType)' provisioner but found end of '$ProvisionerType'" } } $provisionersList = @() $currentProvisioner = $null if ((Get-Content $PackerLogPath -Raw) -notmatch $TelemetryLineRegex) { throw "Packer log doesn't contain diagnostic information. Env PACKER_LOG must be set to 1" } Get-Content $PackerLogPath | ForEach-Object { if ($_ -match $StartProvisionerRegex) { Assert-StartProvisioner -Provisioner $currentProvisioner -ProvisionerType $Matches[2] $currentProvisioner = Start-ProvisionerItem -ProvisionerType $Matches[2] -StartTime $Matches[1] } elseif (($_ -match $EndProvisionerRegex) -and $currentProvisioner) { Assert-EndProvisioner -Provisioner $currentProvisioner -ProvisionerType $Matches[2] End-ProvisionerItem -Provisioner $currentProvisioner -EndTime $Matches[1] Add-ProvisionerSubItem -Provisioner $currentProvisioner -Command $null -DateTime $Matches[1] $provisionersList += $currentProvisioner $currentProvisioner = $null } elseif ($currentProvisioner) { Invoke-TryFindProvisionerSubItem -Provisioner $currentProvisioner -Line $_ } } $totalProvisionersTime = New-TimeSpan $provisionersList | ForEach-Object { $totalProvisionersTime = $totalProvisionersTime.Add($_.Duration) } # Print information about provisioners in order of execution Write-Host "Build timeline:" $provisionersList | ForEach-Object { Write-Host "- $($_.Duration) | $($_.ProvisionerType)" $_.SubItems | ForEach-Object { Write-Host " $($_.Duration) | $($_.Command)" } Write-Host "" } Write-Host "Total provisioners time: $totalProvisionersTime" if ($PrintTopNLongest -gt 0) { Write-Host "`n`nTop longest provisioners:" $provisionersList | ForEach-Object { if ($_.SubItems.Length -gt 0) { $_.SubItems } else { @{ Command = $_.ProvisionerType; Duration = $_.Duration } } } | Sort-Object { $_.Duration } | Select-Object -Last $PrintTopNLongest | ForEach-Object { Write-Host "- $($_.Duration) | $($_.Command)" } } ================================================ FILE: images.CI/shebang-linter.ps1 ================================================ $ErrorActionPreference = "Stop" function Validate-Scripts { Param ( [Parameter(Mandatory=$true)] [string[]]$Path, [Parameter(Mandatory=$true)] [string]$ExpectedShebang ) $ScriptWithoutShebangLine = @() Get-ChildItem $path -Recurse -File -Filter "*.sh" | ForEach-Object { $relativePath = Resolve-Path $_.FullName -Relative $shebangLine = Get-Content -Path $_.FullName | Select-Object -First 1 if ($shebangLine -eq $ExpectedShebang) { Write-Host "[+] '$relativePath'" } else { Write-Host "[-] '$relativePath'" $ScriptWithoutShebangLine += $relativePath } } return $ScriptWithoutShebangLine } $PathUbuntu = "./images/ubuntu/scripts" $PathMacOS = "./images/macos" $PatternUbuntu = "#!/bin/bash -e" $PatternMacOS = "#!/bin/bash -e -o pipefail" $ScriptsWithBrokenShebang = @() $ScriptsWithBrokenShebang += Validate-Scripts -Path $PathUbuntu -ExpectedShebang $PatternUbuntu $ScriptsWithBrokenShebang += Validate-Scripts -Path $PathMacOS -ExpectedShebang $PatternMacOS if ($ScriptsWithBrokenShebang.Length -gt 0) { Write-Host "`n`n`n##[error] The following scripts have incorrect shebang:" $ScriptsWithBrokenShebang | ForEach-Object { Write-Host "##[error] '$_'" } Write-Host "`n`n##[error] Expected shebang for scripts in 'images/ubuntu' folder is '$PatternUbuntu'" Write-Host "##[error] Expected shebang for scripts in 'images/macos' folder is '$PatternMacOS'" exit 1 else { Write-Host "All scripts have correct shebang." } } ================================================ FILE: schemas/toolset-schema.json ================================================ { "$schema": "https://json-schema.org/draft-07/schema#", "type": "object", "patternProperties": { "^.*$": { "if": { "type": "object", "required": [ "version" ], "properties": { "version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+.*$" } } }, "then": { "required": [ "pinnedDetails" ], "properties": { "pinnedDetails": { "type": "object", "properties": { "reason": { "type": "string" }, "link": { "type": "string" }, "review-at": { "type": "string", "format": "date" } } } } } } } }