Skip to main content

SubZeroDev.WinGet

Build

A C# client library for the WinGet COM API (Microsoft.Management.Deployment) — the same in-process API winget.exe itself is built on. Search, install, upgrade, uninstall, download, and repair packages; manage sources; pin packages; export/import package sets.

No console output parsing for anything the COM API can do, and no COM/WinRT types in the public surface — callers only ever see plain C# records, enums, and interfaces.

Features

AreaOperations
PackagesSearch (all sources or one), list installed, list available upgrades, get package, get full manifest details (description, license, agreements, icons, versions)
OperationsInstall, upgrade, uninstall, download-only, repair — with progress reporting, cancellation, and full option control (version, scope, architecture, installer type, silent/interactive, custom arguments, install location)
SourcesList, get, add, remove, refresh, edit (the winget source equivalent, via COM)
PinsList, add (including version gating and blocking), remove
Export/ImportSnapshot installed packages to a winget import-compatible JSON file and restore
ResilienceElevation-aware COM activation fallback chain, unreachable-source recovery, typed WinGetUnavailableException, documented auto-retry policy for known-recoverable WinGet error codes

Pin management and export/import have no COM equivalent in WinGet (verified against the winget-cli IDL), so those two features — and only those — run winget.exe behind an isolated IWinGetCliClient interface. Everything else is pure COM.

Quick Start

using Microsoft.Extensions.DependencyInjection;
using SubZeroDev.WinGet;
using SubZeroDev.WinGet.Abstractions;
using SubZeroDev.WinGet.Models;

var services = new ServiceCollection()
.AddLogging()
.AddPackageManagement()
.BuildServiceProvider();

var packages = services.GetRequiredService<IPackageManagementService>();

// Search across all configured sources (installed state included in results)
var results = await packages.Search("vscode");

// Install with options
var result = await packages.Install("Microsoft.VisualStudioCode", new InstallRequest
{
Scope = PackageScope.User,
Mode = PackageOperationMode.Silent
},
progress: new Progress<PackageOperationProgress>(p =>
Console.WriteLine($"{p.State}: {p.PercentComplete:F0}%")));

if (!result.Succeeded)
{
Console.WriteLine($"{result.Status}: {result.ErrorMessage} (0x{result.ExtendedErrorCode:X8})");
}

// What can be upgraded?
var upgrades = await packages.GetAvailableUpgrades();

// Sources
var sources = services.GetRequiredService<IPackageSourceService>();
var configured = await sources.GetSources();

Prefer raw, single-attempt behavior without the service layer's retry policy? Use IWinGetClient / IWinGetSourceClient / IWinGetCliClient directly — they are registered by AddPackageManagement() too.

Runnable examples

SubZeroDev.WinGet.Examples has a runnable example for every public API:

cd SubZeroDev.WinGet.Examples
dotnet run # lists all examples
dotnet run -- search terminal # read-only examples run live
dotnet run -- install <id> # mutating examples require explicit arguments

Read-only examples (search, installed, upgrades, details, sources, pins, export, version) run safely against your machine; anything that would change it (install, uninstall, pin, source add/remove, import) refuses to run without explicit arguments.

Requirements

  • Windows 10/11 with WinGet (App Installer) installed
  • .NET 8 or newer — the package targets net8.0-windows10.0.26100, so it also runs on net9/net10 apps
  • An explicit supported architecture: x64 or ARM64. The package supplies the matching native WinGet DLL and WinMD; see Architecture configuration for what each is checked against.

Architecture configuration

A package consumer needs only SubZeroDev.WinGet. Its transitive build target supplies the matching x64 or ARM64 Microsoft.Management.Deployment.dll and WinMD to build and publish output. Select the architecture explicitly with RuntimeIdentifier (win-x64 or win-arm64) or PlatformTarget/Platform (x64 or ARM64); ambiguous AnyCPU and unsupported platforms fail at build time.

Both architectures are package-contract-checked: ArchitectureTest verifies the PE shape of each executable/test host, and PackageTest verifies that the packed consumer's build/publish output selects the correct native DLL and WinMD for its RuntimeIdentifier/Platform. Windows x64 selection is additionally executed live — MachineStateTest and PackedConsumerSmokeTest run against a real packed consumer on a GitHub-hosted Windows x64 runner. ARM64 selection has the same package-contract checks but has not run on ARM64 hardware.

The library's managed assembly is IL-only AnyCPU. ArchitectureTest and PackageTest verify that shape and the packed layout for both architectures. On Windows x64, PackedConsumerSmokeTest goes further: it builds, publishes, and runs a real packed consumer against the AnyCPU assembly and observes a non-null WinGet version back, so that package shape is confirmed rather than left open. ARM64 has the same package-contract checks but no hardware execution.

If you consume the repository project through a ProjectReference instead of the packed NuGet package, retain a direct Microsoft.WindowsPackageManager.ComInterop reference on the executable project. Its build assets do not flow through ProjectReference.

Installing from GitHub Packages

Released versions are published to this repo's public GitHub Packages NuGet feed. Add it as a source (once), then install:

dotnet nuget add source https://nuget.pkg.github.com/The-Running-Dev/index.json --name github-trd
dotnet add package SubZeroDev.WinGet

GitHub requires authentication even for public-feed reads — use a personal access token with the read:packages scope as the source's password when prompted.

Building & Testing

dotnet build SubZeroDev.WinGet.sln
dotnet test SubZeroDev.WinGet.sln # mocked unit tests, no COM
./build.ps1 MachineStateTest # 7 local-machine live checks, read-only, needs WinGet
./build.ps1 CatalogIntegrationTest # 6 remote-catalog live checks, read-only, needs WinGet
./build.ps1 IntegrationTest # all 13 live checks

The integration tests are [Explicit], read-only by design, and run against the machine's real WinGet catalog.

CI runs the same steps through a generic Nuke build (build/Build.cs) instead of hand-written dotnet CLI steps. Equivalent locally:

./build.ps1 Test Coverage ArchitectureTest PackageTest

The library, tests, and examples target .NET 8 (net8.0-windows10.0.26100). The Nuke build tooling (build/) targets .NET 10 because Nuke.Common 10.x is net10-only — it's isolated from the product and not in the solution. So building via Nuke needs both SDKs (net8 to build/run the product, net10 to run Nuke); a plain dotnet build/dotnet test needs only the .NET 8 SDK.

See docs/testing.md for the full target list.

CI: .github/workflows/build.yml runs on every push to main and every pull request. It runs Test Coverage ArchitectureTest PackageTest before release: architecture checks verify the managed/executable PE shapes, and package checks build/publish direct and two-hop consumers without live COM activation. Pull requests never publish.

Publishing happens only after the build+test job passes:

  • GitHub Packages — automatic, on two triggers. A push to main (every merged PR) publishes a distinct prerelease 0.2.0-<n>; pushing a v* tag (git push origin v0.2.0) publishes the stable 0.2.0. The version comes from GitVersion via GitVersion.yml, which derives it from git history rather than the .csproj. Auth uses the built-in GITHUB_TOKEN, so no secret setup is needed.
  • NuGet.orgoff by default; runs only on a manual workflow_dispatch with the push_to_nuget input checked, and requires a NUGET_API_KEY repository secret. Publishes the version pinned in the .csproj.

Documentation

winget.subzerodev.com — the hosted docs site (built from docs/ via Docusaurus, website/). The same content is also readable directly on GitHub under docs/; each section below links both.

TopicSiteGitHub
Introduction — why this library, feature overview, the one deliberate CLI exceptionReaddocs/intro.md
Getting Started — install, requirements, and explicit architecture configurationReaddocs/getting-started.md
Managing Packages — search, install, upgrade, uninstall, download, repair, the retry policyReaddocs/usage/packages.md
Managing Sources — the winget source equivalentReaddocs/usage/sources.md
Pins, Export & Import — the CLI-backed featuresReaddocs/usage/pins-export-import.md
Running the Examples — a runnable example for every public APIReaddocs/examples.md
Architecture — layers, retry policy, verified COM API findingsReaddocs/architecture.md
Building & Testing — Nuke targets, coverage, publishingReaddocs/testing.md
Documentation System — the containerised docs-template image, its gate and deployReaddocs/documentation-system.md
Troubleshooting — common runtime errors and fixesReaddocs/troubleshooting.md

Design Notes

The full design document — including the verified COM API findings (OR'd selectors vs AND'd filters, the CsWinRT collection enumeration bug, activation quirks in elevated hosts) and the research summarized from winget-cli, UniGetUI, and Winget-AutoUpdate — lives in SPECIFICATION.md.

Roadmap

Known gaps and planned work — correctness fixes, threading, packaging, API expansion, and new capabilities — are tracked as phases in ROADMAP.md.

License

MIT