Skip to content

Commit b1ad606

Browse files
authored
Add exe wrapper for Microsoft Update scenarios (PowerShell#14737)
1 parent 425c40d commit b1ad606

6 files changed

Lines changed: 457 additions & 40 deletions

File tree

assets/wix/ExeLicense.rtf

Lines changed: 206 additions & 0 deletions
Large diffs are not rendered by default.

assets/wix/bundle.wxs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
3+
<!-- UpgradeCode GUID MUST REMAIN SAME THROUGHOUT ALL VERSIONS, otherwise, updates won't occur. -->
4+
<?if $(sys.BUILDARCH)=x64?>
5+
<?define UpgradeCodePreview = "3C90221B-D500-43C6-A4A6-0BE6C2C1B317"?>
6+
<?define UpgradeCodeRelease = "7A804CBB-648E-4276-9A58-081862DB1B99"?>
7+
<?if $(var.IsPreview)=True?>
8+
<?define UpgradeCode = $(var.UpgradeCodePreview)?>
9+
<?else?>
10+
<?define UpgradeCode = $(var.UpgradeCodeRelease)?>
11+
<?endif?>
12+
<?else?>
13+
<?define UpgradeCodePreview = "4A699A9C-E904-4024-BCD2-44E098A8C6BD"?>
14+
<?define UpgradeCodeRelease = "ED46CB02-64B3-43FD-A63E-6CF269D8C21C"?>
15+
<?if $(var.IsPreview)=True?>
16+
<?define UpgradeCode = $(var.UpgradeCodePreview)?>
17+
<?else?>
18+
<?define UpgradeCode = $(var.UpgradeCodeRelease)?>
19+
<?endif?>
20+
<?endif?>
21+
22+
<Bundle Name="PowerShell $(var.WindowsVersion)-$(sys.BUILDARCH)" Version="$(var.WindowsVersion)" Manufacturer="Microsoft Corporation" UpgradeCode="$(var.UpgradeCode)">
23+
<!-- See https://wixtoolset.org/documentation/manual/v3/bundle/wixstdba/ for a list of WiX standard bootstrapper types. -->
24+
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
25+
<bal:WixStandardBootstrapperApplication LicenseFile="assets\wix\ExeLicense.rtf" LogoFile="assets\ps_black_32x32.ico" />
26+
</BootstrapperApplicationRef>
27+
<Chain>
28+
<MsiPackage SourceFile="$(var.TargetPath)" Compressed="yes" />
29+
</Chain>
30+
</Bundle>
31+
</Wix>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
Describe -Name "Windows EXE" -Fixture {
5+
BeforeAll {
6+
function Test-Elevated {
7+
[CmdletBinding()]
8+
[OutputType([bool])]
9+
Param()
10+
11+
# if the current Powershell session was called with administrator privileges,
12+
# the Administrator Group's well-known SID will show up in the Groups for the current identity.
13+
# Note that the SID won't show up unless the process is elevated.
14+
return (([Security.Principal.WindowsIdentity]::GetCurrent()).Groups -contains "S-1-5-32-544")
15+
}
16+
17+
function Invoke-ExeInstaller {
18+
param(
19+
[Parameter(ParameterSetName = 'Install', Mandatory)]
20+
[Switch]$Install,
21+
22+
[Parameter(ParameterSetName = 'Uninstall', Mandatory)]
23+
[Switch]$Uninstall,
24+
25+
[Parameter(Mandatory)]
26+
[ValidateScript({Test-Path -Path $_})]
27+
[String]$ExePath
28+
)
29+
$action = "$($PSCmdlet.ParameterSetName)ing"
30+
if ($Install) {
31+
$switch = '/install'
32+
} else {
33+
$switch = '/uninstall'
34+
}
35+
36+
$installProcess = Start-Process -wait $ExePath -ArgumentList $switch, '/quiet', '/norestart' -PassThru
37+
if ($installProcess.ExitCode -ne 0) {
38+
$exitCode = $installProcess.ExitCode
39+
throw "$action EXE failed and returned error code $exitCode."
40+
}
41+
}
42+
43+
$exePath = $env:PsExePath
44+
$channel = $env:PSMsiChannel
45+
$runtime = $env:PSMsiRuntime
46+
47+
# Get any existing powershell in the path
48+
$beforePath = @(([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' |
49+
Where-Object {$_ -like '*files\powershell*'})
50+
51+
foreach ($pathPart in $beforePath) {
52+
Write-Warning "Found existing PowerShell path: $pathPart"
53+
}
54+
55+
if (!(Test-Elevated)) {
56+
Write-Warning "Tests must be elevated"
57+
}
58+
}
59+
BeforeEach {
60+
$error.Clear()
61+
}
62+
63+
Context "$Channel-$Runtime" {
64+
BeforeAll {
65+
Write-Verbose "cr-$channel-$runtime" -Verbose
66+
switch ("$channel-$runtime") {
67+
"preview-win7-x64" {
68+
$msiUpgradeCode = '39243d76-adaf-42b1-94fb-16ecf83237c8'
69+
}
70+
"stable-win7-x64" {
71+
$msiUpgradeCode = '31ab5147-9a97-4452-8443-d9709f0516e1'
72+
}
73+
"preview-win7-x86" {
74+
$msiUpgradeCode = '86abcfbd-1ccc-4a88-b8b2-0facfde29094'
75+
}
76+
"stable-win7-x86" {
77+
$msiUpgradeCode = '1d00683b-0f84-4db8-a64f-2f98ad42fe06'
78+
}
79+
default {
80+
throw "'$_' not a valid channel runtime combination"
81+
}
82+
}
83+
}
84+
85+
It "$Channel MSI should not be installed before test" -Skip:(!(Test-Elevated)) {
86+
$result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$msiUpgradeCode}'")
87+
$result.Count | Should -Be 0 -Because "Query should return nothing if $channel $runtime is not installed"
88+
}
89+
90+
It "EXE should install without error" -Skip:(!(Test-Elevated)) {
91+
{
92+
Invoke-ExeInstaller -Install -ExePath $exePath
93+
} | Should -Not -Throw
94+
}
95+
96+
It "Upgrade code should be correct" -Skip:(!(Test-Elevated)) {
97+
$result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$msiUpgradeCode}'")
98+
$result.Count | Should -Be 1 -Because "Query should return 1 result if Upgrade code is for $runtime $channel"
99+
}
100+
101+
It "MSI should have updated path" -Skip:(!(Test-Elevated)) {
102+
if ($channel -eq 'preview') {
103+
$pattern = '*files*\powershell*\preview*'
104+
} else {
105+
$pattern = '*files*\powershell*'
106+
}
107+
108+
$psPath = ([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' |
109+
Where-Object { $_ -like $pattern -and $_ -notin $beforePath }
110+
111+
if (!$psPath) {
112+
([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' |
113+
Where-Object { $_ -notin $beforePath } |
114+
ForEach-Object { Write-Verbose -Verbose $_ }
115+
}
116+
117+
$psPath | Should -Not -BeNullOrEmpty
118+
}
119+
120+
It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) {
121+
{
122+
Invoke-ExeInstaller -Uninstall -ExePath $exePath
123+
} | Should -Not -Throw
124+
}
125+
}
126+
}

test/packaging/windows/msi.tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ Describe -Name "Windows MSI" -Fixture {
106106

107107
It "$Channel MSI should not be installed before test" -Skip:(!(Test-Elevated)) {
108108
$result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$msiUpgradeCode}'")
109-
$result.Count | Should -Be 0 -Because "Query should return nothing if $channel x64 is not installed"
109+
$result.Count | Should -Be 0 -Because "Query should return nothing if $channel $runtime is not installed"
110110
}
111111

112112
It "MSI should install without error" -Skip:(!(Test-Elevated)) {
@@ -117,7 +117,7 @@ Describe -Name "Windows MSI" -Fixture {
117117

118118
It "Upgrade code should be correct" -Skip:(!(Test-Elevated)) {
119119
$result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$msiUpgradeCode}'")
120-
$result.Count | Should -Be 1 -Because "Query should return 1 result if Upgrade code is for x64 $channel"
120+
$result.Count | Should -Be 1 -Because "Query should return 1 result if Upgrade code is for $runtime $channel"
121121
}
122122

123123
It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) {

tools/ci.psm1

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ function Invoke-CIFinish
501501

502502
# the packaging tests find the MSI package using env:PSMsiX64Path
503503
$env:PSMsiX64Path = $artifacts | Where-Object { $_.EndsWith(".msi")}
504+
$env:PSExePath = $artifacts | Where-Object { $_.EndsWith(".exe") }
504505
$env:PSMsiChannel = $Channel
505506
$env:PSMsiRuntime = $Runtime
506507

@@ -509,8 +510,12 @@ function Invoke-CIFinish
509510
Install-Module Pester -Force -SkipPublisherCheck -MaximumVersion $maximumPesterVersion
510511
Import-Module Pester -Force -MaximumVersion $maximumPesterVersion
511512

513+
$testResultPath = Join-Path -Path $env:TEMP -ChildPath "win-package-$channel-$runtime.xml"
514+
512515
# start the packaging tests and get the results
513-
$packagingTestResult = Invoke-Pester -Script (Join-Path $repoRoot '.\test\packaging\windows\') -PassThru
516+
$packagingTestResult = Invoke-Pester -Script (Join-Path $repoRoot '.\test\packaging\windows\') -PassThru -OutputFormat NUnitXml -OutputFile $testResultPath
517+
518+
Publish-TestResults -Title "win-package-$channel-$runtime" -Path $testResultPath
514519

515520
# fail the CI job if the tests failed, or nothing passed
516521
if(-not $packagingTestResult -is [pscustomobject] -or $packagingTestResult.FailedCount -ne 0 -or $packagingTestResult.PassedCount -eq 0)

0 commit comments

Comments
 (0)