[release/v7.5.6] Build, package, and create VPack for the PowerShell-LTS store package within the same msixbundle-vpack pipeline (#150)#27240
Conversation
… within the same `msixbundle-vpack` pipeline (PowerShell#150) (PowerShell#27209)
There was a problem hiding this comment.
Pull request overview
Backport updating the msixbundle-vpack official pipeline on release/v7.5.6 to build/sign LTS MSIX packages from source in the same pipeline run, then bundle/sign and prepare a VPack payload, with symbol publication and a few related string/resource updates.
Changes:
- Refactors
.pipelines/MSIXBundle-vPack-Official.ymlinto multi-stage build/package/bundle-sign flow (x64 + arm64) and adds a symbols publish stage. - Updates console host banner resource key usage and a couple of resource strings.
- Minor cleanup in packaging and telemetry code.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
.pipelines/MSIXBundle-vPack-Official.yml |
Reworks the pipeline to build/sign MSIX from source, bundle/sign MSIXBundle, create VPack, and publish symbols. |
tools/packaging/packaging.psm1 |
Minor comment/format cleanup in MSIX packaging function. |
src/System.Management.Automation/utils/Telemetry.cs |
Small refactor/cleanup in telemetry property initialization and internal call sites. |
src/System.Management.Automation/resources/TabCompletionStrings.resx |
Adds tab-completion help strings for hashtable keys / #requires and related keywords. |
src/System.Management.Automation/resources/RemotingErrorIdStrings.resx |
Updates a remoting error string to “PowerShell 7+”. |
src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx |
Renames the banner resource key. |
src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs |
Updates banner resource reference and minor local variable style. |
src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs |
Adjusts stdout-redirection flag assignment style. |
PowerShell.Common.props |
Minor condition formatting tweak. |
| $cmd = Get-Command makeappx.exe -ErrorAction Ignore | ||
| if ($cmd) { | ||
| Write-Verbose -Verbose 'makeappx available in PATH' | ||
| $exePath = $cmd.Source | ||
| } else { | ||
| $makeappx = Get-ChildItem -Recurse 'C:\Program Files (x86)\Windows Kits\10\makeappx.exe' | | ||
| Where-Object { $_.DirectoryName -match 'x64' } | | ||
| Select-Object -Last 1 | ||
| $exePath = $makeappx.FullName |
There was a problem hiding this comment.
In the makeappx discovery step, if makeappx.exe is not in PATH and the Windows Kits search returns no results (or the Kits path doesn’t exist), $makeappx will be $null and $exePath = $makeappx.FullName will throw. Add explicit checks and fail with a clear error if makeappx can’t be located (and consider using -ErrorAction Stop/SilentlyContinue appropriately on Get-ChildItem).
| $cmd = Get-Command makeappx.exe -ErrorAction Ignore | |
| if ($cmd) { | |
| Write-Verbose -Verbose 'makeappx available in PATH' | |
| $exePath = $cmd.Source | |
| } else { | |
| $makeappx = Get-ChildItem -Recurse 'C:\Program Files (x86)\Windows Kits\10\makeappx.exe' | | |
| Where-Object { $_.DirectoryName -match 'x64' } | | |
| Select-Object -Last 1 | |
| $exePath = $makeappx.FullName | |
| $cmd = Get-Command makeappx.exe -ErrorAction SilentlyContinue | |
| if ($cmd) { | |
| Write-Verbose -Verbose 'makeappx available in PATH' | |
| $exePath = $cmd.Source | |
| } else { | |
| $windowsKitsRoot = 'C:\Program Files (x86)\Windows Kits\10' | |
| $makeAppxExe = $null | |
| if (Test-Path -Path $windowsKitsRoot) { | |
| $makeAppxExe = Get-ChildItem -Path $windowsKitsRoot -Filter makeappx.exe -File -Recurse -ErrorAction SilentlyContinue | | |
| Where-Object { $_.DirectoryName -match 'x64' } | | |
| Select-Object -Last 1 | |
| } | |
| if (-not $makeAppxExe) { | |
| throw "Unable to locate makeappx.exe. It was not found in PATH and no suitable copy was found under '$windowsKitsRoot'." | |
| } | |
| $exePath = $makeAppxExe.FullName |
| $msixFiles = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse | ||
| foreach ($msixFile in $msixFiles) { | ||
| $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose | ||
| } | ||
|
|
||
| $file = Get-ChildItem $sourceDir | Select-Object -First 1 | ||
| $prefix = ($file.BaseName -split "-win")[0] |
There was a problem hiding this comment.
Create MsixBundle assumes at least one .msix was downloaded and that $file.BaseName contains -win. If no .msix files are present (or naming differs), $file will be $null and bundle naming/prefix extraction will fail. Consider validating the .msix count (expected 2: x64 + arm64), filtering Get-ChildItem to *.msix, and throwing a descriptive error when expectations aren’t met.
| $msixFiles = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse | |
| foreach ($msixFile in $msixFiles) { | |
| $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose | |
| } | |
| $file = Get-ChildItem $sourceDir | Select-Object -First 1 | |
| $prefix = ($file.BaseName -split "-win")[0] | |
| $msixFiles = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads" -Filter '*.msix' -Recurse -File | |
| foreach ($msixFile in $msixFiles) { | |
| $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose | |
| } | |
| $copiedMsixFiles = Get-ChildItem -Path $sourceDir -Filter '*.msix' -File | |
| if ($copiedMsixFiles.Count -ne 2) { | |
| $discoveredMsixNames = if ($copiedMsixFiles) { $copiedMsixFiles.Name -join ', ' } else { '<none>' } | |
| throw "Expected exactly 2 .msix files (x64 + arm64) in '$sourceDir', but found $($copiedMsixFiles.Count): $discoveredMsixNames" | |
| } | |
| $bundleSourceFile = $copiedMsixFiles | Select-Object -First 1 | |
| if ($bundleSourceFile.BaseName -notmatch '-win') { | |
| throw "Unable to derive msixbundle name from '$($bundleSourceFile.Name)' because the base name does not contain '-win'." | |
| } | |
| $prefix = ($bundleSourceFile.BaseName -split '-win', 2)[0] | |
| if ([string]::IsNullOrWhiteSpace($prefix)) { | |
| throw "Unable to derive a valid msixbundle prefix from '$($bundleSourceFile.Name)'." | |
| } |
| $msixPkgFile = Get-ChildItem -Path $repoRoot -Filter $msixPkgNameFilter -File | ||
| $msixPkgPath = $msixPkgFile.FullName |
There was a problem hiding this comment.
The packaging step uses Get-ChildItem $repoRoot -Filter "PowerShell*.msix" and then immediately accesses .FullName without validating results. If packaging fails to produce an MSIX (or produces more than one match), this will either throw later or copy the wrong file(s). Add a check that exactly one expected MSIX is found (or tighten the filter to the expected LTS MSIX naming pattern).
| $msixPkgFile = Get-ChildItem -Path $repoRoot -Filter $msixPkgNameFilter -File | |
| $msixPkgPath = $msixPkgFile.FullName | |
| $msixPkgFiles = @(Get-ChildItem -Path $repoRoot -Filter $msixPkgNameFilter -File) | |
| if ($msixPkgFiles.Count -eq 0) { | |
| throw "No MSIX package matching '$msixPkgNameFilter' was found in '$repoRoot' after packaging." | |
| } | |
| if ($msixPkgFiles.Count -gt 1) { | |
| $msixPkgFileList = ($msixPkgFiles | ForEach-Object { $_.FullName }) -join [Environment]::NewLine | |
| throw "Expected exactly one MSIX package matching '$msixPkgNameFilter' in '$repoRoot', but found $($msixPkgFiles.Count):$([Environment]::NewLine)$msixPkgFileList" | |
| } | |
| $msixPkgPath = $msixPkgFiles[0].FullName |
| $signedBundle = Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File | ||
| Write-Verbose -Verbose "Signed bundle: $signedBundle" | ||
|
|
There was a problem hiding this comment.
$signedBundle = Get-ChildItem ... -Filter "*.msixbundle" can return 0 or multiple files; the subsequent .FullName and signature verification will then fail or behave unpredictably. Treat this as an invariant and validate you found exactly one bundle before proceeding.
| $signedBundle = Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File | |
| Write-Verbose -Verbose "Signed bundle: $signedBundle" | |
| $signedBundles = @(Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File) | |
| Write-Verbose -Verbose "Found $($signedBundles.Count) signed bundle(s) in $(BundleDir)" | |
| if ($signedBundles.Count -ne 1) { | |
| $bundlePaths = if ($signedBundles.Count -gt 0) { ($signedBundles.FullName -join ', ') } else { '<none>' } | |
| throw "Expected exactly one signed .msixbundle in $(BundleDir), but found $($signedBundles.Count): $bundlePaths" | |
| } | |
| $signedBundle = $signedBundles[0] | |
| Write-Verbose -Verbose "Signed bundle: $($signedBundle.FullName)" |
| Write-Host ("sending " + $vstsCommandString) | ||
| Write-Host "##$vstsCommandString" | ||
| displayName: Create MsixBundle | ||
| retryCountOnTaskFailure: 1 |
There was a problem hiding this comment.
This job performs signing, but the steps that prepare inputs for the first signing operation (artifact downloads + bundle creation) aren’t marked with env: ob_restore_phase: true. In other signing jobs (e.g. .pipelines/templates/packaging/windows/sign.yml), pre-sign steps run in restore phase to keep signing setup/validation scoped to the signing portion and reduce overhead. Consider moving those pre-sign steps into restore phase and leaving only post-sign steps in build phase.
| retryCountOnTaskFailure: 1 | |
| retryCountOnTaskFailure: 1 | |
| env: | |
| ob_restore_phase: true |
| </data> | ||
| <data name="LabelHashtableKeyDescription" xml:space="preserve"> | ||
| <value>[string] | ||
| Specifies the name of the property being created.</value> |
There was a problem hiding this comment.
LabelHashtableKeyDescription duplicates the NameHashtableKeyDescription text (“Specifies the name…”). This looks like a copy/paste error and will show incorrect help text for the Label hashtable key in tab completion; update it to describe the label/display name semantics instead of the property name.
| Specifies the name of the property being created.</value> | |
| Specifies the label or display name for the property.</value> |
| <data name="DataHashtableKeyDescription" xml:space="preserve"> | ||
| <value>[string[]] | ||
| Selects events with any of the specified values in the EventData section.</value> | ||
| </data> |
There was a problem hiding this comment.
Line has trailing whitespace after </data>; please remove to keep the .resx clean and avoid churn in future diffs.
| </data> | |
| </data> |
| - name: WindowsContainerImage | ||
| value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' | ||
| - name: Codeql.Enabled | ||
| value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack |
There was a problem hiding this comment.
The comment for Codeql.Enabled says this pipeline “is not building artifacts; it repackages existing artifacts”, but this pipeline now builds and signs MSIX packages from source. Please update/remove the comment so it reflects current behavior (and confirm the intended Codeql.Enabled value given CodeQL tasks are enabled later).
| value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack | |
| value: true |
Backport of #27209 to release/v7.5.6
Triggered by @daxian-dbw on behalf of @daxian-dbw
Original CL Label: CL-BuildPackaging
/cc @PowerShell/powershell-maintainers
Impact
REQUIRED: Choose either Tooling Impact or Customer Impact (or both). At least one checkbox must be selected.
Tooling Impact
Updates the release/v7.5.6 VPack pipeline so the PowerShell-LTS store package is built, packaged, and prepared for VPack creation within the same official pipeline, including related packaging and symbol publication changes required for compliance.
Customer Impact
Regression
REQUIRED: Check exactly one box.
This is not a regression.
Testing
Cherry-pick completed on release/v7.5.6 after resolving one resource-file conflict in TabCompletionStrings.resx. Validation included confirming the incoming resource entries were preserved, all conflict markers were removed, and the remaining changes applied cleanly across the pipeline, packaging, and source files. Release branch CI will provide end-to-end verification.
Risk
REQUIRED: Check exactly one box.
Medium risk because the change touches official packaging pipeline configuration and several packaging-related source files, but the conflict resolution was narrowly scoped to a single .resx resource block and the overall backport remains aligned with the original PR intent.
Merge Conflicts
Conflict in src/System.Management.Automation/resources/TabCompletionStrings.resx. Cause: the target release branch and the original PR both changed the same resource region. Resolution: accepted the incoming PR resource entries and preserved the surrounding release branch file structure.