All notable changes to AppxBackup are documented here.
The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
Releases before 2.1.0 were recorded only in the ReleaseNotes field of
AppxBackup.psd1; they are summarised here for continuity.
2.1.0 - 2026-07-28
A correctness release. Six defects shared one root cause: a guard compared the
result of a boolean-returning call against $null. Because [bool] is never
$null, each condition was always false and the code it guarded never ran. One
of them could hang a backup indefinitely; another silently corrupted every
captured tool output. This release also gives the project its first automated
tests, a static-analysis configuration, and CI.
- Process timeouts were never enforced.
Invoke-ProcessSafelycalledWaitForExit([int]), which returns$falseon timeout, and tested the result against$null. The kill branch was unreachable, so control fell through to the unboundedWaitForExit()and blocked the caller forever.MakeAppxis invoked with a 1800 second timeout, so a wedged tool hung the session with no recourse. Measured against the previous commit: a 3 second timeout ran a 30 second process to completion and reported success. It now aborts at 3 seconds and kills the process tree. - Failed process starts went undetected.
Process.Start()returns[bool]; the same$nullcomparison meant a failure to start was never caught. - Almost all captured tool output was discarded. Output was collected with
Register-ObjectEvent -Action. PowerShell dispatches those handlers on its own event loop, which is not pumped while the pipeline is blocked inWaitForExit, so most lines never arrived. Measured against MakeAppx, which emits 36 lines: one line survived.Get-AppxMakeAppxErrorAnalysiswas therefore parsing the MakeAppx banner rather than the error, and a failed packaging run produced no usable diagnosis. Both streams are now read withStreamReader.ReadToEndAsync, started before the wait so neither pipe can fill while the other is being read. Verified end to end against the real MakeAppx: the wrapper now captures the same 17 non-empty lines as a direct invocation, including the specific manifest validation error, and a package builds successfully through the module. This also removes a fixed 1-2 second sleep that existed only to give the event handlers time to drain: acmdinvocation went from 1.05s to 0.06s, and the process test file from 14.5s to 6.1s. - Captured tool output was corrupted. The stdout and stderr buffers were built
with
[StringBuilder]::new($capacity)where$capacitycame fromConvertFrom-Jsonand is thereforeInt64.StringBuilderoffers(Int32 capacity)and(String value)but noInt64overload, so PowerShell bound the string one and seeded each buffer with its own capacity as text. Every captured stream began with16384or4096, which fed directly into MakeAppx error analysis and the diagnostics printed on failure. -MustExistwas not enforced.ConvertTo-SecureFilePathfell through toGet-Itemon a missing path, so callers sawThe property 'PSIsContainer' cannot be found on this objectinstead of a usable message.-CreateIfMissingcreated nothing. In addition to the dead guard, creation was nested inside the-MustExistbranch.Backup-AppxPackagepasses-CreateIfMissingon its own, so-OutputPathwas never created.- Tool discovery returned an array on cache hits. The cache check used
returninside abeginblock.returnthere exits only that block, so the cached value was emitted and the full search then emitted a second one — a two-element array from a function declared[OutputType([string])]. - Real errors were replaced by strict-mode noise.
Backup-AppxPackageread$packageOutputPathin itscatchblock. When a failure occurred before that variable was assigned,Set-StrictModeraisedvariable cannot be retrievedand the genuine cause was lost. - Logger guards. Removed a dead preference check in
Write-AppxLogand corrected a$nullcomparison againstSplit-Path -Parent, which returns an empty string rather than$null.
- Aliases are now exported module members rather than being created with
-Scope Global.Remove-Modulewithdraws them instead of leaving them behind in the caller's session. The four alias names are unchanged. - The module loader discovers
Public/andPrivate/from disk instead of using two hand-maintained ordered lists. Every file in those folders defines only functions, so load order carries no meaning — PowerShell resolves a call target when a function runs, not when it is defined. Adding a file no longer requires registering it separately, which removes a class of silent load failure. CompatiblePSEditionsis nowCoreonly. The Desktop edition tops out at Windows PowerShell 5.1 and could never satisfyPowerShellVersion = '7.4', so advertising it made the module look installable where it is not.AppxBackup.psd1andPrivate/Write-AppxLog.ps1contain non-ASCII characters and now carry a UTF-8 BOM, so ANSI-defaulting hosts read them correctly.Invoke-ProcessSafelyno longer accepts-AsyncWaitMilliseconds. It configured the sleep that compensated for the event-handler drain, which no longer exists. No caller passed it. TheasyncWaitMillisecondskeys inConfig/ToolConfiguration.jsonare retained so existing configuration files stay valid, but they are no longer read.
-
Test-AppxBackupCompatibility -CheckCapabilitiesis now implemented. The package's declared capabilities are classified againstConfig/CapabilityClassification.jsoninto restricted (gated by Microsoft, and the entries most likely to make a sideloaded restore behave differently from the original), device (user consent at first use) and general. Restricted capabilities add a warning, and the result object gainsDeclaredCapabilities,RestrictedCapabilities,DeviceCapabilitiesandCapabilitiesChecked. The classification lives in configuration so it can track new Windows capabilities without a code change. -
tests/— 73 Pester tests covering path validation, external process execution, SDK discovery, configuration loading, and the manifest/loader/disk agreement that defines the public surface. Written in the subset shared by Pester 5 and 6. Every defect listed above has a regression test that fails against the old behaviour. -
End-to-end packaging tests under
tests/, taggedE2E. They build a synthetic but valid package through the real Windows SDK MakeAppx — including a negative case asserting that a failed run surfaces the tool's own error text — and are excluded from the default run. Run them withbuild.ps1 -Task Test -IncludeE2E; they skip cleanly without the SDK. CI runs them as a separate step on windows-latest, which ships the SDK. -
build.ps1— one entry point forBuild,AnalyzeandTest, used by both contributors and CI so that local and CI results mean the same thing.-CIproduces NUnit test results and a CSV of analyzer findings, and sets the exit code. -
PSScriptAnalyzerSettings.psd1— a correctness-scoped rule set. Each exclusion records why the rule does not apply here, so any finding is worth acting on. -
.github/workflows/ci.yml— Windows CI across PowerShell 7.4 and 7.5. -
.gitignore— notably excludes certificates and key material, and the package artefacts produced by running the module in place. -
This changelog.
-
Removed three private parameters that were accepted and ignored:
-ValidateSchema(Get-AppxManifestData),-CreateBundle(New-AppxPackageInternal) and-OutputDirectory(New-AppxBackupManifest). No public surface change. -
The analyzer gate is now clean and CI blocks on warnings, not just errors. The pre-existing warnings were resolved: genuinely dead variables removed, discarded call results made explicit with
$null, safe-fallback catches given non-throwing acknowledgements, and the logger's deliberate silent catches suppressed at function level with justification.
- PSScriptAnalyzer 1.25 raises an internal
NullReferenceExceptionwhile inspectingAppxBackup.psm1through an absolute path. Analysis still completes and returns the full finding set;build.ps1reports the condition instead of failing on it.
2.0.2 - 2026-02-13
- Eleven private helper functions extracted from core logic, removing roughly 600
lines of duplicated code from
Backup-AppxPackageandNew-AppxPackageInternal. - Three-tier fallback directory copying (Robocopy,
Copy-Item, .NET APIs). - Disk space validation, prerequisite checking and architecture compatibility checks.
- Certificate store installation and a SignTool execution wrapper.
Test-AppxBackupModule.ps1interactive harness covering the 8 public functions.
2.0.1 - 2026-01
- Recursive dependency resolution.
2.0.0 - 2025
- Initial 2.x line: ZIP-based dependency packaging (
.appxpack) with installation orchestration, native certificate management, and MSIX support.