mirror of
https://github.com/MAKS-IT-COM/maksit-cluster-console.git
synced 2026-09-30 00:38:10 +02:00
(build): pack a microsoft store msix beside the github setup exe
This commit is contained in:
parent
7e8a4032e2
commit
7e481262d5
@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.8.2] - 2026-09-28
|
||||
|
||||
### Added
|
||||
|
||||
- Microsoft Store package is a full-trust x64 MSIX (`MAKS-IT.ClusterConsole`). The WiX setup exe remains the GitHub installer. The MSIX is not a GitHub release asset.
|
||||
|
||||
## [0.8.1] - 2026-09-27
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -29,6 +29,54 @@ Coverage shields in `README.md` are rewritten by **CoverageBadges**.
|
||||
2. Commit on `main`, tag `v{version}` on HEAD (`v1.2.3` or SemVer prerelease such as `v0.1.0-alpha.1`, `v0.1.0-beta.1`, `v0.1.0-rc.1`). GitHub marks hyphenated versions as prerelease.
|
||||
3. Run `utils\Invoke-ReleasePackage.bat`. That run publishes the portable zip (win-x64), Windows setup exe, and Flatpak (Flatpak via WSL Debian on Windows). Publishing the GitHub Release starts [macOS release assets](.github/workflows/macos-release.yml), which attaches unsigned `osx-arm64` and `osx-x64` DMGs.
|
||||
|
||||
## Microsoft Store (MSIX)
|
||||
|
||||
`MsixPack` writes `releases/maksit-cluster-console-{version}.msix` from the win-x64 publish. It is a full-trust desktop package (`runFullTrust`), x64, language English. Upload that file on an **MSIX** product in Partner Center. The Store re-signs it. An EXE/MSI product listing cannot take this file.
|
||||
|
||||
Partner Center package identity for this product:
|
||||
|
||||
| Field | Value |
|
||||
|-------|--------|
|
||||
| Package/Identity/Name | `MAKS-IT.ClusterConsole` |
|
||||
| Package/Identity/Publisher | `CN=FCC8C0E7-6D5F-4028-B8EE-903B88C0C8F9` |
|
||||
| PublisherDisplayName | `MAKS-IT` |
|
||||
| Package Family Name | `MAKS-IT.ClusterConsole_pt3s39h1tn26a` |
|
||||
| Store ID | `9MX86PTHBNN4` |
|
||||
|
||||
Those name, publisher, and publisher display strings are `packageName`, `publisher`, and `publisherDisplayName` in `utils/engines/release/scriptSettings.json`. A placeholder publisher `CN=PartnerCenter` stops `MsixPack` until it is replaced.
|
||||
|
||||
The `.msix` is not a GitHub release asset.
|
||||
|
||||
### System requirements (Properties)
|
||||
|
||||
Partner Center → **Properties** → **System requirements**. A blank cell stays unset. Minimum is what the Store may warn on; Recommended does not warn.
|
||||
|
||||
The app is a win-x64 desktop console (tables, YAML, terminal). It does not use a camera, microphone, radio, gamepad, or a specific GPU.
|
||||
|
||||
| Feature | Minimum | Recommended |
|
||||
|---------|---------|-------------|
|
||||
| Touch screen | | |
|
||||
| Keyboard | Minimum | |
|
||||
| Mouse | Minimum | |
|
||||
| Camera | | |
|
||||
| NFC HCE | | |
|
||||
| NFC Proximity | | |
|
||||
| Bluetooth LE | | |
|
||||
| Telephony | | |
|
||||
| Microphone | | |
|
||||
| Xbox controller or gamepad | | |
|
||||
| Windows Mixed Reality motion controllers | | |
|
||||
| Windows Mixed Reality immersive headset | | |
|
||||
| Memory | 2 GB | 4 GB |
|
||||
| DirectX | Not specified | Not specified |
|
||||
| Video memory | Not specified | Not specified |
|
||||
| Processor | x64 | Not specified |
|
||||
| Graphics | Not specified | Not specified |
|
||||
|
||||
## GitHub setup exe
|
||||
|
||||
The GitHub Windows installer stays the WiX Burn `setup.exe`. Its switches are `/quiet /norestart` (install), `/repair /quiet /norestart` (repair), and `/uninstall /quiet /norestart` (uninstall). Signing that exe for an EXE/MSI Store listing needs a Trusted Root Authenticode certificate (homelab `works/repostories-maintenance.md`, §11 **Code signing certificate**) and the Burn order: payload PEs, then the MSI, then `wix burn detach` / sign the engine / `wix burn reattach` / sign `setup.exe` ([WiX signing](https://docs.firegiant.com/wix/tools/signing/)).
|
||||
|
||||
## Commit format
|
||||
|
||||
```text
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Version>0.8.1</Version>
|
||||
<Version>0.8.2</Version>
|
||||
<Product>MaksIT.ClusterConsole</Product>
|
||||
<AssemblyTitle>MaksIT.ClusterConsole</AssemblyTitle>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -64,6 +64,19 @@
|
||||
"iconPath": "..\\..\\..\\src\\MaksIT.ClusterConsole.UI\\Assets\\icon.ico",
|
||||
"exeNamePattern": "maksit-cluster-console-{version}.exe"
|
||||
},
|
||||
{
|
||||
"name": "MsixPack",
|
||||
"stageLabel": "build",
|
||||
"enabled": true,
|
||||
"packageName": "MAKS-IT.ClusterConsole",
|
||||
"publisher": "CN=FCC8C0E7-6D5F-4028-B8EE-903B88C0C8F9",
|
||||
"publisherDisplayName": "MaksIT",
|
||||
"displayName": "MaksIT Cluster Console",
|
||||
"executableName": "MaksIT.ClusterConsole.UI.exe",
|
||||
"runtimeIdentifier": "win-x64",
|
||||
"iconPath": "..\\..\\..\\src\\MaksIT.ClusterConsole.UI\\Assets\\icon.png",
|
||||
"msixNamePattern": "maksit-cluster-console-{version}.msix"
|
||||
},
|
||||
{
|
||||
"name": "FlatpakPack",
|
||||
"stageLabel": "build",
|
||||
@ -129,7 +142,8 @@
|
||||
"excludePatterns": [
|
||||
"*.zip",
|
||||
"*.exe",
|
||||
"*.flatpak"
|
||||
"*.flatpak",
|
||||
"*.msix"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@ -27,6 +27,10 @@ function Clear-ExternalCommandTestHandler {
|
||||
$script:ExternalCommandTestHandler = $null
|
||||
}
|
||||
|
||||
function Test-ExternalCommandTestHandler {
|
||||
return ($null -ne $script:ExternalCommandTestHandler)
|
||||
}
|
||||
|
||||
function Set-ExternalCommandAvailability {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
@ -128,4 +132,5 @@ Export-ModuleMember -Function `
|
||||
Invoke-ExternalCommand, `
|
||||
Set-ExternalCommandTestHandler, `
|
||||
Clear-ExternalCommandTestHandler, `
|
||||
Test-ExternalCommandTestHandler, `
|
||||
Set-ExternalCommandAvailability
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Helpers for Community desktop pack plugins (Windows MSI, Linux Flatpak).
|
||||
Helpers for Community desktop pack plugins (Windows MSI, Store MSIX, Linux Flatpak).
|
||||
#>
|
||||
|
||||
function ConvertTo-WixIdentifier {
|
||||
@ -1290,6 +1290,184 @@ function New-WixBundleXml {
|
||||
"@
|
||||
}
|
||||
|
||||
function ConvertTo-MsixPackageVersion {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Version
|
||||
)
|
||||
|
||||
$trimmed = $Version.Trim()
|
||||
if ($trimmed -match '-') {
|
||||
throw "MsixPack version '$Version' cannot include a prerelease label. MSIX Identity Version is four numbers (X.Y.Z.0)."
|
||||
}
|
||||
|
||||
$parts = @($trimmed.Split('.', [System.StringSplitOptions]::RemoveEmptyEntries))
|
||||
if ($parts.Count -lt 1 -or $parts.Count -gt 4) {
|
||||
throw "MsixPack version '$Version' must be one to four numeric fields."
|
||||
}
|
||||
|
||||
$numbers = [System.Collections.Generic.List[int]]::new()
|
||||
foreach ($part in $parts) {
|
||||
$number = 0
|
||||
if (-not [int]::TryParse($part, [ref]$number) -or $number -lt 0 -or $number -gt 65535) {
|
||||
throw "MsixPack version '$Version' has a field outside 0..65535."
|
||||
}
|
||||
|
||||
$numbers.Add($number)
|
||||
}
|
||||
|
||||
while ($numbers.Count -lt 4) {
|
||||
$numbers.Add(0)
|
||||
}
|
||||
|
||||
return ($numbers -join '.')
|
||||
}
|
||||
|
||||
function Assert-MsixPackageName {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PackageName
|
||||
)
|
||||
|
||||
if ($PackageName.Length -lt 3 -or $PackageName.Length -gt 50) {
|
||||
throw "MsixPack packageName must be 3 to 50 characters: $PackageName"
|
||||
}
|
||||
|
||||
if ($PackageName -notmatch '^[A-Za-z0-9][A-Za-z0-9\.\-]*[A-Za-z0-9]$' -or $PackageName.Contains('..')) {
|
||||
throw "MsixPack packageName must be letters, digits, hyphens, and single dots: $PackageName"
|
||||
}
|
||||
}
|
||||
|
||||
function New-MsixManifestXml {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PackageName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Publisher,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PackageVersion,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ProcessorArchitecture,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$DisplayName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PublisherDisplayName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ExecutableName,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Description,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Language = 'en-us'
|
||||
)
|
||||
|
||||
$escape = {
|
||||
param([string]$Value)
|
||||
return [System.Security.SecurityElement]::Escape($Value)
|
||||
}
|
||||
|
||||
$safeDescription = if ([string]::IsNullOrWhiteSpace($Description)) { $DisplayName } else { $Description }
|
||||
$exe = [System.IO.Path]::GetFileName($ExecutableName)
|
||||
|
||||
return @"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap rescap">
|
||||
<Identity Name="$(& $escape $PackageName)" Publisher="$(& $escape $Publisher)" Version="$(& $escape $PackageVersion)" ProcessorArchitecture="$(& $escape $ProcessorArchitecture)" />
|
||||
<Properties>
|
||||
<DisplayName>$(& $escape $DisplayName)</DisplayName>
|
||||
<PublisherDisplayName>$(& $escape $PublisherDisplayName)</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26100.0" />
|
||||
</Dependencies>
|
||||
<Resources>
|
||||
<Resource Language="$(& $escape $Language)" />
|
||||
</Resources>
|
||||
<Applications>
|
||||
<Application Id="App" Executable="$(& $escape $exe)" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements DisplayName="$(& $escape $DisplayName)" Description="$(& $escape $safeDescription)" BackgroundColor="transparent" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" />
|
||||
</Application>
|
||||
</Applications>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
"@
|
||||
}
|
||||
|
||||
function Copy-MsixPackageLogos {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$IconPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AssetsDirectory
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $IconPath -PathType Leaf)) {
|
||||
throw "MsixPack iconPath not found: $IconPath"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $AssetsDirectory -Force | Out-Null
|
||||
$sizes = [ordered]@{
|
||||
'Square44x44Logo.png' = 44
|
||||
'StoreLogo.png' = 50
|
||||
'Square150x150Logo.png' = 150
|
||||
}
|
||||
|
||||
$drew = $false
|
||||
try {
|
||||
Add-Type -AssemblyName System.Drawing -ErrorAction Stop
|
||||
$image = [System.Drawing.Image]::FromFile((Resolve-Path -LiteralPath $IconPath).Path)
|
||||
try {
|
||||
foreach ($name in @($sizes.Keys)) {
|
||||
$size = [int]$sizes[$name]
|
||||
$bitmap = New-Object System.Drawing.Bitmap $size, $size
|
||||
try {
|
||||
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
||||
try {
|
||||
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
||||
$graphics.Clear([System.Drawing.Color]::Transparent)
|
||||
$graphics.DrawImage($image, 0, 0, $size, $size)
|
||||
}
|
||||
finally {
|
||||
$graphics.Dispose()
|
||||
}
|
||||
|
||||
$bitmap.Save((Join-Path $AssetsDirectory $name), [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
}
|
||||
finally {
|
||||
$bitmap.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
$drew = $true
|
||||
}
|
||||
finally {
|
||||
$image.Dispose()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$drew = $false
|
||||
}
|
||||
|
||||
if ($drew) {
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($name in @($sizes.Keys)) {
|
||||
Copy-Item -LiteralPath $IconPath -Destination (Join-Path $AssetsDirectory $name) -Force
|
||||
}
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function `
|
||||
ConvertTo-WixIdentifier, `
|
||||
Get-MsiProductVersion, `
|
||||
@ -1300,6 +1478,10 @@ Export-ModuleMember -Function `
|
||||
Resolve-DesktopPublishDirectory, `
|
||||
Resolve-DesktopExecutablePath, `
|
||||
New-WixPackageXml, `
|
||||
ConvertTo-MsixPackageVersion, `
|
||||
Assert-MsixPackageName, `
|
||||
New-MsixManifestXml, `
|
||||
Copy-MsixPackageLogos, `
|
||||
Get-DerivedBundleUpgradeCode, `
|
||||
New-WixBundleXml, `
|
||||
Get-DefaultFlatpakFinishArgs, `
|
||||
|
||||
275
utils/plugins/Desktop/MsixPack.psm1
Normal file
275
utils/plugins/Desktop/MsixPack.psm1
Normal file
@ -0,0 +1,275 @@
|
||||
#requires -Version 7.0
|
||||
#requires -PSEdition Core
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Store MSIX plugin for a published win-* .NET desktop app (Community).
|
||||
|
||||
.DESCRIPTION
|
||||
Packs the win-* DotNetPublish folder into a full-trust .msix and signs it
|
||||
with a short-lived self-signed certificate whose subject is `publisher`.
|
||||
The Microsoft Store re-signs the package; this signature is only the upload
|
||||
envelope. The .msix is not a GitHub release asset and is not added to the
|
||||
portable zip. Requires makeappx.exe and signtool.exe from the Windows SDK.
|
||||
#>
|
||||
|
||||
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
|
||||
$srcDir = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$pluginSupportModulePath = Join-Path $srcDir "modules/Engine/PluginSupport.psm1"
|
||||
if (Test-Path $pluginSupportModulePath -PathType Leaf) {
|
||||
Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
function Get-WindowsSdkToolPath {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$FileName
|
||||
)
|
||||
|
||||
$roots = @(
|
||||
${env:ProgramFiles(x86)},
|
||||
$env:ProgramFiles
|
||||
) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
|
||||
foreach ($root in $roots) {
|
||||
$bin = Join-Path $root 'Windows Kits\10\bin'
|
||||
if (-not (Test-Path -LiteralPath $bin -PathType Container)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$versions = @(
|
||||
Get-ChildItem -LiteralPath $bin -Directory |
|
||||
Where-Object { $_.Name -match '^10\.' } |
|
||||
Sort-Object { try { [version]$_.Name } catch { [version]'0.0' } } -Descending
|
||||
)
|
||||
foreach ($version in $versions) {
|
||||
$candidate = Join-Path $version.FullName "x64\$FileName"
|
||||
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
|
||||
return $candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-MsixToolCommand {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$FileName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$StubName
|
||||
)
|
||||
|
||||
if (Test-ExternalCommandTestHandler) {
|
||||
return $StubName
|
||||
}
|
||||
|
||||
$path = Get-WindowsSdkToolPath -FileName $FileName
|
||||
if ([string]::IsNullOrWhiteSpace($path)) {
|
||||
throw "Windows SDK tool '$FileName' was not found. Install the Windows 10 SDK so $FileName is under 'Windows Kits\10\bin\<version>\x64'."
|
||||
}
|
||||
|
||||
return $path
|
||||
}
|
||||
|
||||
function Invoke-MsixSign {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$MsixPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Publisher,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$SignTool
|
||||
)
|
||||
|
||||
$thumb = 'TEST'
|
||||
$created = $false
|
||||
try {
|
||||
if (-not (Test-ExternalCommandTestHandler)) {
|
||||
$cert = New-SelfSignedCertificate `
|
||||
-Type Custom `
|
||||
-Subject $Publisher `
|
||||
-KeyUsage DigitalSignature `
|
||||
-KeyAlgorithm RSA `
|
||||
-KeyLength 2048 `
|
||||
-FriendlyName 'MaksIT MsixPack ephemeral' `
|
||||
-CertStoreLocation 'Cert:\CurrentUser\My' `
|
||||
-TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3') `
|
||||
-NotAfter (Get-Date).AddDays(1)
|
||||
$thumb = $cert.Thumbprint
|
||||
$created = $true
|
||||
}
|
||||
|
||||
Invoke-ExternalCommand -Name $SignTool -ArgumentList @(
|
||||
'sign', '/fd', 'SHA256', '/sha1', $thumb,
|
||||
'/tr', 'http://timestamp.digicert.com', '/td', 'SHA256',
|
||||
$MsixPath
|
||||
) | Out-Null
|
||||
}
|
||||
finally {
|
||||
if ($created -and -not [string]::IsNullOrWhiteSpace($thumb)) {
|
||||
$certPath = Join-Path 'Cert:\CurrentUser\My' $thumb
|
||||
if (Test-Path -LiteralPath $certPath) {
|
||||
Remove-Item -LiteralPath $certPath -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-Plugin {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
$Settings
|
||||
)
|
||||
|
||||
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
|
||||
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineFact"
|
||||
Import-PluginDependency -ModuleName "ExternalCommandSupport" -RequiredCommand "Invoke-ExternalCommand"
|
||||
Import-PluginDependency -ModuleName "DesktopPackSupport" -RequiredCommand "New-MsixManifestXml"
|
||||
|
||||
if (-not $IsWindows -and -not (Test-ExternalCommandTestHandler)) {
|
||||
throw "MsixPack requires Windows (Windows SDK makeappx.exe and signtool.exe)."
|
||||
}
|
||||
|
||||
$pluginSettings = $Settings
|
||||
$sharedSettings = $Settings.context
|
||||
$scriptDir = [string]$sharedSettings.scriptDir
|
||||
$version = [string]$sharedSettings.version
|
||||
$artifactsDirectory = [string]$sharedSettings.artifactsDirectory
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||
throw "MsixPack requires a release version in the shared context (DotNetReleaseVersion)."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($artifactsDirectory)) {
|
||||
throw "MsixPack requires an artifacts directory in the shared context."
|
||||
}
|
||||
|
||||
$packageName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'packageName')
|
||||
$publisher = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'publisher')
|
||||
$executableName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'executableName')
|
||||
if ([string]::IsNullOrWhiteSpace($packageName)) {
|
||||
throw "MsixPack requires packageName."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($publisher) -or $publisher -eq 'CN=PartnerCenter') {
|
||||
throw "MsixPack requires publisher set to the Partner Center package identity (CN=...)."
|
||||
}
|
||||
|
||||
if ($publisher -notmatch '(?i)(^|,)\s*CN=') {
|
||||
throw "MsixPack publisher must be a distinguished name starting with CN=: $publisher"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($executableName)) {
|
||||
throw "MsixPack requires executableName."
|
||||
}
|
||||
|
||||
Assert-MsixPackageName -PackageName $packageName
|
||||
$packageVersion = ConvertTo-MsixPackageVersion -Version $version
|
||||
|
||||
$displayName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'displayName' -Default $packageName)
|
||||
$publisherDisplayName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'publisherDisplayName' -Default 'MaksIT')
|
||||
$description = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'description' -Default '')
|
||||
$language = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'language' -Default 'en-us')
|
||||
$runtimeIdentifier = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'runtimeIdentifier' -Default 'win-x64')
|
||||
$architecture = Get-WixArchitectureFromRuntimeIdentifier -RuntimeIdentifier $runtimeIdentifier
|
||||
$publishDirSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'publishDir')
|
||||
$iconSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'iconPath')
|
||||
|
||||
if (-not (Test-Path -LiteralPath $artifactsDirectory -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path $artifactsDirectory | Out-Null
|
||||
}
|
||||
|
||||
$publishDirectory = Resolve-DesktopPublishDirectory `
|
||||
-Context $sharedSettings `
|
||||
-RuntimeIdentifier $runtimeIdentifier `
|
||||
-PublishDir $publishDirSetting `
|
||||
-ScriptDir $scriptDir
|
||||
|
||||
$null = Resolve-DesktopExecutablePath `
|
||||
-PublishDirectory $publishDirectory `
|
||||
-ExecutableName $executableName `
|
||||
-Windows
|
||||
|
||||
$resolvePackPath = {
|
||||
param([string]$Setting)
|
||||
if ([string]::IsNullOrWhiteSpace($Setting)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ([System.IO.Path]::IsPathRooted($Setting)) {
|
||||
return $Setting
|
||||
}
|
||||
|
||||
return [System.IO.Path]::GetFullPath((Join-Path $scriptDir $Setting))
|
||||
}
|
||||
|
||||
$iconPath = & $resolvePackPath $iconSetting
|
||||
if ([string]::IsNullOrWhiteSpace($iconPath)) {
|
||||
$fallback = Join-Path $PSScriptRoot 'brand\mark.png'
|
||||
if (Test-Path -LiteralPath $fallback -PathType Leaf) {
|
||||
$iconPath = $fallback
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($iconPath) -or -not (Test-Path -LiteralPath $iconPath -PathType Leaf)) {
|
||||
throw "MsixPack iconPath not found: $iconSetting"
|
||||
}
|
||||
|
||||
$safeName = ($packageName -replace '[^A-Za-z0-9._-]', '-').Trim('-')
|
||||
$namePattern = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'msixNamePattern' -Default '{name}-{version}.msix')
|
||||
$msixFileName = $namePattern.Replace('{version}', $version).Replace('{name}', $safeName)
|
||||
$msixPath = Join-Path $artifactsDirectory $msixFileName
|
||||
|
||||
$stageDir = Join-Path $artifactsDirectory '.msix-stage'
|
||||
if (Test-Path -LiteralPath $stageDir) {
|
||||
Remove-Item -LiteralPath $stageDir -Recurse -Force
|
||||
}
|
||||
|
||||
$layout = Join-Path $stageDir 'layout'
|
||||
New-Item -ItemType Directory -Path $layout | Out-Null
|
||||
Copy-Item -Path (Join-Path $publishDirectory '*') -Destination $layout -Recurse -Force
|
||||
|
||||
$manifest = New-MsixManifestXml `
|
||||
-PackageName $packageName `
|
||||
-Publisher $publisher `
|
||||
-PackageVersion $packageVersion `
|
||||
-ProcessorArchitecture $architecture `
|
||||
-DisplayName $displayName `
|
||||
-PublisherDisplayName $publisherDisplayName `
|
||||
-ExecutableName $executableName `
|
||||
-Description $description `
|
||||
-Language $language
|
||||
[System.IO.File]::WriteAllText((Join-Path $layout 'AppxManifest.xml'), $manifest, [System.Text.UTF8Encoding]::new($false))
|
||||
Copy-MsixPackageLogos -IconPath $iconPath -AssetsDirectory (Join-Path $layout 'Assets')
|
||||
|
||||
$makeAppx = Get-MsixToolCommand -FileName 'makeappx.exe' -StubName 'makeappx'
|
||||
$signTool = Get-MsixToolCommand -FileName 'signtool.exe' -StubName 'signtool'
|
||||
|
||||
if (Test-Path -LiteralPath $msixPath -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $msixPath -Force
|
||||
}
|
||||
|
||||
Write-Log -Level "STEP" -Message "Packing MSIX for '$displayName' ($architecture, $packageVersion)..."
|
||||
Invoke-ExternalCommand -Name $makeAppx -ArgumentList @('pack', '/d', $layout, '/p', $msixPath, '/o') | Out-Null
|
||||
if (-not (Test-Path -LiteralPath $msixPath -PathType Leaf)) {
|
||||
throw "makeappx completed but MSIX was not produced: $msixPath"
|
||||
}
|
||||
|
||||
Write-Log -Level "STEP" -Message "Signing MSIX with an ephemeral certificate ($publisher)..."
|
||||
Invoke-MsixSign -MsixPath $msixPath -Publisher $publisher -SignTool $signTool
|
||||
|
||||
if (Test-Path -LiteralPath $stageDir) {
|
||||
Remove-Item -LiteralPath $stageDir -Recurse -Force
|
||||
}
|
||||
|
||||
Set-EngineFact -Context $sharedSettings -Namespace 'desktop' -Name 'msixPath' -Value $msixPath -Overwrite Replace
|
||||
Write-Log -Level "OK" -Message " Store MSIX ready: $msixPath"
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Invoke-Plugin
|
||||
Loading…
Reference in New Issue
Block a user