mirror of
https://github.com/MAKS-IT-COM/maksit-cluster-console.git
synced 2026-09-30 00:38:10 +02:00
276 lines
10 KiB
PowerShell
276 lines
10 KiB
PowerShell
#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
|