Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
51 changes: 27 additions & 24 deletions build_scripts/windows/Product.wxs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">

<?define ProductVersion="$(env.CLI_VERSION)" ?>

Expand Down Expand Up @@ -33,16 +35,14 @@
<?error Unsupported platform "$(var.Platform)" ?>
<?endif ?>

<Product Id="*"
Name="$(var.ProductName) ($(var.Architecture))"
<Package Name="$(var.ProductName) ($(var.Architecture))"
Language="1033"
Version="$(var.ProductVersion)"
Manufacturer="$(var.ProductAuthor)"
UpgradeCode="$(var.UpgradeCode)">

<Package InstallerVersion="200"
Compressed="yes"
InstallScope="perMachine" />
UpgradeCode="$(var.UpgradeCode)"
InstallerVersion="200"
Compressed="yes"
Scope="perMachine">

<Upgrade Id="$(var.UpgradeCode)">
<UpgradeVersion Property="WIX_UPGRADE_DETECTED" Maximum="$(var.ProductVersion)" IncludeMaximum="no" MigrateFeatures="yes" />
Expand All @@ -59,9 +59,9 @@
<UpgradeVersion Property="WIX_X86_UPGRADE_DETECTED" Maximum="$(var.ProductVersion)" IncludeMaximum="yes" MigrateFeatures="yes" />
<UpgradeVersion Property="WIX_X86_DOWNGRADE_DETECTED" Minimum="$(var.ProductVersion)" IncludeMinimum="no" OnlyDetect="yes" />
</Upgrade>
<Condition Message="A newer version of $(var.ProductName) is already installed.">NOT (WIX_DOWNGRADE_DETECTED OR WIX_X86_DOWNGRADE_DETECTED)</Condition>
<Launch Condition="NOT (WIX_DOWNGRADE_DETECTED OR WIX_X86_DOWNGRADE_DETECTED)" Message="A newer version of $(var.ProductName) is already installed." />
<?else ?>
<Condition Message="A newer version of $(var.ProductName) is already installed.">NOT WIX_DOWNGRADE_DETECTED</Condition>
<Launch Condition="NOT WIX_DOWNGRADE_DETECTED" Message="A newer version of $(var.ProductName) is already installed." />
<?endif ?>

<Media Id="1" Cabinet="WindowsAzureCLI.cab" EmbedCab="yes" CompressionLevel="high" />
Expand All @@ -77,7 +77,7 @@
<Property Id="WixAppFolder" Value="WixPerMachineFolder" />

<!-- Force reinstall of all files during upgrade -->
<SetProperty Id="REINSTALLMODE" Value="amus" Before="CostFinalize" Sequence="both">WIX_UPGRADE_DETECTED</SetProperty>
<SetProperty Id="REINSTALLMODE" Value="amus" Before="CostFinalize" Sequence="both" Condition="WIX_UPGRADE_DETECTED" />

<Feature Id="ProductFeature"
Title="Microsoft Azure CLI"
Expand All @@ -87,37 +87,35 @@
</Feature>

<!--Custom action to propagate path env variable change-->
<CustomActionRef Id="WixBroadcastEnvironmentChange" />
<util:BroadcastEnvironmentChange />

<!-- User Interface -->
<WixVariable Id="WixUILicenseRtf" Value="$(var.ProductResources)CLI_LICENSE.rtf"/>
<WixVariable Id="WixUIDialogBmp" Value="$(var.ProductResources)Dialog.bmp" />
<WixVariable Id="WixUIBannerBmp" Value="$(var.ProductResources)Banner.bmp" />

<UIRef Id="WixUI_Minimal"/>
<ui:WixUI Id="WixUI_Minimal" />
<UIRef Id="WixUI_ErrorProgressText"/>

<!-- Show message to restart any terminals only if the PATH is changed -->
<CustomAction Id="Set_WIXUI_EXITDIALOGOPTIONALTEXT" Property="WIXUI_EXITDIALOGOPTIONALTEXT" Value="Please close and reopen any active terminal window to use Azure CLI." />
<InstallUISequence>
<Custom Action="Set_WIXUI_EXITDIALOGOPTIONALTEXT" After="CostFinalize">NOT Installed AND NOT WIX_UPGRADE_DETECTED</Custom>
<Custom Action="Set_WIXUI_EXITDIALOGOPTIONALTEXT" After="CostFinalize" Condition="NOT Installed AND NOT WIX_UPGRADE_DETECTED" />
</InstallUISequence>

</Product>
</Package>

<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.ProgramFilesFolder)">
<Directory Id="SDKFOLDER" Name="Microsoft SDKs">
<Directory Id="AZUREFOLDER" Name="Azure">
<Directory Id="AZURECLIFOLDER" Name="CLI2">
<Directory Id="DynamicCliDir"/>
<Directory Id="BIN" Name="Bin" />
</Directory>
<StandardDirectory Id="$(var.ProgramFilesFolder)">
<Directory Id="SDKFOLDER" Name="Microsoft SDKs">
<Directory Id="AZUREFOLDER" Name="Azure">
<Directory Id="AZURECLIFOLDER" Name="CLI2">
<Directory Id="DynamicCliDir"/>
<Directory Id="BIN" Name="Bin" />
</Directory>
</Directory>
</Directory>
</Directory>
</StandardDirectory>
</Fragment>

<Fragment>
Expand Down Expand Up @@ -166,6 +164,11 @@
</Component>
</ComponentGroup>

<!-- Everything build.cmd laid out in the CLI folder (Python, site-packages, wbin, ...) -->
<ComponentGroup Id="AzureCliComponentGroup" Directory="DynamicCliDir">
<Files Include="$(var.AzureCliSource)\**" />
</ComponentGroup>

<ComponentGroup Id="ProductComponents">
<ComponentGroupRef Id="AzureCliComponentGroup"/>
<ComponentGroupRef Id="AzureCliSettingsGroup"/>
Expand Down
15 changes: 8 additions & 7 deletions build_scripts/windows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion build_scripts/windows/azure-cli.sln
Original file line number Diff line number Diff line change
@@ -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
Expand Down
51 changes: 8 additions & 43 deletions build_scripts/windows/azure-cli.wixproj
Original file line number Diff line number Diff line change
@@ -1,68 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project Sdk="WixToolset.Sdk/7.0.0">
<!-- Project -->
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>3.10</ProductVersion>
<ProjectGuid>9a0dc66d-3186-4ee4-b471-6c0f1dd6e159</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>Microsoft Azure CLI</OutputName>
<OutputType>Package</OutputType>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' AND '$(MSBuildExtensionsPath32)' != '' ">$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>
</PropertyGroup>
<!-- Local WiX -->
<PropertyGroup>
<LocalWixRoot>artifacts\wix</LocalWixRoot>
<WixToolPath>$(MSBuildThisFileDirectory)$(LocalWixRoot)</WixToolPath>
<WixTargetsPath Condition="Exists('$(WixToolPath)\Wix.targets')">$(WixToolPath)\Wix.targets</WixTargetsPath>
<WixTasksPath Condition="Exists('$(WixToolPath)\wixtasks.dll')">$(WixToolPath)\wixtasks.dll</WixTasksPath>
<!-- Don't glob for sources: artifacts\ holds the whole CLI layout with tens of thousands of files -->
<EnableDefaultItems>false</EnableDefaultItems>
<AzureCliSource>artifacts\cli</AzureCliSource>
<LinkerAdditionalOptions>-fv</LinkerAdditionalOptions>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>out\$(Configuration)\</OutputPath>
<IntermediateOutputPath>out\obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug;AzureCliSource=$(AzureCliSource)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>out\</OutputPath>
<IntermediateOutputPath>out\obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>AzureCliSource=$(AzureCliSource)</DefineConstants>
<DefineConstants Condition=" '$(Configuration)' == 'Debug' ">Debug;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>out\$(Configuration)\</OutputPath>
<IntermediateOutputPath>out\obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug;AzureCliSource=$(AzureCliSource)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>out\</OutputPath>
<IntermediateOutputPath>out\obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>AzureCliSource=$(AzureCliSource)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="out\azure-cli.wxs">
<Link>azure-cli.wxs</Link>
</Compile>
<Compile Include="Product.wxs" />
</ItemGroup>
<ItemGroup>
<None Include=".\resources\windowsazure.ico" />
</ItemGroup>
<!-- UI -->
<ItemGroup>
<WixExtension Include="WixUIExtension">
<HintPath>$(WixExtDir)\WixUIExtension.dll</HintPath>
<Name>WixUIExtension</Name>
</WixExtension>
<WixExtension Include="WixUtilExtension">
<HintPath>$(WixExtDir)\WixUtilExtension.dll</HintPath>
<Name>WixUtilExtension</Name>
</WixExtension>
<PackageReference Include="WixToolset.UI.wixext" Version="7.0.0" />
<PackageReference Include="WixToolset.Util.wixext" Version="7.0.0" />
</ItemGroup>
<Import Project="$(WixTargetsPath)" />
<Target Name="BeforeBuild">
<HeatDirectory Directory="$(AzureCliSource)" ToolPath="$(WixToolPath)" AutogenerateGuids="true" ComponentGroupName="AzureCliComponentGroup" SuppressRootDirectory="true" DirectoryRefId="DynamicCliDir" OutputFile="out\azure-cli.wxs" PreprocessorVariable="var.AzureCliSource" />
</Target>
</Project>
28 changes: 5 additions & 23 deletions build_scripts/windows/scripts/build.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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%
Expand Down Expand Up @@ -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%\*"
Expand Down
79 changes: 79 additions & 0 deletions build_scripts/windows/scripts/test_msi_upgrade.ps1
Original file line number Diff line number Diff line change
@@ -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"
Loading