This commit is contained in:
+109
-13
@@ -1,14 +1,17 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Builds the CursorLang MSIX package for the Microsoft Store.
|
||||
Builds the CursorLang MSIX package.
|
||||
|
||||
.DESCRIPTION
|
||||
Neither Visual Studio nor the Windows SDK is needed: makeappx arrives as a
|
||||
NuGet package (Tools\SdkTools.csproj) and the application is built by the
|
||||
plain .NET SDK.
|
||||
Neither Visual Studio nor the Windows SDK is needed: makeappx and signtool
|
||||
arrive as a NuGet package (Tools\SdkTools.csproj) and the application is
|
||||
built by the plain .NET SDK.
|
||||
|
||||
The package is handed to Partner Center as it comes out of here — the Store
|
||||
is where it gets everything else done to it.
|
||||
The package comes out of here in one of two shapes. Left unsigned it goes to
|
||||
Partner Center as it is — the Store puts its own signature on it and does the
|
||||
rest. Signed with -CertificateThumbprint it is a package anyone can install
|
||||
from a release, because Windows only takes a package whose signature it
|
||||
trusts.
|
||||
|
||||
The application is published with its own copy of .NET: Windows does not
|
||||
carry one, and MSIX cannot install the runtime as a package dependency.
|
||||
@@ -18,6 +21,17 @@
|
||||
section. It is handed out there together with the reserved application name;
|
||||
the default is only good enough for a check on your own machine.
|
||||
|
||||
.PARAMETER PackageSuffix
|
||||
Goes at the end of the file names. The two builds of a release differ in the
|
||||
identity inside them and in nothing a file listing shows, so the one meant
|
||||
for the Store is told apart by a suffix of its own.
|
||||
|
||||
.PARAMETER CertificateThumbprint
|
||||
Signs the packages with the certificate of this thumbprint from the personal
|
||||
store of the current user. The private key is none of this script's business:
|
||||
signtool asks the store for it, and behind a cloud certificate — eSigner CKA
|
||||
among them — the store answers over the network.
|
||||
|
||||
.PARAMETER Install
|
||||
Puts the package onto this machine to see it working. Developer mode has to
|
||||
be on; a signature is not needed, because what gets registered is the layout
|
||||
@@ -33,8 +47,14 @@
|
||||
|
||||
.EXAMPLE
|
||||
# A build for the Store — the identity comes from Partner Center
|
||||
pwsh -File Packaging\build-msix.ps1 -Version 1.0.1.0 -PackageSuffix store `
|
||||
-IdentityName 12345AleksandrNeichev.CursorLang -Publisher "CN=ABCD1234-..."
|
||||
|
||||
.EXAMPLE
|
||||
# A build for a release — the publisher is the subject of the certificate
|
||||
pwsh -File Packaging\build-msix.ps1 -Version 1.0.1.0 `
|
||||
-IdentityName 12345AleksandrNeychev.CursorLang -Publisher "CN=ABCD1234-..."
|
||||
-Publisher "CN=Aleksandr Neichev, O=Aleksandr Neichev, C=KZ" `
|
||||
-CertificateThumbprint A1B2C3D4E5F60718293A4B5C6D7E8F9012345678
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
@@ -42,12 +62,21 @@ param(
|
||||
[string] $Version = '1.0.0.0',
|
||||
|
||||
[string] $IdentityName = 'CursorLang',
|
||||
[string] $Publisher = 'CN=Aleksandr Neychev',
|
||||
[string] $PublisherDisplayName = 'Aleksandr Neychev',
|
||||
[string] $Publisher = 'CN=Aleksandr Neichev',
|
||||
[string] $PublisherDisplayName = 'Aleksandr Neichev',
|
||||
|
||||
[ValidateSet('x64', 'arm64')]
|
||||
[string[]] $Architectures = @('x64', 'arm64'),
|
||||
|
||||
[string] $PackageSuffix,
|
||||
|
||||
[string] $CertificateThumbprint,
|
||||
|
||||
# SSL.com's timestamp server, to go with the certificate the pipeline signs
|
||||
# with. A signature without a timestamp is only good while the certificate
|
||||
# is: the day it expires the package stops installing everywhere at once
|
||||
[string] $TimestampUrl = 'http://ts.ssl.com',
|
||||
|
||||
[switch] $Install,
|
||||
|
||||
[string] $OutputPath
|
||||
@@ -64,7 +93,14 @@ $assets = Join-Path $root 'Assets'
|
||||
$manifestTemplate = Join-Path $root 'AppxManifest.xml'
|
||||
$toolsProject = Join-Path $root 'Tools\SdkTools.csproj'
|
||||
|
||||
if (-not $OutputPath) { $OutputPath = Join-Path $repository 'artifacts' }
|
||||
if (-not $OutputPath) {
|
||||
$OutputPath = Join-Path $repository 'artifacts'
|
||||
} elseif (-not [System.IO.Path]::IsPathRooted($OutputPath)) {
|
||||
# Windows names the folder a package was registered from in full, and the
|
||||
# layout is looked for by that name below. A relative path would never be
|
||||
# found there, and the registration of a build gone by would be left behind
|
||||
$OutputPath = Join-Path (Get-Location).Path $OutputPath
|
||||
}
|
||||
$layoutRoot = Join-Path $OutputPath 'layout'
|
||||
$packagesPath = Join-Path $OutputPath 'packages'
|
||||
|
||||
@@ -76,6 +112,26 @@ if (-not (Test-Path $assets)) {
|
||||
throw "No logos found in '$assets'. Run Packaging\New-Assets.ps1 first."
|
||||
}
|
||||
|
||||
if ($CertificateThumbprint) {
|
||||
# Both things below are checked before the build for the same reason the
|
||||
# ones under -Install are: the build takes minutes and neither answer
|
||||
# changes while it runs
|
||||
|
||||
$certificate = Get-ChildItem "Cert:\CurrentUser\My\$CertificateThumbprint" -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $certificate) {
|
||||
throw "No certificate with the thumbprint '$CertificateThumbprint' in the personal store of this user. A cloud certificate has to be loaded into the store first — eSigner CKA is what does that."
|
||||
}
|
||||
|
||||
# The Publisher of a package is not a name of the publisher's choosing: it
|
||||
# is the subject of the certificate the package is signed with, letter for
|
||||
# letter. signtool turns down a package that claims any other, and it does
|
||||
# so at the very end — after everything has already been built
|
||||
if ($certificate.Subject -ne $Publisher) {
|
||||
throw "The publisher '$Publisher' is not the subject of the certificate, which reads '$($certificate.Subject)'. A package carries the name of whoever signs it."
|
||||
}
|
||||
}
|
||||
|
||||
if ($Install) {
|
||||
# Both things below are checked before the build rather than after it: the
|
||||
# build takes minutes, and neither of them gets any truer while it runs
|
||||
@@ -160,11 +216,34 @@ function Get-SdkTool {
|
||||
return $tool.FullName
|
||||
}
|
||||
|
||||
function Invoke-Signing {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Signs a package with the certificate the build was given.
|
||||
#>
|
||||
param(
|
||||
[string] $Path,
|
||||
[string] $SignTool
|
||||
)
|
||||
|
||||
Write-Host "Signing $([System.IO.Path]::GetFileName($Path))..." -ForegroundColor Cyan
|
||||
|
||||
Invoke-Tool -Path $SignTool -Arguments @(
|
||||
'sign',
|
||||
'/fd', 'sha256',
|
||||
'/tr', $TimestampUrl,
|
||||
'/td', 'sha256',
|
||||
'/sha1', $CertificateThumbprint,
|
||||
$Path
|
||||
)
|
||||
}
|
||||
|
||||
Write-Host 'Fetching the Windows SDK programs...' -ForegroundColor Cyan
|
||||
Invoke-Tool -Path 'dotnet' -Arguments @('restore', $toolsProject, '--nologo')
|
||||
|
||||
$sdkTools = Get-SdkToolsPath
|
||||
$makeappx = Get-SdkTool -Name 'makeappx.exe' -PackagePath $sdkTools
|
||||
$signtool = if ($CertificateThumbprint) { Get-SdkTool -Name 'signtool.exe' -PackagePath $sdkTools } else { $null }
|
||||
|
||||
# A package registered out of the layout runs straight from that folder, and
|
||||
# the folder is about to be wiped. Left in place, the registration would point
|
||||
@@ -189,6 +268,7 @@ foreach ($installed in @(Get-AppxPackage -Name $IdentityName)) {
|
||||
Remove-Item $layoutRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Path $packagesPath -Force | Out-Null
|
||||
|
||||
$suffix = if ($PackageSuffix) { "-$PackageSuffix" } else { '' }
|
||||
$built = @()
|
||||
|
||||
foreach ($architecture in $Architectures) {
|
||||
@@ -223,7 +303,7 @@ foreach ($architecture in $Architectures) {
|
||||
|
||||
Set-Content -Path (Join-Path $layout 'AppxManifest.xml') -Value $manifest -Encoding UTF8
|
||||
|
||||
$package = Join-Path $packagesPath "CursorLang-$Version-$architecture.msix"
|
||||
$package = Join-Path $packagesPath "CursorLang-$Version-$architecture$suffix.msix"
|
||||
Invoke-Tool -Path $makeappx -Arguments @('pack', '/o', '/d', $layout, '/p', $package)
|
||||
|
||||
$built += $package
|
||||
@@ -242,12 +322,23 @@ if ($built.Count -gt 1) {
|
||||
New-Item -ItemType Directory -Path $bundleInput -Force | Out-Null
|
||||
$built | ForEach-Object { Copy-Item $_ -Destination $bundleInput }
|
||||
|
||||
$result = Join-Path $packagesPath "CursorLang-$Version.msixbundle"
|
||||
$result = Join-Path $packagesPath "CursorLang-$Version$suffix.msixbundle"
|
||||
Invoke-Tool -Path $makeappx -Arguments @('bundle', '/o', '/d', $bundleInput, '/p', $result, '/bv', $Version)
|
||||
|
||||
Remove-Item $bundleInput -Recurse -Force
|
||||
}
|
||||
|
||||
if ($CertificateThumbprint) {
|
||||
# Signing comes after the bundle rather than before it: makeappx copies the
|
||||
# packages into the bundle as they are, and a signature on what lies inside
|
||||
# says nothing about the bundle around it. Windows asks the outer file, so
|
||||
# every file that leaves here is signed on its own — each one of them is a
|
||||
# package someone may install
|
||||
foreach ($package in (@($built) + @($result) | Select-Object -Unique)) {
|
||||
Invoke-Signing -Path $package -SignTool $signtool
|
||||
}
|
||||
}
|
||||
|
||||
if ($Install) {
|
||||
Write-Host "Installing the $machineArchitecture build..." -ForegroundColor Cyan
|
||||
|
||||
@@ -261,7 +352,12 @@ Write-Host 'Done.' -ForegroundColor Green
|
||||
Write-Host " $result"
|
||||
|
||||
Write-Host ''
|
||||
Write-Host ' This file is uploaded to Partner Center as it is.'
|
||||
|
||||
if ($CertificateThumbprint) {
|
||||
Write-Host ' This file is signed: Windows installs it on any machine.'
|
||||
} else {
|
||||
Write-Host ' This file is uploaded to Partner Center as it is.'
|
||||
}
|
||||
|
||||
if ($Install) {
|
||||
Write-Host ''
|
||||
|
||||
Reference in New Issue
Block a user