Add Microsoft Store multi-arch MSIX packaging for EvilFox 4.2.8.1 (x64+Arm64).
Stage Partner Center folder with listing assets and Arm64 binary; MakeAppx not present so README includes pack/bundle commands. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+289
-63
@@ -1,44 +1,50 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pack EvilFox.exe into an MSIX for Microsoft Store / sideload.
|
||||
Pack EvilFox.exe into per-arch MSIX (and optional .msixbundle) for Microsoft Store.
|
||||
|
||||
.DESCRIPTION
|
||||
Builds Store assets, stages AppxManifest + EvilFox.exe, runs MakeAppx.
|
||||
Prefers MSIX over PWA: EvilFox needs Win32/WebView2 VPN and system-proxy access.
|
||||
Builds Store assets, stages AppxManifest + EvilFox.exe per architecture, runs MakeAppx.
|
||||
Default output: dist\microsoft-store\EvilFox_<version>_<arch>.msix
|
||||
Prefer MSIX over PWA: EvilFox needs Win32/WebView2 VPN and system-proxy access.
|
||||
|
||||
resource.syso note (arm64 cross-build): goversioninfo -64 emits an amd64 .syso.
|
||||
For GOARCH=arm64 builds, temporarily rename cmd/vpnapp/resource.syso aside, then restore.
|
||||
|
||||
.EXAMPLE
|
||||
.\scripts\pack-msix.ps1
|
||||
.\scripts\pack-msix.ps1 -Version 4.0.1.1 -Publisher "CN=EvilFox"
|
||||
.\scripts\pack-msix.ps1 -Arch x64 -Version 4.2.8.1
|
||||
.\scripts\pack-msix.ps1 -Arch arm64 -ExePath dist\navis-release\windows\arm64\EvilFox.exe
|
||||
.\scripts\pack-msix.ps1 -Arch all -OutDir dist\microsoft-store
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("x64", "arm64", "all")]
|
||||
[string]$Arch = "all",
|
||||
[string]$ExePath = "",
|
||||
[string]$OutDir = "",
|
||||
[string]$Version = "4.2.3.1",
|
||||
[string]$Version = "4.2.8.1",
|
||||
[string]$Name = "EvilFox.EvilFox",
|
||||
[string]$Publisher = "CN=EvilFox",
|
||||
[string]$DisplayName = "EvilFox",
|
||||
[string]$PublisherDisplayName = "EvilFox",
|
||||
[string]$CertPath = "",
|
||||
[string]$CertPassword = "",
|
||||
[switch]$SkipAssets
|
||||
[switch]$SkipAssets,
|
||||
[switch]$SkipBundle
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
Set-Location $Root
|
||||
|
||||
if (-not $ExePath) {
|
||||
$ExePath = Join-Path $Root "dist\navis-release\windows\EvilFox.exe"
|
||||
}
|
||||
if (-not $OutDir) {
|
||||
$OutDir = Join-Path $Root "dist\navis-release\windows"
|
||||
$OutDir = Join-Path $Root "dist\microsoft-store"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ExePath)) {
|
||||
throw "EvilFox.exe not found at '$ExePath'. Run build.bat first."
|
||||
if (-not [System.IO.Path]::IsPathRooted($OutDir)) {
|
||||
$OutDir = Join-Path $Root $OutDir
|
||||
}
|
||||
$OutDir = [System.IO.Path]::GetFullPath($OutDir)
|
||||
|
||||
function Find-SdkTool([string]$name) {
|
||||
$candidates = @()
|
||||
@@ -59,7 +65,128 @@ function Find-SdkTool([string]$name) {
|
||||
return $null
|
||||
}
|
||||
|
||||
function Resolve-ArchExe([string]$arch, [string]$explicit) {
|
||||
if ($explicit) { return $explicit }
|
||||
switch ($arch) {
|
||||
"x64" {
|
||||
return Join-Path $Root "dist\navis-release\windows\EvilFox.exe"
|
||||
}
|
||||
"arm64" {
|
||||
return Join-Path $Root "dist\navis-release\windows\arm64\EvilFox.exe"
|
||||
}
|
||||
default { throw "Unsupported arch: $arch" }
|
||||
}
|
||||
}
|
||||
|
||||
function Find-WintunDll([string]$arch) {
|
||||
$candidates = @()
|
||||
if ($arch -eq "arm64") {
|
||||
$candidates += @(
|
||||
(Join-Path $Root "assets\wintun\arm64\wintun.dll"),
|
||||
(Join-Path $Root "dist\navis-release\windows\arm64\wintun.dll")
|
||||
)
|
||||
} else {
|
||||
$candidates += @(
|
||||
(Join-Path $Root "assets\wintun\x64\wintun.dll"),
|
||||
(Join-Path $Root "assets\wintun\amd64\wintun.dll"),
|
||||
(Join-Path $Root "dist\navis-release\windows\wintun.dll")
|
||||
)
|
||||
}
|
||||
$candidates += @(
|
||||
(Join-Path $Root "assets\wintun\$arch\wintun.dll"),
|
||||
(Join-Path $Root "assets\wintun\wintun.dll"),
|
||||
(Join-Path $Root "wintun.dll")
|
||||
)
|
||||
foreach ($p in $candidates) {
|
||||
if (Test-Path -LiteralPath $p) { return $p }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Write-StoreReadme(
|
||||
[string]$dir,
|
||||
[string]$version,
|
||||
[string[]]$stagedArchs,
|
||||
[string[]]$msixPaths,
|
||||
[string]$bundlePath,
|
||||
[string]$makeAppxPath,
|
||||
[hashtable]$makeAppxCmds,
|
||||
[bool]$missingWintun
|
||||
) {
|
||||
$tplPath = Join-Path $Root "packaging\msix\STORE_README_TEMPLATE.txt"
|
||||
if (-not (Test-Path -LiteralPath $tplPath)) {
|
||||
throw "Missing template: $tplPath"
|
||||
}
|
||||
$text = [System.IO.File]::ReadAllText($tplPath, [System.Text.Encoding]::UTF8)
|
||||
$text = $text.Replace("{{VERSION}}", $version)
|
||||
$text = $text.Replace("{{GENERATED}}", (Get-Date -Format "yyyy-MM-dd HH:mm:ss"))
|
||||
|
||||
$artifacts = New-Object System.Collections.Generic.List[string]
|
||||
if ($bundlePath -and (Test-Path -LiteralPath $bundlePath)) {
|
||||
$artifacts.Add("Bundle: $bundlePath")
|
||||
} else {
|
||||
$artifacts.Add("Bundle: (not created)")
|
||||
}
|
||||
foreach ($p in $msixPaths) {
|
||||
if (Test-Path -LiteralPath $p) {
|
||||
$artifacts.Add("MSIX: $p")
|
||||
}
|
||||
}
|
||||
foreach ($a in $stagedArchs) {
|
||||
$stg = Join-Path $Root ("packaging\msix\_staging_" + $a)
|
||||
$artifacts.Add("Staging ${a}: $stg")
|
||||
}
|
||||
$text = $text.Replace("{{ARTIFACTS}}", ($artifacts -join "`r`n"))
|
||||
|
||||
$makeLines = New-Object System.Collections.Generic.List[string]
|
||||
if ($makeAppxPath) {
|
||||
$makeLines.Add("Found: $makeAppxPath")
|
||||
} else {
|
||||
$makeLines.Add("MakeAppx.exe NOT FOUND on this machine.")
|
||||
$makeLines.Add("Install Windows 10/11 SDK, then run:")
|
||||
foreach ($a in $stagedArchs) {
|
||||
if ($makeAppxCmds.ContainsKey($a)) {
|
||||
$makeLines.Add(" $($makeAppxCmds[$a])")
|
||||
}
|
||||
}
|
||||
if ($stagedArchs.Count -ge 2) {
|
||||
$bundleDir = Join-Path $dir "_bundle_input"
|
||||
$bundleOut = Join-Path $dir ("EvilFox_" + $version + ".msixbundle")
|
||||
$makeLines.Add(" makeappx bundle /d `"$bundleDir`" /p `"$bundleOut`" /o")
|
||||
$makeLines.Add(" (copy both .msix into _bundle_input first)")
|
||||
}
|
||||
}
|
||||
$text = $text.Replace("{{MAKEAPPX}}", ($makeLines -join "`r`n"))
|
||||
|
||||
if ($missingWintun) {
|
||||
$wintun = @(
|
||||
"wintun.dll was NOT found under assets/ or dist/. Packaging continued.",
|
||||
"VPN (TUN) mode needs wintun.dll next to EvilFox.exe.",
|
||||
"Store users can still use proxy mode without it.",
|
||||
"Add arch-specific wintun.dll later under assets/wintun/<arch>/ if needed."
|
||||
) -join "`r`n"
|
||||
} else {
|
||||
$wintun = "wintun.dll was included in staging where found for the target arch."
|
||||
}
|
||||
$text = $text.Replace("{{WINTUN}}", $wintun)
|
||||
|
||||
$utf8 = New-Object System.Text.UTF8Encoding $false
|
||||
[System.IO.File]::WriteAllText((Join-Path $dir "README.txt"), $text, $utf8)
|
||||
}
|
||||
|
||||
$MakeAppx = Find-SdkTool "makeappx.exe"
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
$archList = @()
|
||||
if ($Arch -eq "all") {
|
||||
$archList = @("x64", "arm64")
|
||||
} else {
|
||||
$archList = @($Arch)
|
||||
}
|
||||
|
||||
# When -Arch all, prefer per-arch default exe paths (ignore -ExePath).
|
||||
$explicitExe = ""
|
||||
if ($Arch -ne "all") { $explicitExe = $ExePath }
|
||||
|
||||
if (-not $SkipAssets) {
|
||||
Write-Host "Generating Store assets..."
|
||||
@@ -67,60 +194,159 @@ if (-not $SkipAssets) {
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
$Staging = Join-Path $Root "packaging\msix\_staging"
|
||||
if (Test-Path $Staging) { Remove-Item -Recurse -Force $Staging }
|
||||
New-Item -ItemType Directory -Path $Staging | Out-Null
|
||||
New-Item -ItemType Directory -Path (Join-Path $Staging "Assets") | Out-Null
|
||||
$producedMsix = @()
|
||||
$stagedArchs = @()
|
||||
$makeAppxCmds = @{}
|
||||
$anyMissingExe = $false
|
||||
$missingWintun = $false
|
||||
$packFailed = $false
|
||||
$makeAppxMissing = -not [bool]$MakeAppx
|
||||
|
||||
$AssetsSrc = Join-Path $Root "packaging\msix\Assets"
|
||||
Copy-Item -Path (Join-Path $AssetsSrc "*") -Destination (Join-Path $Staging "Assets") -Force
|
||||
Copy-Item -LiteralPath $ExePath -Destination (Join-Path $Staging "EvilFox.exe") -Force
|
||||
foreach ($a in $archList) {
|
||||
$exe = Resolve-ArchExe $a $explicitExe
|
||||
if (-not (Test-Path -LiteralPath $exe)) {
|
||||
Write-Host "SKIP ${a}: EvilFox.exe not found at '$exe'" -ForegroundColor Yellow
|
||||
$anyMissingExe = $true
|
||||
continue
|
||||
}
|
||||
|
||||
$ManifestSrc = Join-Path $Root "packaging\msix\AppxManifest.xml"
|
||||
$ManifestDst = Join-Path $Staging "AppxManifest.xml"
|
||||
$manifestText = Get-Content -LiteralPath $ManifestSrc -Raw -Encoding UTF8
|
||||
# Prefer exact placeholder swaps so we never touch <?xml version=...?>.
|
||||
$manifestText = $manifestText.Replace('Name="EvilFox.EvilFox"', "Name=`"$Name`"")
|
||||
$manifestText = $manifestText.Replace('Publisher="CN=EvilFox"', "Publisher=`"$Publisher`"")
|
||||
$manifestText = $manifestText.Replace('Version="4.2.3.1"', "Version=`"$Version`"")
|
||||
$manifestText = $manifestText.Replace('<DisplayName>EvilFox</DisplayName>', "<DisplayName>$DisplayName</DisplayName>")
|
||||
$manifestText = $manifestText.Replace('<PublisherDisplayName>EvilFox</PublisherDisplayName>', "<PublisherDisplayName>$PublisherDisplayName</PublisherDisplayName>")
|
||||
$manifestText = $manifestText.Replace('DisplayName="EvilFox"', "DisplayName=`"$DisplayName`"")
|
||||
[System.IO.File]::WriteAllText($ManifestDst, $manifestText, (New-Object System.Text.UTF8Encoding $false))
|
||||
$Staging = Join-Path $Root ("packaging\msix\_staging_" + $a)
|
||||
if (Test-Path $Staging) { Remove-Item -Recurse -Force $Staging }
|
||||
New-Item -ItemType Directory -Path $Staging | Out-Null
|
||||
New-Item -ItemType Directory -Path (Join-Path $Staging "Assets") | Out-Null
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
$MsixPath = Join-Path $OutDir ("EvilFox_" + $Version + ".msix")
|
||||
if (Test-Path $MsixPath) { Remove-Item -Force $MsixPath }
|
||||
$AssetsSrc = Join-Path $Root "packaging\msix\Assets"
|
||||
if (-not (Test-Path $AssetsSrc)) {
|
||||
throw "Store assets missing at $AssetsSrc (run without -SkipAssets)."
|
||||
}
|
||||
Copy-Item -Path (Join-Path $AssetsSrc "*") -Destination (Join-Path $Staging "Assets") -Force
|
||||
Copy-Item -LiteralPath $exe -Destination (Join-Path $Staging "EvilFox.exe") -Force
|
||||
|
||||
if (-not $MakeAppx) {
|
||||
Write-Host ""
|
||||
Write-Host "MakeAppx.exe not found - install the Windows 10/11 SDK to produce .msix." -ForegroundColor Yellow
|
||||
Write-Host "Staging is ready: $Staging"
|
||||
Write-Host "Then run:"
|
||||
Write-Host (" makeappx pack /d `"{0}`" /p `"{1}`" /o" -f $Staging, $MsixPath)
|
||||
$wintun = Find-WintunDll $a
|
||||
if ($wintun) {
|
||||
Copy-Item -LiteralPath $wintun -Destination (Join-Path $Staging "wintun.dll") -Force
|
||||
Write-Host "Included wintun.dll for $a from $wintun"
|
||||
} else {
|
||||
$missingWintun = $true
|
||||
Write-Host "No wintun.dll for $a (proxy mode still OK; TUN needs dll next to exe)." -ForegroundColor DarkYellow
|
||||
}
|
||||
|
||||
$ManifestSrc = Join-Path $Root "packaging\msix\AppxManifest.xml"
|
||||
$ManifestDst = Join-Path $Staging "AppxManifest.xml"
|
||||
$manifestText = Get-Content -LiteralPath $ManifestSrc -Raw -Encoding UTF8
|
||||
# Prefer exact placeholder swaps so we never touch <?xml version=...?>.
|
||||
$manifestText = $manifestText.Replace('Name="EvilFox.EvilFox"', "Name=`"$Name`"")
|
||||
$manifestText = $manifestText.Replace('Publisher="CN=EvilFox"', "Publisher=`"$Publisher`"")
|
||||
$manifestText = $manifestText.Replace('Version="4.2.8.1"', "Version=`"$Version`"")
|
||||
# Also accept older template version if someone reverts the manifest placeholder.
|
||||
$manifestText = $manifestText.Replace('Version="4.2.3.1"', "Version=`"$Version`"")
|
||||
$manifestText = $manifestText.Replace('ProcessorArchitecture="x64"', "ProcessorArchitecture=`"$a`"")
|
||||
$manifestText = $manifestText.Replace('ProcessorArchitecture="arm64"', "ProcessorArchitecture=`"$a`"")
|
||||
$manifestText = $manifestText.Replace('<DisplayName>EvilFox</DisplayName>', "<DisplayName>$DisplayName</DisplayName>")
|
||||
$manifestText = $manifestText.Replace('<PublisherDisplayName>EvilFox</PublisherDisplayName>', "<PublisherDisplayName>$PublisherDisplayName</PublisherDisplayName>")
|
||||
$manifestText = $manifestText.Replace('DisplayName="EvilFox"', "DisplayName=`"$DisplayName`"")
|
||||
[System.IO.File]::WriteAllText($ManifestDst, $manifestText, (New-Object System.Text.UTF8Encoding $false))
|
||||
|
||||
$MsixPath = Join-Path $OutDir ("EvilFox_" + $Version + "_" + $a + ".msix")
|
||||
$makeAppxCmds[$a] = ("makeappx pack /d `"{0}`" /p `"{1}`" /o" -f $Staging, $MsixPath)
|
||||
$stagedArchs += $a
|
||||
|
||||
if ($makeAppxMissing) {
|
||||
Write-Host "Staged $a (MakeAppx missing): $Staging" -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
|
||||
if (Test-Path $MsixPath) { Remove-Item -Force $MsixPath }
|
||||
Write-Host "Packing $MsixPath ..."
|
||||
& $MakeAppx pack /d $Staging /p $MsixPath /o
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "MakeAppx pack failed for $a (exit $LASTEXITCODE)" -ForegroundColor Red
|
||||
$packFailed = $true
|
||||
continue
|
||||
}
|
||||
|
||||
if ($CertPath) {
|
||||
$SignTool = Find-SdkTool "signtool.exe"
|
||||
if (-not $SignTool) { throw "signtool.exe not found (needed for -CertPath)." }
|
||||
Write-Host "Signing $a with $CertPath ..."
|
||||
if ($CertPassword) {
|
||||
& $SignTool sign /fd SHA256 /a /f $CertPath /p $CertPassword $MsixPath
|
||||
} else {
|
||||
& $SignTool sign /fd SHA256 /a /f $CertPath $MsixPath
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Sign failed for $a" -ForegroundColor Red
|
||||
$packFailed = $true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
$producedMsix += $MsixPath
|
||||
Write-Host "Packed: $MsixPath"
|
||||
}
|
||||
|
||||
$BundlePath = Join-Path $OutDir ("EvilFox_" + $Version + ".msixbundle")
|
||||
if (-not $SkipBundle -and -not $makeAppxMissing -and $producedMsix.Count -ge 2) {
|
||||
$bundleInput = Join-Path $OutDir "_bundle_input"
|
||||
if (Test-Path $bundleInput) { Remove-Item -Recurse -Force $bundleInput }
|
||||
New-Item -ItemType Directory -Path $bundleInput | Out-Null
|
||||
foreach ($p in $producedMsix) {
|
||||
Copy-Item -LiteralPath $p -Destination (Join-Path $bundleInput (Split-Path $p -Leaf)) -Force
|
||||
}
|
||||
if (Test-Path $BundlePath) { Remove-Item -Force $BundlePath }
|
||||
Write-Host "Bundling $BundlePath ..."
|
||||
& $MakeAppx bundle /d $bundleInput /p $BundlePath /o
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "makeappx bundle failed (exit $LASTEXITCODE)" -ForegroundColor Yellow
|
||||
$BundlePath = ""
|
||||
} else {
|
||||
Write-Host "Bundle: $BundlePath"
|
||||
}
|
||||
} elseif ($producedMsix.Count -eq 1) {
|
||||
$BundlePath = ""
|
||||
}
|
||||
|
||||
Write-StoreReadme -dir $OutDir -version $Version -stagedArchs $stagedArchs `
|
||||
-msixPaths $producedMsix -bundlePath $BundlePath -makeAppxPath $MakeAppx `
|
||||
-makeAppxCmds $makeAppxCmds -missingWintun $missingWintun
|
||||
|
||||
# Copy Partner Center listing inputs when present in the repo.
|
||||
$listingDir = Join-Path $OutDir "listing"
|
||||
$listingScreen = Join-Path $listingDir "screen"
|
||||
New-Item -ItemType Directory -Force -Path $listingScreen | Out-Null
|
||||
$storeCsv = Join-Path $Root "store.csv"
|
||||
if (Test-Path -LiteralPath $storeCsv) {
|
||||
Copy-Item -LiteralPath $storeCsv -Destination (Join-Path $listingDir "store.csv") -Force
|
||||
}
|
||||
$screenSrc = Join-Path $Root "screen"
|
||||
if (Test-Path -LiteralPath $screenSrc) {
|
||||
Copy-Item -Path (Join-Path $screenSrc "*") -Destination $listingScreen -Force
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Store folder: $OutDir"
|
||||
Write-Host "README: $(Join-Path $OutDir 'README.txt')"
|
||||
Write-Host "Staged: $($stagedArchs -join ', ')"
|
||||
if ($producedMsix.Count -gt 0) {
|
||||
Write-Host "MSIX: $($producedMsix -join ', ')"
|
||||
}
|
||||
|
||||
# Exit non-zero only after staging both (when requested) if MakeAppx missing,
|
||||
# or if packing failed / required exes missing.
|
||||
if ($makeAppxMissing) {
|
||||
if ($Arch -eq "all" -and $stagedArchs.Count -lt 2) {
|
||||
Write-Host "MakeAppx missing and could not stage both arches." -ForegroundColor Red
|
||||
exit 2
|
||||
}
|
||||
if ($stagedArchs.Count -eq 0) {
|
||||
Write-Host "Nothing staged." -ForegroundColor Red
|
||||
exit 2
|
||||
}
|
||||
Write-Host "MakeAppx missing - staging complete; see README.txt for pack commands." -ForegroundColor Yellow
|
||||
exit 2
|
||||
}
|
||||
|
||||
Write-Host "Packing $MsixPath ..."
|
||||
& $MakeAppx pack /d $Staging /p $MsixPath /o
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
if ($CertPath) {
|
||||
$SignTool = Find-SdkTool "signtool.exe"
|
||||
if (-not $SignTool) { throw "signtool.exe not found (needed for -CertPath)." }
|
||||
Write-Host "Signing with $CertPath ..."
|
||||
if ($CertPassword) {
|
||||
& $SignTool sign /fd SHA256 /a /f $CertPath /p $CertPassword $MsixPath
|
||||
} else {
|
||||
& $SignTool sign /fd SHA256 /a /f $CertPath $MsixPath
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Done:"
|
||||
Write-Host " $MsixPath"
|
||||
Write-Host " Staging: $Staging"
|
||||
Write-Host ""
|
||||
Write-Host "Store: upload MSIX in Partner Center (identity must match reservation)."
|
||||
Write-Host ("Sideload: Add-AppxPackage -Path `"{0}`"" -f $MsixPath)
|
||||
if ($packFailed) { exit 1 }
|
||||
if ($anyMissingExe -and $Arch -eq "all" -and $producedMsix.Count -lt 2) { exit 1 }
|
||||
if ($producedMsix.Count -eq 0) { exit 1 }
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user