Skip to main content

Overview

PackageCatalog represents a searchable package source. Before using a catalog, you must first obtain a PackageCatalogReference and connect to it. Once connected, you can search for packages using various filters.

Class Definitions

PackageCatalogReference

runtimeclass PackageCatalogReference
{
    Boolean IsComposite { get; };
    PackageCatalogInfo Info { get; };
    
    IAsyncOperation<ConnectResult> ConnectAsync();
    ConnectResult Connect();
    
    String AdditionalPackageCatalogArguments;
    IVectorView<SourceAgreement> SourceAgreements { get; };
    Boolean AcceptSourceAgreements;
    TimeSpan PackageCatalogBackgroundUpdateInterval;
    Boolean InstalledPackageInformationOnly;
    AuthenticationArguments AuthenticationArguments;
    AuthenticationInfo AuthenticationInfo { get; };
    
    IAsyncOperationWithProgress<RefreshPackageCatalogResult, Double> 
        RefreshPackageCatalogAsync();
}

PackageCatalog

runtimeclass PackageCatalog
{
    Boolean IsComposite { get; };
    PackageCatalogInfo Info { get; };
    
    IAsyncOperation<FindPackagesResult> FindPackagesAsync(FindPackagesOptions options);
    FindPackagesResult FindPackages(FindPackagesOptions options);
}

PackageCatalogInfo

runtimeclass PackageCatalogInfo
{
    String Id { get; };
    String Name { get; };
    String Type { get; };
    String Argument { get; };
    DateTime LastUpdateTime { get; };
    PackageCatalogOrigin Origin { get; };
    PackageCatalogTrustLevel TrustLevel { get; };
    Boolean Explicit { get; };
    Int32 Priority { get; };
}

Connecting to a Catalog

Basic Connection

var manager = new PackageManager();
var catalogRef = manager.GetPredefinedPackageCatalog(
    PredefinedPackageCatalog.OpenWindowsCatalog
);

// Connect to catalog
var connectResult = await catalogRef.ConnectAsync();

if (connectResult.Status == ConnectResultStatus.Ok)
{
    var catalog = connectResult.PackageCatalog;
    Console.WriteLine("Connected to catalog");
    
    // Now you can search
}
else
{
    Console.WriteLine($"Connection failed: {connectResult.Status}");
    Console.WriteLine($"Error: 0x{connectResult.ExtendedErrorCode:X}");
}

Connection with Agreement Acceptance

Some catalogs require accepting source agreements:
var catalogRef = manager.GetPackageCatalogByName("customsource");

// Check for required agreements
var agreements = catalogRef.SourceAgreements;
if (agreements.Count > 0)
{
    Console.WriteLine("Source requires acceptance of agreements:");
    foreach (var agreement in agreements)
    {
        Console.WriteLine($"- {agreement.Label}");
        Console.WriteLine($"  {agreement.Text}");
        Console.WriteLine($"  URL: {agreement.Url}");
    }
    
    // Accept agreements
    catalogRef.AcceptSourceAgreements = true;
}

var connectResult = await catalogRef.ConnectAsync();

Authentication

For catalogs requiring authentication:
var catalogRef = manager.GetPackageCatalogByName("privatecatalog");

// Check authentication requirements
var authInfo = catalogRef.AuthenticationInfo;
if (authInfo != null && authInfo.AuthenticationType == AuthenticationType.MicrosoftEntraId)
{
    // Set up authentication
    var authArgs = new AuthenticationArguments()
    {
        AuthenticationMode = AuthenticationMode.SilentPreferred,
        AuthenticationAccount = "[email protected]"
    };
    catalogRef.AuthenticationArguments = authArgs;
}

var connectResult = await catalogRef.ConnectAsync();

Searching for Packages

FindPackagesOptions

runtimeclass FindPackagesOptions
{
    FindPackagesOptions();
    
    IVector<PackageMatchFilter> Selectors { get; };
    IVector<PackageMatchFilter> Filters { get; };
    UInt32 ResultLimit;
}
Search Logic:
  • Selectors - You must match at least ONE selector (OR logic)
  • Filters - You must match ALL filters (AND logic)
  • Final result = (Selector1 OR Selector2 OR ...) AND Filter1 AND Filter2 AND ...

PackageMatchFilter

Field
PackageMatchField
required
The field to search
Value
String
required
The value to match
Option
PackageFieldMatchOption
required
How to match the value
PackageMatchField values:
  • CatalogDefault - Default search across all fields
  • Id - Package identifier
  • Name - Package name
  • Moniker - Package moniker (short name)
  • Command - Executable command
  • Tag - Package tags
  • PackageFamilyName - Package family name
  • ProductCode - Product code
PackageFieldMatchOption values:
  • Equals - Case-sensitive exact match
  • EqualsCaseInsensitive - Case-insensitive exact match
  • StartsWithCaseInsensitive - Case-insensitive prefix match
  • ContainsCaseInsensitive - Case-insensitive substring match
var options = new FindPackagesOptions();

// Search by package ID
var filter = new PackageMatchFilter()
{
    Field = PackageMatchField.Id,
    Value = "Microsoft.PowerToys",
    Option = PackageFieldMatchOption.Equals
};
options.Selectors.Add(filter);

var result = await catalog.FindPackagesAsync(options);

if (result.Status == FindPackagesResultStatus.Ok)
{
    Console.WriteLine($"Found {result.Matches.Count} packages");
    
    foreach (var match in result.Matches)
    {
        var package = match.CatalogPackage;
        Console.WriteLine($"ID: {package.Id}");
        Console.WriteLine($"Name: {package.Name}");
        Console.WriteLine($"Version: {package.DefaultInstallVersion.Version}");
    }
    
    if (result.WasLimitExceeded)
    {
        Console.WriteLine("Results were truncated");
    }
}

Search by Name (Partial Match)

var options = new FindPackagesOptions();

// Search for packages containing "visual studio"
var filter = new PackageMatchFilter()
{
    Field = PackageMatchField.Name,
    Value = "visual studio",
    Option = PackageFieldMatchOption.ContainsCaseInsensitive
};
options.Selectors.Add(filter);

// Limit results
options.ResultLimit = 10;

var result = await catalog.FindPackagesAsync(options);

Multiple Selectors (OR Logic)

var options = new FindPackagesOptions();

// Find by ID OR moniker
options.Selectors.Add(new PackageMatchFilter()
{
    Field = PackageMatchField.Id,
    Value = "Microsoft.VisualStudioCode",
    Option = PackageFieldMatchOption.Equals
});

options.Selectors.Add(new PackageMatchFilter()
{
    Field = PackageMatchField.Moniker,
    Value = "vscode",
    Option = PackageFieldMatchOption.EqualsCaseInsensitive
});

// This will find packages matching EITHER selector
var result = await catalog.FindPackagesAsync(options);

Using Filters (AND Logic)

var options = new FindPackagesOptions();

// Selector: search for packages with "microsoft" in name
options.Selectors.Add(new PackageMatchFilter()
{
    Field = PackageMatchField.Name,
    Value = "microsoft",
    Option = PackageFieldMatchOption.ContainsCaseInsensitive
});

// Filter: only packages with specific tag
options.Filters.Add(new PackageMatchFilter()
{
    Field = PackageMatchField.Tag,
    Value = "developer-tools",
    Option = PackageFieldMatchOption.Equals
});

// This finds packages with "microsoft" in name AND tag "developer-tools"
var result = await catalog.FindPackagesAsync(options);

Search All Packages

// Empty options returns all packages (up to ResultLimit)
var options = new FindPackagesOptions()
{
    ResultLimit = 100 // Limit to 100 packages
};

var result = await catalog.FindPackagesAsync(options);

Console.WriteLine($"Total packages: {result.Matches.Count}");
if (result.WasLimitExceeded)
{
    Console.WriteLine("More packages available, but limited to 100");
}

Working with Results

FindPackagesResult

runtimeclass FindPackagesResult
{
    FindPackagesResultStatus Status { get; };
    IVectorView<MatchResult> Matches { get; };
    Boolean WasLimitExceeded { get; };
    HRESULT ExtendedErrorCode { get; };
}
FindPackagesResultStatus values:
  • Ok - Search successful
  • BlockedByPolicy - Blocked by group policy
  • CatalogError - Catalog error occurred
  • InternalError - Internal error
  • InvalidOptions - Invalid search options
  • AuthenticationError - Authentication failed
  • AccessDenied - Access denied to catalog

MatchResult

runtimeclass MatchResult
{
    CatalogPackage CatalogPackage { get; };
    PackageMatchFilter MatchCriteria { get; };
}
foreach (var match in findResult.Matches)
{
    var package = match.CatalogPackage;
    var criteria = match.MatchCriteria;
    
    Console.WriteLine($"Package: {package.Name}");
    Console.WriteLine($"Matched on: {criteria.Field} = {criteria.Value}");
    
    // Access package details
    if (package.DefaultInstallVersion != null)
    {
        var version = package.DefaultInstallVersion;
        Console.WriteLine($"Version: {version.Version}");
        Console.WriteLine($"Publisher: {version.Publisher}");
        Console.WriteLine($"Display Name: {version.DisplayName}");
    }
    
    // Check if update available
    if (package.IsUpdateAvailable)
    {
        Console.WriteLine("Update available!");
    }
}

CatalogPackage

runtimeclass CatalogPackage
{
    String Id { get; };
    String Name { get; };
    PackageVersionInfo InstalledVersion { get; };
    IVectorView<PackageVersionId> AvailableVersions { get; };
    PackageVersionInfo DefaultInstallVersion { get; };
    PackageVersionInfo GetPackageVersionInfo(PackageVersionId versionKey);
    Boolean IsUpdateAvailable { get; };
    
    IAsyncOperation<CheckInstalledStatusResult> CheckInstalledStatusAsync(InstalledStatusType checkTypes);
    CheckInstalledStatusResult CheckInstalledStatus(InstalledStatusType checkTypes);
    
    IReference<Int32> CatalogPriority { get; };
}

Getting Package Metadata

var package = match.CatalogPackage;
var version = package.DefaultInstallVersion;

// Get catalog metadata (requires contract v6+)
var metadata = version.GetCatalogPackageMetadata();

Console.WriteLine($"Publisher: {metadata.Publisher}");
Console.WriteLine($"License: {metadata.License}");
Console.WriteLine($"Description: {metadata.Description}");
Console.WriteLine($"Package URL: {metadata.PackageUrl}");
Console.WriteLine($"Publisher URL: {metadata.PublisherUrl}");

// Tags
if (metadata.Tags.Count > 0)
{
    Console.WriteLine("Tags: " + string.Join(", ", metadata.Tags));
}

// Icons
foreach (var icon in metadata.Icons)
{
    Console.WriteLine($"Icon: {icon.Url} ({icon.Resolution}, {icon.Theme})");
}

// Agreements
foreach (var agreement in metadata.Agreements)
{
    Console.WriteLine($"Agreement: {agreement.Label}");
    Console.WriteLine($"  {agreement.Text}");
    Console.WriteLine($"  URL: {agreement.Url}");
}

Getting Specific Version

var package = match.CatalogPackage;

// List all available versions
foreach (var versionId in package.AvailableVersions)
{
    Console.WriteLine($"Available: {versionId.Version} (Channel: {versionId.Channel})");
}

// Get specific version
if (package.AvailableVersions.Count > 0)
{
    var versionKey = package.AvailableVersions[0];
    var versionInfo = package.GetPackageVersionInfo(versionKey);
    
    Console.WriteLine($"Version: {versionInfo.Version}");
    Console.WriteLine($"Display Name: {versionInfo.DisplayName}");
    Console.WriteLine($"Publisher: {versionInfo.Publisher}");
}

Composite Catalogs

Composite catalogs combine multiple sources for unified searching:
var manager = new PackageManager();

var options = new CreateCompositePackageCatalogOptions();

// Add catalogs to composite
options.Catalogs.Add(
    manager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.OpenWindowsCatalog)
);
options.Catalogs.Add(
    manager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages)
);

// Set search behavior
options.CompositeSearchBehavior = CompositeSearchBehavior.RemotePackagesFromAllCatalogs;

// Set installed scope filter
options.InstalledScope = PackageInstallScope.User;

var compositeCatalog = manager.CreateCompositePackageCatalog(options);
var connectResult = await compositeCatalog.ConnectAsync();

if (connectResult.Status == ConnectResultStatus.Ok)
{
    // Search across all catalogs
    var findOptions = new FindPackagesOptions();
    var result = await connectResult.PackageCatalog.FindPackagesAsync(findOptions);
    
    foreach (var match in result.Matches)
    {
        var package = match.CatalogPackage;
        
        // Check if installed
        if (package.InstalledVersion != null)
        {
            Console.WriteLine($"{package.Name} (Installed: {package.InstalledVersion.Version})");
            
            if (package.IsUpdateAvailable)
            {
                Console.WriteLine($"  Update available: {package.DefaultInstallVersion.Version}");
            }
        }
        else
        {
            Console.WriteLine($"{package.Name} (Not installed)");
        }
    }
}
CompositeSearchBehavior values:
  • LocalCatalogs - Search only local catalogs
  • RemotePackagesFromRemoteCatalogs - Search remote catalogs without checking installed status
  • RemotePackagesFromAllCatalogs - Search remote catalogs and check local catalogs for installed versions
  • AllCatalogs - Search both local and remote catalogs

Catalog Refresh

Update a catalog to get the latest package information:
var catalogRef = manager.GetPredefinedPackageCatalog(
    PredefinedPackageCatalog.OpenWindowsCatalog
);

var operation = catalogRef.RefreshPackageCatalogAsync();
operation.Progress = (op, progress) =>
{
    Console.WriteLine($"Refresh progress: {progress}%");
};

var result = await operation;

if (result.Status == RefreshPackageCatalogStatus.Ok)
{
    Console.WriteLine("Catalog refreshed successfully");
}
else
{
    Console.WriteLine($"Refresh failed: {result.Status}");
    Console.WriteLine($"Error: 0x{result.ExtendedErrorCode:X}");
}

Build docs developers (and LLMs) love