diff --git a/.github/workflows/store-msix.yml b/.github/workflows/store-msix.yml
new file mode 100644
index 0000000..c0f35ef
--- /dev/null
+++ b/.github/workflows/store-msix.yml
@@ -0,0 +1,88 @@
+name: Microsoft Store MSIX
+
+on:
+ pull_request:
+ paths:
+ - ".github/workflows/store-msix.yml"
+ - "packaging/msix/**"
+ - "scripts/build-msix.ps1"
+ - "src-tauri/**"
+ - "crates/**"
+ - "Cargo.toml"
+ - "Cargo.lock"
+ - "Trunk.toml"
+ - "assets/icon.png"
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "Version from src-tauri/tauri.conf.json (for example 0.9.1)"
+ required: true
+ type: string
+ without_service:
+ description: "Omit the packaged Everything Windows service"
+ required: false
+ default: false
+ type: boolean
+
+permissions:
+ contents: read
+
+jobs:
+ build-msix:
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
+ with:
+ fetch-depth: 0
+
+ - name: Validate Store release ref
+ if: github.event_name == 'workflow_dispatch'
+ shell: pwsh
+ run: |
+ $expectedTag = "v${{ inputs.version }}"
+ if ($env:GITHUB_REF_TYPE -ne "tag" -or $env:GITHUB_REF_NAME -ne $expectedTag) {
+ throw "Microsoft Store builds must run from release tag '$expectedTag' (selected ref: '$env:GITHUB_REF_NAME')."
+ }
+ git merge-base --is-ancestor $env:GITHUB_SHA origin/master
+ if ($LASTEXITCODE -ne 0) {
+ throw "Store release tag '$expectedTag' must point to a commit on master."
+ }
+
+ - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
+ with:
+ targets: wasm32-unknown-unknown
+
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
+ with:
+ workspaces: ". -> target"
+
+ - name: Prepare toolchain and bundled Everything runtime
+ shell: pwsh
+ run: .\scripts\setup.ps1 -SkipFormat
+
+ - name: Build unsigned Store MSIX package
+ shell: pwsh
+ env:
+ # Pull requests use harmless placeholders to validate the package layout.
+ # Manual Store builds use the exact values copied from Partner Center.
+ MSIX_IDENTITY_NAME: ${{ vars.MSIX_IDENTITY_NAME || 'EverythingNext' }}
+ MSIX_PUBLISHER: ${{ vars.MSIX_PUBLISHER || 'CN=Everything Next' }}
+ MSIX_PUBLISHER_DISPLAY_NAME: ${{ vars.MSIX_PUBLISHER_DISPLAY_NAME || 'Stephan Orgiazzi' }}
+ run: |
+ $arguments = @()
+ if ('${{ inputs.version }}') {
+ $arguments += @('-Version', '${{ inputs.version }}')
+ }
+ if ('${{ inputs.without_service }}' -eq 'true') {
+ $arguments += '-WithoutService'
+ }
+ & .\scripts\build-msix.ps1 @arguments
+
+ - name: Upload MSIX package
+ if: github.event_name == 'workflow_dispatch'
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: everything-next-msix-${{ inputs.version }}
+ path: artifacts/msix/*.msix
+ if-no-files-found: error
diff --git a/docs/releasing.md b/docs/releasing.md
index 16482e4..2709440 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -39,6 +39,24 @@ Installed release builds query:
When a newer version is available, Everything Next offers **Install** or **Later**. The updater uses the NSIS installer; on Windows the app exits before installation, and the existing installer hooks stop/recreate the private Everything service during the upgrade.
+## Microsoft Store MSIX package
+
+The repository also contains a separate MSIX packaging workflow for the Microsoft Store. It does not change the GitHub/NSIS release channel.
+
+Microsoft re-signs MSIX packages submitted through the Store, so this workflow intentionally produces an unsigned `.msix` artifact. No Authenticode certificate is needed for that Store package.
+
+Before running `.github/workflows/store-msix.yml`, reserve the app in Partner Center and create these repository variables with the exact values supplied by Microsoft:
+
+- `MSIX_IDENTITY_NAME`
+- `MSIX_PUBLISHER`
+- `MSIX_PUBLISHER_DISPLAY_NAME`
+
+Run the workflow from the release tag and enter the same three-part version as `src-tauri/tauri.conf.json`. Download the resulting artifact and upload the `.msix` package to the Store submission.
+
+The default package declares the private `Everything Service (EverythingNext)` as a packaged LocalSystem service. Microsoft may require approval for the `packagedServices` and `localSystemServices` restricted capabilities. If that capability request is not accepted, rerun the workflow with `without_service` enabled; the application will start its own bundled Everything process when it is launched, but it will not maintain the index while the application is closed.
+
+The Store build does not receive the Tauri updater signing key. Store updates are managed by Microsoft; GitHub releases continue to use the existing Tauri updater and NSIS hooks.
+
## First WinGet submission
The package identifier is `StephanOrgiazzi.EverythingNext`. The first submission must be made after the GitHub Release exists because WinGet requires a public, version-specific installer URL and its exact SHA-256 hash.
diff --git a/packaging/msix/AppxManifest.xml.in b/packaging/msix/AppxManifest.xml.in
new file mode 100644
index 0000000..a9d81a6
--- /dev/null
+++ b/packaging/msix/AppxManifest.xml.in
@@ -0,0 +1,63 @@
+
+
+
+
+
+ Everything Next
+ __PUBLISHER_DISPLAY_NAME__
+ Fast, native-style file search for Windows 11.
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+__SERVICE_EXTENSION__
+
+
+
+
+
+
+__SERVICE_CAPABILITIES__
+
+
diff --git a/scripts/build-msix.ps1 b/scripts/build-msix.ps1
new file mode 100644
index 0000000..c53b127
--- /dev/null
+++ b/scripts/build-msix.ps1
@@ -0,0 +1,213 @@
+[CmdletBinding()]
+param(
+ [string]$IdentityName = $env:MSIX_IDENTITY_NAME,
+ [string]$Publisher = $env:MSIX_PUBLISHER,
+ [string]$PublisherDisplayName = $env:MSIX_PUBLISHER_DISPLAY_NAME,
+ [string]$Version,
+ [string]$OutputDirectory = (Join-Path $PSScriptRoot '..\artifacts\msix'),
+ [switch]$SkipBuild,
+ [switch]$WithoutService
+)
+
+$ErrorActionPreference = 'Stop'
+$PSNativeCommandUseErrorActionPreference = $true
+$projectRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
+Set-Location $projectRoot
+
+function Require-Value([string]$Name, [string]$Value) {
+ if ([string]::IsNullOrWhiteSpace($Value)) {
+ throw "$Name is required. Set it as a parameter or repository variable."
+ }
+ if ($Value.Contains('"') -or $Value.Contains('<') -or $Value.Contains('>')) {
+ throw "$Name contains characters that cannot be used in the MSIX manifest."
+ }
+}
+
+Require-Value 'IdentityName' $IdentityName
+Require-Value 'Publisher' $Publisher
+Require-Value 'PublisherDisplayName' $PublisherDisplayName
+
+$tauriConfig = Get-Content 'src-tauri\tauri.conf.json' -Raw | ConvertFrom-Json
+$manifestVersion = [string]$tauriConfig.version
+if ([string]::IsNullOrWhiteSpace($Version)) {
+ $Version = $manifestVersion
+} elseif ($Version -ne $manifestVersion) {
+ throw "Requested MSIX version '$Version' does not match tauri.conf.json version '$manifestVersion'."
+}
+
+if ($Version -notmatch '^\d+\.\d+\.\d+$') {
+ throw "Version '$Version' must contain exactly three numeric components, such as 0.9.1."
+}
+$packageVersion = "$Version.0"
+
+if (-not $SkipBuild) {
+ # Store packages use the manifest startup task and Microsoft Store updates.
+ # Do not compile the registry autostart path or private updater signing material into this build.
+ $savedStoreBuild = $env:TAURI_STORE_BUILD
+ $savedUpdaterKey = $env:TAURI_UPDATER_PUBLIC_KEY
+ $savedSigningKey = $env:TAURI_SIGNING_PRIVATE_KEY
+ try {
+ $env:TAURI_STORE_BUILD = '1'
+ Remove-Item Env:TAURI_UPDATER_PUBLIC_KEY -ErrorAction SilentlyContinue
+ Remove-Item Env:TAURI_SIGNING_PRIVATE_KEY -ErrorAction SilentlyContinue
+
+ & cargo tauri build --no-bundle --config src-tauri/tauri.release.conf.json
+ if ($LASTEXITCODE -ne 0) {
+ throw "Tauri build failed with exit code $LASTEXITCODE."
+ }
+
+ & cargo build --release -p everything-next --bin everything-next-autostart
+ if ($LASTEXITCODE -ne 0) {
+ throw "Autostart launcher build failed with exit code $LASTEXITCODE."
+ }
+ } finally {
+ if ($null -eq $savedStoreBuild) {
+ Remove-Item Env:TAURI_STORE_BUILD -ErrorAction SilentlyContinue
+ } else {
+ $env:TAURI_STORE_BUILD = $savedStoreBuild
+ }
+ if ($null -eq $savedUpdaterKey) {
+ Remove-Item Env:TAURI_UPDATER_PUBLIC_KEY -ErrorAction SilentlyContinue
+ } else {
+ $env:TAURI_UPDATER_PUBLIC_KEY = $savedUpdaterKey
+ }
+ if ($null -eq $savedSigningKey) {
+ Remove-Item Env:TAURI_SIGNING_PRIVATE_KEY -ErrorAction SilentlyContinue
+ } else {
+ $env:TAURI_SIGNING_PRIVATE_KEY = $savedSigningKey
+ }
+ }
+}
+
+$makeAppxCommand = Get-Command makeappx.exe -ErrorAction SilentlyContinue | Select-Object -First 1
+if ($makeAppxCommand) {
+ $makeAppx = $makeAppxCommand.Source
+} else {
+ $makeAppx = Get-ChildItem `
+ -Path (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin') `
+ -Filter makeappx.exe `
+ -Recurse `
+ -File `
+ -ErrorAction SilentlyContinue |
+ Where-Object { $_.Directory.Name -eq 'x64' } |
+ Sort-Object FullName |
+ Select-Object -Last 1 -ExpandProperty FullName
+}
+if (-not $makeAppx) {
+ throw 'makeappx.exe was not found in the installed Windows SDK.'
+}
+
+$binary = Join-Path $projectRoot 'target\release\EverythingNext.exe'
+$autostartLauncher = Join-Path $projectRoot 'target\release\everything-next-autostart.exe'
+$sdk = Join-Path $projectRoot 'src-tauri\Everything3_x64.dll'
+$engine = Join-Path $projectRoot 'src-tauri\engine\Everything.exe'
+$license = Join-Path $projectRoot 'src-tauri\engine\THIRD-PARTY-LICENSES.txt'
+$manifestTemplate = Join-Path $projectRoot 'packaging\msix\AppxManifest.xml.in'
+
+foreach ($requiredFile in @($binary, $autostartLauncher, $sdk, $engine, $license, $manifestTemplate)) {
+ if (-not (Test-Path -LiteralPath $requiredFile -PathType Leaf)) {
+ throw "Required MSIX input is missing: $requiredFile"
+ }
+}
+
+if ([System.IO.Path]::IsPathRooted($OutputDirectory)) {
+ $outputDirectory = [System.IO.Path]::GetFullPath($OutputDirectory)
+} else {
+ $outputDirectory = [System.IO.Path]::GetFullPath((Join-Path $projectRoot $OutputDirectory))
+}
+New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
+$packagePath = Join-Path $outputDirectory "Everything.Next_$Version`_x64.msix"
+
+$stagingRoot = Join-Path ([System.IO.Path]::GetTempPath()) "everything-next-msix-$([guid]::NewGuid().ToString('N'))"
+$stagingEngine = Join-Path $stagingRoot 'engine'
+$stagingAssets = Join-Path $stagingRoot 'Assets'
+
+try {
+ New-Item -ItemType Directory -Path $stagingEngine, $stagingAssets -Force | Out-Null
+
+ Copy-Item -LiteralPath $binary -Destination (Join-Path $stagingRoot 'EverythingNext.exe')
+ Copy-Item -LiteralPath $autostartLauncher -Destination (Join-Path $stagingRoot 'EverythingNextAutostart.exe')
+ Copy-Item -LiteralPath $sdk -Destination (Join-Path $stagingRoot 'Everything3_x64.dll')
+ Copy-Item -LiteralPath $engine -Destination (Join-Path $stagingEngine 'Everything.exe')
+ Copy-Item -LiteralPath $license -Destination (Join-Path $stagingEngine 'THIRD-PARTY-LICENSES.txt')
+
+ $icon = Join-Path $projectRoot 'assets\icon.png'
+ if (-not (Test-Path -LiteralPath $icon -PathType Leaf)) {
+ throw "The MSIX logo source is missing: $icon"
+ }
+ Add-Type -AssemblyName System.Drawing
+ function Write-ScaledPng([string]$SourcePath, [string]$DestinationPath, [int]$Width, [int]$Height) {
+ $sourceImage = [System.Drawing.Image]::FromFile($SourcePath)
+ $bitmap = [System.Drawing.Bitmap]::new($Width, $Height)
+ $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
+ try {
+ $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
+ $graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
+ $graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
+ $graphics.DrawImage($sourceImage, 0, 0, $Width, $Height)
+ $bitmap.Save($DestinationPath, [System.Drawing.Imaging.ImageFormat]::Png)
+ } finally {
+ $graphics.Dispose()
+ $bitmap.Dispose()
+ $sourceImage.Dispose()
+ }
+ }
+
+ Write-ScaledPng $icon (Join-Path $stagingAssets 'StoreLogo.png') 50 50
+ Write-ScaledPng $icon (Join-Path $stagingAssets 'Square44x44Logo.png') 44 44
+ Write-ScaledPng $icon (Join-Path $stagingAssets 'Square150x150Logo.png') 150 150
+
+ $serviceExtension = ''
+ $serviceCapabilities = ''
+ if (-not $WithoutService) {
+ $serviceExtension = @'
+
+
+
+'@
+ $serviceCapabilities = @'
+
+
+'@
+ }
+
+ $manifest = Get-Content -LiteralPath $manifestTemplate -Raw
+ $replacements = @{
+ '__IDENTITY_NAME__' = [System.Security.SecurityElement]::Escape($IdentityName)
+ '__PUBLISHER__' = [System.Security.SecurityElement]::Escape($Publisher)
+ '__PUBLISHER_DISPLAY_NAME__' = [System.Security.SecurityElement]::Escape($PublisherDisplayName)
+ '__VERSION__' = $packageVersion
+ '__SERVICE_EXTENSION__' = $serviceExtension.TrimEnd()
+ '__SERVICE_CAPABILITIES__' = $serviceCapabilities.TrimEnd()
+ }
+ foreach ($token in $replacements.Keys) {
+ $manifest = $manifest.Replace($token, $replacements[$token])
+ }
+ Set-Content -LiteralPath (Join-Path $stagingRoot 'AppxManifest.xml') -Value $manifest -Encoding utf8NoBOM
+
+ if (Test-Path -LiteralPath $packagePath) {
+ Remove-Item -LiteralPath $packagePath -Force
+ }
+ & $makeAppx pack /d $stagingRoot /p $packagePath /o
+ if ($LASTEXITCODE -ne 0) {
+ throw "makeappx.exe failed with exit code $LASTEXITCODE."
+ }
+
+ Write-Host "Unsigned MSIX package written to $packagePath" -ForegroundColor Green
+ Write-Host "Identity: $IdentityName"
+ Write-Host "Publisher: $Publisher"
+ if ($WithoutService) {
+ Write-Host 'Packaged Everything service: disabled (the app starts its own engine process).' -ForegroundColor Yellow
+ } else {
+ Write-Host 'Packaged Everything service: enabled (the Store submission may require restricted capability approval).' -ForegroundColor Yellow
+ }
+} finally {
+ Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue
+}
diff --git a/src-tauri/src/bin/everything-next-autostart.rs b/src-tauri/src/bin/everything-next-autostart.rs
new file mode 100644
index 0000000..d3438f8
--- /dev/null
+++ b/src-tauri/src/bin/everything-next-autostart.rs
@@ -0,0 +1,22 @@
+#![cfg(windows)]
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+use std::os::windows::process::CommandExt;
+use std::process::Command;
+
+const CREATE_NO_WINDOW: u32 = 0x0800_0000;
+
+fn main() {
+ let Ok(launcher) = std::env::current_exe() else {
+ return;
+ };
+ let Some(package_root) = launcher.parent() else {
+ return;
+ };
+
+ let app = package_root.join("EverythingNext.exe");
+ let _ = Command::new(app)
+ .arg("--autostart")
+ .creation_flags(CREATE_NO_WINDOW)
+ .spawn();
+}
diff --git a/src-tauri/src/desktop.rs b/src-tauri/src/desktop.rs
index 01bd598..eaa7594 100644
--- a/src-tauri/src/desktop.rs
+++ b/src-tauri/src/desktop.rs
@@ -14,6 +14,8 @@ const AUTOSTART_ARG: &str = "--autostart";
const SEARCH_ARG: &str = "-s";
#[cfg(not(debug_assertions))]
const AUTOSTART_VALUE_NAME: &str = "Everything Next";
+#[cfg(not(debug_assertions))]
+const IS_STORE_BUILD: bool = option_env!("TAURI_STORE_BUILD").is_some();
const TRAY_OPEN_ID: &str = "open";
const TRAY_QUIT_ID: &str = "quit";
@@ -136,6 +138,10 @@ pub(crate) fn install_tray(app: &tauri::App) -> tauri::Result<()>
#[cfg(not(debug_assertions))]
pub(crate) fn ensure_autostart_registered() {
+ if IS_STORE_BUILD {
+ return;
+ }
+
if let Err(error) = register_windows_autostart() {
eprintln!("Everything Next autostart: {error}");
}