diff --git a/assets/Product.wxs b/assets/Product.wxs index 0054862dcd5..e8f080e6d93 100644 --- a/assets/Product.wxs +++ b/assets/Product.wxs @@ -6,29 +6,28 @@ + - - + - - + + - - + - + @@ -165,6 +164,7 @@ + ADD_PATH=1 - + - + - + - + - + - + - + - + - + - + - + - + @@ -219,14 +219,14 @@ - + @@ -237,11 +237,13 @@ + - - - + + + + diff --git a/test/packaging/windows/msi.tests.ps1 b/test/packaging/windows/msi.tests.ps1 new file mode 100644 index 00000000000..2f67d3a47c0 --- /dev/null +++ b/test/packaging/windows/msi.tests.ps1 @@ -0,0 +1,128 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +function Test-Elevated { + [CmdletBinding()] + [OutputType([bool])] + Param() + + # if the current Powershell session was called with administrator privileges, + # the Administrator Group's well-known SID will show up in the Groups for the current identity. + # Note that the SID won't show up unless the process is elevated. + return (([Security.Principal.WindowsIdentity]::GetCurrent()).Groups -contains "S-1-5-32-544") +} + +function Invoke-Msiexec { + param( + [Parameter(ParameterSetName = 'Install', Mandatory)] + [Switch]$Install, + + [Parameter(ParameterSetName = 'Uninstall', Mandatory)] + [Switch]$Uninstall, + + [Parameter(Mandatory)] + [ValidateScript({Test-Path -Path $_})] + [String]$MsiPath, + + [Parameter(ParameterSetName = 'Install')] + [HashTable] $Properties + + ) + $action = "$($PsCmdlet.ParameterSetName)ing" + if ($Install.IsPresent) { + $switch = '/I' + } else { + $switch = '/x' + } + + $additionalOptions = @() + if ($Properties) { + foreach ($key in $Properties.Keys) { + $additionalOptions += "$key=$($Properties.$key)" + } + } + + $argumentList = "$switch $MsiPath /quiet /l*vx $msiLog $additionalOptions" + $msiExecProcess = Start-Process msiexec.exe -Wait -ArgumentList $argumentList -NoNewWindow -PassThru + if ($msiExecProcess.ExitCode -ne 0) { + $exitCode = $msiExecProcess.ExitCode + throw "$action MSI failed and returned error code $exitCode." + } +} + +Describe -Name "Windows MSI" -Fixture { + BeforeAll { + $msiX64Path = $env:PsMsiX64Path + + # Get any existing powershell core in the path + $beforePath = @(([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | + Where-Object {$_ -like '*files\powershell*'}) + + $msiLog = Join-Path -Path $TestDrive -ChildPath 'msilog.txt' + + foreach ($pathPart in $beforePath) { + Write-Warning "Found existing PowerShell path: $pathPart" + } + + if (!(Test-Elevated)) { + Write-Warning "Tests must be elevated" + } + $uploadedLog = $false + } + BeforeEach { + $Error.Clear() + } + AfterEach { + if ($Error.Count -ne 0 -and !$uploadedLog) { + if ($env:APPVEYOR) { + Push-AppveyorArtifact $msiLog + } else { + Copy-Item -Path $msiLog -Destination $env:temp -Force + Write-Verbose "MSI log is at $env:temp\msilog.txt" -Verbose + } + $uploadedLog = $true + } + } + + Context "Add Path disabled" { + It "MSI should install without error" -Skip:(!(Test-Elevated)) { + { + Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{ADD_PATH = 0} + } | Should -Not -Throw + } + + It "MSI should have not be updated path" -Skip:(!(Test-Elevated)) { + $psPath = ([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | + Where-Object {$_ -like '*files\powershell*' -and $_ -notin $beforePath} + + $psPath | Should -BeNullOrEmpty + } + + It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) { + { + Invoke-MsiExec -Uninstall -MsiPath $msiX64Path + } | Should -Not -Throw + } + } + + Context "Add Path enabled" { + It "MSI should install without error" -Skip:(!(Test-Elevated)) { + { + Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{ADD_PATH = 1} + } | Should -Not -Throw + } + + It "MSI should have updated path" -Skip:(!(Test-Elevated)) { + $psPath = ([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | + Where-Object {$_ -like '*files\powershell*' -and $_ -notin $beforePath} + + $psPath | Should -Not -BeNullOrEmpty + } + + It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) { + { + Invoke-MsiExec -Uninstall -MsiPath $msiX64Path + } | Should -Not -Throw + } + } +} diff --git a/tools/appveyor.psm1 b/tools/appveyor.psm1 index 54b1c5fc569..d290e394a23 100644 --- a/tools/appveyor.psm1 +++ b/tools/appveyor.psm1 @@ -486,36 +486,20 @@ function Invoke-AppveyorFinish $preReleaseVersion = "$previewPrefix-$previewLabel.$env:APPVEYOR_BUILD_NUMBER" } - # Save any PowerShell paths, so we can exclude them - # When we verify we updated the path - $beforePath = @(([System.Environment]::GetEnvironmentVariable('PATH','MACHINE')) -split ';' | - Where-Object {$_ -like '*files\powershell*'}) + # the packaging tests find the MSI package using env:PSMsiX64Path + $env:PSMsiX64Path = $artifacts | Where-Object { $_.EndsWith(".msi")} - foreach($pathPart in $beforePath) - { - Write-Log "Found existing PowerShell path: $pathPart" - } - - # Smoke Test MSI installer - Write-Log "Smoke-Testing MSI installer" - $msi = $artifacts | Where-Object { $_.EndsWith(".msi")} - $msiLog = Join-Path (Get-Location) 'msilog.txt' - $msiExecProcess = Start-Process msiexec.exe -Wait -ArgumentList "/I $msi /quiet /l*vx $msiLog" -NoNewWindow -PassThru - if ($msiExecProcess.ExitCode -ne 0) - { - Push-AppveyorArtifact msiLog.txt - $exitCode = $msiExecProcess.ExitCode - throw "MSI installer failed and returned error code $exitCode. MSI Log was uploaded as artifact." - } - Write-Log "MSI smoke test was successful" + # Install the latest Pester and import it + Install-Module Pester -Force -SkipPublisherCheck + Import-Module Pester -Force - # Verify path was updated by MSI - $psPath = ([System.Environment]::GetEnvironmentVariable('PATH','MACHINE')) -split ';' | - Where-Object {$_ -like '*files\powershell*' -and $_ -notin $beforePath} + # start the packaging tests and get the results + $packagingTestResult = Invoke-Pester -Script (Join-Path $repoRoot '.\test\packaging\windows\') -PassThru - if(!$psPath) + # fail the CI job if the tests failed, or nothing passed + if($packagingTestResult.FailedCount -ne 0 -or !$packagingTestResult.PassedCount) { - throw "MSI did not add powershell to path" + throw "Packaging tests failed ($($packagingTestResult.FailedCount) failed/$($packagingTestResult.PassedCount) passed)" } # only publish assembly nuget packages if it is a daily build and tests passed diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index e12beba9522..6773407b1a0 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -2280,7 +2280,13 @@ function New-MSIPackage $wixPaths = Get-WixPath $ProductSemanticVersion = Get-PackageSemanticVersion -Version $ProductVersion + $simpleProductVersion = '6' $isPreview = $ProductSemanticVersion -like '*-*' + if($isPreview) + { + $simpleProductVersion += '-preview' + } + $ProductVersion = Get-PackageVersionAsMajorMinorBuildRevision -Version $ProductVersion $assetsInSourcePath = Join-Path $ProductSourcePath 'assets' @@ -2301,15 +2307,18 @@ function New-MSIPackage [Environment]::SetEnvironmentVariable("ProductName", $ProductName, "Process") [Environment]::SetEnvironmentVariable("ProductCode", $ProductCode, "Process") [Environment]::SetEnvironmentVariable("ProductVersion", $ProductVersion, "Process") + [Environment]::SetEnvironmentVariable("SimpleProductVersion", $simpleProductVersion, "Process") [Environment]::SetEnvironmentVariable("ProductSemanticVersion", $ProductSemanticVersion, "Process") [Environment]::SetEnvironmentVariable("ProductVersionWithName", $productVersionWithName, "Process") if(!$isPreview) { + [Environment]::SetEnvironmentVariable("AddPathDefault", '1', "Process") [Environment]::SetEnvironmentVariable("UpgradeCodeX64", '31ab5147-9a97-4452-8443-d9709f0516e1', "Process") [Environment]::SetEnvironmentVariable("UpgradeCodeX86", '1d00683b-0f84-4db8-a64f-2f98ad42fe06', "Process") } else { + [Environment]::SetEnvironmentVariable("AddPathDefault", '0', "Process") [Environment]::SetEnvironmentVariable("UpgradeCodeX64", '39243d76-adaf-42b1-94fb-16ecf83237c8', "Process") [Environment]::SetEnvironmentVariable("UpgradeCodeX86", '86abcfbd-1ccc-4a88-b8b2-0facfde29094', "Process") }