diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 280c045d7eb..1a8a70ef590 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -236,6 +236,12 @@ jobs: inputs: artifactFeeds: $(AZURE_ARTIFACTS_FEEDS) + - task: UseDotNet@2 + displayName: Install .NET 8 SDK + inputs: + packageType: sdk + version: 8.0.x + - script: | set ARCH=$(Platform) set TARGET=msi @@ -328,6 +334,11 @@ jobs: TargetPath: '$(Build.ArtifactStagingDirectory)/msi' artifactName: msi-$(Platform) + - task: PowerShell@2 + displayName: Upgrade from Previous Release + inputs: + filePath: build_scripts\windows\scripts\test_msi_upgrade.ps1 + - task: PowerShell@2 displayName: Install and Load CLI inputs: diff --git a/build_scripts/windows/Product.wxs b/build_scripts/windows/Product.wxs index c4d7fe2ebeb..106a6cb6807 100644 --- a/build_scripts/windows/Product.wxs +++ b/build_scripts/windows/Product.wxs @@ -1,5 +1,7 @@ - + @@ -33,16 +35,14 @@ - - - + UpgradeCode="$(var.UpgradeCode)" + InstallerVersion="200" + Compressed="yes" + Scope="perMachine"> @@ -59,9 +59,9 @@ - NOT (WIX_DOWNGRADE_DETECTED OR WIX_X86_DOWNGRADE_DETECTED) + - NOT WIX_DOWNGRADE_DETECTED + @@ -77,7 +77,7 @@ - WIX_UPGRADE_DETECTED + - + - + - NOT Installed AND NOT WIX_UPGRADE_DETECTED + - + - - - - - - - - + + + + + + - + @@ -166,6 +164,11 @@ + + + + + diff --git a/build_scripts/windows/README.md b/build_scripts/windows/README.md index 6ceabff1f9c..52b2c8167b2 100644 --- a/build_scripts/windows/README.md +++ b/build_scripts/windows/README.md @@ -6,19 +6,20 @@ This document provides instructions on creating the MSI. Prerequisites ------------- -1. Turn on the '.NET Framework 3.5' Windows Feature (required for WIX Toolset). +1. Install the .NET SDK 8 or later. + https://dotnet.microsoft.com/download + - The WiX Toolset is restored from NuGet as part of the build, so it doesn't need to be installed separately. -2. Install 'WIX Toolset build tools' if not already installed. (e.g. WiX v3.10.3) - http://wixtoolset.org/releases/ +2. Accept the WiX Toolset Open Source Maintenance Fee (OSMF) EULA, which WiX v7 requires. Either: + - run `dotnet build build_scripts\windows\azure-cli.wixproj -t:AcceptEula -p:EulaId=wix7` once per user and machine, or + - set the `AcceptEula` environment variable to `wix7` for the build (e.g. in CI). + See https://wixtoolset.org/osmf/ for the terms. 3. Get Git for Windows (it has several tools used for the build). - https://github.com/git-for-windows/git/releases/download/v2.12.2.windows.1/Git-2.12.2-32-bit.exe - Choose the 'Use Git and optional Unix tools from the Windows Command Prompt' option. -4. Install 'Microsoft Build Tools 2015'. - https://www.microsoft.com/download/details.aspx?id=48159 - -5. Clone the repository. +4. Clone the repository. - git clone https://github.com/azure/azure-cli Note: The above can be done on a Windows 10 VM. diff --git a/build_scripts/windows/azure-cli.sln b/build_scripts/windows/azure-cli.sln index fa338bf49a3..360525226be 100644 --- a/build_scripts/windows/azure-cli.sln +++ b/build_scripts/windows/azure-cli.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 -Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "azure-cli", "azure-cli.wixproj", "{9A0DC66D-3186-4EE4-B471-6C0F1DD6E159}" +Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "azure-cli", "azure-cli.wixproj", "{9A0DC66D-3186-4EE4-B471-6C0F1DD6E159}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/build_scripts/windows/azure-cli.wixproj b/build_scripts/windows/azure-cli.wixproj index e0497237fbd..ce9f82673b2 100644 --- a/build_scripts/windows/azure-cli.wixproj +++ b/build_scripts/windows/azure-cli.wixproj @@ -1,50 +1,25 @@ - - + Debug x86 - 3.10 - 9a0dc66d-3186-4ee4-b471-6c0f1dd6e159 - 2.0 Microsoft Azure CLI Package - $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets - $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets - - - - artifacts\wix - $(MSBuildThisFileDirectory)$(LocalWixRoot) - $(WixToolPath)\Wix.targets - $(WixToolPath)\wixtasks.dll + + false artifacts\cli - -fv - - - out\$(Configuration)\ - out\obj\$(Configuration)\ - Debug;AzureCliSource=$(AzureCliSource) - - - out\ - out\obj\$(Configuration)\ AzureCliSource=$(AzureCliSource) + Debug;$(DefineConstants) - + out\$(Configuration)\ out\obj\$(Configuration)\ - Debug;AzureCliSource=$(AzureCliSource) - + out\ out\obj\$(Configuration)\ - AzureCliSource=$(AzureCliSource) - - azure-cli.wxs - @@ -52,17 +27,7 @@ - - $(WixExtDir)\WixUIExtension.dll - WixUIExtension - - - $(WixExtDir)\WixUtilExtension.dll - WixUtilExtension - + + - - - - diff --git a/build_scripts/windows/scripts/build.cmd b/build_scripts/windows/scripts/build.cmd index dd11ab9972f..8d47e91276b 100644 --- a/build_scripts/windows/scripts/build.cmd +++ b/build_scripts/windows/scripts/build.cmd @@ -4,10 +4,10 @@ SetLocal EnableDelayedExpansion REM Double colon :: should not be used in parentheses blocks, so we use REM. REM See https://stackoverflow.com/a/12407934/2199657 -echo build a msi installer using local cli sources and python executables. You need to have curl.exe, unzip.exe and msbuild.exe available under PATH +echo build a msi installer using local cli sources and python executables. You need to have curl.exe, unzip.exe and dotnet.exe (.NET SDK 8 or later) available under PATH echo. -set "PATH=%PATH%;%ProgramFiles%\Git\bin;%ProgramFiles%\Git\usr\bin;C:\Program Files (x86)\Git\bin;C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin" +set "PATH=%PATH%;%ProgramFiles%\Git\bin;%ProgramFiles%\Git\usr\bin;C:\Program Files (x86)\Git\bin" if "%CLI_VERSION%"=="" ( echo Please set the CLI_VERSION environment variable, e.g. 2.0.13 @@ -32,7 +32,6 @@ if "%ARCH%"=="x86" ( ) set PYTHON_VERSION=3.14.8 -set WIX_DOWNLOAD_URL="https://azurecliprod.blob.core.windows.net/msi/wix310-binaries-mirror.zip" set PYTHON_DOWNLOAD_URL="https://www.python.org/ftp/python/%PYTHON_VERSION%/python-%PYTHON_VERSION%-embed-%PYTHON_ARCH%.zip" REM https://pip.pypa.io/en/stable/installation/#get-pip-py @@ -48,7 +47,6 @@ set ARTIFACTS_DIR=%~dp0..\artifacts mkdir %ARTIFACTS_DIR% set TEMP_SCRATCH_FOLDER=%ARTIFACTS_DIR%\cli_scratch set BUILDING_DIR=%ARTIFACTS_DIR%\cli -set WIX_DIR=%ARTIFACTS_DIR%\wix set PYTHON_DIR=%ARTIFACTS_DIR%\Python REM Get the absolute directory since we pushd into different levels of subdirectories. @@ -76,24 +74,6 @@ if exist %REPO_ROOT%\privates ( copy %REPO_ROOT%\privates\*.whl %TEMP_SCRATCH_FOLDER% ) -if "%TARGET%" == "msi" ( - REM ensure wix is available - if exist %WIX_DIR% ( - echo Using existing Wix at %WIX_DIR% - ) - if not exist %WIX_DIR% ( - mkdir %WIX_DIR% - pushd %WIX_DIR% - echo Downloading Wix. - curl --output wix-archive.zip %WIX_DOWNLOAD_URL% - unzip wix-archive.zip - if %errorlevel% neq 0 goto ERROR - del wix-archive.zip - echo Wix downloaded and extracted successfully. - popd - ) -) - REM ensure Python is available if exist %PYTHON_DIR% ( echo Using existing Python at %PYTHON_DIR% @@ -231,7 +211,9 @@ popd if "%TARGET%"=="msi" ( echo Building MSI... - msbuild /t:rebuild /p:Configuration=Release /p:Platform=%ARCH% %REPO_ROOT%\build_scripts\windows\azure-cli.wixproj + REM The WiX Toolset SDK is restored from NuGet. WiX v7 requires accepting its OSMF EULA, + REM e.g. by setting the AcceptEula environment variable to wix7. See https://wixtoolset.org/osmf/ + dotnet build --no-incremental --configuration Release -p:Platform=%ARCH% %REPO_ROOT%\build_scripts\windows\azure-cli.wixproj ) else ( echo Building ZIP... "%ProgramFiles%\7-Zip\7z.exe" a -tzip "%OUTPUT_DIR%\Microsoft Azure CLI.zip" "%BUILDING_DIR%\*" diff --git a/build_scripts/windows/scripts/test_msi_upgrade.ps1 b/build_scripts/windows/scripts/test_msi_upgrade.ps1 new file mode 100644 index 00000000000..dac416dc33e --- /dev/null +++ b/build_scripts/windows/scripts/test_msi_upgrade.ps1 @@ -0,0 +1,79 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# Installs the latest released MSI older than the artifact, then upgrades it with the artifact MSI and checks +# that exactly one Azure CLI install of the artifact version remains. +# Expects the same inputs as test_msi_installation.ps1: $env:SYSTEM_ARTIFACTSDIRECTORY\msi, \metadata\version +# and $env:PLATFORM (x86 or x64). Must run elevated. + +$ErrorActionPreference = 'Stop' +# Windows PowerShell's progress bar slows down Invoke-WebRequest considerably +$ProgressPreference = 'SilentlyContinue' + +if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + throw "This script must be run as Administrator." +} + +function Invoke-Msiexec($Arguments, $LogFile) { + Write-Output "Calling: msiexec.exe $Arguments /l*v `"$LogFile`"" + $process = Start-Process msiexec.exe -ArgumentList "$Arguments /l*v `"$LogFile`"" -Wait -NoNewWindow -PassThru + if ($process.ExitCode -ne 0) { + # Show where the installation failed + Select-String -Path $LogFile -Pattern 'Return value 3' -Context 20, 0 | Select-Object -First 1 | Out-String | Write-Output + throw "msiexec.exe exited with $($process.ExitCode)" + } +} + +function Get-AzureCliProducts { + Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' | + Where-Object { $_.DisplayName -like 'Microsoft Azure CLI*' } +} + +$artifact_msi = "$env:SYSTEM_ARTIFACTSDIRECTORY\msi\Microsoft Azure CLI.msi" +$artifact_version = (Get-Content $env:SYSTEM_ARTIFACTSDIRECTORY\metadata\version).Trim() +Write-Output "Artifact version: $artifact_version" + +# Find the latest release older than the artifact. The MSIs are released together with the PyPI packages. +$releases = (Invoke-RestMethod https://pypi.org/pypi/azure-cli/json).releases.PSObject.Properties.Name +$previous_version = $releases | Where-Object { $_ -match '^\d+\.\d+\.\d+$' -and [version]$_ -lt [version]$artifact_version } | + Sort-Object { [version]$_ } | Select-Object -Last 1 +Write-Output "Upgrading from: $previous_version" + +$msi_suffix = if ($env:PLATFORM -eq 'x64') { '-x64' } else { '' } +$previous_msi = "$env:TEMP\azure-cli-$previous_version$msi_suffix.msi" +Invoke-WebRequest "https://azcliprod.blob.core.windows.net/msi/azure-cli-$previous_version$msi_suffix.msi" -OutFile $previous_msi -UseBasicParsing + +# Start from a clean machine, e.g. remove the Azure CLI preinstalled on the agent. +foreach ($product in Get-AzureCliProducts) { + Write-Output "Uninstalling $($product.DisplayName) $($product.DisplayVersion)" + Invoke-Msiexec "/x $($product.PSChildName) /qn /norestart" "$env:TEMP\uninstall_$($product.PSChildName).log" +} +if (Get-AzureCliProducts) { + throw "Failed to uninstall the existing Azure CLI." +} + +Invoke-Msiexec "/i `"$previous_msi`" /qn /norestart" "$env:TEMP\install_previous.log" +Get-AzureCliProducts | Format-List DisplayName, DisplayVersion + +Invoke-Msiexec "/i `"$artifact_msi`" /qn /norestart" "$env:TEMP\upgrade.log" +$products = @(Get-AzureCliProducts) +$products | Format-List DisplayName, DisplayVersion +if ($products.Count -ne 1 -or $products[0].DisplayVersion -ne $artifact_version) { + throw "Expected exactly one Azure CLI $artifact_version installation after the upgrade." +} + +if ($env:PLATFORM -eq 'x64') { + $az_wbin = "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin" +} else { + $az_wbin = "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin" +} +$installed_version = & "$az_wbin\az.cmd" version --query '\"azure-cli\"' -o tsv +Write-Output "Installed version: $installed_version" +if ($installed_version -ne $artifact_version) { + throw "The installed version doesn't match the artifact version." +} + +# test_msi_installation.ps1 runs `az --version` first, so make sure the upgraded CLI is on PATH for later steps. +Write-Output "##vso[task.prependpath]$az_wbin"