feat: msix auto-updater (#49585)

* feat: native auto updater for MSIX on Windows

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* doc: added MSIX debug documentation

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: allow downgrade with json release file and emit update-available

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* test: msix auot-update tests

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* doc: API documentation

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* test: add package version validation

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: docs typo

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: don't allow auto-updating when using appinstaller manifest

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: getPackageInfo interface implementation

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: review feedback, add comment

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: missed filename commit

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: install test cert on demand

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: time stamp mismatch in tests

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: feedback - rename to MSIXPackageInfo

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: update and reference windowsStore property

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: remove getPackagInfo from public API

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: type error bcause of removed API

Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>

* fix: fix Windows MSIX release build errors (#49613)

* fix: fix MSIX release build

* fix: add C++/WinRT headers

* build: modify include paths

* fix: compile msix as seperate source set

* build: add additional needed deps for msix

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Jan Hannemann <jan.hannemann@outlook.com>
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
This commit is contained in:
trop[bot]
2026-02-02 09:33:46 +01:00
committed by GitHub
parent fd4f835f37
commit e6b53033dd
20 changed files with 1738 additions and 8 deletions

View File

@@ -0,0 +1,50 @@
<?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"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
IgnorableNamespaces="uap uap3 desktop2">
<Identity Name="Electron.Dev.MSIX"
ProcessorArchitecture="x64"
Version="1.0.0.0"
Publisher="CN=Electron"/>
<Properties>
<DisplayName>Electron Dev MSIX</DisplayName>
<PublisherDisplayName>Electron</PublisherDisplayName>
<Logo>assets\icon.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.17763.0" />
</Dependencies>
<Resources>
<Resource Language="en-US" />
</Resources>
<Applications>
<Application Id="ElectronMSIX" Executable="Electron.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="Electron Dev MSIX"
Description="Electron running with Identity"
Square44x44Logo="assets\Square44x44Logo.png"
Square150x150Logo="assets\Square150x150Logo.png"
BackgroundColor="transparent">
</uap:VisualElements>
<Extensions>
<uap3:Extension
Category="windows.appExecutionAlias"
Executable="Electron.exe"
EntryPoint="Windows.FullTrustApplication">
<uap3:AppExecutionAlias>
<desktop:ExecutionAlias Alias="ElectronMSIX.exe" />
</uap3:AppExecutionAlias>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<Capability Name="internetClient" />
</Capabilities>
</Package>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,22 @@
Add-Type -AssemblyName System.Security.Cryptography.X509Certificates
# Path to cert file one folder up relative to script location
$scriptDir = Split-Path -Parent $PSCommandPath
$certPath = Join-Path $scriptDir "MSIXDevCert.cer" | Resolve-Path
# Load the certificate from file
$cert = [System.Security.Cryptography.X509Certificates.X509CertificateLoader]::LoadCertificateFromFile($certPath)
$trustedStore = Get-ChildItem -Path "cert:\LocalMachine\TrustedPeople" | Where-Object { $_.Thumbprint -eq $cert.Thumbprint }
if (-not $trustedStore) {
# We gonna need admin privileges to install the cert
if (-Not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
exit
}
# Install the public cert to LocalMachine\TrustedPeople (for MSIX trust)
Import-Certificate -FilePath $certPath -CertStoreLocation "cert:\LocalMachine\TrustedPeople" | Out-Null
Write-Host " 🏛️ Installed to: cert:\LocalMachine\TrustedPeople"
} else {
Write-Host " ✅ Certificate already trusted in: cert:\LocalMachine\TrustedPeople"
}

View File

@@ -0,0 +1,96 @@
const { app, autoUpdater } = require('electron');
// Parse command-line arguments
const args = process.argv.slice(2);
const command = args[0];
const commandArg = args[1];
app.whenReady().then(() => {
try {
// Debug: log received arguments
if (process.env.DEBUG) {
console.log('Command:', command);
console.log('Command arg:', commandArg);
console.log('All args:', JSON.stringify(args));
}
if (command === '--printPackageId') {
const packageInfo = autoUpdater.getPackageInfo();
if (packageInfo.familyName) {
console.log(`Family Name: ${packageInfo.familyName}`);
console.log(`Package ID: ${packageInfo.id || 'N/A'}`);
console.log(`Version: ${packageInfo.version || 'N/A'}`);
console.log(`Development Mode: ${packageInfo.developmentMode ? 'Yes' : 'No'}`);
console.log(`Signature Kind: ${packageInfo.signatureKind || 'N/A'}`);
if (packageInfo.appInstallerUri) {
console.log(`App Installer URI: ${packageInfo.appInstallerUri}`);
}
app.quit();
} else {
console.error('No package identity found. Process is not running in an MSIX package context.');
app.exit(1);
}
} else if (command === '--checkUpdate') {
if (!commandArg) {
console.error('Update URL is required for --checkUpdate');
app.exit(1);
return;
}
// Use hardcoded headers if --useCustomHeaders flag is provided
let headers;
let allowAnyVersion = false;
if (args[2] === '--useCustomHeaders') {
headers = {
'X-AppVersion': '1.0.0',
Authorization: 'Bearer test-token'
};
} else if (args[2] === '--allowAnyVersion') {
allowAnyVersion = true;
}
// Set up event listeners
autoUpdater.on('checking-for-update', () => {
console.log('Checking for update...');
});
autoUpdater.on('update-available', () => {
console.log('Update available');
});
autoUpdater.on('update-not-available', () => {
console.log('Update not available');
app.quit();
});
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName, releaseDate, updateUrl) => {
console.log('Update downloaded');
console.log(`Release Name: ${releaseName || 'N/A'}`);
console.log(`Release Notes: ${releaseNotes || 'N/A'}`);
console.log(`Release Date: ${releaseDate || 'N/A'}`);
console.log(`Update URL: ${updateUrl || 'N/A'}`);
app.quit();
});
autoUpdater.on('error', (error, message) => {
console.error(`Update error: ${message || error.message || 'Unknown error'}`);
app.exit(1);
});
// Set the feed URL with optional headers and allowAnyVersion, then check for updates
if (headers || allowAnyVersion) {
autoUpdater.setFeedURL({ url: commandArg, headers, allowAnyVersion });
} else {
autoUpdater.setFeedURL(commandArg);
}
autoUpdater.checkForUpdates();
} else {
console.error(`Unknown command: ${command || '(none)'}`);
app.exit(1);
}
} catch (error) {
console.error('Unhandled error:', error.message);
console.error(error.stack);
app.exit(1);
}
});