From 32b17d4d8f9473b793cb941a8f85fbba1dffcc43 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 08:58:33 -0500 Subject: [PATCH 01/60] Add note, date created, and show subcommand --- src/AppInstallerCLICore/Argument.cpp | 2 + .../Commands/PinCommand.cpp | 34 +++ src/AppInstallerCLICore/Commands/PinCommand.h | 15 ++ src/AppInstallerCLICore/ExecutionArgs.h | 1 + src/AppInstallerCLICore/Resources.h | 12 + src/AppInstallerCLICore/Workflows/PinFlow.cpp | 115 ++++++++- src/AppInstallerCLICore/Workflows/PinFlow.h | 8 + .../Shared/Strings/en-us/winget.resw | 46 ++++ .../Public/winget/Pin.h | 9 + .../AppInstallerRepositoryCore.vcxproj | 6 + .../Microsoft/PinningIndex.cpp | 54 +++- .../Microsoft/PinningIndex.h | 3 + .../Microsoft/Schema/IPinningIndex.h | 4 + .../Pinning_1_0/PinningIndexInterface.h | 1 + .../Pinning_1_0/PinningIndexInterface_1_0.cpp | 6 + .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 232 ++++++++++++++++++ .../Microsoft/Schema/Pinning_1_1/PinTable.h | 46 ++++ .../Pinning_1_1/PinningIndexInterface.h | 23 ++ .../Pinning_1_1/PinningIndexInterface_1_1.cpp | 118 +++++++++ 19 files changed, 730 insertions(+), 5 deletions(-) create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 99699bdb0a..f22524c8b2 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -203,6 +203,8 @@ namespace AppInstaller::CLI return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; case Execution::Args::Type::PinInstalled: return { type, "installed"_liv, ArgTypeCategory::None }; + case Execution::Args::Type::PinNote: + return { type, "note"_liv, ArgTypeCategory::None }; // Error command case Execution::Args::Type::ErrorInput: diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index 9d9219d68f..f47e81055d 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -23,6 +23,7 @@ namespace AppInstaller::CLI std::make_unique(FullName()), std::make_unique(FullName()), std::make_unique(FullName()), + std::make_unique(FullName()), }); } @@ -65,6 +66,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Force), Argument{ Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag }, Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::PinNote, Resource::String::PinNoteArgumentDescription, ArgumentType::Standard }, }; } @@ -343,4 +345,36 @@ namespace AppInstaller::CLI Workflow::ReportPins; } } + + std::vector PinShowCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Exact), + }; + } + + Resource::LocString PinShowCommand::ShortDescription() const + { + return { Resource::String::PinShowCommandShortDescription }; + } + + Resource::LocString PinShowCommand::LongDescription() const + { + return { Resource::String::PinShowCommandLongDescription }; + } + + Utility::LocIndView PinShowCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinShowCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::OpenPinningIndex(/* readOnly */ true) << + Workflow::ShowPinDetails; + } } diff --git a/src/AppInstallerCLICore/Commands/PinCommand.h b/src/AppInstallerCLICore/Commands/PinCommand.h index 285e66eaa7..2852f26bc0 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.h +++ b/src/AppInstallerCLICore/Commands/PinCommand.h @@ -87,4 +87,19 @@ namespace AppInstaller::CLI protected: void ExecuteInternal(Execution::Context& context) const override; }; + + struct PinShowCommand final : public Command + { + PinShowCommand(std::string_view parent) : Command("show", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index 675576a9d7..947aa7c6fd 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -122,6 +122,7 @@ namespace AppInstaller::CLI::Execution GatedVersion, // Differs from Version in that this supports wildcards BlockingPin, PinInstalled, + PinNote, // User-provided note to attach to a pin // Error command ErrorInput, diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index fa369b8ae5..a7395f6200 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -530,6 +530,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinCannotOpenIndex); WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinDateAdded); WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist); WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting); WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg); @@ -537,6 +538,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledSource); WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinNote); + WINGET_DEFINE_RESOURCE_STRINGID(PinNoteArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist); WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); @@ -546,6 +549,15 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful); WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelDateAdded); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelId); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelNote); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelSource); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelType); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelVersion); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowNoMatchFound); WINGET_DEFINE_RESOURCE_STRINGID(PinType); WINGET_DEFINE_RESOURCE_STRINGID(PinVersion); WINGET_DEFINE_RESOURCE_STRINGID(PlatformArgumentDescription); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index ace205fc44..91d863d5e9 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -3,7 +3,9 @@ #include "pch.h" #include "Resources.h" #include "PinFlow.h" +#include "ShowFlow.h" #include "TableOutput.h" +#include #include #include #include @@ -197,9 +199,21 @@ namespace AppInstaller::CLI::Workflow if (!pinsToAddOrUpdate.empty()) { - for (const auto& pin : pinsToAddOrUpdate) + std::string dateAdded = Utility::TimePointToString( + std::chrono::system_clock::now(), + Utility::TimeFacet::Year | Utility::TimeFacet::Month | Utility::TimeFacet::Day | + Utility::TimeFacet::Hour | Utility::TimeFacet::Minute | Utility::TimeFacet::Second); + std::optional note; + if (context.Args.Contains(Execution::Args::Type::PinNote)) { + note = std::string{ context.Args.GetArg(Execution::Args::Type::PinNote) }; + } + + for (auto& pin : pinsToAddOrUpdate) + { + pin.SetDateAdded(dateAdded); + pin.SetNote(note); pinningData.AddOrUpdatePin(pin); } @@ -335,4 +349,103 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; } } + + void ShowPinDetails(Execution::Context& context) + { + auto& pinningData = context.Get(); + auto allPins = pinningData.GetAllPins(); + + // Apply filtering based on provided arguments + bool hasId = context.Args.Contains(Execution::Args::Type::Id); + bool hasName = context.Args.Contains(Execution::Args::Type::Name); + bool hasQuery = context.Args.Contains(Execution::Args::Type::Query); + bool exactMatch = context.Args.Contains(Execution::Args::Type::Exact); + + std::vector matchingPins; + for (const auto& pin : allPins) + { + const auto& packageId = pin.GetKey().PackageId; + + if (hasId) + { + std::string_view idArg = context.Args.GetArg(Execution::Args::Type::Id); + bool match = exactMatch + ? Utility::CaseInsensitiveEquals(packageId, idArg) + : Utility::CaseInsensitiveContainsSubstring(packageId, idArg); + if (!match) + { + continue; + } + } + else if (hasName || hasQuery) + { + // Without an open source, we can only match against PackageId + std::string_view queryArg = hasName + ? context.Args.GetArg(Execution::Args::Type::Name) + : context.Args.GetArg(Execution::Args::Type::Query); + bool match = exactMatch + ? Utility::CaseInsensitiveEquals(packageId, queryArg) + : Utility::CaseInsensitiveContainsSubstring(packageId, queryArg); + if (!match) + { + continue; + } + } + + matchingPins.push_back(pin); + } + + if (matchingPins.empty()) + { + context.Reporter.Info() << Resource::String::PinShowNoMatchFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + } + + auto info = context.Reporter.Info(); + bool firstPin = true; + for (const auto& pin : matchingPins) + { + if (!firstPin) + { + info << std::endl; + } + firstPin = false; + + const auto& pinKey = pin.GetKey(); + + // ID + ShowSingleLineField(info, Resource::String::PinShowLabelId, Utility::LocIndView{ pinKey.PackageId }); + + // Source + if (!pinKey.SourceId.empty() && !pinKey.IsForInstalled()) + { + ShowSingleLineField(info, Resource::String::PinShowLabelSource, Utility::LocIndView{ pinKey.SourceId }); + } + + // Type + std::string pinTypeStr{ ToString(pin.GetType()) }; + ShowSingleLineField(info, Resource::String::PinShowLabelType, Utility::LocIndView{ pinTypeStr }); + + // Version (gated version string; empty for pinning/blocking pins) + std::string gatedVersionStr = pin.GetGatedVersion().ToString(); + if (!gatedVersionStr.empty()) + { + ShowSingleLineField(info, Resource::String::PinShowLabelVersion, Utility::LocIndView{ gatedVersionStr }); + } + + // Date Added + const auto& dateAdded = pin.GetDateAdded(); + if (!dateAdded.empty()) + { + ShowSingleLineField(info, Resource::String::PinShowLabelDateAdded, Utility::LocIndView{ dateAdded }); + } + + // Note (only shown if present) + const auto& note = pin.GetNote(); + if (note.has_value() && !note->empty()) + { + ShowSingleLineField(info, Resource::String::PinShowLabelNote, Utility::LocIndView{ *note }); + } + } + } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index 2e0e55a5db..e9ff60faa4 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -54,6 +54,14 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void ReportPins(Execution::Context& context); + // Shows details for a single matching pin (for `winget pin show`). + // Filters the pinning index by query/name/id/exact args and outputs + // detailed field-by-field info (Name, ID, Version, Source, Type, Date Added, Note). + // Required Args: None (at least one of --query/--name/--id expected) + // Inputs: PinningIndex + // Outputs: None + void ShowPinDetails(Execution::Context& context); + // Resets all the existing pins. // Required Args: None // Inputs: None diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 177d2828c0..bb3ea1e83d 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1648,6 +1648,10 @@ Please specify one of them using the --source option to proceed. Pin a specific installed version + + Optional note to store with the pin + Description for the --note argument used with `winget pin add` + Installed Value used in a table to indicate that a package comes from the list of packages installed in the machine @@ -1913,6 +1917,48 @@ Please specify one of them using the --source option to proceed. Pinned version Table header for the version to which a package is pinned; meaning it should not update from that version. + + Date added + Label shown in the pin show output for when the pin was added or last updated. + + + Note + Label shown in the pin show output for the user-provided note stored with the pin. + + + Show details about a pin + + + Show detailed information about a specific pin, including the package ID, version, type, date added, and any note stored with the pin. + + + Date added: + Label shown in the `winget pin show` output for when the pin was added or last updated. + + + Id: + Label shown in the `winget pin show` output for the package identifier. + + + Note: + Label shown in the `winget pin show` output for the user-provided note stored with the pin. + + + Source: + Label shown in the `winget pin show` output for the package source. + + + Pin type: + Label shown in the `winget pin show` output for the type of pin (e.g., Pinning, Gating, Blocking). + + + Pinned version: + Label shown in the `winget pin show` output for the version string of a gating pin. + + + No pin found matching the specified criteria. + Shown when `winget pin show` finds no matching pin. + <See the log file for additional details> The brackets are intended to make the value stand out from other text which it will follow. Any locale appropriate mechanism that achieves this is acceptable. diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index 2039b81a5e..752eb02d44 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -3,6 +3,8 @@ #pragma once #include "winget/Manifest.h" #include "AppInstallerVersions.h" +#include +#include namespace AppInstaller::Pinning { @@ -97,6 +99,11 @@ namespace AppInstaller::Pinning PinType GetType() const { return m_type; } const PinKey& GetKey() const { return m_key; } const Utility::GatedVersion& GetGatedVersion() const { return m_gatedVersion; } + const std::string& GetDateAdded() const { return m_dateAdded; } + const std::optional& GetNote() const { return m_note; } + + void SetDateAdded(std::string dateAdded) { m_dateAdded = std::move(dateAdded); } + void SetNote(std::optional note) { m_note = std::move(note); } bool operator==(const Pin& other) const; bool operator<(const Pin& other) const @@ -114,5 +121,7 @@ namespace AppInstaller::Pinning PinType m_type = PinType::Unknown; PinKey m_key; Utility::GatedVersion m_gatedVersion; + std::string m_dateAdded; + std::optional m_note; }; } diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 53a8260f9c..9affa80d82 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -338,6 +338,8 @@ + + @@ -449,6 +451,10 @@ + + + $(IntDir)Schema\Pinning_1_1\ + diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index 645cfb127d..42b96ebf5b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -4,6 +4,7 @@ #include "PinningIndex.h" #include #include "Schema/Pinning_1_0/PinningIndexInterface.h" +#include "Schema/Pinning_1_1/PinningIndexInterface.h" namespace AppInstaller::Repository::Microsoft { @@ -186,13 +187,24 @@ namespace AppInstaller::Repository::Microsoft std::unique_ptr PinningIndex::CreateIPinningIndex() const { - if (m_version == SQLite::Version{ 1, 0 } || - m_version.MajorVersion == 1 || - m_version.IsLatest()) + // Always return the latest interface; migration will handle version mismatches. + return std::make_unique(); + } + + std::unique_ptr PinningIndex::CreateIPinningIndexForVersion(const SQLite::Version& version) + { + if (version == SQLite::Version{ 1, 0 }) { return std::make_unique(); } + if (version == SQLite::Version{ 1, 1 } || + version.MajorVersion == 1 || + version.IsLatest()) + { + return std::make_unique(); + } + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } @@ -201,7 +213,41 @@ namespace AppInstaller::Repository::Microsoft { AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); m_interface = CreateIPinningIndex(); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); + + if (m_version != m_interface->GetVersion()) + { + if (disposition == SQLiteStorageBase::OpenDisposition::ReadWrite) + { + // Attempt to migrate from the stored version to the current version. + AICLI_LOG(Repo, Info, << "Attempting to migrate Pinning Index from [" << m_version << "] to [" << m_interface->GetVersion() << "]"); + + // Create an interface representing the existing (older) schema so MigrateFrom can inspect it. + std::unique_ptr oldInterface = CreateIPinningIndexForVersion(m_version); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_migrate"); + bool migrated = m_interface->MigrateFrom(m_dbconn, oldInterface.get()); + + if (migrated) + { + m_interface->GetVersion().SetSchemaVersion(m_dbconn); + SetLastWriteTime(); + savepoint.Commit(); + m_version = m_interface->GetVersion(); + AICLI_LOG(Repo, Info, << "Migration successful"); + } + else + { + AICLI_LOG(Repo, Error, << "Migration failed"); + THROW_HR(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX); + } + } + else + { + // Read-only open: use an interface matching the stored schema version to avoid querying missing columns. + AICLI_LOG(Repo, Info, << "Read-only open with older schema [" << m_version << "]; using compatible interface"); + m_interface = CreateIPinningIndexForVersion(m_version); + } + } } PinningIndex::PinningIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index 672f8dba8f..346766fd87 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -70,6 +70,9 @@ namespace AppInstaller::Repository::Microsoft // Creates the IPinningIndex interface object for this version. std::unique_ptr CreateIPinningIndex() const; + // Creates an IPinningIndex interface object for a specific version. + static std::unique_ptr CreateIPinningIndexForVersion(const SQLite::Version& version); + std::unique_ptr m_interface; }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h index 8eba0be3d6..9fde5e84a1 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h @@ -17,6 +17,10 @@ namespace AppInstaller::Repository::Microsoft::Schema // Creates all of the version dependent tables within the database. virtual void CreateTables(SQLite::Connection& connection) = 0; + // Migrates the schema from an older version. + // Returns true if migration succeeded, false if migration is not applicable. + virtual bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) = 0; + // Version 1.0 // Adds a pin to the index. virtual SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h index 7d7a38f0dc..42f744c68d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h @@ -10,6 +10,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // Version 1.0 SQLite::Version GetVersion() const override; void CreateTables(SQLite::Connection& connection) override; + bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) override; private: SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index 2d81684846..9d81c5a087 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -35,6 +35,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 savepoint.Commit(); } + bool PinningIndexInterface::MigrateFrom(SQLite::Connection&, const IPinningIndex*) + { + // Version 1.0 cannot migrate from any prior version. + return false; + } + SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) { auto existingPin = GetExistingPinId(connection, pin.GetKey()); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp new file mode 100644 index 0000000000..aa795aef8e --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -0,0 +1,232 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinTable.h" +#include +#include "Microsoft/Schema/IPinningIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 +{ + namespace + { + std::optional GetPinFromRow( + std::string_view packageId, + std::string_view sourceId, + Pinning::PinType type, + std::string_view version, + std::string_view dateAdded, + std::optional note) + { + std::optional result; + + switch (type) + { + case Pinning::PinType::Blocking: + result = Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); + break; + case Pinning::PinType::Pinning: + result = Pinning::Pin::CreatePinningPin({ packageId, sourceId }); + break; + case Pinning::PinType::Gating: + result = Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ version }); + break; + default: + return {}; + } + + result->SetDateAdded(std::string{ dateAdded }); + result->SetNote(std::move(note)); + + return result; + } + } + + using namespace std::string_view_literals; + static constexpr std::string_view s_PinTable_Table_Name = "pin"sv; + static constexpr std::string_view s_PinTable_PackageId_Column = "package_id"sv; + static constexpr std::string_view s_PinTable_SourceId_Column = "source_id"sv; + static constexpr std::string_view s_PinTable_Type_Column = "type"sv; + static constexpr std::string_view s_PinTable_Version_Column = "version"sv; + static constexpr std::string_view s_PinTable_DateAdded_Column = "date_added"sv; + static constexpr std::string_view s_PinTable_Note_Column = "note"sv; + static constexpr std::string_view s_PinTable_Index = "pin_index"sv; + + std::string_view PinTable::TableName() + { + return s_PinTable_Table_Name; + } + + void PinTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_1"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); + + createTableBuilder.Column(ColumnBuilder(s_PinTable_PackageId_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_SourceId_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Type_Column, Type::Int64).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Version_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_DateAdded_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Note_Column, Type::Text)); + + createTableBuilder.EndColumns(); + createTableBuilder.Execute(connection); + + // Create an index over the pairs package,source + StatementBuilder createIndexBuilder; + createIndexBuilder.CreateUniqueIndex(s_PinTable_Index).On(s_PinTable_Table_Name) + .Columns({ s_PinTable_PackageId_Column, s_PinTable_SourceId_Column }); + createIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void PinTable::MigrateFrom1_0(SQLite::Connection& connection) + { + SQLite::Statement addDateAdded = SQLite::Statement::Create(connection, + "ALTER TABLE pin ADD COLUMN date_added TEXT NOT NULL DEFAULT ''"); + addDateAdded.Execute(); + + SQLite::Statement addNote = SQLite::Statement::Create(connection, + "ALTER TABLE pin ADD COLUMN note TEXT"); + addNote.Execute(); + } + + std::optional PinTable::GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) + .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) + .And(s_PinTable_SourceId_Column).Equals((std::string_view)pinKey.SourceId); + + SQLite::Statement select = builder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + SQLite::Builder::StatementBuilder builder; + const auto& pinKey = pin.GetKey(); + builder.InsertInto(s_PinTable_Table_Name) + .Columns({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column, + s_PinTable_DateAdded_Column, + s_PinTable_Note_Column }) + .Values( + (std::string_view)pinKey.PackageId, + pinKey.SourceId, + pin.GetType(), + pin.GetGatedVersion().ToString(), + (std::string_view)pin.GetDateAdded(), + pin.GetNote()); + + builder.Execute(connection); + return connection.GetLastInsertRowID(); + } + + bool PinTable::UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) + { + SQLite::Builder::StatementBuilder builder; + const auto& pinKey = pin.GetKey(); + builder.Update(s_PinTable_Table_Name).Set() + .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) + .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) + .Column(s_PinTable_Type_Column).Equals(pin.GetType()) + .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) + .Column(s_PinTable_DateAdded_Column).Equals((std::string_view)pin.GetDateAdded()) + .Column(s_PinTable_Note_Column).Equals(pin.GetNote()) + .Where(SQLite::RowIDName).Equals(pinId); + + builder.Execute(connection); + return connection.GetChanges() != 0; + } + + void PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); + builder.Execute(connection); + } + + std::optional PinTable::GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column, + s_PinTable_DateAdded_Column, + s_PinTable_Note_Column }) + .From(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); + + SQLite::Statement select = builder.Prepare(connection); + + if (!select.Step()) + { + return {}; + } + + auto [packageId, sourceId, pinType, gatedVersion, dateAdded, note] = + select.GetRow>(); + return GetPinFromRow(packageId, sourceId, pinType, gatedVersion, dateAdded, std::move(note)); + } + + std::vector PinTable::GetAllPins(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column, + s_PinTable_DateAdded_Column, + s_PinTable_Note_Column }) + .From(s_PinTable_Table_Name); + + SQLite::Statement select = builder.Prepare(connection); + + std::vector pins; + while (select.Step()) + { + auto [packageId, sourceId, pinType, gatedVersion, dateAdded, note] = + select.GetRow>(); + auto pin = GetPinFromRow(packageId, sourceId, pinType, gatedVersion, dateAdded, std::move(note)); + if (pin) + { + pins.push_back(std::move(pin.value())); + } + } + + return pins; + } + + bool PinTable::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PinTable_Table_Name); + + if (!sourceId.empty()) + { + builder.Where(s_PinTable_SourceId_Column).Equals(sourceId); + } + + builder.Execute(connection); + + return connection.GetChanges() != 0; + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h new file mode 100644 index 0000000000..b36c954bfd --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "Microsoft/Schema/IPinningIndex.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 +{ + struct PinTable + { + // Get the table name. + static std::string_view TableName(); + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + + // Migrates an existing v1.0 pin table by adding the date_added and note columns. + static void MigrateFrom1_0(SQLite::Connection& connection); + + // Gets the row ID for the pin, if it exists. + static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); + + // Adds a new pin. Returns the row ID of the added pin. + static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); + + // Updates an existing pin. + // Returns a value indicating whether there were any changes. + static bool UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); + + // Removes a pin given its row ID. + static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); + + // Gets a pin by its row ID if it exists. + // Used for testing + static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); + + // Gets all the currently existing pins. + static std::vector GetAllPins(SQLite::Connection& connection); + + // Resets all pins from a given source, or from all sources if none is specified. + // Returns a value indicating whether there were any changes. + static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h new file mode 100644 index 0000000000..5e6e58c441 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/IPinningIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 +{ + struct PinningIndexInterface : public IPinningIndex + { + // Version 1.1 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection) override; + bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) override; + + private: + SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; + std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; + SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; + std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; + std::vector GetAllPins(SQLite::Connection& connection) override; + bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp new file mode 100644 index 0000000000..0d65a8d3c0 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h" +#include "Microsoft/Schema/Pinning_1_0/PinTable.h" +#include "Microsoft/Schema/Pinning_1_1/PinTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 +{ + namespace + { + std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto result = PinTable::GetIdByPinKey(connection, pinKey); + + if (!result) + { + AICLI_LOG(Repo, Verbose, << "Did not find pin " << pinKey.ToString()); + } + + return result; + } + } + + // Version 1.1 + SQLite::Version PinningIndexInterface::GetVersion() const + { + return { 1, 1 }; + } + + void PinningIndexInterface::CreateTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_1"); + Pinning_V1_1::PinTable::Create(connection); + savepoint.Commit(); + } + + bool PinningIndexInterface::MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) + { + if (!current || current->GetVersion() != SQLite::Version{ 1, 0 }) + { + return false; + } + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "migratepintable_v1_0_to_v1_1"); + Pinning_V1_1::PinTable::MigrateFrom1_0(connection); + savepoint.Commit(); + + return true; + } + + SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + auto existingPin = GetExistingPinId(connection, pin.GetKey()); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_1"); + SQLite::rowid_t pinId = PinTable::AddPin(connection, pin); + + savepoint.Commit(); + return pinId; + } + + std::pair PinningIndexInterface::UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + auto existingPinId = GetExistingPinId(connection, pin.GetKey()); + + // If the pin doesn't exist, fail the update + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_1"); + bool status = PinTable::UpdatePinById(connection, existingPinId.value(), pin); + + savepoint.Commit(); + return { status, existingPinId.value() }; + } + + SQLite::rowid_t PinningIndexInterface::RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto existingPinId = GetExistingPinId(connection, pinKey); + + // If the pin doesn't exist, fail the remove + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_1"); + PinTable::RemovePinById(connection, existingPinId.value()); + + savepoint.Commit(); + return existingPinId.value(); + } + + std::optional PinningIndexInterface::GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto existingPinId = GetExistingPinId(connection, pinKey); + + if (!existingPinId) + { + return {}; + } + + return PinTable::GetPinById(connection, existingPinId.value()); + } + + std::vector PinningIndexInterface::GetAllPins(SQLite::Connection& connection) + { + return PinTable::GetAllPins(connection); + } + + bool PinningIndexInterface::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetpins_v1_1"); + bool result = PinTable::ResetAllPins(connection, sourceId); + savepoint.Commit(); + + return result; + } +} From 697ca1dc6553cdfc0e62cf66ea257b693cacf16b Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 10:40:46 -0500 Subject: [PATCH 02/60] Add tests --- src/AppInstallerCLITests/PinFlow.cpp | 219 +++++++++++++++++- src/AppInstallerCLITests/PinningIndex.cpp | 171 ++++++++++++++ .../Microsoft/PinningIndex.cpp | 2 +- .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 16 +- 4 files changed, 403 insertions(+), 5 deletions(-) diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index ade8c5b252..bf7687191c 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -198,4 +198,221 @@ TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") INFO(pinResetOutput.str()); REQUIRE(pinResetOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); -} \ No newline at end of file +} + +TEST_CASE("PinFlow_Add_SetsDateAdded", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); + addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + addContext.Args.AddArg(Execution::Args::Type::BlockingPin); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE_FALSE(pins[0].GetDateAdded().empty()); +} + +TEST_CASE("PinFlow_Add_WithNote", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); + addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + addContext.Args.AddArg(Execution::Args::Type::PinNote, "my test note"sv); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetNote().has_value()); + REQUIRE(pins[0].GetNote().value() == "my test note"); +} + +TEST_CASE("PinFlow_Add_WithoutNote", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); + addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE_FALSE(pins[0].GetNote().has_value()); +} + +// Helper: Creates a v1.1 pinning index at the given path and adds the provided pins directly. +// Each pin should already have date_added and note set as desired. +namespace +{ + void PopulatePinIndexForShow(const std::filesystem::path& indexPath, const std::vector& pins) + { + PinningIndex index = PinningIndex::CreateNew(indexPath.u8string(), AppInstaller::SQLite::Version::Latest()); + for (const auto& pin : pins) + { + index.AddPin(pin); + } + } +} + +TEST_CASE("PinFlow_Show_NoMatch", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin existingPin = Pin::CreateBlockingPin({ "SomePackage.Id", "sourceId" }); + existingPin.SetDateAdded("2026-01-15 10:00:00"); + PopulatePinIndexForShow(indexFile.GetPath(), { existingPin }); + + std::ostringstream showOutput; + TestContext showContext{ showOutput, std::cin }; + showContext.Args.AddArg(Execution::Args::Type::Query, "ThisQueryMatchesNothing"sv); + + PinShowCommand pinShow({}); + pinShow.Execute(showContext); + INFO(showOutput.str()); + + REQUIRE_TERMINATED_WITH(showContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::PinShowNoMatchFound)) != std::string::npos); +} + +TEST_CASE("PinFlow_Show_MatchById", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "MyApp.Package", "sourceId" }); + pin.SetDateAdded("2026-06-01 09:00:00"); + pin.SetNote(std::string{ "keep this one" }); + PopulatePinIndexForShow(indexFile.GetPath(), { pin }); + + std::ostringstream showOutput; + TestContext showContext{ showOutput, std::cin }; + showContext.Args.AddArg(Execution::Args::Type::Id, "MyApp.Package"sv); + + PinShowCommand pinShow({}); + pinShow.Execute(showContext); + INFO(showOutput.str()); + + REQUIRE_FALSE(showContext.IsTerminated()); + REQUIRE(showOutput.str().find("MyApp.Package") != std::string::npos); + REQUIRE(showOutput.str().find("Blocking") != std::string::npos); + REQUIRE(showOutput.str().find("2026-06-01 09:00:00") != std::string::npos); + REQUIRE(showOutput.str().find("keep this one") != std::string::npos); +} + +TEST_CASE("PinFlow_Show_MatchByQuery", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreatePinningPin({ "Contoso.AppOne", "sourceId" }); + pin.SetDateAdded("2026-03-10 12:00:00"); + PopulatePinIndexForShow(indexFile.GetPath(), { pin }); + + std::ostringstream showOutput; + TestContext showContext{ showOutput, std::cin }; + // Partial, case-insensitive match on the package ID + showContext.Args.AddArg(Execution::Args::Type::Query, "appone"sv); + + PinShowCommand pinShow({}); + pinShow.Execute(showContext); + INFO(showOutput.str()); + + REQUIRE_FALSE(showContext.IsTerminated()); + REQUIRE(showOutput.str().find("Contoso.AppOne") != std::string::npos); +} + +TEST_CASE("PinFlow_Show_ExactMatch", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + // Two pins sharing a prefix + Pin pinA = Pin::CreateBlockingPin({ "Vendor.App", "src" }); + pinA.SetDateAdded("2026-01-01 00:00:00"); + + Pin pinB = Pin::CreateBlockingPin({ "Vendor.AppExtra", "src" }); + pinB.SetDateAdded("2026-01-01 00:00:00"); + + PopulatePinIndexForShow(indexFile.GetPath(), { pinA, pinB }); + + std::ostringstream showOutput; + TestContext showContext{ showOutput, std::cin }; + showContext.Args.AddArg(Execution::Args::Type::Id, "Vendor.App"sv); + showContext.Args.AddArg(Execution::Args::Type::Exact); + + PinShowCommand pinShow({}); + pinShow.Execute(showContext); + INFO(showOutput.str()); + + REQUIRE_FALSE(showContext.IsTerminated()); + // Only the exact-match pin should appear + REQUIRE(showOutput.str().find("Vendor.App") != std::string::npos); + // The inexact match should NOT appear + REQUIRE(showOutput.str().find("Vendor.AppExtra") == std::string::npos); +} + +TEST_CASE("PinFlow_Show_NoNote_DoesNotShowNoteLabel", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreatePinningPin({ "NoNote.Package", "src" }); + pin.SetDateAdded("2026-05-01 08:00:00"); + // note intentionally not set + PopulatePinIndexForShow(indexFile.GetPath(), { pin }); + + std::ostringstream showOutput; + TestContext showContext{ showOutput, std::cin }; + showContext.Args.AddArg(Execution::Args::Type::Query, "NoNote.Package"sv); + + PinShowCommand pinShow({}); + pinShow.Execute(showContext); + INFO(showOutput.str()); + + REQUIRE_FALSE(showContext.IsTerminated()); + REQUIRE(showOutput.str().find("NoNote.Package") != std::string::npos); + REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::PinShowLabelNote)) == std::string::npos); +} + +TEST_CASE("PinFlow_Show_EmptyIndex_NoMatch", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + // Create an empty index (no pins) + { PinningIndex::CreateNew(indexFile.GetPath().u8string(), AppInstaller::SQLite::Version::Latest()); } + + std::ostringstream showOutput; + TestContext showContext{ showOutput, std::cin }; + showContext.Args.AddArg(Execution::Args::Type::Query, "AnyQuery"sv); + + PinShowCommand pinShow({}); + pinShow.Execute(showContext); + INFO(showOutput.str()); + + REQUIRE_TERMINATED_WITH(showContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); +} diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index 0682dc31f6..00ea3945d2 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include using namespace std::string_literals; @@ -163,4 +164,174 @@ TEST_CASE("PinningIndex_AddDuplicatePin", "[pinningIndex]") index.AddPin(pin); REQUIRE_THROWS(index.AddPin(pin), ERROR_ALREADY_EXISTS); +} + +TEST_CASE("PinningIndexCreateLatest_Is_V1_1", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + REQUIRE(index.GetVersion() == Version{ 1, 1 }); +} + +TEST_CASE("PinningIndex_V1_1_AddPin_WithDateAndNote", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); + pin.SetDateAdded("2026-01-15 10:30:00"); + pin.SetNote(std::string{ "test note" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + index.AddPin(pin); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + + auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex->GetType() == PinType::Blocking); + REQUIRE(pinFromIndex->GetKey().PackageId == "pkgId"); + REQUIRE(pinFromIndex->GetDateAdded() == "2026-01-15 10:30:00"); + REQUIRE(pinFromIndex->GetNote().has_value()); + REQUIRE(pinFromIndex->GetNote().value() == "test note"); + } +} + +TEST_CASE("PinningIndex_V1_1_AddPin_WithoutNote", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreatePinningPin({ "pkgId", "sourceId" }); + pin.SetDateAdded("2026-01-15 10:30:00"); + // note intentionally left unset + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + index.AddPin(pin); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + + auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex->GetDateAdded() == "2026-01-15 10:30:00"); + REQUIRE_FALSE(pinFromIndex->GetNote().has_value()); + } +} + +TEST_CASE("PinningIndex_V1_1_AddUpdateRemove", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "pkgId", "srcId" }); + pin.SetDateAdded("2026-01-15 10:00:00"); + pin.SetNote(std::string{ "original note" }); + + Pin updatedPin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); + updatedPin.SetDateAdded("2026-01-15 11:00:00"); + updatedPin.SetNote(std::string{ "updated note" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + index.AddPin(pin); + REQUIRE(index.UpdatePin(updatedPin)); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + + auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex->GetType() == PinType::Gating); + REQUIRE(pinFromIndex->GetDateAdded() == "2026-01-15 11:00:00"); + REQUIRE(pinFromIndex->GetNote().has_value()); + REQUIRE(pinFromIndex->GetNote().value() == "updated note"); + } + + { + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePin(updatedPin.GetKey()); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Pinning_V1_1::PinTable::GetAllPins(connection).empty()); + REQUIRE(!Pinning_V1_1::PinTable::GetPinById(connection, 1)); + } +} + +TEST_CASE("PinningIndex_MigrateFrom_1_0_to_1_1", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin1 = Pin::CreateBlockingPin({ "pkg1", "src1" }); + Pin pin2 = Pin::CreateGatingPin({ "pkg2", "src2" }, { "2.*"sv }); + + // Create a v1.0 index and add two pins (no date_added or note columns yet) + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin1); + index.AddPin(pin2); + REQUIRE(index.GetVersion() == Version{ 1, 0 }); + } + + // Re-open with ReadWrite: should trigger automatic migration to v1.1 + { + PinningIndex migratedIndex = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + REQUIRE(migratedIndex.GetVersion() == Version{ 1, 1 }); + + auto pins = migratedIndex.GetAllPins(); + REQUIRE(pins.size() == 2); + + for (const auto& pin : pins) + { + // Migration adds columns with DEFAULT '' and NULL respectively + REQUIRE(pin.GetDateAdded() == ""); + REQUIRE_FALSE(pin.GetNote().has_value()); + } + + // Verify that the original pin data is still intact + auto foundPin1 = migratedIndex.GetPin(pin1.GetKey()); + REQUIRE(foundPin1.has_value()); + REQUIRE(foundPin1->GetType() == PinType::Blocking); + + auto foundPin2 = migratedIndex.GetPin(pin2.GetKey()); + REQUIRE(foundPin2.has_value()); + REQUIRE(foundPin2->GetType() == PinType::Gating); + REQUIRE(foundPin2->GetGatedVersion().ToString() == pin2.GetGatedVersion().ToString()); + } +} + +TEST_CASE("PinningIndex_MigrateFrom_1_0_to_1_1_ReadOnly_Uses_OldInterface", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreatePinningPin({ "pkgId", "srcId" }); + + // Create a v1.0 index and add a pin + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + } + + // Re-open with Read (read-only): should NOT migrate, should use v1.0 interface + { + PinningIndex readOnlyIndex = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + REQUIRE(readOnlyIndex.GetVersion() == Version{ 1, 0 }); + + auto pins = readOnlyIndex.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Pinning); + REQUIRE(pins[0].GetKey() == pin.GetKey()); + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index 42b96ebf5b..c0b8a8150b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -252,7 +252,7 @@ namespace AppInstaller::Repository::Microsoft PinningIndex::PinningIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) { - m_interface = CreateIPinningIndex(); + m_interface = CreateIPinningIndexForVersion(version); m_version = m_interface->GetVersion(); } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp index aa795aef8e..88f819d9a7 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -147,10 +147,20 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) .Column(s_PinTable_Type_Column).Equals(pin.GetType()) .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) - .Column(s_PinTable_DateAdded_Column).Equals((std::string_view)pin.GetDateAdded()) - .Column(s_PinTable_Note_Column).Equals(pin.GetNote()) - .Where(SQLite::RowIDName).Equals(pinId); + .Column(s_PinTable_DateAdded_Column).Equals((std::string_view)pin.GetDateAdded()); + // Use Unbound (= ?) for null note so SQLite stores NULL via = NULL, not the invalid SET syntax IS NULL. + const auto& note = pin.GetNote(); + if (note.has_value()) + { + builder.Column(s_PinTable_Note_Column).Equals(note.value()); + } + else + { + builder.Column(s_PinTable_Note_Column).Equals(SQLite::Builder::Unbound); + } + + builder.Where(SQLite::RowIDName).Equals(pinId); builder.Execute(connection); return connection.GetChanges() != 0; } From b6502ef3b96512ed82e8d7859c8db06ad324d730 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 11:10:02 -0500 Subject: [PATCH 03/60] Add pinning to COM APIs --- .../Package.appxmanifest | 2 + .../ComClsids.cpp | 133 ++++----- .../Converters.cpp | 23 ++ .../Converters.h | 1 + .../Microsoft.Management.Deployment.vcxproj | 6 + .../PackageManager.cpp | 270 ++++++++++++++++++ .../PackageManager.h | 6 + .../PackageManager.idl | 126 +++++++- .../PackagePin.cpp | 75 +++++ .../PackagePin.h | 36 +++ .../PinPackageOptions.cpp | 67 +++++ .../PinPackageOptions.h | 50 ++++ .../PinPackageResult.cpp | 27 ++ .../PinPackageResult.h | 27 ++ .../Public/ComClsids.h | 3 + 15 files changed, 787 insertions(+), 65 deletions(-) create mode 100644 src/Microsoft.Management.Deployment/PackagePin.cpp create mode 100644 src/Microsoft.Management.Deployment/PackagePin.h create mode 100644 src/Microsoft.Management.Deployment/PinPackageOptions.cpp create mode 100644 src/Microsoft.Management.Deployment/PinPackageOptions.h create mode 100644 src/Microsoft.Management.Deployment/PinPackageResult.cpp create mode 100644 src/Microsoft.Management.Deployment/PinPackageResult.h diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest index 8559da43ae..0fac2aad64 100644 --- a/src/AppInstallerCLIPackage/Package.appxmanifest +++ b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -86,6 +86,8 @@ + + diff --git a/src/Microsoft.Management.Deployment/ComClsids.cpp b/src/Microsoft.Management.Deployment/ComClsids.cpp index 24c64ec942..daf023da93 100644 --- a/src/Microsoft.Management.Deployment/ComClsids.cpp +++ b/src/Microsoft.Management.Deployment/ComClsids.cpp @@ -1,64 +1,65 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/ComClsids.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 ) -// 4467 Allow use of uuid attribute for com object creation. -#include "PackageManager.h" -#include "FindPackagesOptions.h" -#include "CreateCompositePackageCatalogOptions.h" -#include "InstallOptions.h" -#include "UninstallOptions.h" -#include "PackageMatchFilter.h" -#include "PackageManagerSettings.h" -#include "DownloadOptions.h" +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/ComClsids.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 ) +// 4467 Allow use of uuid attribute for com object creation. +#include "PackageManager.h" +#include "FindPackagesOptions.h" +#include "CreateCompositePackageCatalogOptions.h" +#include "InstallOptions.h" +#include "UninstallOptions.h" +#include "PackageMatchFilter.h" +#include "PackageManagerSettings.h" +#include "DownloadOptions.h" #include "AuthenticationArguments.h" #include "RepairOptions.h" #include "AddPackageCatalogOptions.h" -#include "RemovePackageCatalogOptions.h" -#include "EditPackageCatalogOptions.h" -#pragma warning( pop ) - -namespace winrt::Microsoft::Management::Deployment -{ - CLSID GetRedirectedClsidFromInProcClsid(REFCLSID clsid) - { - if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageManager)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageManager); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_FindPackagesOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::FindPackagesOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_CreateCompositePackageCatalogOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::CreateCompositePackageCatalogOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_InstallOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::InstallOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_UninstallOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::UninstallOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_DownloadOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::DownloadOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageMatchFilter)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageMatchFilter); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_AuthenticationArguments)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::AuthenticationArguments); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageManagerSettings)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageManagerSettings); +#include "RemovePackageCatalogOptions.h" +#include "EditPackageCatalogOptions.h" +#include "PinPackageOptions.h" +#pragma warning( pop ) + +namespace winrt::Microsoft::Management::Deployment +{ + CLSID GetRedirectedClsidFromInProcClsid(REFCLSID clsid) + { + if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageManager)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageManager); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_FindPackagesOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::FindPackagesOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_CreateCompositePackageCatalogOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::CreateCompositePackageCatalogOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_InstallOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::InstallOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_UninstallOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::UninstallOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_DownloadOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::DownloadOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageMatchFilter)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageMatchFilter); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_AuthenticationArguments)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::AuthenticationArguments); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageManagerSettings)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageManagerSettings); } else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RepairOptions)) { @@ -75,10 +76,14 @@ namespace winrt::Microsoft::Management::Deployment else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions)) { return __uuidof(winrt::Microsoft::Management::Deployment::implementation::EditPackageCatalogOptions); - } - else - { - return CLSID_NULL; - } - } + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PinPackageOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PinPackageOptions); + } + else + { + return CLSID_NULL; + } + } } diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp index 3c3b4f238b..bbd82c6e2e 100644 --- a/src/Microsoft.Management.Deployment/Converters.cpp +++ b/src/Microsoft.Management.Deployment/Converters.cpp @@ -543,6 +543,29 @@ namespace winrt::Microsoft::Management::Deployment::implementation } } + PinResultStatus GetPinOperationStatus(winrt::hresult hresult) + { + switch (hresult) + { + case S_OK: + return PinResultStatus::Ok; + case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED: + return PinResultStatus::BlockedByPolicy; + case APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS: + return PinResultStatus::PackagePinAlreadyExists; + case APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST: + return PinResultStatus::PackagePinNotFound; + case E_INVALIDARG: + case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: + return PinResultStatus::InvalidOptions; + default: + return PinResultStatus::InternalError; + } + } + ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(WindowsPlatform value) { switch (value) diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index 643658705c..888bc72ff6 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -34,6 +34,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult); winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult); winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult); + winrt::Microsoft::Management::Deployment::PinResultStatus GetPinOperationStatus(winrt::hresult hresult); ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(winrt::Microsoft::Management::Deployment::WindowsPlatform value); #define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_, _downloadResultStatus_, _repairResultStatus_) \ diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj index 86dbab6088..89f91e9222 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj @@ -171,6 +171,9 @@ + + + @@ -223,6 +226,9 @@ + + + diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 39d53771b5..5516ebefd6 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -20,6 +20,9 @@ #include // 4467 Allow use of uuid attribute for com object creation. #include "PackageManager.h" +#include "PackagePin.h" +#include "PinPackageOptions.h" +#include "PinPackageResult.h" #pragma warning( pop ) #include "PackageManager.g.cpp" #include "CatalogPackage.h" @@ -40,6 +43,10 @@ #include "AppInstallerRuntime.h" #include #include +#include +#include +#include +#include using namespace std::literals::chrono_literals; using namespace ::AppInstaller::CLI; @@ -1488,5 +1495,268 @@ namespace winrt::Microsoft::Management::Deployment::implementation return GetEditPackageCatalogResult(terminationHR); } + namespace + { + // Builds PackagePin WinRT objects from a vector of internal Pin objects. + winrt::Windows::Foundation::Collections::IVectorView + MakePackagePinVectorView(const std::vector<::AppInstaller::Pinning::Pin>& pins) + { + auto result = winrt::single_threaded_vector(); + for (const auto& pin : pins) + { + auto packagePin = winrt::make_self>(); + packagePin->Initialize(pin); + result.Append(*packagePin); + } + return result.GetView(); + } + + // Collects PinKeys for all available sources of a package, and optionally for its installed identity. + std::vector<::AppInstaller::Pinning::PinKey> GetPinKeysForCatalogPackage( + winrt::Microsoft::Management::Deployment::CatalogPackage const& package, + bool includeInstalled) + { + std::vector<::AppInstaller::Pinning::PinKey> pinKeys; + + // Keys for each available source version + for (auto const& versionId : package.AvailableVersions()) + { + auto versionInfo = package.GetPackageVersionInfo(versionId); + if (versionInfo) + { + std::string packageId = winrt::to_string(package.Id()); + std::string sourceId = winrt::to_string(versionInfo.PackageCatalog().Info().Id()); + if (!packageId.empty() && !sourceId.empty()) + { + ::AppInstaller::Pinning::PinKey key{ packageId, sourceId }; + // Avoid duplicates (same packageId+sourceId may appear for multiple versions) + if (std::find(pinKeys.begin(), pinKeys.end(), key) == pinKeys.end()) + { + pinKeys.push_back(std::move(key)); + } + } + } + } + + // Key for the installed package identity (ProductCode / PackageFamilyName) + if (includeInstalled) + { + auto installedVersion = package.InstalledVersion(); + if (installedVersion) + { + // Prefer PackageFamilyName (MSIX), fall back to ProductCode (MSI/EXE) + auto pfns = installedVersion.PackageFamilyNames(); + if (pfns && pfns.Size() > 0) + { + for (auto const& pfn : pfns) + { + pinKeys.push_back(::AppInstaller::Pinning::PinKey::GetPinKeyForInstalled(winrt::to_string(pfn))); + } + } + else + { + auto productCodes = installedVersion.ProductCodes(); + if (productCodes) + { + for (auto const& productCode : productCodes) + { + pinKeys.push_back(::AppInstaller::Pinning::PinKey::GetPinKeyForInstalled(winrt::to_string(productCode))); + } + } + } + } + } + + return pinKeys; + } + + winrt::Microsoft::Management::Deployment::PinPackageResult MakePinPackageResult(HRESULT hr) + { + auto result = winrt::make_self>(); + result->Initialize(GetPinOperationStatus(hr), hr); + return *result; + } + + // Converts PinPackageOptions.PinType to the internal pin representation. + ::AppInstaller::Pinning::Pin CreatePinFromOptions( + const ::AppInstaller::Pinning::PinKey& pinKey, + winrt::Microsoft::Management::Deployment::PinPackageOptions const& options) + { + switch (options.PinType()) + { + case winrt::Microsoft::Management::Deployment::PackagePinType::Blocking: + return ::AppInstaller::Pinning::Pin::CreateBlockingPin(pinKey); + case winrt::Microsoft::Management::Deployment::PackagePinType::Gating: + return ::AppInstaller::Pinning::Pin::CreateGatingPin( + pinKey, + ::AppInstaller::Utility::GatedVersion{ winrt::to_string(options.GatedVersion()) }); + default: + return ::AppInstaller::Pinning::Pin::CreatePinningPin(pinKey); + } + } + } + + winrt::Windows::Foundation::Collections::IVectorView + PackageManager::GetAllPins() + { + LogStartupIfApplicable(); + + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadOnly }; + return MakePackagePinVectorView(pinningData.GetAllPins()); + } + + winrt::Windows::Foundation::Collections::IVectorView + PackageManager::GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package) + { + LogStartupIfApplicable(); + + THROW_HR_IF_NULL(E_POINTER, package); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + + auto pinKeys = GetPinKeysForCatalogPackage(package, /* includeInstalled */ true); + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadOnly }; + + std::vector<::AppInstaller::Pinning::Pin> pins; + for (const auto& pinKey : pinKeys) + { + auto pin = pinningData.GetPin(pinKey); + if (pin) + { + pins.push_back(std::move(*pin)); + } + } + + return MakePackagePinVectorView(pins); + } + + winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::PinPackage( + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::PinPackageOptions options) + { + LogStartupIfApplicable(); + + HRESULT terminationHR = S_OK; + try + { + THROW_HR_IF_NULL(E_POINTER, package); + THROW_HR_IF_NULL(E_POINTER, options); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + + // Gating pins require a non-empty version range. + if (options.PinType() == winrt::Microsoft::Management::Deployment::PackagePinType::Gating && + options.GatedVersion().empty()) + { + THROW_HR(E_INVALIDARG); + } + + auto pinKeys = GetPinKeysForCatalogPackage(package, options.PinInstalledPackage()); + THROW_HR_IF(E_INVALIDARG, pinKeys.empty()); + + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; + + std::string dateAdded = ::AppInstaller::Utility::TimePointToString( + std::chrono::system_clock::now(), + ::AppInstaller::Utility::TimeFacet::Year | ::AppInstaller::Utility::TimeFacet::Month | + ::AppInstaller::Utility::TimeFacet::Day | ::AppInstaller::Utility::TimeFacet::Hour | + ::AppInstaller::Utility::TimeFacet::Minute | ::AppInstaller::Utility::TimeFacet::Second); + + std::optional note; + if (!options.Note().empty()) + { + note = winrt::to_string(options.Note()); + } + + for (const auto& pinKey : pinKeys) + { + auto newPin = CreatePinFromOptions(pinKey, options); + + auto existingPin = pinningData.GetPin(pinKey); + if (existingPin && !(*existingPin == newPin)) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS, !options.Force()); + } + + newPin.SetDateAdded(dateAdded); + newPin.SetNote(note); + pinningData.AddOrUpdatePin(newPin); + } + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + return MakePinPackageResult(terminationHR); + } + + winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::UnpinPackage( + winrt::Microsoft::Management::Deployment::CatalogPackage package) + { + LogStartupIfApplicable(); + + HRESULT terminationHR = S_OK; + try + { + THROW_HR_IF_NULL(E_POINTER, package); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + + auto pinKeys = GetPinKeysForCatalogPackage(package, /* includeInstalled */ true); + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; + + bool anyRemoved = false; + for (const auto& pinKey : pinKeys) + { + auto existingPin = pinningData.GetPin(pinKey); + if (existingPin) + { + pinningData.RemovePin(pinKey); + anyRemoved = true; + } + } + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST, !anyRemoved); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + return MakePinPackageResult(terminationHR); + } + + winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::ResetAllPins(winrt::hstring const& sourceName) + { + LogStartupIfApplicable(); + + HRESULT terminationHR = S_OK; + try + { + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + + std::string sourceId; + if (!sourceName.empty()) + { + auto matchingSource = GetMatchingSource(winrt::to_string(sourceName)); + if (matchingSource.has_value()) + { + sourceId = matchingSource->Identifier; + } + } + + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; + pinningData.ResetAllPins(sourceId); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + return MakePinPackageResult(terminationHR); + } + CoCreatableMicrosoftManagementDeploymentClass(PackageManager); } diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h index c973df2dbe..d4c2ed5397 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.h +++ b/src/Microsoft.Management.Deployment/PackageManager.h @@ -54,6 +54,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::hstring Version() const; // Contract 28.0 winrt::Microsoft::Management::Deployment::EditPackageCatalogResult EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options); + // Contract 30.0 + winrt::Windows::Foundation::Collections::IVectorView GetAllPins(); + winrt::Windows::Foundation::Collections::IVectorView GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package); + winrt::Microsoft::Management::Deployment::PinPackageResult PinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PinPackageOptions options); + winrt::Microsoft::Management::Deployment::PinPackageResult UnpinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package); + winrt::Microsoft::Management::Deployment::PinPackageResult ResetAllPins(winrt::hstring const& sourceName); }; #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 25567032f2..6a5d6096ae 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment { - [contractversion(29)] // For version 1.29 + [contractversion(30)] // For version 1.30 apicontract WindowsPackageManagerContract{}; /// State of the install @@ -1679,8 +1679,130 @@ namespace Microsoft.Management.Deployment /// Edit an existing Windows Package Catalog. EditPackageCatalogResult EditPackageCatalog(EditPackageCatalogOptions options); } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + { + /// Get all pins across all sources. + Windows.Foundation.Collections.IVectorView GetAllPins(); + + /// Get the pins associated with the specified package. + /// Returns pins for all sources the package is available from, and for the installed + /// package identity if InstalledVersion is present. + Windows.Foundation.Collections.IVectorView GetPins(CatalogPackage package); + + /// Add or update a pin for the specified package. + /// Requires Force = true to overwrite an existing pin of a different type. + PinPackageResult PinPackage(CatalogPackage package, PinPackageOptions options); + + /// Remove all pins associated with the specified package. + /// Returns PackagePinNotFound if no pin exists for the package. + PinPackageResult UnpinPackage(CatalogPackage package); + + /// Reset (remove) all pins. If sourceName is non-empty, only pins from that + /// catalog source are removed; otherwise all pins are removed. + PinPackageResult ResetAllPins(String sourceName); + } } + /// IMPLEMENTATION NOTE: Pinning::PinType from AppInstaller::Pinning + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + enum PackagePinType + { + /// Unknown pin type or not pinned. + Unknown, + /// Pinned by the manifest using the RequiresExplicitUpgrade field. + /// Behaves the same as Pinning. + PinnedByManifest, + /// The package is excluded from upgrade --all, unless --include-pinned is added. + /// upgrade is not blocked. + Pinning, + /// The package is pinned to a specific version range. + Gating, + /// The package is blocked from upgrade --all and upgrade . + /// User must unpin to allow update. + Blocking, + }; + + /// IMPLEMENTATION NOTE: Pinning::Pin from AppInstaller::Pinning + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + runtimeclass PackagePin + { + /// The package ID that the pin applies to (for available-package pins) or the + /// ProductCode/PackageFamilyName (for installed-package pins). + String PackageId { get; }; + + /// The source identifier the pin applies to. Empty for installed-package pins. + String SourceId { get; }; + + /// The type of the pin. + PackagePinType Type { get; }; + + /// The gated version range. Only meaningful when Type is Gating; empty otherwise. + String GatedVersion { get; }; + + /// The UTC date/time when the pin was added (ISO 8601 format). + String DateAdded { get; }; + + /// Optional note associated with the pin. May be empty. + String Note { get; }; + + /// True if this pin is keyed on the installed package identity + /// (ProductCode or PackageFamilyName) rather than available package ID + source. + Boolean IsForInstalledPackage { get; }; + }; + + /// Options for adding or updating a package pin. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + runtimeclass PinPackageOptions + { + PinPackageOptions(); + + /// The type of pin to create. Required. + PackagePinType PinType; + + /// The gated version range. Required when PinType is Gating; ignored otherwise. + /// SAMPLE VALUE: "1.0.*" or "<2.0" + String GatedVersion; + + /// When true, the pin is keyed on the installed package identity (ProductCode or + /// PackageFamilyName) instead of the available package ID and source. + /// Mirrors the winget pin add --installed flag. Default is false. + Boolean PinInstalledPackage; + + /// When true, an existing pin with a different type will be overwritten. + /// When false and a pin already exists with a different type, the operation returns + /// PinResultStatus::PackagePinAlreadyExists. Default is false. + Boolean Force; + + /// Optional note to store with the pin. + String Note; + }; + + /// Status of a pin operation. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + enum PinResultStatus + { + Ok, + BlockedByPolicy, + InternalError, + InvalidOptions, + /// Returned by UnpinPackage when no pin exists for the package. + PackagePinNotFound, + /// Returned by PinPackage when a pin with a different type already exists + /// and Force is false. + PackagePinAlreadyExists, + }; + + /// Result of a pin operation. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + runtimeclass PinPackageResult + { + PinResultStatus Status { get; }; + + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + }; + /// Global settings for PackageManager operations. /// This settings should be invoked prior to invocation of PackageManager class. /// This settings is only exposed in in-proc Com invocation. @@ -1789,5 +1911,7 @@ namespace Microsoft.Management.Deployment interface Windows.Foundation.Collections.IVectorView; interface Windows.Foundation.Collections.IVector; interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; } } diff --git a/src/Microsoft.Management.Deployment/PackagePin.cpp b/src/Microsoft.Management.Deployment/PackagePin.cpp new file mode 100644 index 0000000000..dd06b89ad1 --- /dev/null +++ b/src/Microsoft.Management.Deployment/PackagePin.cpp @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackagePin.h" +#include "PackagePin.g.cpp" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + namespace + { + winrt::Microsoft::Management::Deployment::PackagePinType ConvertPinType(::AppInstaller::Pinning::PinType type) + { + switch (type) + { + case ::AppInstaller::Pinning::PinType::PinnedByManifest: + return winrt::Microsoft::Management::Deployment::PackagePinType::PinnedByManifest; + case ::AppInstaller::Pinning::PinType::Pinning: + return winrt::Microsoft::Management::Deployment::PackagePinType::Pinning; + case ::AppInstaller::Pinning::PinType::Gating: + return winrt::Microsoft::Management::Deployment::PackagePinType::Gating; + case ::AppInstaller::Pinning::PinType::Blocking: + return winrt::Microsoft::Management::Deployment::PackagePinType::Blocking; + default: + return winrt::Microsoft::Management::Deployment::PackagePinType::Unknown; + } + } + } + + void PackagePin::Initialize(const ::AppInstaller::Pinning::Pin& pin) + { + m_packageId = winrt::to_hstring(pin.GetKey().PackageId); + m_sourceId = winrt::to_hstring(pin.GetKey().SourceId); + m_type = ConvertPinType(pin.GetType()); + m_gatedVersion = winrt::to_hstring(pin.GetGatedVersion().ToString()); + m_dateAdded = winrt::to_hstring(pin.GetDateAdded()); + m_note = pin.GetNote() ? winrt::to_hstring(*pin.GetNote()) : hstring{}; + m_isForInstalledPackage = pin.GetKey().IsForInstalled(); + } + + hstring PackagePin::PackageId() + { + return m_packageId; + } + + hstring PackagePin::SourceId() + { + return m_sourceId; + } + + winrt::Microsoft::Management::Deployment::PackagePinType PackagePin::Type() + { + return m_type; + } + + hstring PackagePin::GatedVersion() + { + return m_gatedVersion; + } + + hstring PackagePin::DateAdded() + { + return m_dateAdded; + } + + hstring PackagePin::Note() + { + return m_note; + } + + bool PackagePin::IsForInstalledPackage() + { + return m_isForInstalledPackage; + } +} diff --git a/src/Microsoft.Management.Deployment/PackagePin.h b/src/Microsoft.Management.Deployment/PackagePin.h new file mode 100644 index 0000000000..2d3c0f37ff --- /dev/null +++ b/src/Microsoft.Management.Deployment/PackagePin.h @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackagePin.g.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct PackagePin : PackagePinT + { + PackagePin() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(const ::AppInstaller::Pinning::Pin& pin); +#endif + + hstring PackageId(); + hstring SourceId(); + winrt::Microsoft::Management::Deployment::PackagePinType Type(); + hstring GatedVersion(); + hstring DateAdded(); + hstring Note(); + bool IsForInstalledPackage(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + hstring m_packageId; + hstring m_sourceId; + winrt::Microsoft::Management::Deployment::PackagePinType m_type = winrt::Microsoft::Management::Deployment::PackagePinType::Unknown; + hstring m_gatedVersion; + hstring m_dateAdded; + hstring m_note; + bool m_isForInstalledPackage = false; +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/PinPackageOptions.cpp b/src/Microsoft.Management.Deployment/PinPackageOptions.cpp new file mode 100644 index 0000000000..f7e4359c8e --- /dev/null +++ b/src/Microsoft.Management.Deployment/PinPackageOptions.cpp @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "PinPackageOptions.h" +#pragma warning( pop ) +#include "PinPackageOptions.g.cpp" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + winrt::Microsoft::Management::Deployment::PackagePinType PinPackageOptions::PinType() + { + return m_pinType; + } + + void PinPackageOptions::PinType(winrt::Microsoft::Management::Deployment::PackagePinType value) + { + m_pinType = value; + } + + hstring PinPackageOptions::GatedVersion() + { + return m_gatedVersion; + } + + void PinPackageOptions::GatedVersion(hstring const& value) + { + m_gatedVersion = value; + } + + bool PinPackageOptions::PinInstalledPackage() + { + return m_pinInstalledPackage; + } + + void PinPackageOptions::PinInstalledPackage(bool value) + { + m_pinInstalledPackage = value; + } + + bool PinPackageOptions::Force() + { + return m_force; + } + + void PinPackageOptions::Force(bool value) + { + m_force = value; + } + + hstring PinPackageOptions::Note() + { + return m_note; + } + + void PinPackageOptions::Note(hstring const& value) + { + m_note = value; + } + + CoCreatableMicrosoftManagementDeploymentClass(PinPackageOptions); +} diff --git a/src/Microsoft.Management.Deployment/PinPackageOptions.h b/src/Microsoft.Management.Deployment/PinPackageOptions.h new file mode 100644 index 0000000000..8200d32682 --- /dev/null +++ b/src/Microsoft.Management.Deployment/PinPackageOptions.h @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PinPackageOptions.g.h" +#include "public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_PinPackageOptions)] + struct PinPackageOptions : PinPackageOptionsT + { + PinPackageOptions() = default; + + winrt::Microsoft::Management::Deployment::PackagePinType PinType(); + void PinType(winrt::Microsoft::Management::Deployment::PackagePinType value); + + hstring GatedVersion(); + void GatedVersion(hstring const& value); + + bool PinInstalledPackage(); + void PinInstalledPackage(bool value); + + bool Force(); + void Force(bool value); + + hstring Note(); + void Note(hstring const& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::PackagePinType m_pinType = winrt::Microsoft::Management::Deployment::PackagePinType::Pinning; + hstring m_gatedVersion; + bool m_pinInstalledPackage = false; + bool m_force = false; + hstring m_note; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct PinPackageOptions : + PinPackageOptionsT, + AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/PinPackageResult.cpp b/src/Microsoft.Management.Deployment/PinPackageResult.cpp new file mode 100644 index 0000000000..76e52f5336 --- /dev/null +++ b/src/Microsoft.Management.Deployment/PinPackageResult.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinPackageResult.h" +#include "PinPackageResult.g.cpp" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void PinPackageResult::Initialize( + winrt::Microsoft::Management::Deployment::PinResultStatus status, + winrt::hresult extendedErrorCode) + { + m_status = status; + m_extendedErrorCode = extendedErrorCode; + } + + winrt::Microsoft::Management::Deployment::PinResultStatus PinPackageResult::Status() + { + return m_status; + } + + winrt::hresult PinPackageResult::ExtendedErrorCode() + { + return m_extendedErrorCode; + } +} diff --git a/src/Microsoft.Management.Deployment/PinPackageResult.h b/src/Microsoft.Management.Deployment/PinPackageResult.h new file mode 100644 index 0000000000..f96d1ef343 --- /dev/null +++ b/src/Microsoft.Management.Deployment/PinPackageResult.h @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PinPackageResult.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct PinPackageResult : PinPackageResultT + { + PinPackageResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize( + winrt::Microsoft::Management::Deployment::PinResultStatus status, + winrt::hresult extendedErrorCode); +#endif + + winrt::Microsoft::Management::Deployment::PinResultStatus Status(); + winrt::hresult ExtendedErrorCode(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::PinResultStatus m_status = winrt::Microsoft::Management::Deployment::PinResultStatus::Ok; + winrt::hresult m_extendedErrorCode = S_OK; +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/Public/ComClsids.h b/src/Microsoft.Management.Deployment/Public/ComClsids.h index e9137d788f..0eede1998a 100644 --- a/src/Microsoft.Management.Deployment/Public/ComClsids.h +++ b/src/Microsoft.Management.Deployment/Public/ComClsids.h @@ -18,6 +18,7 @@ #define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "DB9D012D-00D7-47EE-8FB1-606E10AC4F51" #define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "032B1C58-B975-469B-A013-E632B6ECE8D8" #define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "A9F5E736-68CE-463C-BA6D-DE968F0CCE04" +#define WINGET_OUTOFPROC_COM_CLSID_PinPackageOptions "93409EF2-29D0-46D3-8085-13EDE73939C4" #else #define WINGET_OUTOFPROC_COM_CLSID_PackageManager "74CB3139-B7C5-4B9E-9388-E6616DEA288C" #define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96" @@ -32,6 +33,7 @@ #define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "D58C7E4C-70E6-476C-A5D4-80341ED80252" #define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "87A96609-1A39-4955-BE72-7174E147B7DC" #define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "29B19238-81AD-4A8E-A2FC-ADF17C38CAEB" +#define WINGET_OUTOFPROC_COM_CLSID_PinPackageOptions "B3A61CCB-A3D0-497D-B300-A904904EEA56" #endif // Clsids only used in in-proc invocation @@ -53,6 +55,7 @@ namespace winrt::Microsoft::Management::Deployment const CLSID WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions = { 0x24e6f1fa, 0xe4c3, 0x4acd, 0x96, 0x5d, 0xdf, 0x21, 0x3f, 0xd5, 0x8f, 0x15 }; // {24E6F1FA-E4C3-4ACD-965D-DF213FD58F15} const CLSID WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions = { 0x1125d3a6, 0xe2ce, 0x479a, 0x91, 0xd5, 0x71, 0xa3, 0xf6, 0xf8, 0xb0, 0xb }; // {1125D3A6-E2CE-479A-91D5-71A3F6F8B00B} const CLSID WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions = { 0xe8e12fe1, 0xab77, 0x40c4, 0xa5, 0x62, 0xe9, 0x1f, 0xb5, 0x1b, 0x4e, 0x82 }; // {E8E12FE1-AB77-40C4-A562-E91FB51B4E82} + const CLSID WINGET_INPROC_COM_CLSID_PinPackageOptions = { 0xba9bb1af, 0x4453, 0x4274, 0xbb, 0xf9, 0x4c, 0x17, 0x79, 0x4e, 0xfa, 0x8e }; // {BA9BB1AF-4453-4274-BBF9-4C17794EFA8E} CLSID GetRedirectedClsidFromInProcClsid(REFCLSID clsid); } From ae483b07d10064aeae3008a065011173e0792af2 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 11:18:08 -0500 Subject: [PATCH 04/60] Add IsPinned propery for Get-WingetPackage --- .../Helpers/PackageManagerWrapper.cs | 12 ++++++++++++ .../PSObjects/PSInstalledCatalogPackage.cs | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs index f1ef5204e0..6e34ab089d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs @@ -136,6 +136,18 @@ public PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePack false); } + /// + /// Wrapper for GetPins. + /// + /// The package to get pins for. + /// A read-only list of PackagePin objects. + public IReadOnlyList GetPins(CatalogPackage package) + { + return this.Execute( + () => this.packageManager.GetPins(package), + false); + } + /// /// Gets the version of the package manager that is running. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs index 3c3dcb7519..d053cc204d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs @@ -8,6 +8,7 @@ namespace Microsoft.WinGet.Client.Engine.PSObjects { using System; using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Helpers; /// /// InstalledCatalogPackage wrapper object for displaying to PowerShell. @@ -31,6 +32,14 @@ public string InstalledVersion get { return this.CatalogPackageCOM.InstalledVersion.Version; } } + /// + /// Gets a value indicating whether the package is pinned. + /// + public bool IsPinned + { + get { return PackageManagerWrapper.Instance.GetPins(this.CatalogPackageCOM).Count > 0; } + } + /// /// Compares versions. /// From fac4ec49188f44eb4343fe632f3c7897b1c0c92e Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 12:25:22 -0500 Subject: [PATCH 05/60] Add pinning to PowerShell Cmdlets --- .../ClassesDefinition.cs | 32 +- .../WinGetProjectionFactory.cs | 12 +- .../Microsoft.WinGet.Client/Add-WinGetPin.md | 350 ++++++++++++++++++ .../Microsoft.WinGet.Client/Get-WinGetPin.md | 221 +++++++++++ .../Microsoft.WinGet.Client.md | 12 + .../Remove-WinGetPin.md | 276 ++++++++++++++ .../Reset-WinGetPin.md | 132 +++++++ .../Cmdlets/AddPinCmdlet.cs | 92 +++++ .../Cmdlets/GetPinCmdlet.cs | 74 ++++ .../Cmdlets/PSObjects/PSPackagePinType.cs | 29 ++ .../Cmdlets/RemovePinCmdlet.cs | 77 ++++ .../Cmdlets/ResetPinCmdlet.cs | 62 ++++ .../Common/Constants.cs | 15 + .../Commands/PinPackageCommand.cs | 179 +++++++++ .../Commands/ResetPinCommand.cs | 41 ++ .../Helpers/ManagementDeploymentFactory.cs | 14 + .../Helpers/PSEnumHelpers.cs | 16 + .../Helpers/PackageManagerWrapper.cs | 48 +++ .../PSObjects/PSPackagePin.cs | 83 +++++ .../PSObjects/PSPinResult.cs | 62 ++++ .../ModuleFiles/Microsoft.WinGet.Client.psd1 | 4 + 21 files changed, 1816 insertions(+), 15 deletions(-) create mode 100644 src/PowerShell/Help/Microsoft.WinGet.Client/Add-WinGetPin.md create mode 100644 src/PowerShell/Help/Microsoft.WinGet.Client/Get-WinGetPin.md create mode 100644 src/PowerShell/Help/Microsoft.WinGet.Client/Remove-WinGetPin.md create mode 100644 src/PowerShell/Help/Microsoft.WinGet.Client/Reset-WinGetPin.md create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetPinCmdlet.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackagePinType.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemovePinCmdlet.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetPinCmdlet.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/PinPackageCommand.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/ResetPinCommand.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPinResult.cs diff --git a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs index ecae3141cf..02c7d6109f 100644 --- a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs +++ b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs @@ -152,16 +152,28 @@ internal static class ClassesDefinition } }, - [typeof(EditPackageCatalogOptions)] = new() - { - ProjectedClassType = typeof(EditPackageCatalogOptions), - InterfaceType = typeof(IEditPackageCatalogOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("E8E12FE1-AB77-40C4-A562-E91FB51B4E82"), - [ClsidContext.OutOfProc] = new Guid("A9F5E736-68CE-463C-BA6D-DE968F0CCE04"), - [ClsidContext.OutOfProcDev] = new Guid("29B19238-81AD-4A8E-A2FC-ADF17C38CAEB"), - } + [typeof(EditPackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(EditPackageCatalogOptions), + InterfaceType = typeof(IEditPackageCatalogOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("E8E12FE1-AB77-40C4-A562-E91FB51B4E82"), + [ClsidContext.OutOfProc] = new Guid("A9F5E736-68CE-463C-BA6D-DE968F0CCE04"), + [ClsidContext.OutOfProcDev] = new Guid("29B19238-81AD-4A8E-A2FC-ADF17C38CAEB"), + } + }, + + [typeof(PinPackageOptions)] = new() + { + ProjectedClassType = typeof(PinPackageOptions), + InterfaceType = typeof(IPinPackageOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("BA9BB1AF-4453-4274-BBF9-4C17794EFA8E"), + [ClsidContext.OutOfProc] = new Guid("93409EF2-29D0-46D3-8085-13EDE73939C4"), + [ClsidContext.OutOfProcDev] = new Guid("B3A61CCB-A3D0-497D-B300-A904904EEA56"), + } } }; diff --git a/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs b/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs index 356d1ff8b3..84f305b210 100644 --- a/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs +++ b/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs @@ -36,11 +36,13 @@ public WinGetProjectionFactory(IInstanceInitializer instanceInitializer) public PackageManagerSettings CreatePackageManagerSettings() => InstanceInitializer.CreateInstance(); public RepairOptions CreateRepairOptions() => InstanceInitializer.CreateInstance(); - - public AddPackageCatalogOptions CreateAddPackageCatalogOptions() => InstanceInitializer.CreateInstance(); - - public RemovePackageCatalogOptions CreateRemovePackageCatalogOptions() => InstanceInitializer.CreateInstance(); - + + public AddPackageCatalogOptions CreateAddPackageCatalogOptions() => InstanceInitializer.CreateInstance(); + + public RemovePackageCatalogOptions CreateRemovePackageCatalogOptions() => InstanceInitializer.CreateInstance(); + public EditPackageCatalogOptions CreateEditPackageCatalogOptions() => InstanceInitializer.CreateInstance(); + + public PinPackageOptions CreatePinPackageOptions() => InstanceInitializer.CreateInstance(); } } diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Add-WinGetPin.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Add-WinGetPin.md new file mode 100644 index 0000000000..2992b76ed8 --- /dev/null +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Add-WinGetPin.md @@ -0,0 +1,350 @@ +--- +external help file: Microsoft.WinGet.Client.Cmdlets.dll-Help.xml +Module Name: Microsoft.WinGet.Client +ms.date: 08/01/2024 +online version: +schema: 2.0.0 +--- + +# Add-WinGetPin + +## SYNOPSIS +Adds a WinGet package pin. + +## SYNTAX + +### FoundSet (Default) +``` +Add-WinGetPin [-PinType ] [-GatedVersion ] [-PinInstalledPackage] + [-Force] [-Note ] [-Id ] [-Name ] [-Moniker ] [-Source ] + [[-Query] ] [-MatchOption ] [-ProgressAction ] + [-WhatIf] [-Confirm] [] +``` + +### GivenSet +``` +Add-WinGetPin [-PinType ] [-GatedVersion ] [-PinInstalledPackage] + [-Force] [-Note ] [[-PSCatalogPackage] ] + [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION +This command adds a pin for a WinGet package, preventing automatic updates. Mirrors the behavior +of `winget pin add`. By default, all string-based searches are case-insensitive exact matches. +Wildcards are not supported. You can change the search behavior using the **MatchOption** parameter. + +Pin types: +- **Pinning** (default): Prevents automatic updates but allows manual upgrades. +- **Blocking**: Prevents all upgrades, including manual ones. +- **Gating**: Allows upgrades only to versions below the specified **GatedVersion**. + +## EXAMPLES + +### Example 1: Pin a package by Id +```powershell +Add-WinGetPin -Id "Microsoft.PowerShell" +``` +This example adds a Pinning pin for `Microsoft.PowerShell`, preventing automatic updates. + +### Example 2: Add a blocking pin +```powershell +Add-WinGetPin -Id "Microsoft.PowerShell" -PinType Blocking +``` +This example adds a Blocking pin for `Microsoft.PowerShell`, preventing all upgrades. + +### Example 3: Add a gating pin +```powershell +Add-WinGetPin -Id "Microsoft.PowerShell" -PinType Gating -GatedVersion "<7.5" +``` +This example adds a Gating pin that allows upgrades only to versions below 7.5. + +### Example 4: Pin a package using the pipeline +```powershell +Find-WinGetPackage -Id "Microsoft.PowerShell" | Add-WinGetPin +``` +This example pins a package passed from `Find-WinGetPackage`. + +### Example 5: Pin an installed package +```powershell +Get-WinGetPackage -Id "Microsoft.PowerShell" | Add-WinGetPin -PinInstalledPackage +``` +This example adds a pin for the currently installed version of `Microsoft.PowerShell`. + +## PARAMETERS + +### -Confirm + +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force + +Forces the pin operation even if an existing pin is already present. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -GatedVersion + +Specify the version range for a Gating pin. This parameter is required when **PinType** is +`Gating`. The value uses WinGet version range syntax (e.g., `<7.5`, `>=7.0,<8.0`). + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Id + +Specify the package identifier to search for. By default, the command does a case-insensitive +exact match. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -MatchOption + +Specify the match option for a WinGet package query. This parameter accepts the following values: + +```yaml +Type: Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption +Parameter Sets: FoundSet +Aliases: +Accepted values: Equals, EqualsCaseInsensitive, StartsWithCaseInsensitive, ContainsCaseInsensitive + +Required: False +Position: Named +Default value: EqualsCaseInsensitive +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Moniker + +Specify the moniker of the WinGet package to search for. For example, the moniker for the +Microsoft.PowerShell package is `pwsh`. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Name + +Specify the name of the WinGet package to search for. If the name contains spaces, enclose the +name in quotes. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Note + +Specify an optional note to attach to the pin. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PinInstalledPackage + +When specified, pins the currently installed version of the package rather than all versions. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PinType + +Specify the type of pin to add. Accepted values: + +- **Pinning** (default): Prevents automatic upgrades. +- **Blocking**: Prevents all upgrades. +- **Gating**: Limits upgrades to versions below **GatedVersion**. + +```yaml +Type: Microsoft.WinGet.Client.PSObjects.PSPackagePinType +Parameter Sets: (All) +Aliases: +Accepted values: Pinning, Blocking, Gating + +Required: False +Position: Named +Default value: Pinning +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PSCatalogPackage + +Provide a **PSCatalogPackage** object. You can get a **PSCatalogPackage** object by using the +`Find-WinGetPackage` or `Get-WinGetPackage` commands. + +```yaml +Type: Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage +Parameter Sets: GivenSet +Aliases: InputObject + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Query + +Specify one or more strings to search for. By default, the command searches all configured sources. +Wildcards are not supported. The command compares the value provided to the following package +manifest properties: + + - `PackageIdentifier` + - `PackageName` + - `Moniker` + - `Tags` + +The command does a case-insensitive substring comparison of these properties. + +```yaml +Type: System.String[] +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Source + +Specify the name of a configured WinGet source. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -WhatIf + +Shows what would happen if the cmdlet runs. The cmdlet is not run. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage + +### System.String + +### System.String[] + +### Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption + +### Microsoft.WinGet.Client.PSObjects.PSPackagePinType + +## OUTPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSPinResult + +## NOTES + +## RELATED LINKS + +[Get-WinGetPin](Get-WinGetPin.md) + +[Remove-WinGetPin](Remove-WinGetPin.md) + +[Reset-WinGetPin](Reset-WinGetPin.md) + +[Find-WinGetPackage](Find-WinGetPackage.md) + +[Get-WinGetPackage](Get-WinGetPackage.md) diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Get-WinGetPin.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Get-WinGetPin.md new file mode 100644 index 0000000000..09cb3000cf --- /dev/null +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Get-WinGetPin.md @@ -0,0 +1,221 @@ +--- +external help file: Microsoft.WinGet.Client.Cmdlets.dll-Help.xml +Module Name: Microsoft.WinGet.Client +ms.date: 08/01/2024 +online version: +schema: 2.0.0 +--- + +# Get-WinGetPin + +## SYNOPSIS +Gets WinGet package pins. + +## SYNTAX + +### AllSet (Default) +``` +Get-WinGetPin [] +``` + +### GivenSet +``` +Get-WinGetPin [[-PSCatalogPackage] ] [] +``` + +### FoundSet +``` +Get-WinGetPin [-Id ] [-Name ] [-Moniker ] [-Source ] + [[-Query] ] [-MatchOption ] [] +``` + +## DESCRIPTION +This command retrieves WinGet package pins. When called without parameters, it returns all pins. +When called with package search criteria or a catalog package, it returns pins for the matching +package. By default, all string-based searches are case-insensitive substring searches. +Wildcards are not supported. You can change the search behavior using the **MatchOption** parameter. + +## EXAMPLES + +### Example 1: Get all pins +```powershell +Get-WinGetPin +``` +This example gets all package pins configured in WinGet. + +### Example 2: Get the pin for a specific package by Id +```powershell +Get-WinGetPin -Id "Microsoft.PowerShell" +``` +This example gets the pin for the package with the identifier `Microsoft.PowerShell`. + +### Example 3: Get the pin for a package using a query +```powershell +Get-WinGetPin -Query "PowerShell" +``` +This example gets pins for packages matching the query `PowerShell`. + +### Example 4: Get the pin for an installed package using the pipeline +```powershell +Get-WinGetPackage -Id "Microsoft.PowerShell" | Get-WinGetPin +``` +This example gets the pin for an installed package passed from `Get-WinGetPackage`. + +## PARAMETERS + +### -Id + +Specify the package identifier to search for. By default, the command does a case-insensitive +substring match. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -MatchOption + +Specify the match option for a WinGet package query. This parameter accepts the following values: + +```yaml +Type: Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption +Parameter Sets: FoundSet +Aliases: +Accepted values: Equals, EqualsCaseInsensitive, StartsWithCaseInsensitive, ContainsCaseInsensitive + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Moniker + +Specify the moniker of the WinGet package to search for. For example, the moniker for the +Microsoft.PowerShell package is `pwsh`. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Name + +Specify the name of the WinGet package to search for. If the name contains spaces, enclose the +name in quotes. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PSCatalogPackage + +Provide a **PSCatalogPackage** object. You can get a **PSCatalogPackage** object by using the +`Find-WinGetPackage` or `Get-WinGetPackage` commands. + +```yaml +Type: Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage +Parameter Sets: GivenSet +Aliases: InputObject + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Query + +Specify one or more strings to search for. By default, the command searches all configured sources. +Wildcards are not supported. The command compares the value provided to the following package +manifest properties: + + - `PackageIdentifier` + - `PackageName` + - `Moniker` + - `Tags` + +The command does a case-insensitive substring comparison of these properties. + +```yaml +Type: System.String[] +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Source + +Specify the name of a configured WinGet source. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage + +### System.String + +### System.String[] + +### Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption + +## OUTPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSPackagePin + +## NOTES + +## RELATED LINKS + +[Add-WinGetPin](Add-WinGetPin.md) + +[Remove-WinGetPin](Remove-WinGetPin.md) + +[Reset-WinGetPin](Reset-WinGetPin.md) + +[Find-WinGetPackage](Find-WinGetPackage.md) + +[Get-WinGetPackage](Get-WinGetPackage.md) diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Microsoft.WinGet.Client.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Microsoft.WinGet.Client.md index 9765cc16fb..c06de08783 100644 --- a/src/PowerShell/Help/Microsoft.WinGet.Client/Microsoft.WinGet.Client.md +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Microsoft.WinGet.Client.md @@ -13,6 +13,9 @@ title: Microsoft.WinGet.Client Module Microsoft WinGet Client Module for the Windows Package Manager ## Microsoft.WinGet.Client Cmdlets +### [Add-WinGetPin](Add-WinGetPin.md) +Adds a WinGet package pin. + ### [Add-WinGetSource](Add-WinGetSource.md) Adds a new WinGet source. @@ -34,6 +37,9 @@ Searches configured sources for packages. ### [Get-WinGetPackage](Get-WinGetPackage.md) Gets installed packages. +### [Get-WinGetPin](Get-WinGetPin.md) +Gets WinGet package pins. + ### [Get-WinGetSetting](Get-WinGetSetting.md) Gets WinGet settings. @@ -49,6 +55,9 @@ Gets the installed version of WinGet. ### [Install-WinGetPackage](Install-WinGetPackage.md) Install a WinGet Package. +### [Remove-WinGetPin](Remove-WinGetPin.md) +Removes a WinGet package pin. + ### [Remove-WinGetSource](Remove-WinGetSource.md) Removes a configured source. @@ -58,6 +67,9 @@ Repairs a WinGet Package. ### [Repair-WinGetPackageManager](Repair-WinGetPackageManager.md) Repairs the WinGet client. +### [Reset-WinGetPin](Reset-WinGetPin.md) +Resets all WinGet package pins. + ### [Reset-WinGetSource](Reset-WinGetSource.md) Resets default WinGet sources. diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Remove-WinGetPin.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Remove-WinGetPin.md new file mode 100644 index 0000000000..b55d58c831 --- /dev/null +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Remove-WinGetPin.md @@ -0,0 +1,276 @@ +--- +external help file: Microsoft.WinGet.Client.Cmdlets.dll-Help.xml +Module Name: Microsoft.WinGet.Client +ms.date: 08/01/2024 +online version: +schema: 2.0.0 +--- + +# Remove-WinGetPin + +## SYNOPSIS +Removes a WinGet package pin. + +## SYNTAX + +### FoundSet (Default) +``` +Remove-WinGetPin [-Id ] [-Name ] [-Moniker ] [-Source ] + [[-Query] ] [-MatchOption ] [-ProgressAction ] + [-WhatIf] [-Confirm] [] +``` + +### GivenSet +``` +Remove-WinGetPin [[-PSCatalogPackage] ] [-ProgressAction ] + [-WhatIf] [-Confirm] [] +``` + +### PinSet +``` +Remove-WinGetPin [[-PSPackagePin] ] [-ProgressAction ] + [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION +This command removes a pin for a WinGet package. Mirrors the behavior of `winget pin remove`. +The command accepts a **PSPackagePin** object from the pipeline (e.g., from `Get-WinGetPin`), +a **PSCatalogPackage** object from `Get-WinGetPackage` or `Find-WinGetPackage`, or package +search criteria. By default, all string-based searches are case-insensitive exact matches. +Wildcards are not supported. You can change the search behavior using the **MatchOption** parameter. + +## EXAMPLES + +### Example 1: Remove a pin by package Id +```powershell +Remove-WinGetPin -Id "Microsoft.PowerShell" +``` +This example removes the pin for the package with the identifier `Microsoft.PowerShell`. + +### Example 2: Remove a pin using the pipeline from Get-WinGetPin +```powershell +Get-WinGetPin -Id "Microsoft.PowerShell" | Remove-WinGetPin +``` +This example removes a pin passed from `Get-WinGetPin`. + +### Example 3: Remove all pins +```powershell +Get-WinGetPin | Remove-WinGetPin +``` +This example removes all pins by piping the output of `Get-WinGetPin` to `Remove-WinGetPin`. + +### Example 4: Remove a pin using the pipeline from Get-WinGetPackage +```powershell +Get-WinGetPackage -Id "Microsoft.PowerShell" | Remove-WinGetPin +``` +This example removes the pin for an installed package passed from `Get-WinGetPackage`. + +## PARAMETERS + +### -Confirm + +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Id + +Specify the package identifier to search for. By default, the command does a case-insensitive +exact match. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -MatchOption + +Specify the match option for a WinGet package query. This parameter accepts the following values: + +```yaml +Type: Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption +Parameter Sets: FoundSet +Aliases: +Accepted values: Equals, EqualsCaseInsensitive, StartsWithCaseInsensitive, ContainsCaseInsensitive + +Required: False +Position: Named +Default value: EqualsCaseInsensitive +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Moniker + +Specify the moniker of the WinGet package to search for. For example, the moniker for the +Microsoft.PowerShell package is `pwsh`. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Name + +Specify the name of the WinGet package to search for. If the name contains spaces, enclose the +name in quotes. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PSCatalogPackage + +Provide a **PSCatalogPackage** object. You can get a **PSCatalogPackage** object by using the +`Find-WinGetPackage` or `Get-WinGetPackage` commands. + +```yaml +Type: Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage +Parameter Sets: GivenSet +Aliases: InputObject + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -PSPackagePin + +Provide a **PSPackagePin** object. You can get a **PSPackagePin** object by using the +`Get-WinGetPin` command. + +```yaml +Type: Microsoft.WinGet.Client.Engine.PSObjects.PSPackagePin +Parameter Sets: PinSet +Aliases: InputObject + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Query + +Specify one or more strings to search for. By default, the command searches all configured sources. +Wildcards are not supported. The command compares the value provided to the following package +manifest properties: + + - `PackageIdentifier` + - `PackageName` + - `Moniker` + - `Tags` + +The command does a case-insensitive substring comparison of these properties. + +```yaml +Type: System.String[] +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Source + +Specify the name of a configured WinGet source. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -WhatIf + +Shows what would happen if the cmdlet runs. The cmdlet is not run. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSPackagePin + +### Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage + +### System.String + +### System.String[] + +### Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption + +## OUTPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSPinResult + +## NOTES + +## RELATED LINKS + +[Get-WinGetPin](Get-WinGetPin.md) + +[Add-WinGetPin](Add-WinGetPin.md) + +[Reset-WinGetPin](Reset-WinGetPin.md) + +[Find-WinGetPackage](Find-WinGetPackage.md) + +[Get-WinGetPackage](Get-WinGetPackage.md) diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Reset-WinGetPin.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Reset-WinGetPin.md new file mode 100644 index 0000000000..1142a29f5c --- /dev/null +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Reset-WinGetPin.md @@ -0,0 +1,132 @@ +--- +external help file: Microsoft.WinGet.Client.Cmdlets.dll-Help.xml +Module Name: Microsoft.WinGet.Client +ms.date: 08/01/2024 +online version: +schema: 2.0.0 +--- + +# Reset-WinGetPin + +## SYNOPSIS +Resets all WinGet package pins. + +## SYNTAX + +``` +Reset-WinGetPin [-Source ] [-Force] [-ProgressAction ] [-WhatIf] [-Confirm] + [] +``` + +## DESCRIPTION +This command resets all WinGet package pins, removing all pin records. Mirrors the behavior of +`winget pin reset`. You can scope the reset to a specific source by providing the **Source** +parameter. Use the **Force** parameter to skip the confirmation prompt. + +## EXAMPLES + +### Example 1: Reset all pins +```powershell +Reset-WinGetPin +``` +This example resets all package pins across all configured sources. + +### Example 2: Reset pins for a specific source +```powershell +Reset-WinGetPin -Source "winget" +``` +This example resets all package pins for the `winget` source. + +### Example 3: Reset all pins without confirmation +```powershell +Reset-WinGetPin -Force +``` +This example resets all pins without prompting for confirmation. + +## PARAMETERS + +### -Confirm + +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force + +When specified, skips the confirmation prompt and resets pins immediately. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Source + +Specify the name of a configured WinGet source to scope the reset. If not specified, pins +across all sources are reset. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -WhatIf + +Shows what would happen if the cmdlet runs. The cmdlet is not run. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String + +## OUTPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSPinResult + +## NOTES + +## RELATED LINKS + +[Get-WinGetPin](Get-WinGetPin.md) + +[Add-WinGetPin](Add-WinGetPin.md) + +[Remove-WinGetPin](Remove-WinGetPin.md) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs new file mode 100644 index 0000000000..8542dab160 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs @@ -0,0 +1,92 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Client.PSObjects; + + /// + /// Adds a pin for a package. Mirrors the behavior of winget pin add. + /// + [Cmdlet( + VerbsCommon.Add, + Constants.WinGetNouns.Pin, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [OutputType(typeof(PSPinResult))] + public sealed class AddPinCmdlet : PackageCmdlet + { + private PinPackageCommand command = null; + + /// + /// Gets or sets the pin type. Defaults to . + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackagePinType PinType { get; set; } = PSPackagePinType.Pinning; + + /// + /// Gets or sets the gated version range. Required when is Gating. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string GatedVersion { get; set; } + + /// + /// Gets or sets a value indicating whether to pin the installed version of the package. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter PinInstalledPackage { get; set; } + + /// + /// Gets or sets a value indicating whether to force the pin even if an existing pin is present. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// + /// Gets or sets an optional note to attach to the pin. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Note { get; set; } + + /// + /// Adds a pin for the specified package. + /// + protected override void ProcessRecord() + { + this.command = new PinPackageCommand( + this, + this.PSCatalogPackage, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query); + this.command.Add( + this.MatchOption.ToString(), + this.PinType.ToString(), + this.GatedVersion, + this.PinInstalledPackage.ToBool(), + this.Force.ToBool(), + this.Note); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetPinCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetPinCmdlet.cs new file mode 100644 index 0000000000..e53ecf8c37 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetPinCmdlet.cs @@ -0,0 +1,74 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// Gets package pins. If no filter is provided, returns all pins. + /// + [Cmdlet( + VerbsCommon.Get, + Constants.WinGetNouns.Pin, + DefaultParameterSetName = Constants.AllSet)] + [OutputType(typeof(PSPackagePin))] + public sealed class GetPinCmdlet : FinderCmdlet + { + private PinPackageCommand command = null; + + /// + /// Gets or sets the package to retrieve pins for. + /// + [Alias("InputObject")] + [ValidateNotNull] + [Parameter( + ParameterSetName = Constants.GivenSet, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSCatalogPackage PSCatalogPackage { get; set; } = null; + + /// + /// Retrieves pins based on the specified parameters. + /// + protected override void ProcessRecord() + { + this.command = new PinPackageCommand( + this, + this.PSCatalogPackage, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query); + + if (this.ParameterSetName == Constants.AllSet) + { + this.command.GetAll(); + } + else + { + this.command.Get(this.MatchOption.ToString()); + } + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackagePinType.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackagePinType.cs new file mode 100644 index 0000000000..705bde2a49 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackagePinType.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// Must match the user-settable values of Microsoft.Management.Deployment.PackagePinType. + /// + public enum PSPackagePinType + { + /// + /// Pinning - prevents automatic updates to the current version. + /// + Pinning, + + /// + /// Blocking - prevents installation or upgrade of the package. + /// + Blocking, + + /// + /// Gating - limits updates to versions below the specified GatedVersion. + /// + Gating, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemovePinCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemovePinCmdlet.cs new file mode 100644 index 0000000000..791de2b270 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemovePinCmdlet.cs @@ -0,0 +1,77 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Client.PSObjects; + + /// + /// Removes a package pin. Mirrors the behavior of winget pin remove. + /// Accepts a from the pipeline (e.g., from Get-WinGetPin), + /// a from the pipeline, or package search criteria. + /// + [Cmdlet( + VerbsCommon.Remove, + Constants.WinGetNouns.Pin, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [OutputType(typeof(PSPinResult))] + public sealed class RemovePinCmdlet : PackageCmdlet + { + private PinPackageCommand command = null; + + /// + /// Gets or sets the pin object to remove. Accepts pipeline input from Get-WinGetPin. + /// + [ValidateNotNull] + [Parameter( + ParameterSetName = Constants.PinSet, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSPackagePin PSPackagePin { get; set; } = null; + + /// + /// Removes the pin for the specified package. + /// + protected override void ProcessRecord() + { + PSCatalogPackage catalogPackage = this.PSCatalogPackage; + string id = this.Id; + + if (this.ParameterSetName == Constants.PinSet) + { + id = this.PSPackagePin.PackageId; + } + + this.command = new PinPackageCommand( + this, + catalogPackage, + id, + this.Name, + this.Moniker, + this.Source, + this.Query); + this.command.Remove(this.MatchOption.ToString()); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetPinCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetPinCmdlet.cs new file mode 100644 index 0000000000..b1e1360d2e --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetPinCmdlet.cs @@ -0,0 +1,62 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// Resets all package pins, optionally scoped to a source. Mirrors the behavior of winget pin reset. + /// + [Cmdlet( + VerbsCommon.Reset, + Constants.WinGetNouns.Pin, + SupportsShouldProcess = true)] + [OutputType(typeof(PSPinResult))] + public sealed class ResetPinCmdlet : PSCmdlet + { + private ResetPinCommand command = null; + + /// + /// Gets or sets the source name to scope the reset. If not specified, all sources are reset. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Source { get; set; } + + /// + /// Gets or sets a value indicating whether to skip confirmation. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// + /// Resets pins. + /// + protected override void ProcessRecord() + { + string target = string.IsNullOrEmpty(this.Source) ? "All sources" : this.Source; + if (this.Force || this.ShouldProcess(target)) + { + this.command = new ResetPinCommand(this); + this.command.Reset(this.Source); + } + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs index 62847c6567..dca94fbc28 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs @@ -52,6 +52,16 @@ internal static class Constants /// public const string IntegrityLatestSet = "IntegrityLatestSet"; + /// + /// This parameter set indicates that no filter is applied and all items should be returned. + /// + public const string AllSet = "AllSet"; + + /// + /// This parameter set indicates that a pin object was provided via a parameter or the pipeline. + /// + public const string PinSet = "PinSet"; + /// /// Nouns used for different cmdlets. Changing this will alter the names of the related commands. /// @@ -86,6 +96,11 @@ public static class WinGetNouns /// The noun for enable/disable winget admin settings. /// public const string Setting = "WinGetSetting"; + + /// + /// WinGetPin. + /// + public const string Pin = "WinGetPin"; } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/PinPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/PinPackageCommand.cs new file mode 100644 index 0000000000..314a818acd --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/PinPackageCommand.cs @@ -0,0 +1,179 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Collections.Generic; + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + + /// + /// Engine command for pin operations (get, add, remove). + /// + public sealed class PinPackageCommand : PackageCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + /// PSCatalogPackage (optional). + /// Package identifier. + /// Name of package. + /// Moniker of package. + /// Source to search. If null, all are searched. + /// Match against any field of a package. + public PinPackageCommand( + PSCmdlet psCmdlet, + PSCatalogPackage psCatalogPackage, + string id, + string name, + string moniker, + string source, + string[] query) + : base(psCmdlet) + { + if (psCatalogPackage != null) + { + this.CatalogPackage = psCatalogPackage; + } + + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + } + + /// + /// Gets all pins. + /// + public void GetAll() + { + IReadOnlyList pins = this.Execute( + () => PackageManagerWrapper.Instance.GetAllPins()); + + for (int i = 0; i < pins.Count; i++) + { + this.Write(StreamType.Object, new PSPackagePin(pins[i])); + } + } + + /// + /// Gets pins for a specific package. + /// + /// PSPackageFieldMatchOption string. + public void Get(string psPackageFieldMatchOption) + { + IReadOnlyList pins = this.Execute(() => + { + CatalogPackage package = this.FindCatalogPackage( + CompositeSearchBehavior.AllCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption)); + return PackageManagerWrapper.Instance.GetPins(package); + }); + + for (int i = 0; i < pins.Count; i++) + { + this.Write(StreamType.Object, new PSPackagePin(pins[i])); + } + } + + /// + /// Adds a pin for a package. + /// + /// PSPackageFieldMatchOption string. + /// Pin type string. + /// Gated version range (for Gating pins). + /// Whether to pin the installed package. + /// Whether to force the pin. + /// Optional user note. + public void Add( + string psPackageFieldMatchOption, + string pinType, + string gatedVersion, + bool pinInstalledPackage, + bool force, + string note) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.AllCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + var options = ManagementDeploymentFactory.Instance.CreatePinPackageOptions(); + options.PinType = PSEnumHelpers.ToPackagePinType(pinType); + if (!string.IsNullOrEmpty(gatedVersion)) + { + options.GatedVersion = gatedVersion; + } + + options.PinInstalledPackage = pinInstalledPackage; + options.Force = force; + if (!string.IsNullOrEmpty(note)) + { + options.Note = note; + } + + return await Task.FromResult(PackageManagerWrapper.Instance.PinPackage(package, options)); + })); + + if (result != null) + { + this.Write(StreamType.Object, new PSPinResult(result.Item1)); + } + } + + /// + /// Removes the pin for a package. + /// + /// PSPackageFieldMatchOption string. + public void Remove(string psPackageFieldMatchOption) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.AllCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + return await Task.FromResult(PackageManagerWrapper.Instance.UnpinPackage(package)); + })); + + if (result != null) + { + this.Write(StreamType.Object, new PSPinResult(result.Item1)); + } + } + + private CatalogPackage FindCatalogPackage(CompositeSearchBehavior behavior, PackageFieldMatchOption match) + { + if (this.CatalogPackage != null) + { + return this.CatalogPackage.CatalogPackageCOM; + } + + IReadOnlyList results = this.FindPackages(behavior, 0, match); + if (results.Count == 0) + { + throw new NoPackageFoundException(); + } + else if (results.Count == 1) + { + return results[0].CatalogPackage; + } + else + { + throw new VagueCriteriaException(results); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/ResetPinCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/ResetPinCommand.cs new file mode 100644 index 0000000000..b25b9871e4 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/ResetPinCommand.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + + /// + /// Engine command for resetting all pins. + /// + public sealed class ResetPinCommand : ManagementDeploymentCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + public ResetPinCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Resets all pins, optionally scoped to a source. + /// + /// The source name to scope the reset. Pass null or empty to reset all sources. + public void Reset(string sourceName) + { + var result = this.Execute( + () => PackageManagerWrapper.Instance.ResetAllPins(sourceName ?? string.Empty)); + + this.Write(StreamType.Object, new PSPinResult(result)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs index 76d9121e4a..5528da6149 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs @@ -31,6 +31,7 @@ internal sealed class ManagementDeploymentFactory private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); private static readonly Guid DownloadOptionsClsid = Guid.Parse("4CBABE76-7322-4BE4-9CEA-2589A80682DC"); private static readonly Guid RepairOptionsClsid = Guid.Parse("0498F441-3097-455F-9CAF-148F28293865"); + private static readonly Guid PinPackageOptionsClsid = Guid.Parse("93409EF2-29D0-46D3-8085-13EDE73939C4"); #else private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); @@ -40,6 +41,7 @@ internal sealed class ManagementDeploymentFactory private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); private static readonly Guid DownloadOptionsClsid = Guid.Parse("8EF324ED-367C-4880-83E5-BB2ABD0B72F6"); private static readonly Guid RepairOptionsClsid = Guid.Parse("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"); + private static readonly Guid PinPackageOptionsClsid = Guid.Parse("B3A61CCB-A3D0-497D-B300-A904904EEA56"); #endif [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] private static readonly Type? PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); @@ -57,6 +59,8 @@ internal sealed class ManagementDeploymentFactory private static readonly Type? DownloadOptionsType = Type.GetTypeFromCLSID(DownloadOptionsClsid); [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] private static readonly Type? RepairOptionsType = Type.GetTypeFromCLSID(RepairOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? PinPackageOptionsType = Type.GetTypeFromCLSID(PinPackageOptionsClsid); // These GUIDs correspond to the CSWinRT interface IIDs generated for Microsoft.Management.Deployment.Projection // and are auto-generated by the WinRT tool. @@ -68,6 +72,7 @@ internal sealed class ManagementDeploymentFactory private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); private static readonly Guid DownloadOptionsIid = Guid.Parse("94C92C4B-43F5-5CA3-BBBE-9F432C9546BC"); private static readonly Guid RepairOptionsIid = Guid.Parse("263F0546-2D7E-53A0-B8D1-75B74817FF18"); + private static readonly Guid PinPackageOptionsIid = Guid.Parse("8AB6949E-BB04-5F77-8B69-EF444D9C1635"); private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; @@ -176,6 +181,15 @@ public RepairOptions CreateRepairOptions() return Create(RepairOptionsType, RepairOptionsIid); } + /// + /// Creates an instance of the class. + /// + /// A instance. + public PinPackageOptions CreatePinPackageOptions() + { + return Create(PinPackageOptionsType, PinPackageOptionsIid); + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] private static T Create(Type? type, in Guid iid) where T : new() diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs index 2228a1c1c4..45e7e368f5 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs @@ -150,6 +150,22 @@ public static PackageRepairMode ToPackageRepairMode(string value) }; } + /// + /// Converts PSPackagePinType string value to PackagePinType. + /// + /// PSPackagePinType string value. + /// PackagePinType. + public static PackagePinType ToPackagePinType(string value) + { + return value switch + { + "Pinning" => PackagePinType.Pinning, + "Blocking" => PackagePinType.Blocking, + "Gating" => PackagePinType.Gating, + _ => throw new InvalidOperationException(), + }; + } + /// /// Converts PSWindowsPlatform string value to WindowsPlatform. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs index 6e34ab089d..aedf6c9402 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs @@ -148,6 +148,54 @@ public IReadOnlyList GetPins(CatalogPackage package) false); } + /// + /// Wrapper for GetAllPins. + /// + /// A read-only list of all PackagePin objects. + public IReadOnlyList GetAllPins() + { + return this.Execute( + () => this.packageManager.GetAllPins(), + false); + } + + /// + /// Wrapper for PinPackage. + /// + /// The package to pin. + /// The pin options. + /// A PinPackageResult. + public PinPackageResult PinPackage(CatalogPackage package, PinPackageOptions options) + { + return this.Execute( + () => this.packageManager.PinPackage(package, options), + false); + } + + /// + /// Wrapper for UnpinPackage. + /// + /// The package to unpin. + /// A PinPackageResult. + public PinPackageResult UnpinPackage(CatalogPackage package) + { + return this.Execute( + () => this.packageManager.UnpinPackage(package), + false); + } + + /// + /// Wrapper for ResetAllPins. + /// + /// The source name to reset pins for. Pass empty string to reset all. + /// A PinPackageResult. + public PinPackageResult ResetAllPins(string sourceName) + { + return this.Execute( + () => this.packageManager.ResetAllPins(sourceName), + false); + } + /// /// Gets the version of the package manager that is running. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs new file mode 100644 index 0000000000..48ae65c8d9 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs @@ -0,0 +1,83 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using Microsoft.Management.Deployment; + + /// + /// PSPackagePin wraps a PackagePin COM object for PowerShell output. + /// + public sealed class PSPackagePin + { + private readonly PackagePin packagePin; + + /// + /// Initializes a new instance of the class. + /// + /// The PackagePin COM object. + internal PSPackagePin(PackagePin packagePin) + { + this.packagePin = packagePin; + } + + /// + /// Gets the package identifier. + /// + public string PackageId + { + get { return this.packagePin.PackageId; } + } + + /// + /// Gets the source identifier the pin applies to. + /// + public string SourceId + { + get { return this.packagePin.SourceId; } + } + + /// + /// Gets the pin type. + /// + public string Type + { + get { return this.packagePin.Type.ToString(); } + } + + /// + /// Gets the gated version range (for Gating pins only). + /// + public string GatedVersion + { + get { return this.packagePin.GatedVersion; } + } + + /// + /// Gets the date the pin was added (UTC ISO 8601 string). + /// + public string DateAdded + { + get { return this.packagePin.DateAdded; } + } + + /// + /// Gets the optional user note for this pin. + /// + public string Note + { + get { return this.packagePin.Note; } + } + + /// + /// Gets a value indicating whether this pin applies to an installed package. + /// + public bool IsForInstalledPackage + { + get { return this.packagePin.IsForInstalledPackage; } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPinResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPinResult.cs new file mode 100644 index 0000000000..fdfed20f94 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPinResult.cs @@ -0,0 +1,62 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + + /// + /// PSPinResult wraps a PinPackageResult COM object for PowerShell output. + /// + public sealed class PSPinResult + { + private readonly PinPackageResult pinResult; + + /// + /// Initializes a new instance of the class. + /// + /// The PinPackageResult COM object. + internal PSPinResult(PinPackageResult pinResult) + { + this.pinResult = pinResult; + } + + /// + /// Gets the status of the pin operation. + /// + public string Status + { + get { return this.pinResult.Status.ToString(); } + } + + /// + /// Gets the extended error code of the pin operation. + /// + public Exception ExtendedErrorCode + { + get { return this.pinResult.ExtendedErrorCode; } + } + + /// + /// Returns whether the pin operation succeeded. + /// + /// True if the pin operation succeeded. + public bool Succeeded() + { + return this.pinResult.Status == PinResultStatus.Ok; + } + + /// + /// Returns a formatted error message. + /// + /// Error message string. + public string ErrorMessage() + { + return $"PinStatus: '{this.Status}' ExtendedError: '0x{this.ExtendedErrorCode.HResult:X8}'"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 index 2603df2cfa..71e981fd0f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 +++ b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 @@ -95,6 +95,10 @@ CmdletsToExport = @( 'Reset-WinGetSource' 'Export-WinGetPackage' 'Repair-WinGetPackage' + 'Get-WinGetPin' + 'Add-WinGetPin' + 'Remove-WinGetPin' + 'Reset-WinGetPin' ) # Variables to export from this module From 7ce3b272846e3e7e2e026924fc5826109e8ee768 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 13:00:18 -0500 Subject: [PATCH 06/60] Spelling --- .github/actions/spelling/expect.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 53175f0afa..8eb3002073 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -29,6 +29,7 @@ appdata appinstallertest applic appname +appone appshutdown APPTERMINATION archs @@ -339,6 +340,7 @@ megamorf microsoftentraid microsoftentraidforazureblobstorage midl +migratepintable minidump MINORVERSION missingdependency From 35322e72f6c01801074b4068f0d0164c1fcc6cd3 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 13:04:47 -0500 Subject: [PATCH 07/60] Inherit from 1.0 Interface --- .../Pinning_1_1/PinningIndexInterface.h | 6 ++--- .../Pinning_1_1/PinningIndexInterface_1_1.cpp | 24 ------------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h index 5e6e58c441..8e9a078d17 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "Microsoft/Schema/IPinningIndex.h" +#include "Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h" namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { - struct PinningIndexInterface : public IPinningIndex + struct PinningIndexInterface : public Pinning_V1_0::PinningIndexInterface { // Version 1.1 SQLite::Version GetVersion() const override; @@ -15,9 +15,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 private: SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; - SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; std::vector GetAllPins(SQLite::Connection& connection) override; - bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) override; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp index 0d65a8d3c0..247f945de0 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -2,7 +2,6 @@ // Licensed under the MIT License. #include "pch.h" #include "Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h" -#include "Microsoft/Schema/Pinning_1_0/PinTable.h" #include "Microsoft/Schema/Pinning_1_1/PinTable.h" namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 @@ -76,20 +75,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return { status, existingPinId.value() }; } - SQLite::rowid_t PinningIndexInterface::RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - auto existingPinId = GetExistingPinId(connection, pinKey); - - // If the pin doesn't exist, fail the remove - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_1"); - PinTable::RemovePinById(connection, existingPinId.value()); - - savepoint.Commit(); - return existingPinId.value(); - } - std::optional PinningIndexInterface::GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) { auto existingPinId = GetExistingPinId(connection, pinKey); @@ -106,13 +91,4 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { return PinTable::GetAllPins(connection); } - - bool PinningIndexInterface::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetpins_v1_1"); - bool result = PinTable::ResetAllPins(connection, sourceId); - savepoint.Commit(); - - return result; - } } From 236e667faa5eb4a2667bfcd941bd3e900327941e Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 13:11:17 -0500 Subject: [PATCH 08/60] Contract 29 is not released yet --- .../PackageManager.idl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 6a5d6096ae..822d9c037b 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment { - [contractversion(30)] // For version 1.30 + [contractversion(29)] // For version 1.29 apicontract WindowsPackageManagerContract{}; /// State of the install @@ -1680,7 +1680,7 @@ namespace Microsoft.Management.Deployment EditPackageCatalogResult EditPackageCatalog(EditPackageCatalogOptions options); } - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] { /// Get all pins across all sources. Windows.Foundation.Collections.IVectorView GetAllPins(); @@ -1705,7 +1705,7 @@ namespace Microsoft.Management.Deployment } /// IMPLEMENTATION NOTE: Pinning::PinType from AppInstaller::Pinning - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] enum PackagePinType { /// Unknown pin type or not pinned. @@ -1724,7 +1724,7 @@ namespace Microsoft.Management.Deployment }; /// IMPLEMENTATION NOTE: Pinning::Pin from AppInstaller::Pinning - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] runtimeclass PackagePin { /// The package ID that the pin applies to (for available-package pins) or the @@ -1752,7 +1752,7 @@ namespace Microsoft.Management.Deployment }; /// Options for adding or updating a package pin. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] runtimeclass PinPackageOptions { PinPackageOptions(); @@ -1779,7 +1779,7 @@ namespace Microsoft.Management.Deployment }; /// Status of a pin operation. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] enum PinResultStatus { Ok, @@ -1794,7 +1794,7 @@ namespace Microsoft.Management.Deployment }; /// Result of a pin operation. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] runtimeclass PinPackageResult { PinResultStatus Status { get; }; From 1c89957ccf526212db99d4b0c5339e9cd270b41e Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 20:48:52 -0500 Subject: [PATCH 09/60] Adddress comments about resource strings --- src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index bb3ea1e83d..a9c0f437a7 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1918,7 +1918,7 @@ Please specify one of them using the --source option to proceed. Table header for the version to which a package is pinned; meaning it should not update from that version. - Date added + Time Pinned Label shown in the pin show output for when the pin was added or last updated. @@ -1927,9 +1927,11 @@ Please specify one of them using the --source option to proceed. Show details about a pin + Short description of the 'winget pin show' subcommand, shown in help and usage text. - Show detailed information about a specific pin, including the package ID, version, type, date added, and any note stored with the pin. + Show detailed information about a specific pin, including the package identifier, version, type, date added, and any note stored with the pin. + Long description of the 'winget pin show' subcommand, shown in detailed help text. Date added: From fa617a69ed05f5cae38a60a53821fb7a88d4968a Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 20:52:18 -0500 Subject: [PATCH 10/60] Update comment to indicate the appropriate contract version --- src/Microsoft.Management.Deployment/PackageManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h index d4c2ed5397..f1086736fb 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.h +++ b/src/Microsoft.Management.Deployment/PackageManager.h @@ -54,7 +54,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::hstring Version() const; // Contract 28.0 winrt::Microsoft::Management::Deployment::EditPackageCatalogResult EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options); - // Contract 30.0 + // Contract 29.0 winrt::Windows::Foundation::Collections::IVectorView GetAllPins(); winrt::Windows::Foundation::Collections::IVectorView GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package); winrt::Microsoft::Management::Deployment::PinPackageResult PinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PinPackageOptions options); From 7f33f56e87cc726faa001f86cb8e4a92c63cffa0 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 21:04:08 -0500 Subject: [PATCH 11/60] Make methods public --- .../Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h index 8e9a078d17..1a13194076 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h @@ -11,8 +11,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Version GetVersion() const override; void CreateTables(SQLite::Connection& connection) override; bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) override; - - private: SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; From 3c7d4a6df75600516497a357c1f70bf56e03001c Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 21:11:25 -0500 Subject: [PATCH 12/60] Adjust how new schema is created --- src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp | 8 +------- src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h | 3 --- .../Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp | 6 +++--- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index c0b8a8150b..0bcddd1e62 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -185,12 +185,6 @@ namespace AppInstaller::Repository::Microsoft return m_interface->ResetAllPins(m_dbconn, sourceId); } - std::unique_ptr PinningIndex::CreateIPinningIndex() const - { - // Always return the latest interface; migration will handle version mismatches. - return std::make_unique(); - } - std::unique_ptr PinningIndex::CreateIPinningIndexForVersion(const SQLite::Version& version) { if (version == SQLite::Version{ 1, 0 }) @@ -212,7 +206,7 @@ namespace AppInstaller::Repository::Microsoft SQLiteStorageBase(target, disposition, std::move(indexFile)) { AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); - m_interface = CreateIPinningIndex(); + m_interface = CreateIPinningIndexForVersion(SQLite::Version::Latest()); if (m_version != m_interface->GetVersion()) { diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index 346766fd87..c015dd8366 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -67,9 +67,6 @@ namespace AppInstaller::Repository::Microsoft // Constructor used to create a new index. PinningIndex(const std::string& target, SQLite::Version version); - // Creates the IPinningIndex interface object for this version. - std::unique_ptr CreateIPinningIndex() const; - // Creates an IPinningIndex interface object for a specific version. static std::unique_ptr CreateIPinningIndexForVersion(const SQLite::Version& version); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp index 247f945de0..cd6bd5be73 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -29,9 +29,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 void PinningIndexInterface::CreateTables(SQLite::Connection& connection) { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_1"); - Pinning_V1_1::PinTable::Create(connection); - savepoint.Commit(); + Pinning_V1_0::PinningIndexInterface base; + base.CreateTables(connection); + MigrateFrom(connection, &base); } bool PinningIndexInterface::MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) From 99a876004aec9247e5bb46c78ae863e14bfa93f2 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 21:25:56 -0500 Subject: [PATCH 13/60] Reduce duplicated code through inheritance --- .../Microsoft/Schema/Pinning_1_0/PinTable.h | 9 ++- .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 74 ------------------- .../Microsoft/Schema/Pinning_1_1/PinTable.h | 19 +---- 3 files changed, 8 insertions(+), 94 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index 4bfafd616a..8deb47e6d1 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -13,9 +13,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // Get the table name. static std::string_view TableName(); - // Creates the table with named indices. - static void Create(SQLite::Connection& connection); - // Gets the row ID for the pin, if it exists. static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); @@ -39,5 +36,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // Resets all pins from a given source, or from all sources if none is specified. // Returns a value indicating whether there were any changes. static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); + + protected: + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + + friend struct PinningIndexInterface; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp index 88f819d9a7..56f1db51d1 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -49,40 +49,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 static constexpr std::string_view s_PinTable_Version_Column = "version"sv; static constexpr std::string_view s_PinTable_DateAdded_Column = "date_added"sv; static constexpr std::string_view s_PinTable_Note_Column = "note"sv; - static constexpr std::string_view s_PinTable_Index = "pin_index"sv; - - std::string_view PinTable::TableName() - { - return s_PinTable_Table_Name; - } - - void PinTable::Create(SQLite::Connection& connection) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_1"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); - - createTableBuilder.Column(ColumnBuilder(s_PinTable_PackageId_Column, Type::Text).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_SourceId_Column, Type::Text).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_Type_Column, Type::Int64).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_Version_Column, Type::Text).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_DateAdded_Column, Type::Text).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_Note_Column, Type::Text)); - - createTableBuilder.EndColumns(); - createTableBuilder.Execute(connection); - - // Create an index over the pairs package,source - StatementBuilder createIndexBuilder; - createIndexBuilder.CreateUniqueIndex(s_PinTable_Index).On(s_PinTable_Table_Name) - .Columns({ s_PinTable_PackageId_Column, s_PinTable_SourceId_Column }); - createIndexBuilder.Execute(connection); - - savepoint.Commit(); - } void PinTable::MigrateFrom1_0(SQLite::Connection& connection) { @@ -95,25 +61,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 addNote.Execute(); } - std::optional PinTable::GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) - .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) - .And(s_PinTable_SourceId_Column).Equals((std::string_view)pinKey.SourceId); - - SQLite::Statement select = builder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) { SQLite::Builder::StatementBuilder builder; @@ -165,13 +112,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return connection.GetChanges() != 0; } - void PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); - builder.Execute(connection); - } - std::optional PinTable::GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId) { SQLite::Builder::StatementBuilder builder; @@ -225,18 +165,4 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return pins; } - bool PinTable::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PinTable_Table_Name); - - if (!sourceId.empty()) - { - builder.Where(s_PinTable_SourceId_Column).Equals(sourceId); - } - - builder.Execute(connection); - - return connection.GetChanges() != 0; - } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h index b36c954bfd..dbd3a56638 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h @@ -4,24 +4,16 @@ #include #include #include "Microsoft/Schema/IPinningIndex.h" +#include "Microsoft/Schema/Pinning_1_0/PinTable.h" #include namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { - struct PinTable + struct PinTable : Pinning_V1_0::PinTable { - // Get the table name. - static std::string_view TableName(); - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection); - // Migrates an existing v1.0 pin table by adding the date_added and note columns. static void MigrateFrom1_0(SQLite::Connection& connection); - // Gets the row ID for the pin, if it exists. - static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); - // Adds a new pin. Returns the row ID of the added pin. static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); @@ -29,18 +21,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 // Returns a value indicating whether there were any changes. static bool UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); - // Removes a pin given its row ID. - static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); - // Gets a pin by its row ID if it exists. // Used for testing static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); // Gets all the currently existing pins. static std::vector GetAllPins(SQLite::Connection& connection); - - // Resets all pins from a given source, or from all sources if none is specified. - // Returns a value indicating whether there were any changes. - static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); }; } From e336194eb622a1fa4e6b7017a60971ea78013f0f Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 21:45:41 -0500 Subject: [PATCH 14/60] Use statement builder, savepoints, and tuples for ownership --- .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp index 56f1db51d1..800524dab2 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -9,32 +9,34 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { namespace { - std::optional GetPinFromRow( - std::string_view packageId, - std::string_view sourceId, - Pinning::PinType type, - std::string_view version, - std::string_view dateAdded, - std::optional note) + using PinRow = std::tuple>; + + std::optional GetPinFromRow(PinRow&& row) { + auto [packageId, sourceId, type, version, dateAdded, note] = std::move(row); + std::optional result; + Pinning::PinKey key; + key.PackageId = std::move(packageId); + key.SourceId = std::move(sourceId); + switch (type) { case Pinning::PinType::Blocking: - result = Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); + result = Pinning::Pin::CreateBlockingPin(std::move(key)); break; case Pinning::PinType::Pinning: - result = Pinning::Pin::CreatePinningPin({ packageId, sourceId }); + result = Pinning::Pin::CreatePinningPin(std::move(key)); break; case Pinning::PinType::Gating: - result = Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ version }); + result = Pinning::Pin::CreateGatingPin(std::move(key), Utility::GatedVersion{ std::move(version) }); break; default: return {}; } - result->SetDateAdded(std::string{ dateAdded }); + result->SetDateAdded(std::move(dateAdded)); result->SetNote(std::move(note)); return result; @@ -52,13 +54,19 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 void PinTable::MigrateFrom1_0(SQLite::Connection& connection) { - SQLite::Statement addDateAdded = SQLite::Statement::Create(connection, - "ALTER TABLE pin ADD COLUMN date_added TEXT NOT NULL DEFAULT ''"); - addDateAdded.Execute(); + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "migratepintable_v1_0_to_v1_1_pintable"); + + StatementBuilder addDateAdded; + addDateAdded.AlterTable(s_PinTable_Table_Name).Add(s_PinTable_DateAdded_Column, Type::Text).NotNull().Default("''"sv); + addDateAdded.Execute(connection); + + StatementBuilder addNote; + addNote.AlterTable(s_PinTable_Table_Name).Add(s_PinTable_Note_Column, Type::Text); + addNote.Execute(connection); - SQLite::Statement addNote = SQLite::Statement::Create(connection, - "ALTER TABLE pin ADD COLUMN note TEXT"); - addNote.Execute(); + savepoint.Commit(); } SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) @@ -74,11 +82,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 s_PinTable_DateAdded_Column, s_PinTable_Note_Column }) .Values( - (std::string_view)pinKey.PackageId, + pinKey.PackageId, pinKey.SourceId, pin.GetType(), pin.GetGatedVersion().ToString(), - (std::string_view)pin.GetDateAdded(), + pin.GetDateAdded(), pin.GetNote()); builder.Execute(connection); @@ -90,11 +98,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Builder::StatementBuilder builder; const auto& pinKey = pin.GetKey(); builder.Update(s_PinTable_Table_Name).Set() - .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) + .Column(s_PinTable_PackageId_Column).Equals(pinKey.PackageId) .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) .Column(s_PinTable_Type_Column).Equals(pin.GetType()) .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) - .Column(s_PinTable_DateAdded_Column).Equals((std::string_view)pin.GetDateAdded()); + .Column(s_PinTable_DateAdded_Column).Equals(pin.GetDateAdded()); // Use Unbound (= ?) for null note so SQLite stores NULL via = NULL, not the invalid SET syntax IS NULL. const auto& note = pin.GetNote(); @@ -131,9 +139,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return {}; } - auto [packageId, sourceId, pinType, gatedVersion, dateAdded, note] = - select.GetRow>(); - return GetPinFromRow(packageId, sourceId, pinType, gatedVersion, dateAdded, std::move(note)); + return GetPinFromRow(select.GetRow>()); } std::vector PinTable::GetAllPins(SQLite::Connection& connection) @@ -153,9 +159,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 std::vector pins; while (select.Step()) { - auto [packageId, sourceId, pinType, gatedVersion, dateAdded, note] = - select.GetRow>(); - auto pin = GetPinFromRow(packageId, sourceId, pinType, gatedVersion, dateAdded, std::move(note)); + auto pin = GetPinFromRow(select.GetRow>()); if (pin) { pins.push_back(std::move(pin.value())); From 0cb653b5eb38d27551480a0140491e1a79064f95 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 22:30:35 -0500 Subject: [PATCH 15/60] Use a time point instead of strings --- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 14 +++---- src/AppInstallerCLITests/PinFlow.cpp | 19 ++++----- src/AppInstallerCLITests/PinTestCommon.h | 16 ++++++++ src/AppInstallerCLITests/PinningIndex.cpp | 20 +++++----- .../Public/winget/Pin.h | 7 ++-- .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 39 +++++++++++++++---- .../PackageManager.cpp | 8 +--- .../PackageManager.idl | 4 +- .../PackagePin.cpp | 8 +++- .../PackagePin.h | 4 +- .../PSObjects/PSPackagePin.cs | 4 +- 11 files changed, 93 insertions(+), 50 deletions(-) create mode 100644 src/AppInstallerCLITests/PinTestCommon.h diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 91d863d5e9..3f7440e2a4 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -199,10 +199,7 @@ namespace AppInstaller::CLI::Workflow if (!pinsToAddOrUpdate.empty()) { - std::string dateAdded = Utility::TimePointToString( - std::chrono::system_clock::now(), - Utility::TimeFacet::Year | Utility::TimeFacet::Month | Utility::TimeFacet::Day | - Utility::TimeFacet::Hour | Utility::TimeFacet::Minute | Utility::TimeFacet::Second); + auto pinTime = std::chrono::system_clock::now(); std::optional note; if (context.Args.Contains(Execution::Args::Type::PinNote)) @@ -212,7 +209,7 @@ namespace AppInstaller::CLI::Workflow for (auto& pin : pinsToAddOrUpdate) { - pin.SetDateAdded(dateAdded); + pin.SetDateAdded(pinTime); pin.SetNote(note); pinningData.AddOrUpdatePin(pin); } @@ -435,9 +432,12 @@ namespace AppInstaller::CLI::Workflow // Date Added const auto& dateAdded = pin.GetDateAdded(); - if (!dateAdded.empty()) + if (dateAdded.has_value()) { - ShowSingleLineField(info, Resource::String::PinShowLabelDateAdded, Utility::LocIndView{ dateAdded }); + std::string dateAddedStr = Utility::TimePointToString(*dateAdded, + Utility::TimeFacet::Year | Utility::TimeFacet::Month | Utility::TimeFacet::Day | + Utility::TimeFacet::Hour | Utility::TimeFacet::Minute | Utility::TimeFacet::Second); + ShowSingleLineField(info, Resource::String::PinShowLabelDateAdded, Utility::LocIndView{ dateAddedStr }); } // Note (only shown if present) diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index bf7687191c..d998cdb586 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -3,6 +3,8 @@ #include "pch.h" #include "WorkflowCommon.h" #include "TestHooks.h" +#include "PinTestCommon.h" +#include #include #include #include @@ -14,7 +16,6 @@ using namespace TestCommon; using namespace AppInstaller::CLI; using namespace AppInstaller::CLI::Workflow; using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::Utility; using namespace AppInstaller::Pinning; using namespace AppInstaller::SQLite; @@ -218,7 +219,7 @@ TEST_CASE("PinFlow_Add_SetsDateAdded", "[PinFlow][workflow]") auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); auto pins = index.GetAllPins(); REQUIRE(pins.size() == 1); - REQUIRE_FALSE(pins[0].GetDateAdded().empty()); + REQUIRE(pins[0].GetDateAdded().has_value()); } TEST_CASE("PinFlow_Add_WithNote", "[PinFlow][workflow]") @@ -283,7 +284,7 @@ TEST_CASE("PinFlow_Show_NoMatch", "[PinFlow][workflow]") TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); Pin existingPin = Pin::CreateBlockingPin({ "SomePackage.Id", "sourceId" }); - existingPin.SetDateAdded("2026-01-15 10:00:00"); + existingPin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1000)); PopulatePinIndexForShow(indexFile.GetPath(), { existingPin }); std::ostringstream showOutput; @@ -304,7 +305,7 @@ TEST_CASE("PinFlow_Show_MatchById", "[PinFlow][workflow]") TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); Pin pin = Pin::CreateBlockingPin({ "MyApp.Package", "sourceId" }); - pin.SetDateAdded("2026-06-01 09:00:00"); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jun2026_01_0900)); pin.SetNote(std::string{ "keep this one" }); PopulatePinIndexForShow(indexFile.GetPath(), { pin }); @@ -319,7 +320,7 @@ TEST_CASE("PinFlow_Show_MatchById", "[PinFlow][workflow]") REQUIRE_FALSE(showContext.IsTerminated()); REQUIRE(showOutput.str().find("MyApp.Package") != std::string::npos); REQUIRE(showOutput.str().find("Blocking") != std::string::npos); - REQUIRE(showOutput.str().find("2026-06-01 09:00:00") != std::string::npos); + REQUIRE(showOutput.str().find("Date added:") != std::string::npos); REQUIRE(showOutput.str().find("keep this one") != std::string::npos); } @@ -329,7 +330,7 @@ TEST_CASE("PinFlow_Show_MatchByQuery", "[PinFlow][workflow]") TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); Pin pin = Pin::CreatePinningPin({ "Contoso.AppOne", "sourceId" }); - pin.SetDateAdded("2026-03-10 12:00:00"); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Mar2026_10_1200)); PopulatePinIndexForShow(indexFile.GetPath(), { pin }); std::ostringstream showOutput; @@ -352,10 +353,10 @@ TEST_CASE("PinFlow_Show_ExactMatch", "[PinFlow][workflow]") // Two pins sharing a prefix Pin pinA = Pin::CreateBlockingPin({ "Vendor.App", "src" }); - pinA.SetDateAdded("2026-01-01 00:00:00"); + pinA.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_01_0000)); Pin pinB = Pin::CreateBlockingPin({ "Vendor.AppExtra", "src" }); - pinB.SetDateAdded("2026-01-01 00:00:00"); + pinB.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_01_0000)); PopulatePinIndexForShow(indexFile.GetPath(), { pinA, pinB }); @@ -381,7 +382,7 @@ TEST_CASE("PinFlow_Show_NoNote_DoesNotShowNoteLabel", "[PinFlow][workflow]") TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); Pin pin = Pin::CreatePinningPin({ "NoNote.Package", "src" }); - pin.SetDateAdded("2026-05-01 08:00:00"); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::May2026_01_0800)); // note intentionally not set PopulatePinIndexForShow(indexFile.GetPath(), { pin }); diff --git a/src/AppInstallerCLITests/PinTestCommon.h b/src/AppInstallerCLITests/PinTestCommon.h new file mode 100644 index 0000000000..982737dc3b --- /dev/null +++ b/src/AppInstallerCLITests/PinTestCommon.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +// Named UTC epoch constants (seconds since Unix epoch) shared across pinning tests. +// Use with Utility::ConvertUnixEpochToSystemClock to obtain a time_point. +namespace PinTestEpoch +{ + constexpr int64_t Jan2026_01_0000 = 1767225600LL; // 2026-01-01 00:00:00 UTC + constexpr int64_t Jan2026_15_1000 = 1768471200LL; // 2026-01-15 10:00:00 UTC + constexpr int64_t Jan2026_15_1030 = 1768473000LL; // 2026-01-15 10:30:00 UTC + constexpr int64_t Jan2026_15_1100 = 1768474800LL; // 2026-01-15 11:00:00 UTC + constexpr int64_t Mar2026_10_1200 = 1773144000LL; // 2026-03-10 12:00:00 UTC + constexpr int64_t May2026_01_0800 = 1777622400LL; // 2026-05-01 08:00:00 UTC + constexpr int64_t Jun2026_01_0900 = 1780304400LL; // 2026-06-01 09:00:00 UTC +} diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index 00ea3945d2..d3127d8d9e 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -2,6 +2,8 @@ // Licensed under the MIT License. #include "pch.h" #include "TestCommon.h" +#include "PinTestCommon.h" +#include #include #include #include @@ -181,7 +183,7 @@ TEST_CASE("PinningIndex_V1_1_AddPin_WithDateAndNote", "[pinningIndex]") INFO("Using temporary file named: " << tempFile.GetPath()); Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); - pin.SetDateAdded("2026-01-15 10:30:00"); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); pin.SetNote(std::string{ "test note" }); { @@ -196,7 +198,7 @@ TEST_CASE("PinningIndex_V1_1_AddPin_WithDateAndNote", "[pinningIndex]") REQUIRE(pinFromIndex.has_value()); REQUIRE(pinFromIndex->GetType() == PinType::Blocking); REQUIRE(pinFromIndex->GetKey().PackageId == "pkgId"); - REQUIRE(pinFromIndex->GetDateAdded() == "2026-01-15 10:30:00"); + REQUIRE(pinFromIndex->GetDateAdded() == AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); REQUIRE(pinFromIndex->GetNote().has_value()); REQUIRE(pinFromIndex->GetNote().value() == "test note"); } @@ -208,7 +210,7 @@ TEST_CASE("PinningIndex_V1_1_AddPin_WithoutNote", "[pinningIndex]") INFO("Using temporary file named: " << tempFile.GetPath()); Pin pin = Pin::CreatePinningPin({ "pkgId", "sourceId" }); - pin.SetDateAdded("2026-01-15 10:30:00"); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); // note intentionally left unset { @@ -221,7 +223,7 @@ TEST_CASE("PinningIndex_V1_1_AddPin_WithoutNote", "[pinningIndex]") auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); REQUIRE(pinFromIndex.has_value()); - REQUIRE(pinFromIndex->GetDateAdded() == "2026-01-15 10:30:00"); + REQUIRE(pinFromIndex->GetDateAdded() == AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); REQUIRE_FALSE(pinFromIndex->GetNote().has_value()); } } @@ -232,11 +234,11 @@ TEST_CASE("PinningIndex_V1_1_AddUpdateRemove", "[pinningIndex]") INFO("Using temporary file named: " << tempFile.GetPath()); Pin pin = Pin::CreateBlockingPin({ "pkgId", "srcId" }); - pin.SetDateAdded("2026-01-15 10:00:00"); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1000)); pin.SetNote(std::string{ "original note" }); Pin updatedPin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); - updatedPin.SetDateAdded("2026-01-15 11:00:00"); + updatedPin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1100)); updatedPin.SetNote(std::string{ "updated note" }); { @@ -251,7 +253,7 @@ TEST_CASE("PinningIndex_V1_1_AddUpdateRemove", "[pinningIndex]") auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); REQUIRE(pinFromIndex.has_value()); REQUIRE(pinFromIndex->GetType() == PinType::Gating); - REQUIRE(pinFromIndex->GetDateAdded() == "2026-01-15 11:00:00"); + REQUIRE(pinFromIndex->GetDateAdded() == AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1100)); REQUIRE(pinFromIndex->GetNote().has_value()); REQUIRE(pinFromIndex->GetNote().value() == "updated note"); } @@ -294,8 +296,8 @@ TEST_CASE("PinningIndex_MigrateFrom_1_0_to_1_1", "[pinningIndex]") for (const auto& pin : pins) { - // Migration adds columns with DEFAULT '' and NULL respectively - REQUIRE(pin.GetDateAdded() == ""); + // Migration adds nullable INTEGER and NULL respectively; existing rows get NULL for both + REQUIRE_FALSE(pin.GetDateAdded().has_value()); REQUIRE_FALSE(pin.GetNote().has_value()); } diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index 752eb02d44..247b9d7176 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -3,6 +3,7 @@ #pragma once #include "winget/Manifest.h" #include "AppInstallerVersions.h" +#include #include #include @@ -99,10 +100,10 @@ namespace AppInstaller::Pinning PinType GetType() const { return m_type; } const PinKey& GetKey() const { return m_key; } const Utility::GatedVersion& GetGatedVersion() const { return m_gatedVersion; } - const std::string& GetDateAdded() const { return m_dateAdded; } + const std::optional& GetDateAdded() const { return m_dateAdded; } const std::optional& GetNote() const { return m_note; } - void SetDateAdded(std::string dateAdded) { m_dateAdded = std::move(dateAdded); } + void SetDateAdded(std::optional dateAdded) { m_dateAdded = std::move(dateAdded); } void SetNote(std::optional note) { m_note = std::move(note); } bool operator==(const Pin& other) const; @@ -121,7 +122,7 @@ namespace AppInstaller::Pinning PinType m_type = PinType::Unknown; PinKey m_key; Utility::GatedVersion m_gatedVersion; - std::string m_dateAdded; + std::optional m_dateAdded; std::optional m_note; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp index 800524dab2..4a49697add 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "PinTable.h" +#include #include #include "Microsoft/Schema/IPinningIndex.h" @@ -9,11 +10,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { namespace { - using PinRow = std::tuple>; + using PinRow = std::tuple, std::optional>; std::optional GetPinFromRow(PinRow&& row) { - auto [packageId, sourceId, type, version, dateAdded, note] = std::move(row); + auto [packageId, sourceId, type, version, epochOpt, note] = std::move(row); std::optional result; @@ -36,6 +37,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return {}; } + std::optional dateAdded; + if (epochOpt.has_value()) + { + dateAdded = Utility::ConvertUnixEpochToSystemClock(*epochOpt); + } + result->SetDateAdded(std::move(dateAdded)); result->SetNote(std::move(note)); @@ -59,7 +66,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "migratepintable_v1_0_to_v1_1_pintable"); StatementBuilder addDateAdded; - addDateAdded.AlterTable(s_PinTable_Table_Name).Add(s_PinTable_DateAdded_Column, Type::Text).NotNull().Default("''"sv); + addDateAdded.AlterTable(s_PinTable_Table_Name).Add(s_PinTable_DateAdded_Column, Type::Integer); addDateAdded.Execute(connection); StatementBuilder addNote; @@ -73,6 +80,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { SQLite::Builder::StatementBuilder builder; const auto& pinKey = pin.GetKey(); + + const auto& dateAdded = pin.GetDateAdded(); + std::optional epochOpt = dateAdded.has_value() + ? std::optional{ Utility::ConvertSystemClockToUnixEpoch(*dateAdded) } + : std::nullopt; + builder.InsertInto(s_PinTable_Table_Name) .Columns({ s_PinTable_PackageId_Column, @@ -86,7 +99,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 pinKey.SourceId, pin.GetType(), pin.GetGatedVersion().ToString(), - pin.GetDateAdded(), + epochOpt, pin.GetNote()); builder.Execute(connection); @@ -101,8 +114,18 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 .Column(s_PinTable_PackageId_Column).Equals(pinKey.PackageId) .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) .Column(s_PinTable_Type_Column).Equals(pin.GetType()) - .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) - .Column(s_PinTable_DateAdded_Column).Equals(pin.GetDateAdded()); + .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()); + + // Use Unbound (= ?) for null date so SQLite stores NULL via = NULL, not the invalid SET syntax IS NULL. + const auto& dateAdded = pin.GetDateAdded(); + if (dateAdded.has_value()) + { + builder.Column(s_PinTable_DateAdded_Column).Equals(Utility::ConvertSystemClockToUnixEpoch(*dateAdded)); + } + else + { + builder.Column(s_PinTable_DateAdded_Column).Equals(SQLite::Builder::Unbound); + } // Use Unbound (= ?) for null note so SQLite stores NULL via = NULL, not the invalid SET syntax IS NULL. const auto& note = pin.GetNote(); @@ -139,7 +162,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return {}; } - return GetPinFromRow(select.GetRow>()); + return GetPinFromRow(select.GetRow, std::optional>()); } std::vector PinTable::GetAllPins(SQLite::Connection& connection) @@ -159,7 +182,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 std::vector pins; while (select.Step()) { - auto pin = GetPinFromRow(select.GetRow>()); + auto pin = GetPinFromRow(select.GetRow, std::optional>()); if (pin) { pins.push_back(std::move(pin.value())); diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 5516ebefd6..cdb43a6479 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1658,11 +1658,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; - std::string dateAdded = ::AppInstaller::Utility::TimePointToString( - std::chrono::system_clock::now(), - ::AppInstaller::Utility::TimeFacet::Year | ::AppInstaller::Utility::TimeFacet::Month | - ::AppInstaller::Utility::TimeFacet::Day | ::AppInstaller::Utility::TimeFacet::Hour | - ::AppInstaller::Utility::TimeFacet::Minute | ::AppInstaller::Utility::TimeFacet::Second); + auto pinTime = std::chrono::system_clock::now(); std::optional note; if (!options.Note().empty()) @@ -1680,7 +1676,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation THROW_HR_IF(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS, !options.Force()); } - newPin.SetDateAdded(dateAdded); + newPin.SetDateAdded(pinTime); newPin.SetNote(note); pinningData.AddOrUpdatePin(newPin); } diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 822d9c037b..50f8b86c9e 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1740,8 +1740,8 @@ namespace Microsoft.Management.Deployment /// The gated version range. Only meaningful when Type is Gating; empty otherwise. String GatedVersion { get; }; - /// The UTC date/time when the pin was added (ISO 8601 format). - String DateAdded { get; }; + /// The UTC date/time when the pin was added. Null if not set. + Windows.Foundation.IReference DateAdded { get; }; /// Optional note associated with the pin. May be empty. String Note { get; }; diff --git a/src/Microsoft.Management.Deployment/PackagePin.cpp b/src/Microsoft.Management.Deployment/PackagePin.cpp index dd06b89ad1..b419320926 100644 --- a/src/Microsoft.Management.Deployment/PackagePin.cpp +++ b/src/Microsoft.Management.Deployment/PackagePin.cpp @@ -33,7 +33,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation m_sourceId = winrt::to_hstring(pin.GetKey().SourceId); m_type = ConvertPinType(pin.GetType()); m_gatedVersion = winrt::to_hstring(pin.GetGatedVersion().ToString()); - m_dateAdded = winrt::to_hstring(pin.GetDateAdded()); + const auto& dateAdded = pin.GetDateAdded(); + if (dateAdded.has_value()) + { + m_dateAdded = winrt::clock::from_sys(*dateAdded); + } m_note = pin.GetNote() ? winrt::to_hstring(*pin.GetNote()) : hstring{}; m_isForInstalledPackage = pin.GetKey().IsForInstalled(); } @@ -58,7 +62,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation return m_gatedVersion; } - hstring PackagePin::DateAdded() + winrt::Windows::Foundation::IReference PackagePin::DateAdded() { return m_dateAdded; } diff --git a/src/Microsoft.Management.Deployment/PackagePin.h b/src/Microsoft.Management.Deployment/PackagePin.h index 2d3c0f37ff..384170843e 100644 --- a/src/Microsoft.Management.Deployment/PackagePin.h +++ b/src/Microsoft.Management.Deployment/PackagePin.h @@ -18,7 +18,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation hstring SourceId(); winrt::Microsoft::Management::Deployment::PackagePinType Type(); hstring GatedVersion(); - hstring DateAdded(); + winrt::Windows::Foundation::IReference DateAdded(); hstring Note(); bool IsForInstalledPackage(); @@ -28,7 +28,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation hstring m_sourceId; winrt::Microsoft::Management::Deployment::PackagePinType m_type = winrt::Microsoft::Management::Deployment::PackagePinType::Unknown; hstring m_gatedVersion; - hstring m_dateAdded; + winrt::Windows::Foundation::IReference m_dateAdded{ nullptr }; hstring m_note; bool m_isForInstalledPackage = false; #endif diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs index 48ae65c8d9..7bc288acbf 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs @@ -57,9 +57,9 @@ public string GatedVersion } /// - /// Gets the date the pin was added (UTC ISO 8601 string). + /// Gets the UTC date/time when the pin was added. Null if not set. /// - public string DateAdded + public DateTimeOffset? DateAdded { get { return this.packagePin.DateAdded; } } From 29d62037415100d64c3e238dea2914d936ee65f5 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 22:33:05 -0500 Subject: [PATCH 16/60] Guard against non-existent source --- src/Microsoft.Management.Deployment/PackageManager.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index cdb43a6479..a34ddc5180 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1737,10 +1737,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation if (!sourceName.empty()) { auto matchingSource = GetMatchingSource(winrt::to_string(sourceName)); - if (matchingSource.has_value()) - { - sourceId = matchingSource->Identifier; - } + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !matchingSource.has_value()); + + sourceId = matchingSource->Identifier; } auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; From 184cc7abcc0745556cfb1cbf15ba1edaaaf38bf4 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 22:34:15 -0500 Subject: [PATCH 17/60] Cache IsPinned --- .../PSObjects/PSInstalledCatalogPackage.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs index d053cc204d..f8ade5e2bf 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs @@ -15,6 +15,8 @@ namespace Microsoft.WinGet.Client.Engine.PSObjects /// public sealed class PSInstalledCatalogPackage : PSCatalogPackage { + private bool? isPinned; + /// /// Initializes a new instance of the class. /// @@ -37,7 +39,15 @@ public string InstalledVersion /// public bool IsPinned { - get { return PackageManagerWrapper.Instance.GetPins(this.CatalogPackageCOM).Count > 0; } + get + { + if (!this.isPinned.HasValue) + { + this.isPinned = PackageManagerWrapper.Instance.GetPins(this.CatalogPackageCOM).Count > 0; + } + + return this.isPinned.Value; + } } /// From 952e56ee06c1a466847c1693f6982390b07fd918 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 22:36:56 -0500 Subject: [PATCH 18/60] Merge default pin add behavior into single test --- src/AppInstallerCLITests/PinFlow.cpp | 43 ++-------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index d998cdb586..baaaf7752a 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -43,6 +43,8 @@ TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") REQUIRE(pins[0].GetGatedVersion().ToString() == ""); REQUIRE(pins[0].GetKey().PackageId == "AppInstallerCliTest.TestExeInstaller"); REQUIRE(pins[0].GetKey().SourceId == "*TestSource"); + REQUIRE(pins[0].GetDateAdded().has_value()); + REQUIRE_FALSE(pins[0].GetNote().has_value()); std::ostringstream pinListOutput; TestContext listContext{ pinListOutput, std::cin }; @@ -201,27 +203,6 @@ TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") REQUIRE(pinResetOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); } -TEST_CASE("PinFlow_Add_SetsDateAdded", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinAddOutput; - TestContext addContext{ pinAddOutput, std::cin }; - OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); - addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - addContext.Args.AddArg(Execution::Args::Type::BlockingPin); - - PinAddCommand pinAdd({}); - pinAdd.Execute(addContext); - INFO(pinAddOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetDateAdded().has_value()); -} - TEST_CASE("PinFlow_Add_WithNote", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); @@ -244,26 +225,6 @@ TEST_CASE("PinFlow_Add_WithNote", "[PinFlow][workflow]") REQUIRE(pins[0].GetNote().value() == "my test note"); } -TEST_CASE("PinFlow_Add_WithoutNote", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinAddOutput; - TestContext addContext{ pinAddOutput, std::cin }; - OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); - addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - PinAddCommand pinAdd({}); - pinAdd.Execute(addContext); - INFO(pinAddOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE_FALSE(pins[0].GetNote().has_value()); -} - // Helper: Creates a v1.1 pinning index at the given path and adds the provided pins directly. // Each pin should already have date_added and note set as desired. namespace From e823b75f8433a975bb71cccfbc33425e9a5e21df Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 22:55:02 -0500 Subject: [PATCH 19/60] Ensure table is not created in a partial state --- .../Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp index cd6bd5be73..606b51993f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -29,9 +29,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 void PinningIndexInterface::CreateTables(SQLite::Connection& connection) { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_1"); Pinning_V1_0::PinningIndexInterface base; base.CreateTables(connection); MigrateFrom(connection, &base); + savepoint.Commit(); } bool PinningIndexInterface::MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) From 195846f652a4f9dc0005d716bace56b5da816dbe Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 22:59:14 -0500 Subject: [PATCH 20/60] Add an alternate function for optional parms --- .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 32 ++++++------------- .../Public/winget/SQLiteStatementBuilder.h | 18 +++++++++++ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp index 4a49697add..523c5a2938 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -110,33 +110,19 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { SQLite::Builder::StatementBuilder builder; const auto& pinKey = pin.GetKey(); + + const auto& dateAdded = pin.GetDateAdded(); + std::optional epochOpt = dateAdded.has_value() + ? std::optional{ Utility::ConvertSystemClockToUnixEpoch(*dateAdded) } + : std::nullopt; + builder.Update(s_PinTable_Table_Name).Set() .Column(s_PinTable_PackageId_Column).Equals(pinKey.PackageId) .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) .Column(s_PinTable_Type_Column).Equals(pin.GetType()) - .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()); - - // Use Unbound (= ?) for null date so SQLite stores NULL via = NULL, not the invalid SET syntax IS NULL. - const auto& dateAdded = pin.GetDateAdded(); - if (dateAdded.has_value()) - { - builder.Column(s_PinTable_DateAdded_Column).Equals(Utility::ConvertSystemClockToUnixEpoch(*dateAdded)); - } - else - { - builder.Column(s_PinTable_DateAdded_Column).Equals(SQLite::Builder::Unbound); - } - - // Use Unbound (= ?) for null note so SQLite stores NULL via = NULL, not the invalid SET syntax IS NULL. - const auto& note = pin.GetNote(); - if (note.has_value()) - { - builder.Column(s_PinTable_Note_Column).Equals(note.value()); - } - else - { - builder.Column(s_PinTable_Note_Column).Equals(SQLite::Builder::Unbound); - } + .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) + .Column(s_PinTable_DateAdded_Column).AssignValue(epochOpt) + .Column(s_PinTable_Note_Column).AssignValue(pin.GetNote()); builder.Where(SQLite::RowIDName).Equals(pinId); builder.Execute(connection); diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h index 7de19998d7..c013b7ce9c 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h @@ -278,6 +278,24 @@ namespace AppInstaller::SQLite::Builder return IsNull(); } } + + // Assigns a value using "= ?" binding semantics. When the optional is empty, binds NULL + // via the "= ?" parameter rather than producing "IS NULL". Use this instead of + // Equals(optional) in UPDATE SET clauses, where "IS NULL" is invalid SQL. + template + StatementBuilder& AssignValue(const std::optional& value) + { + if (value) + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), value.value()); + } + else + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), nullptr); + } + return *this; + } + // The optional index value can be used to specify the parameter index. StatementBuilder& Equals(details::unbound_t, std::optional index = {}); StatementBuilder& Equals(std::nullptr_t); From 0468b3458529cb9ebe119a11434bec2461f8af72 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 23:08:06 -0500 Subject: [PATCH 21/60] Move to PackageCatalogReference --- .../PackageManager.cpp | 9 +++------ src/Microsoft.Management.Deployment/PackageManager.h | 2 +- .../PackageManager.idl | 6 +++--- .../Commands/ResetPinCommand.cs | 11 ++++++++++- .../Helpers/PackageManagerWrapper.cs | 6 +++--- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index a34ddc5180..73a68965ac 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1724,7 +1724,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation return MakePinPackageResult(terminationHR); } - winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::ResetAllPins(winrt::hstring const& sourceName) + winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::ResetAllPins(winrt::Microsoft::Management::Deployment::PackageCatalogReference packageCatalogReference) { LogStartupIfApplicable(); @@ -1734,12 +1734,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); std::string sourceId; - if (!sourceName.empty()) + if (packageCatalogReference) { - auto matchingSource = GetMatchingSource(winrt::to_string(sourceName)); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !matchingSource.has_value()); - - sourceId = matchingSource->Identifier; + sourceId = winrt::to_string(packageCatalogReference.Info().Id()); } auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h index f1086736fb..a61057fc28 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.h +++ b/src/Microsoft.Management.Deployment/PackageManager.h @@ -59,7 +59,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Windows::Foundation::Collections::IVectorView GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package); winrt::Microsoft::Management::Deployment::PinPackageResult PinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PinPackageOptions options); winrt::Microsoft::Management::Deployment::PinPackageResult UnpinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package); - winrt::Microsoft::Management::Deployment::PinPackageResult ResetAllPins(winrt::hstring const& sourceName); + winrt::Microsoft::Management::Deployment::PinPackageResult ResetAllPins(winrt::Microsoft::Management::Deployment::PackageCatalogReference packageCatalogReference); }; #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 50f8b86c9e..6a6340b9a4 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1698,9 +1698,9 @@ namespace Microsoft.Management.Deployment /// Returns PackagePinNotFound if no pin exists for the package. PinPackageResult UnpinPackage(CatalogPackage package); - /// Reset (remove) all pins. If sourceName is non-empty, only pins from that - /// catalog source are removed; otherwise all pins are removed. - PinPackageResult ResetAllPins(String sourceName); + /// Reset (remove) all pins. If packageCatalogReference is non-null, only pins from that + /// catalog are removed; otherwise all pins are removed. + PinPackageResult ResetAllPins(PackageCatalogReference packageCatalogReference); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/ResetPinCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/ResetPinCommand.cs index b25b9871e4..d5f10e5144 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/ResetPinCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/ResetPinCommand.cs @@ -7,7 +7,9 @@ namespace Microsoft.WinGet.Client.Engine.Commands { using System.Management.Automation; + using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; using Microsoft.WinGet.Client.Engine.Helpers; using Microsoft.WinGet.Client.Engine.PSObjects; using Microsoft.WinGet.Common.Command; @@ -32,8 +34,15 @@ public ResetPinCommand(PSCmdlet psCmdlet) /// The source name to scope the reset. Pass null or empty to reset all sources. public void Reset(string sourceName) { + PackageCatalogReference? catalogReference = null; + if (!string.IsNullOrEmpty(sourceName)) + { + catalogReference = PackageManagerWrapper.Instance.GetPackageCatalogByName(sourceName) + ?? throw new InvalidSourceException(sourceName); + } + var result = this.Execute( - () => PackageManagerWrapper.Instance.ResetAllPins(sourceName ?? string.Empty)); + () => PackageManagerWrapper.Instance.ResetAllPins(catalogReference)); this.Write(StreamType.Object, new PSPinResult(result)); } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs index aedf6c9402..0d1c593c34 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs @@ -187,12 +187,12 @@ public PinPackageResult UnpinPackage(CatalogPackage package) /// /// Wrapper for ResetAllPins. /// - /// The source name to reset pins for. Pass empty string to reset all. + /// The catalog reference to reset pins for. Pass null to reset all. /// A PinPackageResult. - public PinPackageResult ResetAllPins(string sourceName) + public PinPackageResult ResetAllPins(PackageCatalogReference? packageCatalogReference) { return this.Execute( - () => this.packageManager.ResetAllPins(sourceName), + () => this.packageManager.ResetAllPins(packageCatalogReference!), false); } From 4bfde99e7cac82fefad950f5093d982d7dca1bf0 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 23:10:53 -0500 Subject: [PATCH 22/60] Move functionality to converters --- .../Converters.cpp | 17 +++++++++++++++ .../Converters.h | 2 ++ .../PackagePin.cpp | 21 +------------------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp index bbd82c6e2e..da6ae41e91 100644 --- a/src/Microsoft.Management.Deployment/Converters.cpp +++ b/src/Microsoft.Management.Deployment/Converters.cpp @@ -579,4 +579,21 @@ namespace winrt::Microsoft::Management::Deployment::implementation default: return AppInstaller::Manifest::PlatformEnum::Unknown; } } + + winrt::Microsoft::Management::Deployment::PackagePinType ConvertPinType(::AppInstaller::Pinning::PinType type) + { + switch (type) + { + case ::AppInstaller::Pinning::PinType::PinnedByManifest: + return winrt::Microsoft::Management::Deployment::PackagePinType::PinnedByManifest; + case ::AppInstaller::Pinning::PinType::Pinning: + return winrt::Microsoft::Management::Deployment::PackagePinType::Pinning; + case ::AppInstaller::Pinning::PinType::Gating: + return winrt::Microsoft::Management::Deployment::PackagePinType::Gating; + case ::AppInstaller::Pinning::PinType::Blocking: + return winrt::Microsoft::Management::Deployment::PackagePinType::Blocking; + default: + return winrt::Microsoft::Management::Deployment::PackagePinType::Unknown; + } + } } diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index 888bc72ff6..903a77e61e 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -3,6 +3,7 @@ #pragma once #include "PackageMatchFilter.g.h" #include +#include #include #include #include @@ -35,6 +36,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult); winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult); winrt::Microsoft::Management::Deployment::PinResultStatus GetPinOperationStatus(winrt::hresult hresult); + winrt::Microsoft::Management::Deployment::PackagePinType ConvertPinType(::AppInstaller::Pinning::PinType type); ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(winrt::Microsoft::Management::Deployment::WindowsPlatform value); #define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_, _downloadResultStatus_, _repairResultStatus_) \ diff --git a/src/Microsoft.Management.Deployment/PackagePin.cpp b/src/Microsoft.Management.Deployment/PackagePin.cpp index b419320926..d0b649da89 100644 --- a/src/Microsoft.Management.Deployment/PackagePin.cpp +++ b/src/Microsoft.Management.Deployment/PackagePin.cpp @@ -3,30 +3,11 @@ #include "pch.h" #include "PackagePin.h" #include "PackagePin.g.cpp" +#include "Converters.h" #include namespace winrt::Microsoft::Management::Deployment::implementation { - namespace - { - winrt::Microsoft::Management::Deployment::PackagePinType ConvertPinType(::AppInstaller::Pinning::PinType type) - { - switch (type) - { - case ::AppInstaller::Pinning::PinType::PinnedByManifest: - return winrt::Microsoft::Management::Deployment::PackagePinType::PinnedByManifest; - case ::AppInstaller::Pinning::PinType::Pinning: - return winrt::Microsoft::Management::Deployment::PackagePinType::Pinning; - case ::AppInstaller::Pinning::PinType::Gating: - return winrt::Microsoft::Management::Deployment::PackagePinType::Gating; - case ::AppInstaller::Pinning::PinType::Blocking: - return winrt::Microsoft::Management::Deployment::PackagePinType::Blocking; - default: - return winrt::Microsoft::Management::Deployment::PackagePinType::Unknown; - } - } - } - void PackagePin::Initialize(const ::AppInstaller::Pinning::Pin& pin) { m_packageId = winrt::to_hstring(pin.GetKey().PackageId); From 243798b67b536a7b9b03fc64d2be9750054f7fc0 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 23:17:01 -0500 Subject: [PATCH 23/60] Remove version comment It is no longer needed since the interface surface is so small --- .../Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h index 1a13194076..57ba50cc7d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h @@ -7,7 +7,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { struct PinningIndexInterface : public Pinning_V1_0::PinningIndexInterface { - // Version 1.1 SQLite::Version GetVersion() const override; void CreateTables(SQLite::Connection& connection) override; bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) override; From 4b7b8efdd7b7375d6746f1732255fe0d1fa94c64 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 23:20:44 -0500 Subject: [PATCH 24/60] Update filters so 1.1 schema is visible --- .../AppInstallerRepositoryCore.vcxproj.filters | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index d877dedeab..b5381284b1 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -82,6 +82,9 @@ {f05e19bb-2161-4ab0-9d04-2dfa2d3eb3c6} + + {a3e2f7c1-5d84-4b9e-8f02-1c6d3a7b0e45} + {21da32f5-b918-436e-96a9-465525f90259} @@ -375,6 +378,12 @@ Microsoft\Schema\Pinning_1_0 + + Microsoft\Schema\Pinning_1_1 + + + Microsoft\Schema\Pinning_1_1 + Public\winget @@ -695,6 +704,9 @@ Microsoft\Schema\Pinning_1_0 + + Microsoft\Schema\Pinning_1_1 + Source Files @@ -728,6 +740,9 @@ Microsoft\Schema\Pinning_1_0 + + Microsoft\Schema\Pinning_1_1 + Rest\Schema\1_7 From e93e3d1f041c63a83299b3640d49eda9b686cf01 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 23:29:04 -0500 Subject: [PATCH 25/60] Create the correct interface and migrate only if newer --- .../Microsoft/PinningIndex.cpp | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index 0bcddd1e62..e9081b3c6a 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -206,27 +206,28 @@ namespace AppInstaller::Repository::Microsoft SQLiteStorageBase(target, disposition, std::move(indexFile)) { AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); - m_interface = CreateIPinningIndexForVersion(SQLite::Version::Latest()); - if (m_version != m_interface->GetVersion()) + // Create the correct interface for the stored schema version. + m_interface = CreateIPinningIndexForVersion(m_version); + + if (disposition == SQLiteStorageBase::OpenDisposition::ReadWrite) { - if (disposition == SQLiteStorageBase::OpenDisposition::ReadWrite) - { - // Attempt to migrate from the stored version to the current version. - AICLI_LOG(Repo, Info, << "Attempting to migrate Pinning Index from [" << m_version << "] to [" << m_interface->GetVersion() << "]"); + // For writable opens, create a latest interface and migrate if the stored version is older. + auto latestInterface = CreateIPinningIndexForVersion(SQLite::Version::Latest()); - // Create an interface representing the existing (older) schema so MigrateFrom can inspect it. - std::unique_ptr oldInterface = CreateIPinningIndexForVersion(m_version); + if (m_version != latestInterface->GetVersion()) + { + AICLI_LOG(Repo, Info, << "Attempting to migrate Pinning Index from [" << m_version << "] to [" << latestInterface->GetVersion() << "]"); SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_migrate"); - bool migrated = m_interface->MigrateFrom(m_dbconn, oldInterface.get()); + bool migrated = latestInterface->MigrateFrom(m_dbconn, m_interface.get()); if (migrated) { - m_interface->GetVersion().SetSchemaVersion(m_dbconn); + latestInterface->GetVersion().SetSchemaVersion(m_dbconn); SetLastWriteTime(); savepoint.Commit(); - m_version = m_interface->GetVersion(); + m_version = latestInterface->GetVersion(); AICLI_LOG(Repo, Info, << "Migration successful"); } else @@ -235,12 +236,8 @@ namespace AppInstaller::Repository::Microsoft THROW_HR(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX); } } - else - { - // Read-only open: use an interface matching the stored schema version to avoid querying missing columns. - AICLI_LOG(Repo, Info, << "Read-only open with older schema [" << m_version << "]; using compatible interface"); - m_interface = CreateIPinningIndexForVersion(m_version); - } + + m_interface = std::move(latestInterface); } } From ae87ff17b457e5233029e2121ef53df377d5989f Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 23:31:40 -0500 Subject: [PATCH 26/60] Use correct version info --- src/Microsoft.Management.Deployment/PackageManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 73a68965ac..77c86b54f5 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1525,7 +1525,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation auto versionInfo = package.GetPackageVersionInfo(versionId); if (versionInfo) { - std::string packageId = winrt::to_string(package.Id()); + std::string packageId = winrt::to_string(versionInfo.Id()); std::string sourceId = winrt::to_string(versionInfo.PackageCatalog().Info().Id()); if (!packageId.empty() && !sourceId.empty()) { From 917db77865b6e46143eae516cbd9a15a2be95a64 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 23:35:00 -0500 Subject: [PATCH 27/60] Move invariants outside try catch --- .../PackageManager.cpp | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 77c86b54f5..200728d155 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1639,20 +1639,18 @@ namespace winrt::Microsoft::Management::Deployment::implementation { LogStartupIfApplicable(); + THROW_HR_IF_NULL(E_POINTER, package); + THROW_HR_IF_NULL(E_POINTER, options); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + + // Gating pins require a non-empty version range. + THROW_HR_IF(E_INVALIDARG, + options.PinType() == winrt::Microsoft::Management::Deployment::PackagePinType::Gating && + options.GatedVersion().empty()); + HRESULT terminationHR = S_OK; try { - THROW_HR_IF_NULL(E_POINTER, package); - THROW_HR_IF_NULL(E_POINTER, options); - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); - - // Gating pins require a non-empty version range. - if (options.PinType() == winrt::Microsoft::Management::Deployment::PackagePinType::Gating && - options.GatedVersion().empty()) - { - THROW_HR(E_INVALIDARG); - } - auto pinKeys = GetPinKeysForCatalogPackage(package, options.PinInstalledPackage()); THROW_HR_IF(E_INVALIDARG, pinKeys.empty()); @@ -1694,12 +1692,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation { LogStartupIfApplicable(); + THROW_HR_IF_NULL(E_POINTER, package); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + HRESULT terminationHR = S_OK; try { - THROW_HR_IF_NULL(E_POINTER, package); - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); - auto pinKeys = GetPinKeysForCatalogPackage(package, /* includeInstalled */ true); auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; @@ -1728,11 +1726,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation { LogStartupIfApplicable(); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + HRESULT terminationHR = S_OK; try { - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); - std::string sourceId; if (packageCatalogReference) { From 34a727c1612ade9c84e3da17877b56c042af26be Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 30 Apr 2026 23:53:06 -0500 Subject: [PATCH 28/60] Add TryRemovePin for simplicity --- src/AppInstallerRepositoryCore/PinningData.cpp | 11 +++++++++++ .../Public/winget/PinningData.h | 2 ++ .../PackageManager.cpp | 7 +------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/AppInstallerRepositoryCore/PinningData.cpp b/src/AppInstallerRepositoryCore/PinningData.cpp index ac29bb0d0a..801f4f5fe1 100644 --- a/src/AppInstallerRepositoryCore/PinningData.cpp +++ b/src/AppInstallerRepositoryCore/PinningData.cpp @@ -93,6 +93,17 @@ namespace AppInstaller::Pinning m_database->RemovePin(pinKey); } + bool PinningData::TryRemovePin(const PinKey& pinKey) + { + THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); + if (!m_database->GetPin(pinKey)) + { + return false; + } + m_database->RemovePin(pinKey); + return true; + } + std::optional PinningData::GetPin(const PinKey& pinKey) { return IsDatabaseConnected() ? m_database->GetPin(pinKey) : std::nullopt; diff --git a/src/AppInstallerRepositoryCore/Public/winget/PinningData.h b/src/AppInstallerRepositoryCore/Public/winget/PinningData.h index ca1e3b36ce..d754611593 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PinningData.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PinningData.h @@ -58,6 +58,8 @@ namespace AppInstaller::Pinning // Pass through functions to the index itself void AddOrUpdatePin(const Pin& pin); void RemovePin(const PinKey& pinKey); + // Removes the pin if it exists. Returns true if a pin was removed, false if none was found. + bool TryRemovePin(const PinKey& pinKey); std::optional GetPin(const PinKey& pinKey); std::vector GetAllPins(); bool ResetAllPins(std::string_view sourceId = {}); diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 200728d155..44fdeab48f 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1704,12 +1704,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool anyRemoved = false; for (const auto& pinKey : pinKeys) { - auto existingPin = pinningData.GetPin(pinKey); - if (existingPin) - { - pinningData.RemovePin(pinKey); - anyRemoved = true; - } + anyRemoved |= pinningData.TryRemovePin(pinKey); } THROW_HR_IF(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST, !anyRemoved); From 01aad8e071cc782f24046186700006c47555974f Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 00:11:19 -0500 Subject: [PATCH 29/60] Refactor to use -Blocking and -GatedVersion --- .../Microsoft.WinGet.Client/Add-WinGetPin.md | 35 +++++++++---------- .../Cmdlets/AddPinCmdlet.cs | 33 ++++++++++++++--- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Add-WinGetPin.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Add-WinGetPin.md index 2992b76ed8..b0bb276916 100644 --- a/src/PowerShell/Help/Microsoft.WinGet.Client/Add-WinGetPin.md +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Add-WinGetPin.md @@ -15,7 +15,7 @@ Adds a WinGet package pin. ### FoundSet (Default) ``` -Add-WinGetPin [-PinType ] [-GatedVersion ] [-PinInstalledPackage] +Add-WinGetPin [-Blocking] [-GatedVersion ] [-PinInstalledPackage] [-Force] [-Note ] [-Id ] [-Name ] [-Moniker ] [-Source ] [[-Query] ] [-MatchOption ] [-ProgressAction ] [-WhatIf] [-Confirm] [] @@ -23,7 +23,7 @@ Add-WinGetPin [-PinType ] [-GatedVersion ] [-PinInstal ### GivenSet ``` -Add-WinGetPin [-PinType ] [-GatedVersion ] [-PinInstalledPackage] +Add-WinGetPin [-Blocking] [-GatedVersion ] [-PinInstalledPackage] [-Force] [-Note ] [[-PSCatalogPackage] ] [-ProgressAction ] [-WhatIf] [-Confirm] [] ``` @@ -33,10 +33,10 @@ This command adds a pin for a WinGet package, preventing automatic updates. Mirr of `winget pin add`. By default, all string-based searches are case-insensitive exact matches. Wildcards are not supported. You can change the search behavior using the **MatchOption** parameter. -Pin types: +Pin types are determined by the parameters you provide: - **Pinning** (default): Prevents automatic updates but allows manual upgrades. -- **Blocking**: Prevents all upgrades, including manual ones. -- **Gating**: Allows upgrades only to versions below the specified **GatedVersion**. +- **Blocking**: Use `-Blocking` to prevent all upgrades, including manual ones. +- **Gating**: Use `-GatedVersion` to allow upgrades only to versions satisfying the specified range. ## EXAMPLES @@ -48,13 +48,13 @@ This example adds a Pinning pin for `Microsoft.PowerShell`, preventing automatic ### Example 2: Add a blocking pin ```powershell -Add-WinGetPin -Id "Microsoft.PowerShell" -PinType Blocking +Add-WinGetPin -Id "Microsoft.PowerShell" -Blocking ``` This example adds a Blocking pin for `Microsoft.PowerShell`, preventing all upgrades. ### Example 3: Add a gating pin ```powershell -Add-WinGetPin -Id "Microsoft.PowerShell" -PinType Gating -GatedVersion "<7.5" +Add-WinGetPin -Id "Microsoft.PowerShell" -GatedVersion "<7.5" ``` This example adds a Gating pin that allows upgrades only to versions below 7.5. @@ -106,8 +106,9 @@ Accept wildcard characters: False ### -GatedVersion -Specify the version range for a Gating pin. This parameter is required when **PinType** is -`Gating`. The value uses WinGet version range syntax (e.g., `<7.5`, `>=7.0,<8.0`). +Specify the version range for a Gating pin. When provided, a gating pin is created that limits +upgrades to versions satisfying this range. Uses WinGet version range syntax (e.g., `<7.5`, +`>=7.0,<8.0`). Cannot be combined with **-Blocking**. ```yaml Type: System.String @@ -221,23 +222,19 @@ Accept pipeline input: True (ByPropertyName) Accept wildcard characters: False ``` -### -PinType +### -Blocking -Specify the type of pin to add. Accepted values: - -- **Pinning** (default): Prevents automatic upgrades. -- **Blocking**: Prevents all upgrades. -- **Gating**: Limits upgrades to versions below **GatedVersion**. +When specified, adds a Blocking pin that prevents all upgrades, including manual ones. Cannot be +combined with **-GatedVersion**. ```yaml -Type: Microsoft.WinGet.Client.PSObjects.PSPackagePinType +Type: System.Management.Automation.SwitchParameter Parameter Sets: (All) Aliases: -Accepted values: Pinning, Blocking, Gating Required: False Position: Named -Default value: Pinning +Default value: None Accept pipeline input: True (ByPropertyName) Accept wildcard characters: False ``` @@ -329,7 +326,7 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ### Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption -### Microsoft.WinGet.Client.PSObjects.PSPackagePinType +### System.Management.Automation.SwitchParameter ## OUTPUTS diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs index 8542dab160..1f4defdd9c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs @@ -16,6 +16,14 @@ namespace Microsoft.WinGet.Client.Commands /// /// Adds a pin for a package. Mirrors the behavior of winget pin add. /// + /// + /// Pin type is determined by which parameters are provided, matching winget pin add behavior: + /// + /// present → Gating pin + /// present → Blocking pin + /// Neither → Pinning pin (default) + /// + /// [Cmdlet( VerbsCommon.Add, Constants.WinGetNouns.Pin, @@ -27,15 +35,19 @@ public sealed class AddPinCmdlet : PackageCmdlet private PinPackageCommand command = null; /// - /// Gets or sets the pin type. Defaults to . + /// Gets or sets a value indicating whether to add a blocking pin, which prevents all upgrades. + /// Cannot be combined with . /// [Parameter(ValueFromPipelineByPropertyName = true)] - public PSPackagePinType PinType { get; set; } = PSPackagePinType.Pinning; + public SwitchParameter Blocking { get; set; } /// - /// Gets or sets the gated version range. Required when is Gating. + /// Gets or sets the gated version range, which creates a gating pin that limits upgrades to + /// versions satisfying this range (e.g., <7.5, >=7.0,<8.0). + /// Cannot be combined with . /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] public string GatedVersion { get; set; } /// @@ -61,6 +73,19 @@ public sealed class AddPinCmdlet : PackageCmdlet /// protected override void ProcessRecord() { + if (this.Blocking && !string.IsNullOrEmpty(this.GatedVersion)) + { + this.ThrowTerminatingError(new ErrorRecord( + new System.ArgumentException("Cannot specify both -Blocking and -GatedVersion. Use -GatedVersion for a gating pin or -Blocking for a blocking pin."), + "ConflictingPinTypeParameters", + ErrorCategory.InvalidArgument, + null)); + } + + PSPackagePinType pinType = !string.IsNullOrEmpty(this.GatedVersion) ? PSPackagePinType.Gating + : this.Blocking ? PSPackagePinType.Blocking + : PSPackagePinType.Pinning; + this.command = new PinPackageCommand( this, this.PSCatalogPackage, @@ -71,7 +96,7 @@ protected override void ProcessRecord() this.Query); this.command.Add( this.MatchOption.ToString(), - this.PinType.ToString(), + pinType.ToString(), this.GatedVersion, this.PinInstalledPackage.ToBool(), this.Force.ToBool(), From ad9932fda2439eb03714423e75e47074e115d42a Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 08:43:39 -0500 Subject: [PATCH 30/60] Spelling --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 8eb3002073..982fc5b913 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -433,6 +433,7 @@ Pherson pid pidl pidlist +pintable PKCS pkgmgr pkindex From a96e43e0c7763aad2459b57277e323703b683de5 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 08:44:43 -0500 Subject: [PATCH 31/60] Add Pester Tests --- .../Cmdlets/AddPinCmdlet.cs | 11 ++ .../Cmdlets/RemovePinCmdlet.cs | 11 ++ .../tests/Microsoft.WinGet.Client.Tests.ps1 | 187 ++++++++++++++++++ 3 files changed, 209 insertions(+) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs index 1f4defdd9c..15e489b2b4 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddPinCmdlet.cs @@ -86,6 +86,17 @@ protected override void ProcessRecord() : this.Blocking ? PSPackagePinType.Blocking : PSPackagePinType.Pinning; + string target = this.PSCatalogPackage?.Id + ?? this.Id + ?? this.Name + ?? (this.Query != null ? string.Join(" ", this.Query) : null) + ?? "package"; + + if (!this.ShouldProcess(target)) + { + return; + } + this.command = new PinPackageCommand( this, this.PSCatalogPackage, diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemovePinCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemovePinCmdlet.cs index 791de2b270..d00bf2070b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemovePinCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemovePinCmdlet.cs @@ -52,6 +52,17 @@ protected override void ProcessRecord() id = this.PSPackagePin.PackageId; } + string target = id + ?? this.PSCatalogPackage?.Id + ?? this.Name + ?? (this.Query != null ? string.Join(" ", this.Query) : null) + ?? "package"; + + if (!this.ShouldProcess(target)) + { + return; + } + this.command = new PinPackageCommand( this, catalogPackage, diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index f8042c2565..8fecf43d74 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -1019,6 +1019,193 @@ Describe 'WindowsPackageManagerServer' -Skip:($PSEdition -eq "Desktop") { } } +Describe 'Add|Get|Remove|Reset-WinGetPin' { + + BeforeAll { + AddTestSource + } + + AfterAll { + Reset-WinGetPin -Force | Out-Null + RemoveTestSource + } + + Context 'Add-WinGetPin' { + + AfterEach { + Reset-WinGetPin -Force | Out-Null + } + + It 'Add pinning pin by Id' { + $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' + $pin.Type | Should -Be 'Pinning' + } + + It 'Add blocking pin by Id' { + $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Blocking + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + $pin.Type | Should -Be 'Blocking' + } + + It 'Add gating pin with GatedVersion' { + $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -GatedVersion '<2.0.0.0' + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + $pin.Type | Should -Be 'Gating' + $pin.GatedVersion | Should -Be '<2.0.0.0' + } + + It 'Add pin with Note' { + $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Note 'test note' + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + $pin.Note | Should -Be 'test note' + } + + It 'Add with -Blocking and -GatedVersion throws' { + { Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Blocking -GatedVersion '<2.0.0.0' } | Should -Throw + } + + It 'Add with -WhatIf does not create pin' { + Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -WhatIf + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -BeNullOrEmpty + } + } + + Context 'Get-WinGetPin' { + + BeforeAll { + Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null + } + + AfterAll { + Reset-WinGetPin -Force | Out-Null + } + + It 'Get all pins returns non-empty list' { + $pins = Get-WinGetPin + $pins | Should -Not -BeNullOrEmpty + } + + It 'Get pin by Id' { + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + + $pin | Should -Not -BeNullOrEmpty + $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' + } + + It 'Get pin scoped to Source' { + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + + $pin | Should -Not -BeNullOrEmpty + $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' + $pin.SourceId | Should -Not -BeNullOrEmpty + } + + It 'Get pin via pipeline from Find-WinGetPackage' { + $pin = Find-WinGetPackage -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -MatchOption Equals | Get-WinGetPin + + $pin | Should -Not -BeNullOrEmpty + $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' + } + } + + Context 'Remove-WinGetPin' { + + BeforeEach { + Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null + } + + AfterEach { + Reset-WinGetPin -Force | Out-Null + } + + It 'Remove pin by Id' { + $result = Remove-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -BeNullOrEmpty + } + + It 'Remove pin via pipeline from Get-WinGetPin' { + Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Remove-WinGetPin + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -BeNullOrEmpty + } + + It 'Remove with -WhatIf does not remove pin' { + Remove-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -WhatIf + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + } + } + + Context 'Reset-WinGetPin' { + + BeforeEach { + Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null + } + + It 'Reset all pins with -Force' { + $result = Reset-WinGetPin -Force + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pins = Get-WinGetPin + $pins | Should -BeNullOrEmpty + } + + It 'Reset with -Source scopes to that source' { + $result = Reset-WinGetPin -Source 'TestSource' -Force + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -BeNullOrEmpty + } + + It 'Reset with -WhatIf does not remove pins' { + Reset-WinGetPin -WhatIf + + $pins = Get-WinGetPin + $pins | Should -Not -BeNullOrEmpty + } + + AfterEach { + Reset-WinGetPin -Force | Out-Null + } + } +} + AfterAll { RestoreWinGetSettings RemoveTestSource From fde73827382cf9ec4b1763c745183b1839d29942 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 08:46:33 -0500 Subject: [PATCH 32/60] Tests should now be part of default creation test --- src/AppInstallerCLITests/PinningIndex.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index d3127d8d9e..6718f212e7 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -204,29 +204,6 @@ TEST_CASE("PinningIndex_V1_1_AddPin_WithDateAndNote", "[pinningIndex]") } } -TEST_CASE("PinningIndex_V1_1_AddPin_WithoutNote", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreatePinningPin({ "pkgId", "sourceId" }); - pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); - // note intentionally left unset - - { - PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); - index.AddPin(pin); - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - - auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); - REQUIRE(pinFromIndex.has_value()); - REQUIRE(pinFromIndex->GetDateAdded() == AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); - REQUIRE_FALSE(pinFromIndex->GetNote().has_value()); - } -} TEST_CASE("PinningIndex_V1_1_AddUpdateRemove", "[pinningIndex]") { From d5f682c63334557bc3a70d0f61de37cc9adbdc52 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 09:08:37 -0500 Subject: [PATCH 33/60] Further abstract the interface --- .../Pinning_1_0/PinningIndexInterface.h | 6 +++ .../Pinning_1_0/PinningIndexInterface_1_0.cpp | 28 ++++++++-- .../Pinning_1_1/PinningIndexInterface.h | 10 ++-- .../Pinning_1_1/PinningIndexInterface_1_1.cpp | 53 +++---------------- 4 files changed, 43 insertions(+), 54 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h index 42f744c68d..8cc664e60c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h @@ -19,5 +19,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; std::vector GetAllPins(SQLite::Connection& connection) override; bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) override; + + protected: + virtual SQLite::rowid_t IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin); + virtual bool IUpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); + virtual std::optional IGetPinById(SQLite::Connection& connection, SQLite::rowid_t pinId); + virtual std::vector IGetAllPins(SQLite::Connection& connection); }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index 9d81c5a087..38a95fcbc3 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -48,7 +48,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_0"); - SQLite::rowid_t pinId = PinTable::AddPin(connection, pin); + SQLite::rowid_t pinId = IAddPin(connection, pin); savepoint.Commit(); return pinId; @@ -63,7 +63,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_0"); - bool status = PinTable::UpdatePinById(connection, existingPinId.value(), pin); + bool status = IUpdatePinById(connection, existingPinId.value(), pin); savepoint.Commit(); return { status, existingPinId.value() }; @@ -93,12 +93,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 return {}; } - return PinTable::GetPinById(connection, existingPinId.value()); + return IGetPinById(connection, existingPinId.value()); } std::vector PinningIndexInterface::GetAllPins(SQLite::Connection& connection) { - return PinTable::GetAllPins(connection); + return IGetAllPins(connection); } bool PinningIndexInterface::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) @@ -109,4 +109,24 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 return result; } + + SQLite::rowid_t PinningIndexInterface::IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + return PinTable::AddPin(connection, pin); + } + + bool PinningIndexInterface::IUpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) + { + return PinTable::UpdatePinById(connection, pinId, pin); + } + + std::optional PinningIndexInterface::IGetPinById(SQLite::Connection& connection, SQLite::rowid_t pinId) + { + return PinTable::GetPinById(connection, pinId); + } + + std::vector PinningIndexInterface::IGetAllPins(SQLite::Connection& connection) + { + return PinTable::GetAllPins(connection); + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h index 57ba50cc7d..8c8820127f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h @@ -10,9 +10,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Version GetVersion() const override; void CreateTables(SQLite::Connection& connection) override; bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) override; - SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; - std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; - std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; - std::vector GetAllPins(SQLite::Connection& connection) override; + + protected: + SQLite::rowid_t IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; + bool IUpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) override; + std::optional IGetPinById(SQLite::Connection& connection, SQLite::rowid_t pinId) override; + std::vector IGetAllPins(SQLite::Connection& connection) override; }; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp index 606b51993f..21ff29cc22 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -6,21 +6,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { - namespace - { - std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - auto result = PinTable::GetIdByPinKey(connection, pinKey); - - if (!result) - { - AICLI_LOG(Repo, Verbose, << "Did not find pin " << pinKey.ToString()); - } - - return result; - } - } - // Version 1.1 SQLite::Version PinningIndexInterface::GetVersion() const { @@ -50,46 +35,22 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return true; } - SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + SQLite::rowid_t PinningIndexInterface::IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin) { - auto existingPin = GetExistingPinId(connection, pin.GetKey()); - - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_1"); - SQLite::rowid_t pinId = PinTable::AddPin(connection, pin); - - savepoint.Commit(); - return pinId; + return PinTable::AddPin(connection, pin); } - std::pair PinningIndexInterface::UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) + bool PinningIndexInterface::IUpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) { - auto existingPinId = GetExistingPinId(connection, pin.GetKey()); - - // If the pin doesn't exist, fail the update - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_1"); - bool status = PinTable::UpdatePinById(connection, existingPinId.value(), pin); - - savepoint.Commit(); - return { status, existingPinId.value() }; + return PinTable::UpdatePinById(connection, pinId, pin); } - std::optional PinningIndexInterface::GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + std::optional PinningIndexInterface::IGetPinById(SQLite::Connection& connection, SQLite::rowid_t pinId) { - auto existingPinId = GetExistingPinId(connection, pinKey); - - if (!existingPinId) - { - return {}; - } - - return PinTable::GetPinById(connection, existingPinId.value()); + return PinTable::GetPinById(connection, pinId); } - std::vector PinningIndexInterface::GetAllPins(SQLite::Connection& connection) + std::vector PinningIndexInterface::IGetAllPins(SQLite::Connection& connection) { return PinTable::GetAllPins(connection); } From 8f819c1e5695d18c748e4ca60393d7de3933166a Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 09:15:38 -0500 Subject: [PATCH 34/60] Update test name --- src/AppInstallerCLITests/PinningIndex.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index 6718f212e7..ef6a01c64b 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -168,7 +168,7 @@ TEST_CASE("PinningIndex_AddDuplicatePin", "[pinningIndex]") REQUIRE_THROWS(index.AddPin(pin), ERROR_ALREADY_EXISTS); } -TEST_CASE("PinningIndexCreateLatest_Is_V1_1", "[pinningIndex]") +TEST_CASE("PinningIndexCreateLatest_HasCorrectVersion", "[pinningIndex]") { TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; INFO("Using temporary file named: " << tempFile.GetPath()); @@ -313,4 +313,4 @@ TEST_CASE("PinningIndex_MigrateFrom_1_0_to_1_1_ReadOnly_Uses_OldInterface", "[pi REQUIRE(pins[0].GetType() == PinType::Pinning); REQUIRE(pins[0].GetKey() == pin.GetKey()); } -} \ No newline at end of file +} From 91f532a18b01989d8b923e042e81dbfb99b05881 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 09:20:41 -0500 Subject: [PATCH 35/60] Add comment --- .../Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp index 21ff29cc22..48785c69cf 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -35,6 +35,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return true; } + // Override the pin methods to use the correct PinTable methods for version 1.1 + SQLite::rowid_t PinningIndexInterface::IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin) { return PinTable::AddPin(connection, pin); From 595abca17efb269f8b6762615f8bec49290410f4 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 09:30:54 -0500 Subject: [PATCH 36/60] Appease our AI overlords with an update to their tomes * Updated .github/copilot-instructions.md --- .github/copilot-instructions.md | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 77aabce02b..ea607123ce 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -133,6 +133,105 @@ void WorkflowTask(Execution::Context& context) - Parsing in `AppInstallerCommonCore/Manifest/` - Multi-file manifests: installer, locale, version, defaultLocale +## SQLite Statement Builder + +**Always use `AppInstaller::SQLite::Builder::StatementBuilder` when writing SQLite database code. Never write raw SQL strings.** + +The builder is in `` (namespace `AppInstaller::SQLite::Builder`). It generates type-safe, parameterized SQL and ensures symbolic names are used for tables and columns throughout. + +### Key conventions + +- Define table and column names as `constexpr std::string_view` constants, then pass them to the builder. +- Use `ColumnBuilder` with chained modifiers (`.NotNull()`, `.Unique()`, `.Default(value)`) when creating tables. +- Use `IntegerPrimaryKey()` for auto-increment rowid primary keys. +- Use `Unbound` as a placeholder and bind values later via `Statement::Bind()`, or pass values directly to have them bound automatically. + +### Common operations + +```cpp +using namespace AppInstaller::SQLite::Builder; + +// Symbolic names +static constexpr std::string_view s_MyTable = "my_table"sv; +static constexpr std::string_view s_Col_Id = "id"sv; +static constexpr std::string_view s_Col_Name = "name"sv; +static constexpr std::string_view s_Col_Value = "value"sv; + +// CREATE TABLE +StatementBuilder builder; +builder.CreateTable(s_MyTable).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_Col_Name, Type::Text).NotNull().Unique(), + ColumnBuilder(s_Col_Value, Type::Int64).NotNull().Default(0), +}); +builder.Execute(connection); + +// SELECT +StatementBuilder builder; +builder.Select({ s_Col_Id, s_Col_Name }) + .From(s_MyTable) + .Where(s_Col_Name).Equals(nameValue); +auto stmt = builder.Prepare(connection); +while (stmt.Step()) { /* stmt.GetColumn(index) */ } + +// INSERT +StatementBuilder builder; +builder.InsertInto(s_MyTable) + .Columns({ s_Col_Name, s_Col_Value }) + .Values(nameValue, intValue); +builder.Execute(connection); + +// UPDATE +StatementBuilder builder; +builder.Update(s_MyTable).Set() + .Column(s_Col_Value).Equals(newValue) + .Where(s_Col_Id).Equals(rowId); +builder.Execute(connection); + +// DELETE +StatementBuilder builder; +builder.DeleteFrom(s_MyTable) + .Where(s_Col_Id).Equals(rowId); +builder.Execute(connection); + +// ALTER TABLE – add a column +StatementBuilder builder; +builder.AlterTable(s_MyTable).Add(s_Col_NewCol, Type::Text).NotNull().Default(0); +builder.Execute(connection); +``` + +### Nullable values: `Equals()` vs `AssignValue()` + +Both accept `std::optional`, but they behave differently when the optional is empty and must be used in the right context: + +| Method | Empty optional emits | Use in | +|---|---|---| +| `Equals(optional)` | `IS NULL` | **WHERE** / filter clauses | +| `AssignValue(optional)` | `= ?` (binds NULL) | **UPDATE SET** assignments | + +Using `Equals(optional)` in an UPDATE SET clause is a bug — SQLite does not accept `col = IS NULL`. + +```cpp +// ✅ Correct: Equals for WHERE filter, AssignValue for SET assignment +std::optional maybeEpoch = ...; +std::optional maybeNote = ...; + +builder.Update(s_MyTable).Set() + .Column(s_Col_Name).Equals(requiredName) // non-optional: fine in SET + .Column(s_Col_Epoch).AssignValue(maybeEpoch) // nullable: must use AssignValue in SET + .Column(s_Col_Note).AssignValue(maybeNote) // nullable: must use AssignValue in SET + .Where(s_Col_Id).Equals(rowId); // filter: Equals is correct here + +// ✅ Correct: Equals(optional) in WHERE — emits "IS NULL" when empty +builder.Select(s_Col_Id).From(s_MyTable) + .Where(s_Col_Note).Equals(maybeNote); // → "WHERE note IS NULL" when empty +``` + +### Execution + +- `builder.Execute(connection)` — prepares, binds, and runs a statement that returns no rows. +- `builder.Prepare(connection)` — returns a `SQLite::Statement`; call `.Step()` to iterate, `.GetColumn(index)` to read values. + ## Naming Conventions - **Namespace structure**: `AppInstaller::[::]` From 7e83a832b5d34a72c506a29f4e279c0977ec4cb7 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 09:42:20 -0500 Subject: [PATCH 37/60] Unify on AssignValue to avoid future confusion --- src/AppInstallerCLITests/SQLiteWrapper.cpp | 2 +- .../Microsoft/Schema/1_0/OneToOneTable.cpp | 2 +- .../Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp | 4 ++-- .../Microsoft/Schema/1_1/ManifestMetadataTable.cpp | 2 +- .../Microsoft/Schema/2_0/OneToManyTableWithMap.cpp | 2 +- .../Schema/2_0/PackageUpdateTrackingTable.cpp | 6 +++--- .../Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp | 4 ++-- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 8 ++++---- .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 8 ++++---- .../Microsoft/Schema/Portable_1_0/PortableTable.cpp | 8 ++++---- .../Public/winget/SQLiteStatementBuilder.h | 10 ++++++++++ .../Database/Schema/0_1/SetInfoTable.cpp | 12 ++++++------ .../Database/Schema/0_2/QueueTable.cpp | 2 +- 13 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/AppInstallerCLITests/SQLiteWrapper.cpp b/src/AppInstallerCLITests/SQLiteWrapper.cpp index 7fa70aa955..01f9affc82 100644 --- a/src/AppInstallerCLITests/SQLiteWrapper.cpp +++ b/src/AppInstallerCLITests/SQLiteWrapper.cpp @@ -54,7 +54,7 @@ void InsertIntoSimpleTestTable(Connection& connection, int firstVal, const std:: void UpdateSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) { Builder::StatementBuilder update; - update.Update(s_tableName).Set().Column(s_firstColumn).Equals(firstVal).Column(s_secondColumn).Equals(secondVal); + update.Update(s_tableName).Set().Column(s_firstColumn).AssignValue(firstVal).Column(s_secondColumn).AssignValue(secondVal); update.Execute(connection); } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp index 2301d0d4b3..ed88f44f17 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp @@ -133,7 +133,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 if (tableValue.value() != value) { SQLite::Builder::StatementBuilder updateBuilder; - updateBuilder.Update(tableName).Set().Column(valueName).Equals(value).Where(SQLite::RowIDName).Equals(selectResult); + updateBuilder.Update(tableName).Set().Column(valueName).AssignValue(value).Where(SQLite::RowIDName).Equals(selectResult); updateBuilder.Execute(connection); } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp index dd34ecbc57..d4fd017d64 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp @@ -135,7 +135,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 { // Reset all filter values to unselected SQLite::Builder::StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(false); + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(false); builder.Execute(m_connection); } @@ -153,7 +153,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 // ) // ) StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(true).Where(s_SearchResultsTable_Manifest).In().BeginParenthetical(). + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(true).Where(s_SearchResultsTable_Manifest).In().BeginParenthetical(). Select(s_SearchResultsTable_SubSelect_ManifestAlias).From().BeginParenthetical(); // Add the field specific portion diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp index dd461b5f41..dd52c5e8c8 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp @@ -103,7 +103,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_1 // UPSERT (aka ON CONFLICT) is not available to us, as it was only introduced in 3.24.0 (2018-06-04), // and we need to support Windows 10 (17763) which was released in 2017. StatementBuilder updateBuilder; - updateBuilder.Update(s_ManifestMetadataTable_Table_Name).Set().Column(s_ManifestMetadataTable_Value_Column).Equals(value). + updateBuilder.Update(s_ManifestMetadataTable_Table_Name).Set().Column(s_ManifestMetadataTable_Value_Column).AssignValue(value). Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId).And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); updateBuilder.Execute(connection); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp index c0b2b75dde..8f0678427e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp @@ -134,7 +134,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V2_0 if (tableValue.value() != value) { SQLite::Builder::StatementBuilder updateBuilder; - updateBuilder.Update(tableName).Set().Column(valueName).Equals(value).Where(SQLite::RowIDName).Equals(selectResult); + updateBuilder.Update(tableName).Set().Column(valueName).AssignValue(value).Where(SQLite::RowIDName).Equals(selectResult); updateBuilder.Execute(connection); } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp index 1728a5d6e7..fceea986ad 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp @@ -123,9 +123,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::V2_0 // First attempt to update the row and then insert it if no modification occurred. Builder::StatementBuilder updateBuilder; updateBuilder.Update(s_PUTT_Table_Name).Set(). - Column(s_PUTT_WriteTime).Equals(currentTime). - Column(s_PUTT_Manifest).Equals(compressedManifest). - Column(s_PUTT_Hash).Equals(manifestHash). + Column(s_PUTT_WriteTime).AssignValue(currentTime). + Column(s_PUTT_Manifest).AssignValue(compressedManifest). + Column(s_PUTT_Hash).AssignValue(manifestHash). Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); updateBuilder.Execute(connection); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp index 52dea8d7f0..735a34f9e9 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp @@ -137,7 +137,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V2_0 { // Reset all filter values to unselected SQLite::Builder::StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(false); + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(false); builder.Execute(m_connection); } @@ -155,7 +155,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V2_0 // ) // ) StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(true).Where(s_SearchResultsTable_Package).In().BeginParenthetical(). + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(true).Where(s_SearchResultsTable_Package).In().BeginParenthetical(). Select(s_SearchResultsTable_SubSelect_PackageAlias).From().BeginParenthetical(); // Add the field specific portion diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index 370c3f1e29..da89a2e1d7 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -113,10 +113,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 SQLite::Builder::StatementBuilder builder; const auto& pinKey = pin.GetKey(); builder.Update(s_PinTable_Table_Name).Set() - .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) - .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) - .Column(s_PinTable_Type_Column).Equals(pin.GetType()) - .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) + .Column(s_PinTable_PackageId_Column).AssignValue((std::string_view)pinKey.PackageId) + .Column(s_PinTable_SourceId_Column).AssignValue(pinKey.SourceId) + .Column(s_PinTable_Type_Column).AssignValue(pin.GetType()) + .Column(s_PinTable_Version_Column).AssignValue(pin.GetGatedVersion().ToString()) .Where(SQLite::RowIDName).Equals(pinId); builder.Execute(connection); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp index 523c5a2938..2975bcd486 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -117,10 +117,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 : std::nullopt; builder.Update(s_PinTable_Table_Name).Set() - .Column(s_PinTable_PackageId_Column).Equals(pinKey.PackageId) - .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) - .Column(s_PinTable_Type_Column).Equals(pin.GetType()) - .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) + .Column(s_PinTable_PackageId_Column).AssignValue(pinKey.PackageId) + .Column(s_PinTable_SourceId_Column).AssignValue(pinKey.SourceId) + .Column(s_PinTable_Type_Column).AssignValue(pin.GetType()) + .Column(s_PinTable_Version_Column).AssignValue(pin.GetGatedVersion().ToString()) .Column(s_PinTable_DateAdded_Column).AssignValue(epochOpt) .Column(s_PinTable_Note_Column).AssignValue(pin.GetNote()); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp index 7557493f97..bcded14e6e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp @@ -82,10 +82,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 { SQLite::Builder::StatementBuilder builder; builder.Update(s_PortableTable_Table_Name).Set() - .Column(s_PortableTable_FilePath_Column).Equals(file.GetFilePath().u8string()) - .Column(s_PortableTable_FileType_Column).Equals(file.FileType) - .Column(s_PortableTable_SHA256_Column).Equals(file.SHA256) - .Column(s_PortableTable_SymlinkTarget_Column).Equals(file.SymlinkTarget) + .Column(s_PortableTable_FilePath_Column).AssignValue(file.GetFilePath().u8string()) + .Column(s_PortableTable_FileType_Column).AssignValue(file.FileType) + .Column(s_PortableTable_SHA256_Column).AssignValue(file.SHA256) + .Column(s_PortableTable_SymlinkTarget_Column).AssignValue(file.SymlinkTarget) .Where(SQLite::RowIDName).Equals(id); builder.Execute(connection); diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h index c013b7ce9c..0452f4f5d0 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h @@ -296,6 +296,16 @@ namespace AppInstaller::SQLite::Builder return *this; } + // Assigns a non-nullable value using "= ?" binding semantics. Prefer this over Equals() + // in UPDATE SET clauses to clearly signal assignment intent and prevent future breakage + // if the type is later made optional. + template + StatementBuilder& AssignValue(const ValueType& value) + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), value); + return *this; + } + // The optional index value can be used to specify the parameter index. StatementBuilder& Equals(details::unbound_t, std::optional index = {}); StatementBuilder& Equals(std::nullptr_t); diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp index e2003aa68f..669826fa86 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp @@ -169,12 +169,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: StatementBuilder builder; builder.Update(s_SetInfoTable_Table).Set(). - Column(s_SetInfoTable_Column_Name).Equals(ConvertToUTF8(configurationSet.Name())). - Column(s_SetInfoTable_Column_Origin).Equals(ConvertToUTF8(configurationSet.Origin())). - Column(s_SetInfoTable_Column_Path).Equals(ConvertToUTF8(configurationSet.Path())). - Column(s_SetInfoTable_Column_SchemaVersion).Equals(ConvertToUTF8(schemaVersion)). - Column(s_SetInfoTable_Column_Metadata).Equals(serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment())). - Column(s_SetInfoTable_Column_Variables).Equals(serializer->SerializeValueSet(configurationSet.Variables())). + Column(s_SetInfoTable_Column_Name).AssignValue(ConvertToUTF8(configurationSet.Name())). + Column(s_SetInfoTable_Column_Origin).AssignValue(ConvertToUTF8(configurationSet.Origin())). + Column(s_SetInfoTable_Column_Path).AssignValue(ConvertToUTF8(configurationSet.Path())). + Column(s_SetInfoTable_Column_SchemaVersion).AssignValue(ConvertToUTF8(schemaVersion)). + Column(s_SetInfoTable_Column_Metadata).AssignValue(serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment())). + Column(s_SetInfoTable_Column_Variables).AssignValue(serializer->SerializeValueSet(configurationSet.Variables())). Where(RowIDName).Equals(target); builder.Execute(m_connection); diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp index adcd41ccc4..7920aa8fc5 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp @@ -95,7 +95,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: void QueueTable::SetActiveQueueItem(const std::string& objectName) { StatementBuilder builder; - builder.Update(s_QueueTable_Table).Set().Column(s_QueueTable_Column_Active).Equals(true).Where(s_QueueTable_Column_ObjectName).Equals(objectName); + builder.Update(s_QueueTable_Table).Set().Column(s_QueueTable_Column_Active).AssignValue(true).Where(s_QueueTable_Column_ObjectName).Equals(objectName); builder.Execute(m_connection); } From 615ddca641ea0725f72c9d8d566958dca35b32b7 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 09:45:11 -0500 Subject: [PATCH 38/60] Ensure pin writes always record the last updated time --- .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp index 2975bcd486..9786363a38 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -81,11 +81,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Builder::StatementBuilder builder; const auto& pinKey = pin.GetKey(); - const auto& dateAdded = pin.GetDateAdded(); - std::optional epochOpt = dateAdded.has_value() - ? std::optional{ Utility::ConvertSystemClockToUnixEpoch(*dateAdded) } - : std::nullopt; - builder.InsertInto(s_PinTable_Table_Name) .Columns({ s_PinTable_PackageId_Column, @@ -99,7 +94,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 pinKey.SourceId, pin.GetType(), pin.GetGatedVersion().ToString(), - epochOpt, + Utility::ConvertSystemClockToUnixEpoch(std::chrono::system_clock::now()), pin.GetNote()); builder.Execute(connection); @@ -111,17 +106,12 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Builder::StatementBuilder builder; const auto& pinKey = pin.GetKey(); - const auto& dateAdded = pin.GetDateAdded(); - std::optional epochOpt = dateAdded.has_value() - ? std::optional{ Utility::ConvertSystemClockToUnixEpoch(*dateAdded) } - : std::nullopt; - builder.Update(s_PinTable_Table_Name).Set() .Column(s_PinTable_PackageId_Column).AssignValue(pinKey.PackageId) .Column(s_PinTable_SourceId_Column).AssignValue(pinKey.SourceId) .Column(s_PinTable_Type_Column).AssignValue(pin.GetType()) .Column(s_PinTable_Version_Column).AssignValue(pin.GetGatedVersion().ToString()) - .Column(s_PinTable_DateAdded_Column).AssignValue(epochOpt) + .Column(s_PinTable_DateAdded_Column).AssignValue(Utility::ConvertSystemClockToUnixEpoch(std::chrono::system_clock::now())) .Column(s_PinTable_Note_Column).AssignValue(pin.GetNote()); builder.Where(SQLite::RowIDName).Equals(pinId); From 1f8932dc3caa4022d7da35f39fcce27072fc9cfa Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 09:48:15 -0500 Subject: [PATCH 39/60] Use original symbol for creation --- src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp | 8 ++++---- src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index e9081b3c6a..bb03125e7c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -185,7 +185,7 @@ namespace AppInstaller::Repository::Microsoft return m_interface->ResetAllPins(m_dbconn, sourceId); } - std::unique_ptr PinningIndex::CreateIPinningIndexForVersion(const SQLite::Version& version) + std::unique_ptr PinningIndex::CreateIPinningIndex(const SQLite::Version& version) { if (version == SQLite::Version{ 1, 0 }) { @@ -208,12 +208,12 @@ namespace AppInstaller::Repository::Microsoft AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); // Create the correct interface for the stored schema version. - m_interface = CreateIPinningIndexForVersion(m_version); + m_interface = CreateIPinningIndex(m_version); if (disposition == SQLiteStorageBase::OpenDisposition::ReadWrite) { // For writable opens, create a latest interface and migrate if the stored version is older. - auto latestInterface = CreateIPinningIndexForVersion(SQLite::Version::Latest()); + auto latestInterface = CreateIPinningIndex(SQLite::Version::Latest()); if (m_version != latestInterface->GetVersion()) { @@ -243,7 +243,7 @@ namespace AppInstaller::Repository::Microsoft PinningIndex::PinningIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) { - m_interface = CreateIPinningIndexForVersion(version); + m_interface = CreateIPinningIndex(version); m_version = m_interface->GetVersion(); } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index c015dd8366..62a92a17e0 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -68,7 +68,7 @@ namespace AppInstaller::Repository::Microsoft PinningIndex(const std::string& target, SQLite::Version version); // Creates an IPinningIndex interface object for a specific version. - static std::unique_ptr CreateIPinningIndexForVersion(const SQLite::Version& version); + static std::unique_ptr CreateIPinningIndex(const SQLite::Version& version); std::unique_ptr m_interface; }; From df5509625b040f9aef582b28201dd18f88ce338a Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 10:09:01 -0500 Subject: [PATCH 40/60] Add missing System namepace --- .../Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs index 7bc288acbf..27a11477aa 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackagePin.cs @@ -6,6 +6,7 @@ namespace Microsoft.WinGet.Client.Engine.PSObjects { + using System; using Microsoft.Management.Deployment; /// From 6d085dd4da785c973fd9422c73b217535d7b7b50 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 10:12:29 -0500 Subject: [PATCH 41/60] Restore conditional setting of date --- .../Microsoft/Schema/Pinning_1_1/PinTable.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp index 9786363a38..ac45dec42b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp @@ -81,6 +81,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Builder::StatementBuilder builder; const auto& pinKey = pin.GetKey(); + int64_t epochToStore = pin.GetDateAdded().has_value() + ? Utility::ConvertSystemClockToUnixEpoch(*pin.GetDateAdded()) + : Utility::ConvertSystemClockToUnixEpoch(std::chrono::system_clock::now()); + builder.InsertInto(s_PinTable_Table_Name) .Columns({ s_PinTable_PackageId_Column, @@ -94,7 +98,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 pinKey.SourceId, pin.GetType(), pin.GetGatedVersion().ToString(), - Utility::ConvertSystemClockToUnixEpoch(std::chrono::system_clock::now()), + epochToStore, pin.GetNote()); builder.Execute(connection); @@ -106,12 +110,16 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Builder::StatementBuilder builder; const auto& pinKey = pin.GetKey(); + int64_t epochToStore = pin.GetDateAdded().has_value() + ? Utility::ConvertSystemClockToUnixEpoch(*pin.GetDateAdded()) + : Utility::ConvertSystemClockToUnixEpoch(std::chrono::system_clock::now()); + builder.Update(s_PinTable_Table_Name).Set() .Column(s_PinTable_PackageId_Column).AssignValue(pinKey.PackageId) .Column(s_PinTable_SourceId_Column).AssignValue(pinKey.SourceId) .Column(s_PinTable_Type_Column).AssignValue(pin.GetType()) .Column(s_PinTable_Version_Column).AssignValue(pin.GetGatedVersion().ToString()) - .Column(s_PinTable_DateAdded_Column).AssignValue(Utility::ConvertSystemClockToUnixEpoch(std::chrono::system_clock::now())) + .Column(s_PinTable_DateAdded_Column).AssignValue(epochToStore) .Column(s_PinTable_Note_Column).AssignValue(pin.GetNote()); builder.Where(SQLite::RowIDName).Equals(pinId); From ea9f0d8e29334aff3aeb7c59b36b2e831b266009 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 10:15:16 -0500 Subject: [PATCH 42/60] Ensure ShouldProcess is always respected --- .../Cmdlets/ResetPinCmdlet.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetPinCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetPinCmdlet.cs index b1e1360d2e..47543b6d9f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetPinCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetPinCmdlet.cs @@ -41,11 +41,22 @@ public sealed class ResetPinCmdlet : PSCmdlet protected override void ProcessRecord() { string target = string.IsNullOrEmpty(this.Source) ? "All sources" : this.Source; - if (this.Force || this.ShouldProcess(target)) + if (!this.ShouldProcess(target)) { - this.command = new ResetPinCommand(this); - this.command.Reset(this.Source); + return; } + + if (!this.Force && !this.ShouldContinue( + string.IsNullOrEmpty(this.Source) + ? "This will reset all pins for all sources." + : $"This will reset all pins for source '{this.Source}'.", + "Confirm")) + { + return; + } + + this.command = new ResetPinCommand(this); + this.command.Reset(this.Source); } /// From d207c7712e53aed1a3300ec7ce5d453b32a2a2af Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 10:16:38 -0500 Subject: [PATCH 43/60] Fix minor issue in copilot instructions --- .github/copilot-instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ea607123ce..f5ce805ac7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -184,7 +184,7 @@ builder.Execute(connection); // UPDATE StatementBuilder builder; builder.Update(s_MyTable).Set() - .Column(s_Col_Value).Equals(newValue) + .Column(s_Col_Value).AssignValue(newValue) .Where(s_Col_Id).Equals(rowId); builder.Execute(connection); @@ -217,7 +217,7 @@ std::optional maybeEpoch = ...; std::optional maybeNote = ...; builder.Update(s_MyTable).Set() - .Column(s_Col_Name).Equals(requiredName) // non-optional: fine in SET + .Column(s_Col_Name).AssignValue(requiredName) // non-optional: AssignValue in SET .Column(s_Col_Epoch).AssignValue(maybeEpoch) // nullable: must use AssignValue in SET .Column(s_Col_Note).AssignValue(maybeNote) // nullable: must use AssignValue in SET .Where(s_Col_Id).Equals(rowId); // filter: Equals is correct here From 349a0a13e1f7f244e098adb6e1f2d49253dec4e1 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 10:19:34 -0500 Subject: [PATCH 44/60] Validate provided pin types --- src/Microsoft.Management.Deployment/PackageManager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 44fdeab48f..50d83853a3 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1586,6 +1586,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation { switch (options.PinType()) { + case winrt::Microsoft::Management::Deployment::PackagePinType::Pinning: + return ::AppInstaller::Pinning::Pin::CreatePinningPin(pinKey); case winrt::Microsoft::Management::Deployment::PackagePinType::Blocking: return ::AppInstaller::Pinning::Pin::CreateBlockingPin(pinKey); case winrt::Microsoft::Management::Deployment::PackagePinType::Gating: @@ -1593,7 +1595,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation pinKey, ::AppInstaller::Utility::GatedVersion{ winrt::to_string(options.GatedVersion()) }); default: - return ::AppInstaller::Pinning::Pin::CreatePinningPin(pinKey); + // Unknown, PinnedByManifest, and any future unrecognized values are not valid + // user-settable pin types. + THROW_HR(E_INVALIDARG); } } } From 840e5ae2089aeba84b3798dd90dff684360dea8c Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 10:27:56 -0500 Subject: [PATCH 45/60] Cache all pins and only fall back to get if set is empty --- .../Commands/FinderPackageCommand.cs | 16 +++++++++++++++- .../PSObjects/PSInstalledCatalogPackage.cs | 13 +++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs index fdfa06ce3e..4d5f1e06d1 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs @@ -6,6 +6,8 @@ namespace Microsoft.WinGet.Client.Engine.Commands { + using System.Collections.Generic; + using System.Linq; using System.Management.Automation; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Commands.Common; @@ -82,9 +84,21 @@ public void Get(string psPackageFieldMatchOption) () => this.FindPackages( CompositeSearchBehavior.LocalCatalogs, PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); + + if (results.Count == 0) + { + return; + } + + // Fetch all pins in a single COM call and build a lookup set to avoid + // one GetPins() roundtrip per package when IsPinned is accessed during output. + IReadOnlyList allPins = this.Execute( + () => PackageManagerWrapper.Instance.GetAllPins()); + var pinnedPackageIds = allPins.Select(p => p.PackageId).ToHashSet(); + for (var i = 0; i < results.Count; i++) { - this.Write(StreamType.Object, new PSInstalledCatalogPackage(results[i].CatalogPackage)); + this.Write(StreamType.Object, new PSInstalledCatalogPackage(results[i].CatalogPackage, pinnedPackageIds)); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs index f8ade5e2bf..ef1d40a197 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs @@ -7,6 +7,7 @@ namespace Microsoft.WinGet.Client.Engine.PSObjects { using System; + using System.Collections.Generic; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Helpers; @@ -15,15 +16,21 @@ namespace Microsoft.WinGet.Client.Engine.PSObjects /// public sealed class PSInstalledCatalogPackage : PSCatalogPackage { + private readonly IReadOnlySet? pinnedPackageIds; private bool? isPinned; /// /// Initializes a new instance of the class. /// /// The catalog package COM object. - internal PSInstalledCatalogPackage(CatalogPackage catalogPackage) + /// + /// Optional pre-fetched set of pinned package IDs. When provided, + /// uses this set instead of issuing a per-package COM call. + /// + internal PSInstalledCatalogPackage(CatalogPackage catalogPackage, IReadOnlySet? pinnedPackageIds = null) : base(catalogPackage) { + this.pinnedPackageIds = pinnedPackageIds; } /// @@ -43,7 +50,9 @@ public bool IsPinned { if (!this.isPinned.HasValue) { - this.isPinned = PackageManagerWrapper.Instance.GetPins(this.CatalogPackageCOM).Count > 0; + this.isPinned = this.pinnedPackageIds != null + ? this.pinnedPackageIds.Contains(this.CatalogPackageCOM.Id) + : PackageManagerWrapper.Instance.GetPins(this.CatalogPackageCOM).Count > 0; } return this.isPinned.Value; From 65f8373041ff4b944ede269e30b474837b9630b6 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 May 2026 10:48:20 -0500 Subject: [PATCH 46/60] Change file name to remove override --- src/AppInstallerCLITests/PinningIndex.cpp | 2 +- .../AppInstallerRepositoryCore.vcxproj | 6 ++---- .../AppInstallerRepositoryCore.vcxproj.filters | 4 ++-- .../Schema/Pinning_1_1/{PinTable.cpp => PinTable_1_1.cpp} | 2 +- .../Schema/Pinning_1_1/{PinTable.h => PinTable_1_1.h} | 0 .../Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp | 2 +- 6 files changed, 7 insertions(+), 9 deletions(-) rename src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/{PinTable.cpp => PinTable_1_1.cpp} (99%) rename src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/{PinTable.h => PinTable_1_1.h} (100%) diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index ef6a01c64b..51f2738ef2 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include using namespace std::string_literals; diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 9affa80d82..f18955f27e 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -339,7 +339,7 @@ - + @@ -452,9 +452,7 @@ - - $(IntDir)Schema\Pinning_1_1\ - + diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index b5381284b1..897c8ab613 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -381,7 +381,7 @@ Microsoft\Schema\Pinning_1_1 - + Microsoft\Schema\Pinning_1_1 @@ -704,7 +704,7 @@ Microsoft\Schema\Pinning_1_0 - + Microsoft\Schema\Pinning_1_1 diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp similarity index 99% rename from src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp rename to src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp index ac45dec42b..77ba57d6fd 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "PinTable.h" +#include "PinTable_1_1.h" #include #include #include "Microsoft/Schema/IPinningIndex.h" diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.h similarity index 100% rename from src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h rename to src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.h diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp index 48785c69cf..76fe9e209d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h" -#include "Microsoft/Schema/Pinning_1_1/PinTable.h" +#include "Microsoft/Schema/Pinning_1_1/PinTable_1_1.h" namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { From 27928346998d2cea2a86808a1c8972d00e2fd67c Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 11:35:25 -0500 Subject: [PATCH 47/60] Rename back to PinTable.h --- src/AppInstallerCLITests/PinningIndex.cpp | 2 +- .../Microsoft/Schema/Pinning_1_1/{PinTable_1_1.h => PinTable.h} | 0 .../Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp | 2 +- .../Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/{PinTable_1_1.h => PinTable.h} (100%) diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index 51f2738ef2..ef6a01c64b 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include using namespace std::string_literals; diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h similarity index 100% rename from src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.h rename to src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp index 77ba57d6fd..ac45dec42b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "PinTable_1_1.h" +#include "PinTable.h" #include #include #include "Microsoft/Schema/IPinningIndex.h" diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp index 76fe9e209d..48785c69cf 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h" -#include "Microsoft/Schema/Pinning_1_1/PinTable_1_1.h" +#include "Microsoft/Schema/Pinning_1_1/PinTable.h" namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { From b350a9181f641e419a20248e7ffd164288957034 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 11:43:04 -0500 Subject: [PATCH 48/60] Update filters for file name --- .../AppInstallerRepositoryCore.vcxproj | 2 +- .../AppInstallerRepositoryCore.vcxproj.filters | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 3ad723a9c5..48f807841f 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -339,7 +339,7 @@ - + diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index bd14eb5b93..c8eaf55cbc 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -387,7 +387,7 @@ Microsoft\Schema\Pinning_1_1 - + Microsoft\Schema\Pinning_1_1 From 04b52108f890efb6cf9c247006c469d755d03072 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 11:43:19 -0500 Subject: [PATCH 49/60] Move SQLite builder instructions to separate file --- .github/copilot-instructions.md | 119 +----------------- .../sqlite-builder.instructions.md | 114 +++++++++++++++++ 2 files changed, 118 insertions(+), 115 deletions(-) create mode 100644 .github/instructions/sqlite-builder.instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index cb897b6461..6dd5ce1dd3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -105,16 +105,16 @@ void WorkflowTask(Execution::Context& context) { // Check if already terminated AICLI_RETURN_IF_TERMINATED(context); - + // Access data auto& data = context.Get(); - + // Report to user context.Reporter.Info() << "Doing something"; - + // Store data for next workflow context.Add(result); - + // Terminate on error if (failed) { @@ -133,117 +133,6 @@ void WorkflowTask(Execution::Context& context) - Parsing in `AppInstallerCommonCore/Manifest/` - Multi-file manifests: installer, locale, version, defaultLocale -## SQLite Statement Builder - -**Always use `AppInstaller::SQLite::Builder::StatementBuilder` when writing SQLite database code. Never write raw SQL strings.** - -The builder is in `` (namespace `AppInstaller::SQLite::Builder`). It generates type-safe, parameterized SQL and ensures symbolic names are used for tables and columns throughout. - -### Key conventions - -- Define table and column names as `constexpr std::string_view` constants, then pass them to the builder. -- Use `ColumnBuilder` with chained modifiers (`.NotNull()`, `.Unique()`, `.Default(value)`) when creating tables. -- Use `IntegerPrimaryKey()` for auto-increment rowid primary keys. -- Use `Unbound` as a placeholder and bind values later via `Statement::Bind()`, or pass values directly to have them bound automatically. - -### Common operations - -```cpp -using namespace AppInstaller::SQLite::Builder; - -// Symbolic names -static constexpr std::string_view s_MyTable = "my_table"sv; -static constexpr std::string_view s_Col_Id = "id"sv; -static constexpr std::string_view s_Col_Name = "name"sv; -static constexpr std::string_view s_Col_Value = "value"sv; - -// CREATE TABLE -StatementBuilder builder; -builder.CreateTable(s_MyTable).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_Col_Name, Type::Text).NotNull().Unique(), - ColumnBuilder(s_Col_Value, Type::Int64).NotNull().Default(0), -}); -builder.Execute(connection); - -// SELECT -StatementBuilder builder; -builder.Select({ s_Col_Id, s_Col_Name }) - .From(s_MyTable) - .Where(s_Col_Name).Equals(nameValue); -auto stmt = builder.Prepare(connection); -while (stmt.Step()) { /* stmt.GetColumn(index) */ } - -// INSERT -StatementBuilder builder; -builder.InsertInto(s_MyTable) - .Columns({ s_Col_Name, s_Col_Value }) - .Values(nameValue, intValue); -builder.Execute(connection); - -// UPDATE -StatementBuilder builder; -builder.Update(s_MyTable).Set() - .Column(s_Col_Value).AssignValue(newValue) - .Where(s_Col_Id).Equals(rowId); -builder.Execute(connection); - -// DELETE -StatementBuilder builder; -builder.DeleteFrom(s_MyTable) - .Where(s_Col_Id).Equals(rowId); -builder.Execute(connection); - -// ALTER TABLE – add a column -StatementBuilder builder; -builder.AlterTable(s_MyTable).Add(s_Col_NewCol, Type::Text).NotNull().Default(0); -builder.Execute(connection); -``` - -### Nullable values: `Equals()` vs `AssignValue()` - -Both accept `std::optional`, but they behave differently when the optional is empty and must be used in the right context: - -| Method | Empty optional emits | Use in | -|---|---|---| -| `Equals(optional)` | `IS NULL` | **WHERE** / filter clauses | -| `AssignValue(optional)` | `= ?` (binds NULL) | **UPDATE SET** assignments | - -Using `Equals(optional)` in an UPDATE SET clause is a bug — SQLite does not accept `col = IS NULL`. - -```cpp -// ✅ Correct: Equals for WHERE filter, AssignValue for SET assignment -std::optional maybeEpoch = ...; -std::optional maybeNote = ...; - -builder.Update(s_MyTable).Set() - .Column(s_Col_Name).AssignValue(requiredName) // non-optional: AssignValue in SET - .Column(s_Col_Epoch).AssignValue(maybeEpoch) // nullable: must use AssignValue in SET - .Column(s_Col_Note).AssignValue(maybeNote) // nullable: must use AssignValue in SET - .Where(s_Col_Id).Equals(rowId); // filter: Equals is correct here - -// ✅ Correct: Equals(optional) in WHERE — emits "IS NULL" when empty -builder.Select(s_Col_Id).From(s_MyTable) - .Where(s_Col_Note).Equals(maybeNote); // → "WHERE note IS NULL" when empty -``` - -### Execution - -- `builder.Execute(connection)` — prepares, binds, and runs a statement that returns no rows. -- `builder.Prepare(connection)` — returns a `SQLite::Statement`; call `.Step()` to iterate, `.GetColumn(index)` to read values. - -## Naming Conventions - -- **Namespace structure**: `AppInstaller::[::]` - - `AppInstaller::CLI::Execution` - CLI execution context - - `AppInstaller::CLI::Workflow` - Workflow functions - - `AppInstaller::Repository` - Repository/source logic - - `AppInstaller::Manifest` - Manifest types - - `AppInstaller::Settings` - User/admin settings - -- **Macros**: Prefixed with `AICLI_` for CLI, `WINGET_` for general -- **Data keys**: ExecutionContextData uses enum keys to type-safely store/retrieve data - ## Windows-Specific Considerations - Use Windows-style paths with backslashes (`\`) diff --git a/.github/instructions/sqlite-builder.instructions.md b/.github/instructions/sqlite-builder.instructions.md new file mode 100644 index 0000000000..0aa634c165 --- /dev/null +++ b/.github/instructions/sqlite-builder.instructions.md @@ -0,0 +1,114 @@ +--- +applyTo: "src/AppInstallerRepositoryCore/Microsoft/Schema/*, src/Microsoft.Management.Configuration/Database/Schema/*" +--- + +## SQLite Statement Builder + +**Always use `AppInstaller::SQLite::Builder::StatementBuilder` when writing SQLite database code. Never write raw SQL strings.** + +The builder is in `` (namespace `AppInstaller::SQLite::Builder`). It generates type-safe, parameterized SQL and ensures symbolic names are used for tables and columns throughout. + +### Key conventions + +- Define table and column names as `constexpr std::string_view` constants, then pass them to the builder. +- Use `ColumnBuilder` with chained modifiers (`.NotNull()`, `.Unique()`, `.Default(value)`) when creating tables. +- Use `IntegerPrimaryKey()` for auto-increment rowid primary keys. +- Use `Unbound` as a placeholder and bind values later via `Statement::Bind()`, or pass values directly to have them bound automatically. + +### Common operations + +```cpp +using namespace AppInstaller::SQLite::Builder; + +// Symbolic names +static constexpr std::string_view s_MyTable = "my_table"sv; +static constexpr std::string_view s_Col_Id = "id"sv; +static constexpr std::string_view s_Col_Name = "name"sv; +static constexpr std::string_view s_Col_Value = "value"sv; + +// CREATE TABLE +StatementBuilder builder; +builder.CreateTable(s_MyTable).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_Col_Name, Type::Text).NotNull().Unique(), + ColumnBuilder(s_Col_Value, Type::Int64).NotNull().Default(0), +}); +builder.Execute(connection); + +// SELECT +StatementBuilder builder; +builder.Select({ s_Col_Id, s_Col_Name }) + .From(s_MyTable) + .Where(s_Col_Name).Equals(nameValue); +auto stmt = builder.Prepare(connection); +while (stmt.Step()) { /* stmt.GetColumn(index) */ } + +// INSERT +StatementBuilder builder; +builder.InsertInto(s_MyTable) + .Columns({ s_Col_Name, s_Col_Value }) + .Values(nameValue, intValue); +builder.Execute(connection); + +// UPDATE +StatementBuilder builder; +builder.Update(s_MyTable).Set() + .Column(s_Col_Value).AssignValue(newValue) + .Where(s_Col_Id).Equals(rowId); +builder.Execute(connection); + +// DELETE +StatementBuilder builder; +builder.DeleteFrom(s_MyTable) + .Where(s_Col_Id).Equals(rowId); +builder.Execute(connection); + +// ALTER TABLE – add a column +StatementBuilder builder; +builder.AlterTable(s_MyTable).Add(s_Col_NewCol, Type::Text).NotNull().Default(0); +builder.Execute(connection); +``` + +### Nullable values: `Equals()` vs `AssignValue()` + +Both accept `std::optional`, but they behave differently when the optional is empty and must be used in the right context: + +| Method | Empty optional emits | Use in | +|---|---|---| +| `Equals(optional)` | `IS NULL` | **WHERE** / filter clauses | +| `AssignValue(optional)` | `= ?` (binds NULL) | **UPDATE SET** assignments | + +Using `Equals(optional)` in an UPDATE SET clause is a bug — SQLite does not accept `col = IS NULL`. + +```cpp +// ✅ Correct: Equals for WHERE filter, AssignValue for SET assignment +std::optional maybeEpoch = ...; +std::optional maybeNote = ...; + +builder.Update(s_MyTable).Set() + .Column(s_Col_Name).AssignValue(requiredName) // non-optional: AssignValue in SET + .Column(s_Col_Epoch).AssignValue(maybeEpoch) // nullable: must use AssignValue in SET + .Column(s_Col_Note).AssignValue(maybeNote) // nullable: must use AssignValue in SET + .Where(s_Col_Id).Equals(rowId); // filter: Equals is correct here + +// ✅ Correct: Equals(optional) in WHERE — emits "IS NULL" when empty +builder.Select(s_Col_Id).From(s_MyTable) + .Where(s_Col_Note).Equals(maybeNote); // → "WHERE note IS NULL" when empty +``` + +### Execution + +- `builder.Execute(connection)` — prepares, binds, and runs a statement that returns no rows. +- `builder.Prepare(connection)` — returns a `SQLite::Statement`; call `.Step()` to iterate, `.GetColumn(index)` to read values. + +## Naming Conventions + +- **Namespace structure**: `AppInstaller::[::]` + - `AppInstaller::CLI::Execution` - CLI execution context + - `AppInstaller::CLI::Workflow` - Workflow functions + - `AppInstaller::Repository` - Repository/source logic + - `AppInstaller::Manifest` - Manifest types + - `AppInstaller::Settings` - User/admin settings + +- **Macros**: Prefixed with `AICLI_` for CLI, `WINGET_` for general +- **Data keys**: ExecutionContextData uses enum keys to type-safely store/retrieve data From e350988a047332852d9c80aad18aecf910f2b674 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 11:54:22 -0500 Subject: [PATCH 50/60] Make adds flow through 1.0 into 1.1 --- src/AppInstallerCLITests/PinningIndex.cpp | 647 +++++++++--------- .../Pinning_1_0/PinningIndexInterface.h | 59 +- .../Pinning_1_1/PinningIndexInterface.h | 1 + .../Pinning_1_1/PinningIndexInterface_1_1.cpp | 12 + 4 files changed, 374 insertions(+), 345 deletions(-) diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index ef6a01c64b..d6dd3bdab6 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -1,316 +1,331 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "PinTestCommon.h" -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace TestCommon; -using namespace AppInstaller::Pinning; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Repository::Microsoft::Schema; - -TEST_CASE("PinningIndexCreateLatestAndReopen", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Version versionCreated; - - // Create the index - { - PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); - versionCreated = index.GetVersion(); - } - - // Reopen the index for read only - { - INFO("Trying with Read"); - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } - - // Reopen the index for read/write - { - INFO("Trying with ReadWrite"); - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } - - // Reopen the index for immutable read - { - INFO("Trying with Immutable"); - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } -} - -TEST_CASE("PinningIndexAddEntryToTable", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); - - { - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - auto pins = Pinning_V1_0::PinTable::GetAllPins(connection); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0] == pin); - - auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); - REQUIRE(pinFromIndex.has_value()); - REQUIRE(pinFromIndex.value() == pin); - - REQUIRE(pinFromIndex->GetType() == pin.GetType()); - REQUIRE(pinFromIndex->GetKey() == pin.GetKey()); - } - - { - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.RemovePin(pin.GetKey()); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); - REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); - } -} - -TEST_CASE("PinningIndex_AddUpdateRemove", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); - Pin updatedPin = Pin::CreatePinningPin({ "pkgId", "srcId" }); - - { - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin); - REQUIRE(index.UpdatePin(updatedPin)); - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); - REQUIRE(pinFromIndex.has_value()); - REQUIRE(pinFromIndex.value() == updatedPin); - } - - { - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.RemovePin(updatedPin.GetKey()); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); - REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); - } -} - -TEST_CASE("PinningIndex_ResetAll", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin1 = Pin::CreateBlockingPin({ "pkg1", "src1" }); - Pin pin2 = Pin::CreatePinningPin({ "pkg2", "src2" }); - - // Add two pins to the index, then check that they show up when queried - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin1); - index.AddPin(pin2); - - REQUIRE(index.GetAllPins().size() == 2); - REQUIRE(index.GetPin(pin1.GetKey()).has_value()); - REQUIRE(index.GetPin(pin2.GetKey()).has_value()); - REQUIRE(!index.GetPin({ "pkg", "src" }).has_value()); - - // Reset the index, then check that there are no pins - index.ResetAllPins(); - REQUIRE(index.GetAllPins().empty()); - REQUIRE(!index.GetPin(pin1.GetKey()).has_value()); - REQUIRE(!index.GetPin(pin2.GetKey()).has_value()); -} - -TEST_CASE("PinningIndex_AddDuplicatePin", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreateGatingPin({ "pkg", "src" }, { "1.*"sv }); - - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin); - - REQUIRE_THROWS(index.AddPin(pin), ERROR_ALREADY_EXISTS); -} - -TEST_CASE("PinningIndexCreateLatest_HasCorrectVersion", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); - REQUIRE(index.GetVersion() == Version{ 1, 1 }); -} - -TEST_CASE("PinningIndex_V1_1_AddPin_WithDateAndNote", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); - pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); - pin.SetNote(std::string{ "test note" }); - - { - PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); - index.AddPin(pin); - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - - auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); - REQUIRE(pinFromIndex.has_value()); - REQUIRE(pinFromIndex->GetType() == PinType::Blocking); - REQUIRE(pinFromIndex->GetKey().PackageId == "pkgId"); - REQUIRE(pinFromIndex->GetDateAdded() == AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); - REQUIRE(pinFromIndex->GetNote().has_value()); - REQUIRE(pinFromIndex->GetNote().value() == "test note"); - } -} - - -TEST_CASE("PinningIndex_V1_1_AddUpdateRemove", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreateBlockingPin({ "pkgId", "srcId" }); - pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1000)); - pin.SetNote(std::string{ "original note" }); - - Pin updatedPin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); - updatedPin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1100)); - updatedPin.SetNote(std::string{ "updated note" }); - - { - PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); - index.AddPin(pin); - REQUIRE(index.UpdatePin(updatedPin)); - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - - auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); - REQUIRE(pinFromIndex.has_value()); - REQUIRE(pinFromIndex->GetType() == PinType::Gating); - REQUIRE(pinFromIndex->GetDateAdded() == AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1100)); - REQUIRE(pinFromIndex->GetNote().has_value()); - REQUIRE(pinFromIndex->GetNote().value() == "updated note"); - } - - { - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.RemovePin(updatedPin.GetKey()); - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(Pinning_V1_1::PinTable::GetAllPins(connection).empty()); - REQUIRE(!Pinning_V1_1::PinTable::GetPinById(connection, 1)); - } -} - -TEST_CASE("PinningIndex_MigrateFrom_1_0_to_1_1", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin1 = Pin::CreateBlockingPin({ "pkg1", "src1" }); - Pin pin2 = Pin::CreateGatingPin({ "pkg2", "src2" }, { "2.*"sv }); - - // Create a v1.0 index and add two pins (no date_added or note columns yet) - { - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin1); - index.AddPin(pin2); - REQUIRE(index.GetVersion() == Version{ 1, 0 }); - } - - // Re-open with ReadWrite: should trigger automatic migration to v1.1 - { - PinningIndex migratedIndex = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - REQUIRE(migratedIndex.GetVersion() == Version{ 1, 1 }); - - auto pins = migratedIndex.GetAllPins(); - REQUIRE(pins.size() == 2); - - for (const auto& pin : pins) - { - // Migration adds nullable INTEGER and NULL respectively; existing rows get NULL for both - REQUIRE_FALSE(pin.GetDateAdded().has_value()); - REQUIRE_FALSE(pin.GetNote().has_value()); - } - - // Verify that the original pin data is still intact - auto foundPin1 = migratedIndex.GetPin(pin1.GetKey()); - REQUIRE(foundPin1.has_value()); - REQUIRE(foundPin1->GetType() == PinType::Blocking); - - auto foundPin2 = migratedIndex.GetPin(pin2.GetKey()); - REQUIRE(foundPin2.has_value()); - REQUIRE(foundPin2->GetType() == PinType::Gating); - REQUIRE(foundPin2->GetGatedVersion().ToString() == pin2.GetGatedVersion().ToString()); - } -} - -TEST_CASE("PinningIndex_MigrateFrom_1_0_to_1_1_ReadOnly_Uses_OldInterface", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreatePinningPin({ "pkgId", "srcId" }); - - // Create a v1.0 index and add a pin - { - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin); - } - - // Re-open with Read (read-only): should NOT migrate, should use v1.0 interface - { - PinningIndex readOnlyIndex = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); - REQUIRE(readOnlyIndex.GetVersion() == Version{ 1, 0 }); - - auto pins = readOnlyIndex.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Pinning); - REQUIRE(pins[0].GetKey() == pin.GetKey()); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "PinTestCommon.h" +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace TestCommon; +using namespace AppInstaller::Pinning; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Repository::Microsoft::Schema; + +TEST_CASE("PinningIndexCreateLatestAndReopen", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Version versionCreated; + + // Create the index + { + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + versionCreated = index.GetVersion(); + } + + // Reopen the index for read only + { + INFO("Trying with Read"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for read/write + { + INFO("Trying with ReadWrite"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for immutable read + { + INFO("Trying with Immutable"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } +} + +TEST_CASE("PinningIndexAddEntryToTable", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + auto pins = Pinning_V1_0::PinTable::GetAllPins(connection); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0] == pin); + + auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex.value() == pin); + + REQUIRE(pinFromIndex->GetType() == pin.GetType()); + REQUIRE(pinFromIndex->GetKey() == pin.GetKey()); + } + + { + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePin(pin.GetKey()); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); + REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); + } +} + +TEST_CASE("PinningIndex_AddUpdateRemove", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); + Pin updatedPin = Pin::CreatePinningPin({ "pkgId", "srcId" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + REQUIRE(index.UpdatePin(updatedPin)); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex.value() == updatedPin); + } + + { + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePin(updatedPin.GetKey()); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); + REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); + } +} + +TEST_CASE("PinningIndex_ResetAll", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin1 = Pin::CreateBlockingPin({ "pkg1", "src1" }); + Pin pin2 = Pin::CreatePinningPin({ "pkg2", "src2" }); + + // Add two pins to the index, then check that they show up when queried + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin1); + index.AddPin(pin2); + + REQUIRE(index.GetAllPins().size() == 2); + REQUIRE(index.GetPin(pin1.GetKey()).has_value()); + REQUIRE(index.GetPin(pin2.GetKey()).has_value()); + REQUIRE(!index.GetPin({ "pkg", "src" }).has_value()); + + // Reset the index, then check that there are no pins + index.ResetAllPins(); + REQUIRE(index.GetAllPins().empty()); + REQUIRE(!index.GetPin(pin1.GetKey()).has_value()); + REQUIRE(!index.GetPin(pin2.GetKey()).has_value()); +} + +TEST_CASE("PinningIndex_AddDuplicatePin", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateGatingPin({ "pkg", "src" }, { "1.*"sv }); + + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + + REQUIRE_THROWS(index.AddPin(pin), ERROR_ALREADY_EXISTS); +} + +TEST_CASE("PinningIndexCreateLatest_HasCorrectVersion", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + REQUIRE(index.GetVersion() == Version{ 1, 1 }); +} + +TEST_CASE("PinningIndex_V1_1_AddPin_WithDateAndNote", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); + pin.SetNote(std::string{ "test note" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + index.AddPin(pin); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + + auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex->GetType() == PinType::Blocking); + REQUIRE(pinFromIndex->GetKey().PackageId == "pkgId"); + REQUIRE(pinFromIndex->GetDateAdded() == AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); + REQUIRE(pinFromIndex->GetNote().has_value()); + REQUIRE(pinFromIndex->GetNote().value() == "test note"); + } +} + +TEST_CASE("PinningIndex_V1_1_AddDuplicatePin", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateGatingPin({ "pkg", "src" }, { "1.0.*"sv }); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1030)); + pin.SetNote(std::string{ "duplicate test" }); + + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + index.AddPin(pin); + + REQUIRE_THROWS(index.AddPin(pin), ERROR_ALREADY_EXISTS); +} + + +TEST_CASE("PinningIndex_V1_1_AddUpdateRemove", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "pkgId", "srcId" }); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1000)); + pin.SetNote(std::string{ "original note" }); + + Pin updatedPin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); + updatedPin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1100)); + updatedPin.SetNote(std::string{ "updated note" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + index.AddPin(pin); + REQUIRE(index.UpdatePin(updatedPin)); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + + auto pinFromIndex = Pinning_V1_1::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex->GetType() == PinType::Gating); + REQUIRE(pinFromIndex->GetDateAdded() == AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1100)); + REQUIRE(pinFromIndex->GetNote().has_value()); + REQUIRE(pinFromIndex->GetNote().value() == "updated note"); + } + + { + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePin(updatedPin.GetKey()); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Pinning_V1_1::PinTable::GetAllPins(connection).empty()); + REQUIRE(!Pinning_V1_1::PinTable::GetPinById(connection, 1)); + } +} + +TEST_CASE("PinningIndex_MigrateFrom_1_0_to_1_1", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin1 = Pin::CreateBlockingPin({ "pkg1", "src1" }); + Pin pin2 = Pin::CreateGatingPin({ "pkg2", "src2" }, { "2.*"sv }); + + // Create a v1.0 index and add two pins (no date_added or note columns yet) + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin1); + index.AddPin(pin2); + REQUIRE(index.GetVersion() == Version{ 1, 0 }); + } + + // Re-open with ReadWrite: should trigger automatic migration to v1.1 + { + PinningIndex migratedIndex = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + REQUIRE(migratedIndex.GetVersion() == Version{ 1, 1 }); + + auto pins = migratedIndex.GetAllPins(); + REQUIRE(pins.size() == 2); + + for (const auto& pin : pins) + { + // Migration adds nullable INTEGER and NULL respectively; existing rows get NULL for both + REQUIRE_FALSE(pin.GetDateAdded().has_value()); + REQUIRE_FALSE(pin.GetNote().has_value()); + } + + // Verify that the original pin data is still intact + auto foundPin1 = migratedIndex.GetPin(pin1.GetKey()); + REQUIRE(foundPin1.has_value()); + REQUIRE(foundPin1->GetType() == PinType::Blocking); + + auto foundPin2 = migratedIndex.GetPin(pin2.GetKey()); + REQUIRE(foundPin2.has_value()); + REQUIRE(foundPin2->GetType() == PinType::Gating); + REQUIRE(foundPin2->GetGatedVersion().ToString() == pin2.GetGatedVersion().ToString()); + } +} + +TEST_CASE("PinningIndex_MigrateFrom_1_0_to_1_1_ReadOnly_Uses_OldInterface", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreatePinningPin({ "pkgId", "srcId" }); + + // Create a v1.0 index and add a pin + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + } + + // Re-open with Read (read-only): should NOT migrate, should use v1.0 interface + { + PinningIndex readOnlyIndex = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + REQUIRE(readOnlyIndex.GetVersion() == Version{ 1, 0 }); + + auto pins = readOnlyIndex.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Pinning); + REQUIRE(pins[0].GetKey() == pin.GetKey()); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h index 8cc664e60c..2110909210 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h @@ -1,29 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/IPinningIndex.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 -{ - struct PinningIndexInterface : public IPinningIndex - { - // Version 1.0 - SQLite::Version GetVersion() const override; - void CreateTables(SQLite::Connection& connection) override; - bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) override; - - private: - SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; - std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; - SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; - std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; - std::vector GetAllPins(SQLite::Connection& connection) override; - bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) override; - - protected: - virtual SQLite::rowid_t IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin); - virtual bool IUpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); - virtual std::optional IGetPinById(SQLite::Connection& connection, SQLite::rowid_t pinId); - virtual std::vector IGetAllPins(SQLite::Connection& connection); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/IPinningIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + struct PinningIndexInterface : public IPinningIndex + { + // Version 1.0 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection) override; + bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) override; + + SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; + + private: + std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; + SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; + std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; + std::vector GetAllPins(SQLite::Connection& connection) override; + bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) override; + + protected: + virtual SQLite::rowid_t IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin); + virtual bool IUpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); + virtual std::optional IGetPinById(SQLite::Connection& connection, SQLite::rowid_t pinId); + virtual std::vector IGetAllPins(SQLite::Connection& connection); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h index 8c8820127f..2930e99630 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface.h @@ -10,6 +10,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 SQLite::Version GetVersion() const override; void CreateTables(SQLite::Connection& connection) override; bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) override; + SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; protected: SQLite::rowid_t IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp index 48785c69cf..7a43f658db 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinningIndexInterface_1_1.cpp @@ -35,6 +35,18 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 return true; } + SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_1"); + + Pinning_V1_0::PinningIndexInterface base; + SQLite::rowid_t pinId = base.AddPin(connection, pin); + THROW_HR_IF(E_UNEXPECTED, !Pinning_V1_1::PinTable::UpdatePinById(connection, pinId, pin)); + + savepoint.Commit(); + return pinId; + } + // Override the pin methods to use the correct PinTable methods for version 1.1 SQLite::rowid_t PinningIndexInterface::IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin) From 3d92b41b45f9a9479d7b8776ef1b424df4387d90 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 12:04:10 -0500 Subject: [PATCH 51/60] Use shared column names --- .../Microsoft/PinningIndex.cpp | 3 +-- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 8 -------- .../Microsoft/Schema/Pinning_1_0/PinTable.h | 7 +++++++ .../Microsoft/Schema/Pinning_1_1/PinTable.h | 3 +++ .../Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp | 9 --------- 5 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index bb03125e7c..38c801b4c0 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -228,6 +228,7 @@ namespace AppInstaller::Repository::Microsoft SetLastWriteTime(); savepoint.Commit(); m_version = latestInterface->GetVersion(); + m_interface = std::move(latestInterface); AICLI_LOG(Repo, Info, << "Migration successful"); } else @@ -236,8 +237,6 @@ namespace AppInstaller::Repository::Microsoft THROW_HR(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX); } } - - m_interface = std::move(latestInterface); } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index da89a2e1d7..d998a21a07 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -30,14 +30,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 } } - using namespace std::string_view_literals; - static constexpr std::string_view s_PinTable_Table_Name = "pin"sv; - static constexpr std::string_view s_PinTable_PackageId_Column = "package_id"sv; - static constexpr std::string_view s_PinTable_SourceId_Column = "source_id"sv; - static constexpr std::string_view s_PinTable_Type_Column = "type"sv; - static constexpr std::string_view s_PinTable_Version_Column = "version"sv; - static constexpr std::string_view s_PinTable_Index = "pin_index"sv; - std::string_view PinTable::TableName() { return s_PinTable_Table_Name; diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index 8deb47e6d1..0a8fae989c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -10,6 +10,13 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 { struct PinTable { + static inline constexpr std::string_view s_PinTable_Table_Name = "pin"; + static inline constexpr std::string_view s_PinTable_PackageId_Column = "package_id"; + static inline constexpr std::string_view s_PinTable_SourceId_Column = "source_id"; + static inline constexpr std::string_view s_PinTable_Type_Column = "type"; + static inline constexpr std::string_view s_PinTable_Version_Column = "version"; + static inline constexpr std::string_view s_PinTable_Index = "pin_index"; + // Get the table name. static std::string_view TableName(); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h index dbd3a56638..434126d9e2 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable.h @@ -11,6 +11,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 { struct PinTable : Pinning_V1_0::PinTable { + static inline constexpr std::string_view s_PinTable_DateAdded_Column = "date_added"; + static inline constexpr std::string_view s_PinTable_Note_Column = "note"; + // Migrates an existing v1.0 pin table by adding the date_added and note columns. static void MigrateFrom1_0(SQLite::Connection& connection); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp index ac45dec42b..ee4099c488 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_1/PinTable_1_1.cpp @@ -50,15 +50,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_1 } } - using namespace std::string_view_literals; - static constexpr std::string_view s_PinTable_Table_Name = "pin"sv; - static constexpr std::string_view s_PinTable_PackageId_Column = "package_id"sv; - static constexpr std::string_view s_PinTable_SourceId_Column = "source_id"sv; - static constexpr std::string_view s_PinTable_Type_Column = "type"sv; - static constexpr std::string_view s_PinTable_Version_Column = "version"sv; - static constexpr std::string_view s_PinTable_DateAdded_Column = "date_added"sv; - static constexpr std::string_view s_PinTable_Note_Column = "note"sv; - void PinTable::MigrateFrom1_0(SQLite::Connection& connection) { using namespace SQLite::Builder; From 37344eaa68d291f43ffe11fa69f8233546314a67 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 12:08:40 -0500 Subject: [PATCH 52/60] Add SQLIte Nullopt test --- src/AppInstallerCLITests/SQLiteWrapper.cpp | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/AppInstallerCLITests/SQLiteWrapper.cpp b/src/AppInstallerCLITests/SQLiteWrapper.cpp index 01f9affc82..7aae4c75be 100644 --- a/src/AppInstallerCLITests/SQLiteWrapper.cpp +++ b/src/AppInstallerCLITests/SQLiteWrapper.cpp @@ -535,6 +535,39 @@ TEST_CASE("SQLBuilder_Update", "[sqlbuilder]") SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); } +TEST_CASE("SQLBuilder_UpdateEmptyOptional", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + int firstVal = 1; + std::string secondVal = "test"; + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + std::optional emptySecondVal; + + Builder::StatementBuilder update; + update.Update(s_tableName) + .Set() + .Column(s_secondColumn).AssignValue(emptySecondVal) + .Where(s_firstColumn).Equals(firstVal); + + update.Execute(connection); + + Builder::StatementBuilder select; + select.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(firstVal); + + auto statement = select.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == firstVal); + REQUIRE(statement.GetColumnIsNull(1)); + + REQUIRE(!statement.Step()); +} + TEST_CASE("SQLBuilder_CaseInsensitive", "[sqlbuilder]") { Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); From d061054281bc30c5fe896b9b0c8c676b0234126b Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 12:17:17 -0500 Subject: [PATCH 53/60] Remove protected --- .../Microsoft/Schema/Pinning_1_0/PinTable.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index 0a8fae989c..56a6b0a8ad 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -20,6 +20,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // Get the table name. static std::string_view TableName(); + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + // Gets the row ID for the pin, if it exists. static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); @@ -43,11 +46,5 @@ namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 // Resets all pins from a given source, or from all sources if none is specified. // Returns a value indicating whether there were any changes. static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); - - protected: - // Creates the table with named indices. - static void Create(SQLite::Connection& connection); - - friend struct PinningIndexInterface; }; } From b939b8eaf69241d53871d963fbb7ad4031419d3c Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 12:58:19 -0500 Subject: [PATCH 54/60] Lazy load all pins for catalog packages --- .../Commands/FinderPackageCommand.cs | 15 +------ .../PSObjects/PSInstalledCatalogPackage.cs | 22 +++++----- .../PSInstalledCatalogPackageTests.cs | 43 +++++++++++++++++++ 3 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 src/PowerShell/Microsoft.WinGet.UnitTests/PSInstalledCatalogPackageTests.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs index 4d5f1e06d1..cee14bfdbb 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs @@ -6,8 +6,6 @@ namespace Microsoft.WinGet.Client.Engine.Commands { - using System.Collections.Generic; - using System.Linq; using System.Management.Automation; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Commands.Common; @@ -85,20 +83,9 @@ public void Get(string psPackageFieldMatchOption) CompositeSearchBehavior.LocalCatalogs, PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); - if (results.Count == 0) - { - return; - } - - // Fetch all pins in a single COM call and build a lookup set to avoid - // one GetPins() roundtrip per package when IsPinned is accessed during output. - IReadOnlyList allPins = this.Execute( - () => PackageManagerWrapper.Instance.GetAllPins()); - var pinnedPackageIds = allPins.Select(p => p.PackageId).ToHashSet(); - for (var i = 0; i < results.Count; i++) { - this.Write(StreamType.Object, new PSInstalledCatalogPackage(results[i].CatalogPackage, pinnedPackageIds)); + this.Write(StreamType.Object, new PSInstalledCatalogPackage(results[i].CatalogPackage)); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs index ef1d40a197..b33449cf42 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs @@ -7,7 +7,6 @@ namespace Microsoft.WinGet.Client.Engine.PSObjects { using System; - using System.Collections.Generic; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Helpers; @@ -16,21 +15,21 @@ namespace Microsoft.WinGet.Client.Engine.PSObjects /// public sealed class PSInstalledCatalogPackage : PSCatalogPackage { - private readonly IReadOnlySet? pinnedPackageIds; + private readonly Func isPinnedLookup; private bool? isPinned; /// /// Initializes a new instance of the class. /// /// The catalog package COM object. - /// - /// Optional pre-fetched set of pinned package IDs. When provided, - /// uses this set instead of issuing a per-package COM call. + /// + /// Optional lookup used to determine whether the package is pinned. When not supplied, + /// the lookup is resolved on demand through the package manager. /// - internal PSInstalledCatalogPackage(CatalogPackage catalogPackage, IReadOnlySet? pinnedPackageIds = null) + internal PSInstalledCatalogPackage(CatalogPackage catalogPackage, Func? isPinnedLookup = null) : base(catalogPackage) { - this.pinnedPackageIds = pinnedPackageIds; + this.isPinnedLookup = isPinnedLookup ?? this.DefaultIsPinnedLookup; } /// @@ -50,15 +49,18 @@ public bool IsPinned { if (!this.isPinned.HasValue) { - this.isPinned = this.pinnedPackageIds != null - ? this.pinnedPackageIds.Contains(this.CatalogPackageCOM.Id) - : PackageManagerWrapper.Instance.GetPins(this.CatalogPackageCOM).Count > 0; + this.isPinned = this.isPinnedLookup(this.CatalogPackageCOM); } return this.isPinned.Value; } } + private bool DefaultIsPinnedLookup(CatalogPackage catalogPackage) + { + return PackageManagerWrapper.Instance.GetPins(catalogPackage).Count > 0; + } + /// /// Compares versions. /// diff --git a/src/PowerShell/Microsoft.WinGet.UnitTests/PSInstalledCatalogPackageTests.cs b/src/PowerShell/Microsoft.WinGet.UnitTests/PSInstalledCatalogPackageTests.cs new file mode 100644 index 0000000000..7f2a353cde --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.UnitTests/PSInstalledCatalogPackageTests.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.UnitTests +{ + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Xunit; + + /// + /// Tests for . + /// + public class PSInstalledCatalogPackageTests + { + /// + /// Tests that IsPinned is computed lazily and cached after the first access. + /// + [Fact] + public void IsPinned_ComputesOnceAndCachesResult() + { + var calls = 0; + CatalogPackage catalogPackage = new (objRef: null!); + PSInstalledCatalogPackage package = new ( + catalogPackage, + _ => + { + calls++; + return true; + }); + + Assert.Equal(0, calls); + + Assert.True(package.IsPinned); + Assert.Equal(1, calls); + + Assert.True(package.IsPinned); + Assert.Equal(1, calls); + } + } +} From ff1224519572258969f1d224c4cf4da0078c6bf2 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 13:05:14 -0500 Subject: [PATCH 55/60] Contract version 30 --- .../PackageManager.h | 2 +- .../PackageManager.idl | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h index a61057fc28..014c49e0b2 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.h +++ b/src/Microsoft.Management.Deployment/PackageManager.h @@ -54,7 +54,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::hstring Version() const; // Contract 28.0 winrt::Microsoft::Management::Deployment::EditPackageCatalogResult EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options); - // Contract 29.0 + // Contract 30.0 winrt::Windows::Foundation::Collections::IVectorView GetAllPins(); winrt::Windows::Foundation::Collections::IVectorView GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package); winrt::Microsoft::Management::Deployment::PinPackageResult PinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PinPackageOptions options); diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 5efac7164c..2292b6d755 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment { - [contractversion(29)] // For version 1.29 + [contractversion(30)] // For version 1.30 apicontract WindowsPackageManagerContract{}; /// State of the install @@ -1717,7 +1717,7 @@ namespace Microsoft.Management.Deployment EditPackageCatalogResult EditPackageCatalog(EditPackageCatalogOptions options); } - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] { /// Get all pins across all sources. Windows.Foundation.Collections.IVectorView GetAllPins(); @@ -1742,7 +1742,7 @@ namespace Microsoft.Management.Deployment } /// IMPLEMENTATION NOTE: Pinning::PinType from AppInstaller::Pinning - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] enum PackagePinType { /// Unknown pin type or not pinned. @@ -1761,7 +1761,7 @@ namespace Microsoft.Management.Deployment }; /// IMPLEMENTATION NOTE: Pinning::Pin from AppInstaller::Pinning - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] runtimeclass PackagePin { /// The package ID that the pin applies to (for available-package pins) or the @@ -1789,7 +1789,7 @@ namespace Microsoft.Management.Deployment }; /// Options for adding or updating a package pin. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] runtimeclass PinPackageOptions { PinPackageOptions(); @@ -1816,7 +1816,7 @@ namespace Microsoft.Management.Deployment }; /// Status of a pin operation. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] enum PinResultStatus { Ok, @@ -1831,7 +1831,7 @@ namespace Microsoft.Management.Deployment }; /// Result of a pin operation. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] runtimeclass PinPackageResult { PinResultStatus Status { get; }; From c920cdd3625078cd6295140b3bf12c7e5d314ba0 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 14:20:34 -0500 Subject: [PATCH 56/60] Remove pin show - move into pin list with details --- .../Commands/PinCommand.cpp | 33 +-- src/AppInstallerCLICore/Commands/PinCommand.h | 14 - src/AppInstallerCLICore/Resources.h | 8 - src/AppInstallerCLICore/Workflows/PinFlow.cpp | 259 +++++++++--------- src/AppInstallerCLICore/Workflows/PinFlow.h | 8 - .../Shared/Strings/en-us/winget.resw | 40 +-- src/AppInstallerCLITests/PinFlow.cpp | 234 +++++++++++----- 7 files changed, 308 insertions(+), 288 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index f47e81055d..5ad615ebfc 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -23,7 +23,6 @@ namespace AppInstaller::CLI std::make_unique(FullName()), std::make_unique(FullName()), std::make_unique(FullName()), - std::make_unique(FullName()), }); } @@ -246,6 +245,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::AuthenticationMode), Argument::ForType(Args::Type::AuthenticationAccount), Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::ListDetails), }; } @@ -346,35 +346,4 @@ namespace AppInstaller::CLI } } - std::vector PinShowCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Exact), - }; - } - - Resource::LocString PinShowCommand::ShortDescription() const - { - return { Resource::String::PinShowCommandShortDescription }; - } - - Resource::LocString PinShowCommand::LongDescription() const - { - return { Resource::String::PinShowCommandLongDescription }; - } - - Utility::LocIndView PinShowCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinShowCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::OpenPinningIndex(/* readOnly */ true) << - Workflow::ShowPinDetails; - } } diff --git a/src/AppInstallerCLICore/Commands/PinCommand.h b/src/AppInstallerCLICore/Commands/PinCommand.h index 2852f26bc0..88f74b8caa 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.h +++ b/src/AppInstallerCLICore/Commands/PinCommand.h @@ -88,18 +88,4 @@ namespace AppInstaller::CLI void ExecuteInternal(Execution::Context& context) const override; }; - struct PinShowCommand final : public Command - { - PinShowCommand(std::string_view parent) : Command("show", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 7548a01db1..8633ef57b6 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -559,14 +559,6 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful); WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelDateAdded); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelId); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelNote); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelSource); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelType); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelVersion); WINGET_DEFINE_RESOURCE_STRINGID(PinShowNoMatchFound); WINGET_DEFINE_RESOURCE_STRINGID(PinType); WINGET_DEFINE_RESOURCE_STRINGID(PinVersion); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 3f7440e2a4..7bc9b84e8a 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -3,7 +3,6 @@ #include "pch.h" #include "Resources.h" #include "PinFlow.h" -#include "ShowFlow.h" #include "TableOutput.h" #include #include @@ -16,6 +15,18 @@ namespace AppInstaller::CLI::Workflow { namespace { + struct PinRowData + { + Utility::LocIndString PackageName; + Utility::LocIndString PackageId; + Utility::LocIndString Version; + Utility::LocIndString SourceName; + Utility::LocIndString PinType; + Utility::LocIndString PinnedVersion; + Utility::LocIndString DateAdded; + Utility::LocIndString Note; + }; + // Creates a Pin appropriate for the context based on the arguments provided Pinning::Pin CreatePin(Execution::Context& context, const Pinning::PinKey& pinKey) { @@ -97,6 +108,37 @@ namespace AppInstaller::CLI::Workflow return searchRequest; } + + bool IsMatchForListQuery(const Pinning::Pin& pin, Execution::Context& context) + { + const auto& packageId = pin.GetKey().PackageId; + + bool hasId = context.Args.Contains(Execution::Args::Type::Id); + bool hasName = context.Args.Contains(Execution::Args::Type::Name); + bool hasQuery = context.Args.Contains(Execution::Args::Type::Query); + bool exactMatch = context.Args.Contains(Execution::Args::Type::Exact); + + if (hasId) + { + std::string_view idArg = context.Args.GetArg(Execution::Args::Type::Id); + return exactMatch + ? Utility::CaseInsensitiveEquals(packageId, idArg) + : Utility::CaseInsensitiveContainsSubstring(packageId, idArg); + } + + if (hasName || hasQuery) + { + std::string_view queryArg = hasName + ? context.Args.GetArg(Execution::Args::Type::Name) + : context.Args.GetArg(Execution::Args::Type::Query); + + return exactMatch + ? Utility::CaseInsensitiveEquals(packageId, queryArg) + : Utility::CaseInsensitiveContainsSubstring(packageId, queryArg); + } + + return true; + } } void OpenPinningIndex::operator()(Execution::Context& context) const @@ -249,24 +291,39 @@ namespace AppInstaller::CLI::Workflow void ReportPins(Execution::Context& context) { const auto& pins = context.Get(); - if (pins.empty()) + bool hasListQuery = context.Args.Contains(Execution::Args::Type::Query) || + context.Args.Contains(Execution::Args::Type::Id) || + context.Args.Contains(Execution::Args::Type::Name); + std::vector filteredPins; + filteredPins.reserve(pins.size()); + + for (const auto& pin : pins) { - context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; - return; + if (IsMatchForListQuery(pin, context)) + { + filteredPins.push_back(pin); + } } - Execution::TableOutput<6> table(context.Reporter, + if (filteredPins.empty()) + { + if (hasListQuery) + { + context.Reporter.Info() << Resource::String::PinShowNoMatchFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + } + else { - Resource::String::SearchName, - Resource::String::SearchId, - Resource::String::SearchVersion, - Resource::String::SearchSource, - Resource::String::PinType, - Resource::String::PinVersion, - }); + context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; + } + return; + } + + std::vector rowData; + rowData.reserve(filteredPins.size()); const auto& source = context.Get(); - for (const auto& pin : pins) + for (const auto& pin : filteredPins) { const auto& pinKey = pin.GetKey(); auto searchRequest = GetSearchRequestForPin(pin.GetKey()); @@ -303,18 +360,76 @@ namespace AppInstaller::CLI::Workflow version = installedVersion->GetProperty(PackageVersionProperty::Version); } - table.OutputLine({ - packageName, - pinKey.PackageId, - version, - sourceName, - std::string{ ToString(pin.GetType()) }, - pin.GetGatedVersion().ToString(), + Utility::LocIndString dateAdded; + const auto& pinDateAdded = pin.GetDateAdded(); + if (pinDateAdded.has_value()) + { + dateAdded = Utility::LocIndString{ Utility::TimePointToString(*pinDateAdded, + Utility::TimeFacet::Year | Utility::TimeFacet::Month | Utility::TimeFacet::Day | + Utility::TimeFacet::Hour | Utility::TimeFacet::Minute | Utility::TimeFacet::Second) }; + } + + Utility::LocIndString note; + const auto& pinNote = pin.GetNote(); + if (pinNote.has_value() && !pinNote->empty()) + { + note = Utility::LocIndString{ *pinNote }; + } + + rowData.push_back(PinRowData{ + std::move(packageName), + Utility::LocIndString{ pinKey.PackageId }, + std::move(version), + std::move(sourceName), + Utility::LocIndString{ std::string{ ToString(pin.GetType()) } }, + Utility::LocIndString{ pin.GetGatedVersion().ToString() }, + std::move(dateAdded), + std::move(note), + }); + } + } + + bool showDetails = context.Args.Contains(Execution::Args::Type::ListDetails); + if (!showDetails) + { + Execution::TableOutput<6> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId, + Resource::String::SearchVersion, + Resource::String::SearchSource, + Resource::String::PinType, + Resource::String::PinVersion, }); + + for (const auto& row : rowData) + { + table.OutputLine({ row.PackageName, row.PackageId, row.Version, row.SourceName, row.PinType, row.PinnedVersion }); } + + table.Complete(); } + else + { + Execution::TableOutput<8> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId, + Resource::String::SearchVersion, + Resource::String::SearchSource, + Resource::String::PinType, + Resource::String::PinVersion, + Resource::String::PinDateAdded, + Resource::String::PinNote, + }); - table.Complete(); + for (const auto& row : rowData) + { + table.OutputLine({ row.PackageName, row.PackageId, row.Version, row.SourceName, row.PinType, row.PinnedVersion, row.DateAdded, row.Note }); + } + + table.Complete(); + } } void ResetAllPins(Execution::Context& context) @@ -346,106 +461,4 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; } } - - void ShowPinDetails(Execution::Context& context) - { - auto& pinningData = context.Get(); - auto allPins = pinningData.GetAllPins(); - - // Apply filtering based on provided arguments - bool hasId = context.Args.Contains(Execution::Args::Type::Id); - bool hasName = context.Args.Contains(Execution::Args::Type::Name); - bool hasQuery = context.Args.Contains(Execution::Args::Type::Query); - bool exactMatch = context.Args.Contains(Execution::Args::Type::Exact); - - std::vector matchingPins; - for (const auto& pin : allPins) - { - const auto& packageId = pin.GetKey().PackageId; - - if (hasId) - { - std::string_view idArg = context.Args.GetArg(Execution::Args::Type::Id); - bool match = exactMatch - ? Utility::CaseInsensitiveEquals(packageId, idArg) - : Utility::CaseInsensitiveContainsSubstring(packageId, idArg); - if (!match) - { - continue; - } - } - else if (hasName || hasQuery) - { - // Without an open source, we can only match against PackageId - std::string_view queryArg = hasName - ? context.Args.GetArg(Execution::Args::Type::Name) - : context.Args.GetArg(Execution::Args::Type::Query); - bool match = exactMatch - ? Utility::CaseInsensitiveEquals(packageId, queryArg) - : Utility::CaseInsensitiveContainsSubstring(packageId, queryArg); - if (!match) - { - continue; - } - } - - matchingPins.push_back(pin); - } - - if (matchingPins.empty()) - { - context.Reporter.Info() << Resource::String::PinShowNoMatchFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); - } - - auto info = context.Reporter.Info(); - bool firstPin = true; - for (const auto& pin : matchingPins) - { - if (!firstPin) - { - info << std::endl; - } - firstPin = false; - - const auto& pinKey = pin.GetKey(); - - // ID - ShowSingleLineField(info, Resource::String::PinShowLabelId, Utility::LocIndView{ pinKey.PackageId }); - - // Source - if (!pinKey.SourceId.empty() && !pinKey.IsForInstalled()) - { - ShowSingleLineField(info, Resource::String::PinShowLabelSource, Utility::LocIndView{ pinKey.SourceId }); - } - - // Type - std::string pinTypeStr{ ToString(pin.GetType()) }; - ShowSingleLineField(info, Resource::String::PinShowLabelType, Utility::LocIndView{ pinTypeStr }); - - // Version (gated version string; empty for pinning/blocking pins) - std::string gatedVersionStr = pin.GetGatedVersion().ToString(); - if (!gatedVersionStr.empty()) - { - ShowSingleLineField(info, Resource::String::PinShowLabelVersion, Utility::LocIndView{ gatedVersionStr }); - } - - // Date Added - const auto& dateAdded = pin.GetDateAdded(); - if (dateAdded.has_value()) - { - std::string dateAddedStr = Utility::TimePointToString(*dateAdded, - Utility::TimeFacet::Year | Utility::TimeFacet::Month | Utility::TimeFacet::Day | - Utility::TimeFacet::Hour | Utility::TimeFacet::Minute | Utility::TimeFacet::Second); - ShowSingleLineField(info, Resource::String::PinShowLabelDateAdded, Utility::LocIndView{ dateAddedStr }); - } - - // Note (only shown if present) - const auto& note = pin.GetNote(); - if (note.has_value() && !note->empty()) - { - ShowSingleLineField(info, Resource::String::PinShowLabelNote, Utility::LocIndView{ *note }); - } - } - } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index e9ff60faa4..2e0e55a5db 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -54,14 +54,6 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void ReportPins(Execution::Context& context); - // Shows details for a single matching pin (for `winget pin show`). - // Filters the pinning index by query/name/id/exact args and outputs - // detailed field-by-field info (Name, ID, Version, Source, Type, Date Added, Note). - // Required Args: None (at least one of --query/--name/--id expected) - // Inputs: PinningIndex - // Outputs: None - void ShowPinDetails(Execution::Context& context); - // Resets all the existing pins. // Required Args: None // Inputs: None diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 270ee50729..e375e36b36 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1919,47 +1919,15 @@ Please specify one of them using the --source option to proceed. Time Pinned - Label shown in the pin show output for when the pin was added or last updated. + Table header shown with `winget pin list --details` for when the pin was added or last updated. Note - Label shown in the pin show output for the user-provided note stored with the pin. - - - Show details about a pin - Short description of the 'winget pin show' subcommand, shown in help and usage text. - - - Show detailed information about a specific pin, including the package identifier, version, type, date added, and any note stored with the pin. - Long description of the 'winget pin show' subcommand, shown in detailed help text. - - - Date added: - Label shown in the `winget pin show` output for when the pin was added or last updated. - - - Id: - Label shown in the `winget pin show` output for the package identifier. - - - Note: - Label shown in the `winget pin show` output for the user-provided note stored with the pin. - - - Source: - Label shown in the `winget pin show` output for the package source. - - - Pin type: - Label shown in the `winget pin show` output for the type of pin (e.g., Pinning, Gating, Blocking). - - - Pinned version: - Label shown in the `winget pin show` output for the version string of a gating pin. + Table header shown with `winget pin list --details` for the user-provided note stored with the pin. No pin found matching the specified criteria. - Shown when `winget pin show` finds no matching pin. + Shown when a filtered `winget pin list` command finds no matching pin. <See the log file for additional details> @@ -3697,4 +3665,4 @@ An unlocalized JSON fragment will follow on another line. The DSC processor hash provided does not match hash of the target file. - \ No newline at end of file + diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index baaaf7752a..739077c3c6 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -4,7 +4,9 @@ #include "WorkflowCommon.h" #include "TestHooks.h" #include "PinTestCommon.h" +#include "TestSource.h" #include +#include #include #include #include @@ -225,13 +227,54 @@ TEST_CASE("PinFlow_Add_WithNote", "[PinFlow][workflow]") REQUIRE(pins[0].GetNote().value() == "my test note"); } -// Helper: Creates a v1.1 pinning index at the given path and adds the provided pins directly. +// Helper: Creates a pinning index at the given path and adds the provided pins directly. // Each pin should already have date_added and note set as desired. namespace { - void PopulatePinIndexForShow(const std::filesystem::path& indexPath, const std::vector& pins) + AppInstaller::Manifest::Manifest MakeListTestManifest(std::string_view id) { - PinningIndex index = PinningIndex::CreateNew(indexPath.u8string(), AppInstaller::SQLite::Version::Latest()); + AppInstaller::Manifest::Manifest result; + result.Id = std::string{ id }; + result.DefaultLocalization.Add(std::string{ id }); + result.DefaultLocalization.Add("TestPublisher"); + result.Version = "1.0.0"; + result.Installers.push_back({}); + return result; + } + + TestSourceResult MakeListTestSourceResult(std::string_view id) + { + std::string packageId{ id }; + return TestSourceResult( + packageId, + [packageId](std::vector& matches, std::weak_ptr source) + { + auto manifest = MakeListTestManifest(packageId); + matches.emplace_back( + AppInstaller::Repository::ResultMatch( + TestCompositePackage::Make(std::vector{ manifest }, source), + AppInstaller::Repository::PackageMatchFilter( + AppInstaller::Repository::PackageMatchField::Id, + AppInstaller::Repository::MatchType::Exact, + packageId))); + }); + } + + std::shared_ptr CreateListTestSource(std::initializer_list ids) + { + std::vector results; + results.reserve(ids.size()); + for (auto id : ids) + { + results.emplace_back(MakeListTestSourceResult(id)); + } + + return CreateTestSource(std::move(results)); + } + + void PopulatePinIndexForList(const std::filesystem::path& indexPath, const std::vector& pins, AppInstaller::SQLite::Version version = AppInstaller::SQLite::Version::Latest()) + { + PinningIndex index = PinningIndex::CreateNew(indexPath.u8string(), version); for (const auto& pin : pins) { index.AddPin(pin); @@ -239,28 +282,29 @@ namespace } } -TEST_CASE("PinFlow_Show_NoMatch", "[PinFlow][workflow]") +TEST_CASE("PinFlow_List_Filter_NoMatch", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); Pin existingPin = Pin::CreateBlockingPin({ "SomePackage.Id", "sourceId" }); existingPin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1000)); - PopulatePinIndexForShow(indexFile.GetPath(), { existingPin }); + PopulatePinIndexForList(indexFile.GetPath(), { existingPin }); - std::ostringstream showOutput; - TestContext showContext{ showOutput, std::cin }; - showContext.Args.AddArg(Execution::Args::Type::Query, "ThisQueryMatchesNothing"sv); + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "ThisQueryMatchesNothing"sv); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "SomePackage.Id" })); - PinShowCommand pinShow({}); - pinShow.Execute(showContext); - INFO(showOutput.str()); + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); - REQUIRE_TERMINATED_WITH(showContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); - REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::PinShowNoMatchFound)) != std::string::npos); + REQUIRE_TERMINATED_WITH(listContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinShowNoMatchFound)) != std::string::npos); } -TEST_CASE("PinFlow_Show_MatchById", "[PinFlow][workflow]") +TEST_CASE("PinFlow_List_Filter_MatchById_WithDetails", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); @@ -268,46 +312,49 @@ TEST_CASE("PinFlow_Show_MatchById", "[PinFlow][workflow]") Pin pin = Pin::CreateBlockingPin({ "MyApp.Package", "sourceId" }); pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jun2026_01_0900)); pin.SetNote(std::string{ "keep this one" }); - PopulatePinIndexForShow(indexFile.GetPath(), { pin }); + PopulatePinIndexForList(indexFile.GetPath(), { pin }); - std::ostringstream showOutput; - TestContext showContext{ showOutput, std::cin }; - showContext.Args.AddArg(Execution::Args::Type::Id, "MyApp.Package"sv); + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Id, "MyApp.Package"sv); + listContext.Args.AddArg(Execution::Args::Type::ListDetails); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "MyApp.Package" })); - PinShowCommand pinShow({}); - pinShow.Execute(showContext); - INFO(showOutput.str()); + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); - REQUIRE_FALSE(showContext.IsTerminated()); - REQUIRE(showOutput.str().find("MyApp.Package") != std::string::npos); - REQUIRE(showOutput.str().find("Blocking") != std::string::npos); - REQUIRE(showOutput.str().find("Date added:") != std::string::npos); - REQUIRE(showOutput.str().find("keep this one") != std::string::npos); + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("MyApp.Package") != std::string::npos); + REQUIRE(listOutput.str().find("Blocking") != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) != std::string::npos); + REQUIRE(listOutput.str().find("keep this one") != std::string::npos); } -TEST_CASE("PinFlow_Show_MatchByQuery", "[PinFlow][workflow]") +TEST_CASE("PinFlow_List_Filter_MatchByQuery", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); Pin pin = Pin::CreatePinningPin({ "Contoso.AppOne", "sourceId" }); pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Mar2026_10_1200)); - PopulatePinIndexForShow(indexFile.GetPath(), { pin }); + PopulatePinIndexForList(indexFile.GetPath(), { pin }); - std::ostringstream showOutput; - TestContext showContext{ showOutput, std::cin }; + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; // Partial, case-insensitive match on the package ID - showContext.Args.AddArg(Execution::Args::Type::Query, "appone"sv); + listContext.Args.AddArg(Execution::Args::Type::Query, "appone"sv); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Contoso.AppOne" })); - PinShowCommand pinShow({}); - pinShow.Execute(showContext); - INFO(showOutput.str()); + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); - REQUIRE_FALSE(showContext.IsTerminated()); - REQUIRE(showOutput.str().find("Contoso.AppOne") != std::string::npos); + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("Contoso.AppOne") != std::string::npos); } -TEST_CASE("PinFlow_Show_ExactMatch", "[PinFlow][workflow]") +TEST_CASE("PinFlow_List_Filter_ExactMatch", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); @@ -319,25 +366,26 @@ TEST_CASE("PinFlow_Show_ExactMatch", "[PinFlow][workflow]") Pin pinB = Pin::CreateBlockingPin({ "Vendor.AppExtra", "src" }); pinB.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_01_0000)); - PopulatePinIndexForShow(indexFile.GetPath(), { pinA, pinB }); + PopulatePinIndexForList(indexFile.GetPath(), { pinA, pinB }); - std::ostringstream showOutput; - TestContext showContext{ showOutput, std::cin }; - showContext.Args.AddArg(Execution::Args::Type::Id, "Vendor.App"sv); - showContext.Args.AddArg(Execution::Args::Type::Exact); + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Id, "Vendor.App"sv); + listContext.Args.AddArg(Execution::Args::Type::Exact); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Vendor.App", "Vendor.AppExtra" })); - PinShowCommand pinShow({}); - pinShow.Execute(showContext); - INFO(showOutput.str()); + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); - REQUIRE_FALSE(showContext.IsTerminated()); + REQUIRE_FALSE(listContext.IsTerminated()); // Only the exact-match pin should appear - REQUIRE(showOutput.str().find("Vendor.App") != std::string::npos); + REQUIRE(listOutput.str().find("Vendor.App") != std::string::npos); // The inexact match should NOT appear - REQUIRE(showOutput.str().find("Vendor.AppExtra") == std::string::npos); + REQUIRE(listOutput.str().find("Vendor.AppExtra") == std::string::npos); } -TEST_CASE("PinFlow_Show_NoNote_DoesNotShowNoteLabel", "[PinFlow][workflow]") +TEST_CASE("PinFlow_List_DetailsWithoutNotes_ShowsEmptyNoteColumn", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); @@ -345,22 +393,49 @@ TEST_CASE("PinFlow_Show_NoNote_DoesNotShowNoteLabel", "[PinFlow][workflow]") Pin pin = Pin::CreatePinningPin({ "NoNote.Package", "src" }); pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::May2026_01_0800)); // note intentionally not set - PopulatePinIndexForShow(indexFile.GetPath(), { pin }); + PopulatePinIndexForList(indexFile.GetPath(), { pin }); - std::ostringstream showOutput; - TestContext showContext{ showOutput, std::cin }; - showContext.Args.AddArg(Execution::Args::Type::Query, "NoNote.Package"sv); + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "NoNote.Package"sv); + listContext.Args.AddArg(Execution::Args::Type::ListDetails); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "NoNote.Package" })); - PinShowCommand pinShow({}); - pinShow.Execute(showContext); - INFO(showOutput.str()); + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); - REQUIRE_FALSE(showContext.IsTerminated()); - REQUIRE(showOutput.str().find("NoNote.Package") != std::string::npos); - REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::PinShowLabelNote)) == std::string::npos); + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("NoNote.Package") != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinNote)) != std::string::npos); } -TEST_CASE("PinFlow_Show_EmptyIndex_NoMatch", "[PinFlow][workflow]") +TEST_CASE("PinFlow_List_V1_0Index_NoMigrationRequired", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "Legacy.Package", "src" }); + PopulatePinIndexForList(indexFile.GetPath(), { pin }, { 1, 0 }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "Legacy.Package"sv); + listContext.Args.AddArg(Execution::Args::Type::ListDetails); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Legacy.Package" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("Legacy.Package") != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) == std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinNote)) == std::string::npos); +} + +TEST_CASE("PinFlow_List_Filter_EmptyIndex_NoMatch", "[PinFlow][workflow]") { TempFile indexFile("pinningIndex", ".db"); TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); @@ -368,13 +443,38 @@ TEST_CASE("PinFlow_Show_EmptyIndex_NoMatch", "[PinFlow][workflow]") // Create an empty index (no pins) { PinningIndex::CreateNew(indexFile.GetPath().u8string(), AppInstaller::SQLite::Version::Latest()); } - std::ostringstream showOutput; - TestContext showContext{ showOutput, std::cin }; - showContext.Args.AddArg(Execution::Args::Type::Query, "AnyQuery"sv); + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "AnyQuery"sv); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({})); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); - PinShowCommand pinShow({}); - pinShow.Execute(showContext); - INFO(showOutput.str()); + REQUIRE_TERMINATED_WITH(listContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); +} + +TEST_CASE("PinFlow_List_DefaultOutput_DoesNotShowDetailsColumns", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "Has.Note.Package", "sourceId" }); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jun2026_01_0900)); + pin.SetNote(std::string{ "some note" }); + PopulatePinIndexForList(indexFile.GetPath(), { pin }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "Has.Note.Package"sv); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Has.Note.Package" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); - REQUIRE_TERMINATED_WITH(showContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("Has.Note.Package") != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) == std::string::npos); } From 41e10afc4fb23f2f42cda51de3a6a8ca3c98ed68 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 14:40:45 -0500 Subject: [PATCH 57/60] Update release notes --- doc/ReleaseNotes.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 57c970aeb6..8955dd2f15 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -1,6 +1,12 @@ ## New in v1.29 -Nothing yet. +## New Features + +### Pinning improvements + +- `winget pin add` now supports an optional `--note` value for pin metadata. +- `winget pin show` was added to display detailed pin information for a package. +- The PowerShell pin cmdlets now expose the new pinning capabilities and metadata. ## Bug Fixes From f05bcc83aa5de5df978cc877b66dc6cdb87d614c Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 14:41:33 -0500 Subject: [PATCH 58/60] Revert instructions edit --- .github/copilot-instructions.md | 372 ++++++++++++++++---------------- 1 file changed, 192 insertions(+), 180 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6dd5ce1dd3..cff632ea80 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,180 +1,192 @@ -# WinGet CLI Development Guide - -## Project Overview - -This is the Windows Package Manager (WinGet) CLI client - a native Windows application for discovering and installing packages. The codebase consists of: - -- **C++/WinRT client** (`src/AppInstallerCLI*`) - The main CLI and core logic -- **COM API** (`src/Microsoft.Management.Deployment`) - Public Windows Runtime API for programmatic access -- **PowerShell modules** (`src/PowerShell`) - Microsoft.WinGet.Client and Microsoft.WinGet.Configuration cmdlets -- **Configuration system** - DSC-based system configuration using WinGet - -## Building, Testing, and Running - -### Initial Setup - -Use a configuration file in `.config` as in `winget configure .config/configuration.winget` (alternatives provided for other VS SKUs). - -Manual steps: - -1. Install Visual Studio 2022 with required workloads (see `.vsconfig`) -2. Install Windows SDK 10.0.26100: `winget install Microsoft.WindowsSDK.10.0.26100` -3. Enable developer mode in Windows -4. Run `vcpkg integrate install` from Developer Command Prompt - -### Building - -Open `src\AppInstallerCLI.sln` in Visual Studio and build the solution (Ctrl+Shift+B) or use msbuild.exe to build from the command line. - -The solution uses: -- MSBuild -- vcpkg for C++ dependencies -- NuGet for C++ and .NET dependencies - -### Running/Debugging - -1. Deploy solution: Build > Deploy Solution -2. Run from command line: `wingetdev` -3. For debugging: - - Right-click `AppInstallerCLIPackage` > Properties > Debug tab - - Set Debugger type to "Native Only" for both Application and Background task processes - - Select "Do not launch, but debug my code when it starts" - - Press F5 and run `wingetdev` in a separate terminal - -Entry point: `src/AppInstallerCLI/main.cpp` - -### Testing - -#### C++ Unit Tests (Catch2) -Located in `AppInstallerCLITests` project. After building: - -```powershell -# Run all tests -src\\\AppInstallerCLITests\AppInstallerCLITests.exe - -# Run specific test -src\\\AppInstallerCLITests\AppInstallerCLITests.exe TestName - -# Available options -AppInstallerCLITests.exe --help -``` - -#### .NET Tests -- `Microsoft.WinGet.UnitTests` - PowerShell module tests -- `Microsoft.Management.Configuration.UnitTests` - Configuration system tests -- `WinGetUtilInterop.UnitTests` - Interop layer tests - -#### E2E Tests -`AppInstallerCLIE2ETests` project contains end-to-end integration tests. - -## Architecture - -### Core Components - -**AppInstallerCLICore** - Core CLI logic organized around: -- **ExecutionContext**: State container that flows through workflows. Contains arguments, reporter, flags, and data (ExecutionContextData.h) -- **Workflows**: Composable functions that take ExecutionContext and perform operations (e.g., InstallFlow, UpdateFlow, SearchFlow) -- **Commands**: Parse arguments and orchestrate workflows -- **Reporter**: Handles all user output (ExecutionReporter.h) - -**AppInstallerRepositoryCore** - Package source abstraction: -- Interfaces for different source types (REST, SQLite index, Microsoft Store, composite) -- Search, match, and correlation logic -- Package version selection and dependencies - -**AppInstallerCommonCore** - Shared utilities: -- Manifest parsing (YAML/JSON) -- Settings and group policy -- Telemetry and logging -- HTTP client, downloader, archive handling - -**Microsoft.Management.Deployment** - COM API surface: -- IDL definitions in `PackageManager.idl` -- WinRT projections for external consumption -- Used by PowerShell modules and third-party integrations - -**AppInstallerCLIPackage** - Dev MSIX package definition: -- Models the release package definition as closely as possible. -- Contains localized string resources at src\AppInstallerCLIPackage\Shared\Strings\en-us\winget.resw - -### Key Patterns - -**Workflow Pattern**: Functions that operate on ExecutionContext: -```cpp -void WorkflowTask(Execution::Context& context) -{ - // Check if already terminated - AICLI_RETURN_IF_TERMINATED(context); - - // Access data - auto& data = context.Get(); - - // Report to user - context.Reporter.Info() << "Doing something"; - - // Store data for next workflow - context.Add(result); - - // Terminate on error - if (failed) - { - AICLI_TERMINATE_CONTEXT(HRESULT); - } -} -``` - -**Source Composition**: Multiple package sources can be composed: -- CompositeSource combines multiple sources with conflict resolution -- Installed source tracks locally installed packages -- Available sources provide packages to install - -**Manifest Schema**: Package manifests use versioned YAML schemas: -- Schema definitions in `schemas/JSON/manifests/` -- Parsing in `AppInstallerCommonCore/Manifest/` -- Multi-file manifests: installer, locale, version, defaultLocale - -## Windows-Specific Considerations - -- Use Windows-style paths with backslashes (`\`) -- Leverage WinRT APIs via C++/WinRT projections -- COM threading models matter - client uses multi-threaded apartment (MTA) -- Package deployment uses Windows App SDK / MSIX infrastructure -- Requires Windows 10 1809+ (build 17763) - -## Contributing - -- Review `CONTRIBUTING.md` for workflow -- File/discuss issues before starting work -- Specs required for features (stored in `doc/specs/`); see `.github/instructions/specs.instructions.md` for detailed guidance -- Follow existing code style (see `stylecop.json`) -- CI runs on Azure Pipelines (`azure-pipelines.yml`) - -## Useful Commands - -```powershell -# Get WinGet client info -wingetdev --info - -# Show experimental features -wingetdev features - -# Check sources -wingetdev source list -``` - -## Localization - -### Source of truth - -The English resource file `src\AppInstallerCLIPackage\Shared\Strings\en-us\winget.resw` is the only file contributors should edit for string changes. It feeds the Microsoft localization pipeline. - -The files under `Localization\Resources\\` are **automatically synced from Microsoft's internal localization system and must not be edited**. Any manual edits will be overwritten on the next sync. - -Every string that could be misunderstood without context should have a ``. - -### Triggering retranslation - -- **Changing a string's ``** automatically queues it for retranslation on the next localization sync. -- **Changing only a ``** does NOT trigger retranslation. Comments improve future translations but do not fix existing ones. - -To fix an existing bad translation, a bug has to be filed internally with the localization team. +# WinGet CLI Development Guide + +## Project Overview + +This is the Windows Package Manager (WinGet) CLI client - a native Windows application for discovering and installing packages. The codebase consists of: + +- **C++/WinRT client** (`src/AppInstallerCLI*`) - The main CLI and core logic +- **COM API** (`src/Microsoft.Management.Deployment`) - Public Windows Runtime API for programmatic access +- **PowerShell modules** (`src/PowerShell`) - Microsoft.WinGet.Client and Microsoft.WinGet.Configuration cmdlets +- **Configuration system** - DSC-based system configuration using WinGet + +## Building, Testing, and Running + +### Initial Setup + +Use a configuration file in `.config` as in `winget configure .config/configuration.winget` (alternatives provided for other VS SKUs). + +Manual steps: + +1. Install Visual Studio 2022 with required workloads (see `.vsconfig`) +2. Install Windows SDK 10.0.26100: `winget install Microsoft.WindowsSDK.10.0.26100` +3. Enable developer mode in Windows +4. Run `vcpkg integrate install` from Developer Command Prompt + +### Building + +Open `src\AppInstallerCLI.sln` in Visual Studio and build the solution (Ctrl+Shift+B) or use msbuild.exe to build from the command line. + +The solution uses: +- MSBuild +- vcpkg for C++ dependencies +- NuGet for C++ and .NET dependencies + +### Running/Debugging + +1. Deploy solution: Build > Deploy Solution +2. Run from command line: `wingetdev` +3. For debugging: + - Right-click `AppInstallerCLIPackage` > Properties > Debug tab + - Set Debugger type to "Native Only" for both Application and Background task processes + - Select "Do not launch, but debug my code when it starts" + - Press F5 and run `wingetdev` in a separate terminal + +Entry point: `src/AppInstallerCLI/main.cpp` + +### Testing + +#### C++ Unit Tests (Catch2) +Located in `AppInstallerCLITests` project. After building: + +```powershell +# Run all tests +src\\\AppInstallerCLITests\AppInstallerCLITests.exe + +# Run specific test +src\\\AppInstallerCLITests\AppInstallerCLITests.exe TestName + +# Available options +AppInstallerCLITests.exe --help +``` + +#### .NET Tests +- `Microsoft.WinGet.UnitTests` - PowerShell module tests +- `Microsoft.Management.Configuration.UnitTests` - Configuration system tests +- `WinGetUtilInterop.UnitTests` - Interop layer tests + +#### E2E Tests +`AppInstallerCLIE2ETests` project contains end-to-end integration tests. + +## Architecture + +### Core Components + +**AppInstallerCLICore** - Core CLI logic organized around: +- **ExecutionContext**: State container that flows through workflows. Contains arguments, reporter, flags, and data (ExecutionContextData.h) +- **Workflows**: Composable functions that take ExecutionContext and perform operations (e.g., InstallFlow, UpdateFlow, SearchFlow) +- **Commands**: Parse arguments and orchestrate workflows +- **Reporter**: Handles all user output (ExecutionReporter.h) + +**AppInstallerRepositoryCore** - Package source abstraction: +- Interfaces for different source types (REST, SQLite index, Microsoft Store, composite) +- Search, match, and correlation logic +- Package version selection and dependencies + +**AppInstallerCommonCore** - Shared utilities: +- Manifest parsing (YAML/JSON) +- Settings and group policy +- Telemetry and logging +- HTTP client, downloader, archive handling + +**Microsoft.Management.Deployment** - COM API surface: +- IDL definitions in `PackageManager.idl` +- WinRT projections for external consumption +- Used by PowerShell modules and third-party integrations + +**AppInstallerCLIPackage** - Dev MSIX package definition: +- Models the release package definition as closely as possible. +- Contains localized string resources at src\AppInstallerCLIPackage\Shared\Strings\en-us\winget.resw + +### Key Patterns + +**Workflow Pattern**: Functions that operate on ExecutionContext: +```cpp +void WorkflowTask(Execution::Context& context) +{ + // Check if already terminated + AICLI_RETURN_IF_TERMINATED(context); + + // Access data + auto& data = context.Get(); + + // Report to user + context.Reporter.Info() << "Doing something"; + + // Store data for next workflow + context.Add(result); + + // Terminate on error + if (failed) + { + AICLI_TERMINATE_CONTEXT(HRESULT); + } +} +``` + +**Source Composition**: Multiple package sources can be composed: +- CompositeSource combines multiple sources with conflict resolution +- Installed source tracks locally installed packages +- Available sources provide packages to install + +**Manifest Schema**: Package manifests use versioned YAML schemas: +- Schema definitions in `schemas/JSON/manifests/` +- Parsing in `AppInstallerCommonCore/Manifest/` +- Multi-file manifests: installer, locale, version, defaultLocale + +## Naming Conventions + +- **Namespace structure**: `AppInstaller::[::]` + - `AppInstaller::CLI::Execution` - CLI execution context + - `AppInstaller::CLI::Workflow` - Workflow functions + - `AppInstaller::Repository` - Repository/source logic + - `AppInstaller::Manifest` - Manifest types + - `AppInstaller::Settings` - User/admin settings + +- **Macros**: Prefixed with `AICLI_` for CLI, `WINGET_` for general +- **Data keys**: ExecutionContextData uses enum keys to type-safely store/retrieve data + +## Windows-Specific Considerations + +- Use Windows-style paths with backslashes (`\`) +- Leverage WinRT APIs via C++/WinRT projections +- COM threading models matter - client uses multi-threaded apartment (MTA) +- Package deployment uses Windows App SDK / MSIX infrastructure +- Requires Windows 10 1809+ (build 17763) + +## Contributing + +- Review `CONTRIBUTING.md` for workflow +- File/discuss issues before starting work +- Specs required for features (stored in `doc/specs/`); see `.github/instructions/specs.instructions.md` for detailed guidance +- Follow existing code style (see `stylecop.json`) +- CI runs on Azure Pipelines (`azure-pipelines.yml`) + +## Useful Commands + +```powershell +# Get WinGet client info +wingetdev --info + +# Show experimental features +wingetdev features + +# Check sources +wingetdev source list +``` + +## Localization + +### Source of truth + +The English resource file `src\AppInstallerCLIPackage\Shared\Strings\en-us\winget.resw` is the only file contributors should edit for string changes. It feeds the Microsoft localization pipeline. + +The files under `Localization\Resources\\` are **automatically synced from Microsoft's internal localization system and must not be edited**. Any manual edits will be overwritten on the next sync. + +Every string that could be misunderstood without context should have a ``. + +### Triggering retranslation + +- **Changing a string's ``** automatically queues it for retranslation on the next localization sync. +- **Changing only a ``** does NOT trigger retranslation. Comments improve future translations but do not fix existing ones. + +To fix an existing bad translation, a bug has to be filed internally with the localization team. From fdcd860cae243c0854b236ecf7251006c7d55a65 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 14:42:09 -0500 Subject: [PATCH 59/60] Run renormalize --- src/AppInstallerCLICore/Argument.cpp | 1298 +-- .../Commands/PinCommand.cpp | 698 +- src/AppInstallerCLICore/Commands/PinCommand.h | 182 +- src/AppInstallerCLICore/ExecutionArgs.h | 616 +- src/AppInstallerCLICore/Resources.h | 1748 ++-- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 926 +-- src/AppInstallerCLICore/Workflows/PinFlow.h | 124 +- .../Package.appxmanifest | 320 +- .../Shared/Strings/en-us/winget.resw | 7336 ++++++++--------- src/AppInstallerCLITests/PinFlow.cpp | 960 +-- src/AppInstallerCLITests/SQLiteWrapper.cpp | 1778 ++-- .../Public/winget/Pin.h | 256 +- .../AppInstallerRepositoryCore.vcxproj | 1060 +-- ...AppInstallerRepositoryCore.vcxproj.filters | 1674 ++-- .../Microsoft/PinningIndex.cpp | 494 +- .../Microsoft/PinningIndex.h | 148 +- .../Microsoft/Schema/1_0/OneToOneTable.cpp | 434 +- .../Schema/1_0/SearchResultsTable_1_0.cpp | 604 +- .../Schema/1_1/ManifestMetadataTable.cpp | 262 +- .../Schema/2_0/OneToManyTableWithMap.cpp | 900 +- .../Schema/2_0/PackageUpdateTrackingTable.cpp | 498 +- .../Schema/2_0/SearchResultsTable_2_0.cpp | 640 +- .../Microsoft/Schema/IPinningIndex.h | 88 +- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 374 +- .../Microsoft/Schema/Pinning_1_0/PinTable.h | 100 +- .../Pinning_1_0/PinningIndexInterface_1_0.cpp | 262 +- .../Schema/Portable_1_0/PortableTable.cpp | 356 +- .../PinningData.cpp | 454 +- .../Public/winget/PinningData.h | 220 +- .../Public/winget/SQLiteStatementBuilder.h | 1196 +-- .../Database/Schema/0_1/SetInfoTable.cpp | 528 +- .../Database/Schema/0_2/QueueTable.cpp | 310 +- .../ClassesDefinition.cs | 108 +- .../ComClsids.cpp | 50 +- .../Converters.cpp | 1198 +-- .../Converters.h | 422 +- .../Microsoft.Management.Deployment.vcxproj | 582 +- .../PackageManager.cpp | 3502 ++++---- .../PackageManager.h | 156 +- .../PackageManager.idl | 3908 ++++----- .../Public/ComClsids.h | 122 +- .../Commands/FinderPackageCommand.cs | 184 +- .../Helpers/ManagementDeploymentFactory.cs | 500 +- .../Helpers/PSEnumHelpers.cs | 376 +- .../Helpers/PackageManagerWrapper.cs | 498 +- .../PSObjects/PSInstalledCatalogPackage.cs | 162 +- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 2424 +++--- 47 files changed, 20518 insertions(+), 20518 deletions(-) diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index f22524c8b2..dc82fec770 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -1,649 +1,649 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Argument.h" -#include "Command.h" -#include "Resources.h" -#include -#include - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace Settings; - using namespace AppInstaller::Utility::literals; - - namespace - { - bool ContainsArgumentFromList(const Execution::Args& args, const std::vector& argTypes) - { - return std::any_of(argTypes.begin(), argTypes.end(), [&](Execution::Args::Type arg) { return args.Contains(arg); }); - } - } - - ArgumentCommon ArgumentCommon::ForType(Execution::Args::Type type) - { - // A test ensures that all types are listed here - switch (type) - { - // Args to specify where to get app - case Execution::Args::Type::Query: - return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::MultiQuery: - return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::MultiplePackages }; - case Execution::Args::Type::Manifest: - return { type, "manifest"_liv, 'm', ArgTypeCategory::Manifest }; - - // Query filtering criteria and query behavior - case Execution::Args::Type::Id: - return { type, "id"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Name: - return { type, "name"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Moniker: - return { type, "moniker"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Tag: - return { type, "tag"_liv, ArgTypeCategory::PackageQuery }; - case Execution::Args::Type::Command: - return { type, "command"_liv, "cmd"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Source: - return { type, "source"_liv, 's', ArgTypeCategory::QuerySource }; - case Execution::Args::Type::Count: - return { type, "count"_liv, 'n', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Exact: - return { type, "exact"_liv, 'e', ArgTypeCategory::PackageQuery }; - - // Manifest selection behavior after an app is found - case Execution::Args::Type::Version: - return { type, "version"_liv, 'v', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Channel: - return { type, "channel"_liv, 'c', ArgTypeCategory::PackageQuery }; - - // Install behavior - case Execution::Args::Type::Interactive: - return { type, "interactive"_liv, 'i', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::Silent: - return { type, "silent"_liv, 'h', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::Locale: - return { type, "locale"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::Log: - return { type, "log"_liv, 'o', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; - case Execution::Args::Type::CustomSwitches: - return { type, "custom"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; - case Execution::Args::Type::Override: - return { type, "override"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; - case Execution::Args::Type::InstallLocation: - return { type, "location"_liv, 'l', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; - case Execution::Args::Type::InstallScope: - return { type, "scope"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::InstallArchitecture: - return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::InstallerArchitecture: // Used for input architecture that does not need applicability check. E.g. Download, Show. - return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::InstallerType: - return { type, "installer-type"_liv, ArgTypeCategory::InstallerSelection }; - case Execution::Args::Type::HashOverride: - return { type, "ignore-security-hash"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::IgnoreLocalArchiveMalwareScan: - return { type, "ignore-local-archive-malware-scan"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::AcceptPackageAgreements: - return { type, "accept-package-agreements"_liv, ArgTypeCategory::InstallerBehavior }; - case Execution::Args::Type::Rename: - return { type, "rename"_liv, 'r' }; - case Execution::Args::Type::NoUpgrade: - return { type, "no-upgrade"_liv, ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::SkipDependencies: - return { type, "skip-dependencies"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::DependenciesConflict }; - case Execution::Args::Type::DependenciesOnly: - return { type, "dependencies-only"_liv, ArgTypeCategory::InstallerBehavior, ArgTypeExclusiveSet::DependenciesConflict }; - case Execution::Args::Type::AllowReboot: - return { type, "allow-reboot"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - - // Uninstall behavior - case Execution::Args::Type::Purge: - return { type, "purge"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; - case Execution::Args::Type::Preserve: - return { type, "preserve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; - case Execution::Args::Type::ProductCode: - return { type, "product-code"_liv, ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::AllVersions: - return { type, "all-versions"_liv, "all"_liv, ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::AllAndTargetVersion }; - case Execution::Args::Type::TargetVersion: - return { type, "version"_liv, 'v', ArgTypeCategory::SinglePackageQuery, ArgTypeExclusiveSet::AllAndTargetVersion }; - - // Source Command - case Execution::Args::Type::SourceName: - return { type, "name"_liv, 'n' }; - case Execution::Args::Type::SourceType: - return { type, "type"_liv, 't' }; - case Execution::Args::Type::SourceArg: - return { type, "arg"_liv, 'a' }; - case Execution::Args::Type::ForceSourceReset: - return { type, "force"_liv }; - case Execution::Args::Type::SourceExplicit: - return { type, "explicit"_liv }; - case Execution::Args::Type::SourceTrustLevel: - return { type, "trust-level"_liv }; - case Execution::Args::Type::SourceEditExplicit: - return { type, "explicit"_liv, 'e' }; - case Execution::Args::Type::SourcePriority: - return { type, "priority"_liv, 'p' }; - - // Hash Command - case Execution::Args::Type::HashFile: - return { type, "file"_liv, 'f' }; - case Execution::Args::Type::Msix: - return { type, "msix"_liv, 'm' }; - - // Validate Command - case Execution::Args::Type::ValidateManifest: - return { type, "manifest"_liv }; - case Execution::Args::Type::IgnoreWarnings: - return { type, "ignore-warnings"_liv, "nowarn"_liv}; - - // Complete Command - case Execution::Args::Type::Word: - return { type, "word"_liv }; - case Execution::Args::Type::CommandLine: - return { type, "commandline"_liv }; - case Execution::Args::Type::Position: - return { type, "position"_liv }; - - // Export Command - case Execution::Args::Type::IncludeVersions: - return { type, "include-versions"_liv }; - - // Import Command - case Execution::Args::Type::ImportFile: - return { type, "import-file"_liv, 'i' }; - case Execution::Args::Type::IgnoreUnavailable: - return { type, "ignore-unavailable"_liv }; - case Execution::Args::Type::IgnoreVersions: - return { type, "ignore-versions"_liv }; - - // Setting Command - case Execution::Args::Type::AdminSettingEnable: - return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; - case Execution::Args::Type::AdminSettingDisable: - return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; - case Execution::Args::Type::SettingName: - return { type, "setting"_liv }; - case Execution::Args::Type::SettingValue: - return { type, "value"_liv }; - - // Upgrade command - case Execution::Args::Type::All: - return { type, "all"_liv, 'r', "recurse"_liv, ArgTypeCategory::MultiplePackages }; - case Execution::Args::Type::IncludeUnknown: - return { type, "include-unknown"_liv, 'u', "unknown"_liv, ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::IncludePinned: - return { type, "include-pinned"_liv, "pinned"_liv, ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::UninstallPrevious: - return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - - // Show command - case Execution::Args::Type::ListVersions: - return { type, "versions"_liv }; - - // List command - case Execution::Args::Type::Upgrade: - return { type, "upgrade-available"_liv}; - case Execution::Args::Type::ListDetails: - return { type, "details"_liv }; - case Execution::Args::Type::Sort: - return { type, "sort"_liv }; - case Execution::Args::Type::SortAscending: - return { type, "ascending"_liv, "asc"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::SortDirection }; - case Execution::Args::Type::SortDescending: - return { type, "descending"_liv, "desc"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::SortDirection }; - - // Pin command - case Execution::Args::Type::GatedVersion: - return { type, "version"_liv, 'v', ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; - case Execution::Args::Type::BlockingPin: - return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; - case Execution::Args::Type::PinInstalled: - return { type, "installed"_liv, ArgTypeCategory::None }; - case Execution::Args::Type::PinNote: - return { type, "note"_liv, ArgTypeCategory::None }; - - // Error command - case Execution::Args::Type::ErrorInput: - return { type, "input"_liv, ArgTypeCategory::None }; - - // Resume command - case Execution::Args::Type::ResumeId: - return { type, "resume-id"_liv, 'g', ArgTypeCategory::None }; - case Execution::Args::Type::IgnoreResumeLimit: - return { type, "ignore-resume-limit"_liv, ArgTypeCategory::None }; - - // Font command - case Execution::Args::Type::Family: - return { type, "family"_liv, ArgTypeCategory::None }; - case Execution::Args::Type::Details: - return { type, "details"_liv, ArgTypeCategory::None }; - - // Configuration commands - case Execution::Args::Type::ConfigurationFile: - return { type, "file"_liv, 'f', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; - case Execution::Args::Type::ConfigurationAcceptWarning: - return { type, "accept-configuration-agreements"_liv }; - case Execution::Args::Type::ConfigurationSuppressPrologue: - return { type, "suppress-initial-details"_liv }; - case Execution::Args::Type::ExtendedFeaturesEnable: - return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; - case Execution::Args::Type::ExtendedFeaturesDisable: - return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; - case Execution::Args::Type::ConfigurationModulePath: - return { type, "module-path"_liv }; - case Execution::Args::Type::ConfigurationProcessorPath: - return { type, "processor-path"_liv }; - case Execution::Args::Type::ConfigurationExportPackageId: - return { type, "package-id"_liv }; - case Execution::Args::Type::ConfigurationExportModule: - return { type, "module"_liv }; - case Execution::Args::Type::ConfigurationExportResource: - return { type, "resource"_liv }; - case Execution::Args::Type::ConfigurationExportAll: - return { type, "all"_liv, 'r', "recurse"_liv }; - case Execution::Args::Type::ConfigurationHistoryItem: - return { type, "history"_liv, 'h', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; - case Execution::Args::Type::ConfigurationHistoryRemove: - return { type, "remove"_liv }; - case Execution::Args::Type::ConfigurationStatusWatch: - return { type, "live"_liv }; - - // DSCv3 resources - case Execution::Args::Type::DscResourceFunctionGet: - return { type, "get"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionSet: - return { type, "set"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionWhatIf: - return { type, "whatIf"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionTest: - return { type, "test"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionDelete: - return { type, "delete"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionExport: - return { type, "export"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionValidate: - return { type, "validate"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionResolve: - return { type, "resolve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionAdapter: - return { type, "adapter"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionSchema: - return { type, "schema"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionManifest: - return { type, "manifest"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - - // Download command - case Execution::Args::Type::DownloadDirectory: - return { type, "download-directory"_liv, 'd', ArgTypeCategory::None }; - case Execution::Args::Type::Platform: - return { type, "platform"_liv, ArgTypeCategory::None }; - case Execution::Args::Type::SkipMicrosoftStorePackageLicense: - return { type, "skip-microsoft-store-package-license"_liv, "skip-license"_liv, ArgTypeCategory::None }; - case Execution::Args::Type::OSVersion: - return { type, "os-version"_liv, ArgTypeCategory::None }; - - // Common arguments - case Execution::Args::Type::NoVT: - return { type, "no-vt"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; - case Execution::Args::Type::RetroStyle: - return { type, "retro"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; - case Execution::Args::Type::RainbowStyle: - return { type, "rainbow"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; - case Execution::Args::Type::NoProgress: - return { type, "no-progress"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; - case Execution::Args::Type::Help: - return { type, "help"_liv, APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR }; - case Execution::Args::Type::Info: - return { type, "info"_liv }; - case Execution::Args::Type::VerboseLogs: - return { type, "verbose-logs"_liv, "verbose"_liv }; - case Execution::Args::Type::DisableInteractivity: - return { type, "disable-interactivity"_liv }; - case Execution::Args::Type::Wait: - return { type, "wait"_liv }; - case Execution::Args::Type::OpenLogs: - return { type, "open-logs"_liv, "logs"_liv }; - case Execution::Args::Type::Force: - return { type, "force"_liv, ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::OutputFile: - return { type, "output"_liv, 'o' }; - case Execution::Args::Type::Correlation: - return { type, "correlation"_liv }; - - case Execution::Args::Type::DependencySource: - return { type, "dependency-source"_liv, ArgTypeCategory::ExtendedSource }; - case Execution::Args::Type::CustomHeader: - return { type, "header"_liv, ArgTypeCategory::ExtendedSource }; - case Execution::Args::Type::AcceptSourceAgreements: - return { type, "accept-source-agreements"_liv, ArgTypeCategory::ExtendedSource }; - - case Execution::Args::Type::Proxy: - return { type, "proxy"_liv, ArgTypeCategory::CopyValueToSubContext, ArgTypeExclusiveSet::Proxy }; - case Execution::Args::Type::NoProxy: - return { type, "no-proxy"_liv, ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::Proxy }; - - case Execution::Args::Type::ToolVersion: - return { type, "version"_liv, 'v' }; - - // Authentication arguments - case Execution::Args::Type::AuthenticationMode: - return { type, "authentication-mode"_liv, ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::AuthenticationAccount: - return { type, "authentication-account"_liv, ArgTypeCategory::CopyValueToSubContext }; - - // Used for demonstration purposes - case Execution::Args::Type::ExperimentalArg: - return { type, "arg"_liv }; - - default: - THROW_HR(E_UNEXPECTED); - } - } - - std::vector ArgumentCommon::GetFromExecArgs(const Execution::Args& execArgs) - { - auto argTypes = execArgs.GetTypes(); - std::vector result; - std::transform(argTypes.begin(), argTypes.end(), std::back_inserter(result), ArgumentCommon::ForType); - return result; - } - - Argument Argument::ForType(Execution::Args::Type type) - { - switch (type) - { - case Args::Type::Query: - return Argument{ type, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; - case Args::Type::MultiQuery: - return Argument{ type, Resource::String::MultiQueryArgumentDescription, ArgumentType::Positional }.SetCountLimit(128); - case Args::Type::Manifest: - return Argument{ type, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles, Settings::BoolAdminSetting::LocalManifestFiles }; - case Args::Type::Id: - return Argument{ type, Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Name: - return Argument{ type, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Moniker: - return Argument{ type, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Tag: - return Argument{ type, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Command: - return Argument{ type, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Source: - return Argument{ type, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; - case Args::Type::DependencySource: - return Argument{ type, Resource::String::DependencySourceArgumentDescription, ArgumentType::Standard }; - case Args::Type::Count: - return Argument{ type, Resource::String::CountArgumentDescription, ArgumentType::Standard }; - case Args::Type::Exact: - return Argument{ type, Resource::String::ExactArgumentDescription, ArgumentType::Flag }; - case Args::Type::Version: - return Argument{ type, Resource::String::VersionArgumentDescription, ArgumentType::Standard }; - case Args::Type::Channel: - return Argument{ type, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; - case Args::Type::Interactive: - return Argument{ type, Resource::String::InteractiveArgumentDescription, ArgumentType::Flag }; - case Args::Type::Silent: - return Argument{ type, Resource::String::SilentArgumentDescription, ArgumentType::Flag }; - case Args::Type::Locale: - return Argument{ type, Resource::String::LocaleArgumentDescription, ArgumentType::Standard }; - case Args::Type::InstallArchitecture: - return Argument{ type, Resource::String::ArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::InstallerArchitecture: - return Argument{ type, Resource::String::ArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Log: - return Argument{ type, Resource::String::LogArgumentDescription, ArgumentType::Standard }; - case Args::Type::CustomSwitches: - return Argument{ type, Resource::String::CustomSwitchesArgumentDescription, ArgumentType::Standard }; - case Args::Type::Override: - return Argument{ type, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::InstallLocation: - return Argument{ type, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; - case Args::Type::HashOverride: - return Argument{ type, Resource::String::HashOverrideArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::HashOverride, Settings::BoolAdminSetting::InstallerHashOverride }; - case Args::Type::AcceptPackageAgreements: - return Argument{ type, Resource::String::AcceptPackageAgreementsArgumentDescription, ArgumentType::Flag }; - case Args::Type::NoUpgrade: - return Argument{ type, Resource::String::NoUpgradeArgumentDescription, ArgumentType::Flag }; - case Args::Type::HashFile: - return Argument{ type, Resource::String::FileArgumentDescription, ArgumentType::Positional, true }; - case Args::Type::Msix: - return Argument{ type, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; - case Args::Type::ListVersions: - return Argument{ type, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; - case Args::Type::Help: - return Argument{ type, Resource::String::HelpArgumentDescription, ArgumentType::Flag }; - case Args::Type::SkipDependencies: - return Argument{ type, Resource::String::SkipDependenciesArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::DependenciesOnly: - return Argument{ type, Resource::String::DependenciesOnlyArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::IgnoreLocalArchiveMalwareScan: - return Argument{ type, Resource::String::IgnoreLocalArchiveMalwareScanArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::LocalArchiveMalwareScanOverride, Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride }; - case Args::Type::SourceName: - return Argument{ type, Resource::String::SourceNameArgumentDescription, ArgumentType::Positional, false }; - case Args::Type::SourceArg: - return Argument{ type, Resource::String::SourceArgArgumentDescription, ArgumentType::Positional, true }; - case Args::Type::SourceType: - return Argument{ type, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional }; - case Args::Type::SourceExplicit: - return Argument{ type, Resource::String::SourceExplicitArgumentDescription, ArgumentType::Flag }; - case Args::Type::SourceEditExplicit: - return Argument{ type, Resource::String::SourceEditExplicitArgumentDescription, ArgumentType::Standard }; - case Args::Type::SourcePriority: - return Argument{ type, Resource::String::SourcePriorityArgumentDescription, ArgumentType::Standard, ExperimentalFeature::Feature::SourcePriority }; - case Args::Type::SourceTrustLevel: - return Argument{ type, Resource::String::SourceTrustLevelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::ValidateManifest: - return Argument{ type, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; - case Args::Type::IgnoreWarnings: - return Argument{ type, Resource::String::IgnoreWarningsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - case Args::Type::NoVT: - return Argument{ type, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::RainbowStyle: - return Argument{ type, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::RetroStyle: - return Argument{ type, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::NoProgress: - return Argument{ type, Resource::String::NoProgressArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::VerboseLogs: - return Argument{ type, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; - case Args::Type::CustomHeader: - return Argument{ type, Resource::String::HeaderArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::AcceptSourceAgreements: - return Argument{ type, Resource::String::AcceptSourceAgreementsArgumentDescription, ArgumentType::Flag }; - case Args::Type::AuthenticationMode: - return Argument{ type, Resource::String::AuthenticationModeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::AuthenticationAccount: - return Argument{ type, Resource::String::AuthenticationAccountArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::ExperimentalArg: - return Argument{ type, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; - case Args::Type::Rename: - return Argument{ type, Resource::String::RenameArgumentDescription, ArgumentType::Standard, false }; - case Args::Type::Purge: - return Argument{ type, Resource::String::PurgeArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::Preserve: - return Argument{ type, Resource::String::PreserveArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::Wait: - return Argument{ type, Resource::String::WaitArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::ProductCode: - return Argument{ type, Resource::String::ProductCodeArgumentDescription, ArgumentType::Standard, false }; - case Args::Type::OpenLogs: - return Argument{ type, Resource::String::OpenLogsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - case Args::Type::UninstallPrevious: - return Argument{ type, Resource::String::UninstallPreviousArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - case Args::Type::Force: - return Argument{ type, Resource::String::ForceArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::DownloadDirectory: - return Argument{ type, Resource::String::DownloadDirectoryArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; - case Args::Type::SkipMicrosoftStorePackageLicense: - return Argument{ type, Resource::String::SkipMicrosoftStorePackageLicenseArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help, false }; - case Args::Type::Platform: - return Argument{ type, Resource::String::PlatformArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; - case Args::Type::InstallerType: - return Argument{ type, Resource::String::InstallerTypeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; - case Args::Type::ResumeId: - return Argument{ type, Resource::String::ResumeIdArgumentDescription, ArgumentType::Standard, true }; - case Args::Type::AllowReboot: - return Argument{ type, Resource::String::AllowRebootArgumentDescription, ArgumentType::Flag }; - case Args::Type::IgnoreResumeLimit: - return Argument{ type, Resource::String::IgnoreResumeLimitArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::Resume }; - case Args::Type::AllVersions: - return Argument{ type, Resource::String::UninstallAllVersionsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - case Args::Type::TargetVersion: - return Argument{ type, Resource::String::TargetVersionArgumentDescription, ArgumentType::Standard }; - case Args::Type::Proxy: - return Argument{ type, Resource::String::ProxyArgumentDescription, ArgumentType::Standard, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; - case Args::Type::NoProxy: - return Argument{ type, Resource::String::NoProxyArgumentDescription, ArgumentType::Flag, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; - case Args::Type::ConfigurationProcessorPath: - return Argument{ type, Resource::String::ConfigurationProcessorPath, ArgumentType::Standard, Argument::Visibility::Help, TogglePolicy::Policy::ConfigurationProcessorPath, BoolAdminSetting::ConfigurationProcessorPath }; - case Args::Type::Family: - return Argument{ type, Resource::String::FontFamilyNameArgumentDescription, ArgumentType::Positional, false }; - case Args::Type::Details: - return Argument{ type, Resource::String::FontDetailsArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::Correlation: - return Argument{ type, Resource::String::CorrelationArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; - case Args::Type::ListDetails: - return Argument{ type, Resource::String::ListDetailsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - default: - THROW_HR(E_UNEXPECTED); - } - } - - void Argument::GetCommon(std::vector& args) - { - args.push_back(ForType(Args::Type::Help)); - args.push_back(ForType(Args::Type::Wait)); - args.push_back(ForType(Args::Type::OpenLogs)); - args.push_back(ForType(Args::Type::NoVT)); - args.push_back(ForType(Args::Type::RainbowStyle)); - args.push_back(ForType(Args::Type::RetroStyle)); - args.push_back(ForType(Args::Type::NoProgress)); - args.push_back(ForType(Args::Type::VerboseLogs)); - args.push_back(ForType(Args::Type::IgnoreWarnings)); - args.emplace_back(Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Flag, false); - args.push_back(ForType(Args::Type::Proxy)); - args.push_back(ForType(Args::Type::NoProxy)); - args.push_back(ForType(Args::Type::Correlation)); - } - - std::string Argument::GetUsageString() const - { - std::ostringstream strstr; - if (Alias() != ArgumentCommon::NoAlias) - { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Alias() << ','; - } - if (AlternateName() != Argument::NoAlternateName) - { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << AlternateName() << ','; - } - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Name(); - return strstr.str(); - } - - void Argument::ValidateExclusiveArguments(const Execution::Args& args) - { - auto argProperties = ArgumentCommon::GetFromExecArgs(args); - - using ExclusiveSet_t = std::underlying_type_t; - for (ExclusiveSet_t i = 1 + static_cast(ArgTypeExclusiveSet::None); i < static_cast(ArgTypeExclusiveSet::Max); i <<= 1) - { - std::vector argsFromSet; - std::copy_if( - argProperties.begin(), - argProperties.end(), - std::back_inserter(argsFromSet), - [=](const ArgumentCommon& arg) { return static_cast(arg.ExclusiveSet) & i; }); - - if (argsFromSet.size() > 1) - { - // Create a string showing the exclusive args. - std::string argsString; - for (const auto& arg : argsFromSet) - { - if (!argsString.empty()) - { - argsString += '|'; - - } - - argsString += arg.Name; - } - - throw CommandException(Resource::String::MultipleExclusiveArgumentsProvided(Utility::LocIndString{ argsString })); - } - } - } - - void Argument::ValidateArgumentDependency(const Execution::Args& args, Execution::Args::Type type, Execution::Args::Type dependencyArgType) - { - if (args.Contains(type) && !args.Contains(dependencyArgType)) - { - throw CommandException(Resource::String::DependencyArgumentMissing( - Utility::LocIndString{ ArgumentCommon::ForType(type).Name }, - Utility::LocIndString{ ArgumentCommon::ForType(dependencyArgType).Name })); - } - } - - ArgTypeCategory Argument::GetCategoriesPresent(const Execution::Args& args) - { - auto argProperties = ArgumentCommon::GetFromExecArgs(args); - - ArgTypeCategory result = ArgTypeCategory::None; - for (const auto& arg : argProperties) - { - result |= arg.TypeCategory; - } - - return result; - } - - ArgTypeCategory Argument::GetCategoriesAndValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg) - { - const auto categories = GetCategoriesPresent(args); - - // Commands like install require some argument to select a package - if (requirePackageSelectionArg) - { - if (WI_AreAllFlagsClear(categories, ArgTypeCategory::Manifest | ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery)) - { - throw CommandException(Resource::String::NoPackageSelectionArgumentProvided); - } - } - - // If a manifest is specified, we cannot also have arguments for searching - if (WI_IsFlagSet(categories, ArgTypeCategory::Manifest) && - WI_IsAnyFlagSet(categories, ArgTypeCategory::PackageQuery | ArgTypeCategory::QuerySource)) - { - throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); - } - - // If we have multiple packages, we cannot have arguments that only make sense for a single package - if (WI_IsFlagSet(categories, ArgTypeCategory::MultiplePackages) && - WI_IsAnyFlagSet(categories, ArgTypeCategory::SinglePackageQuery | ArgTypeCategory::SingleInstallerBehavior)) - { - throw CommandException(Resource::String::ArgumentForSinglePackageProvidedWithMultipleQueries); - } - - return categories; - } - - Argument::Visibility Argument::GetVisibility() const - { - if (!ExperimentalFeature::IsEnabled(m_feature)) - { - return Argument::Visibility::Hidden; - } - - if (!GroupPolicies().IsEnabled(m_groupPolicy)) - { - return Argument::Visibility::Hidden; - } - - return m_visibility; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Argument.h" +#include "Command.h" +#include "Resources.h" +#include +#include + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace Settings; + using namespace AppInstaller::Utility::literals; + + namespace + { + bool ContainsArgumentFromList(const Execution::Args& args, const std::vector& argTypes) + { + return std::any_of(argTypes.begin(), argTypes.end(), [&](Execution::Args::Type arg) { return args.Contains(arg); }); + } + } + + ArgumentCommon ArgumentCommon::ForType(Execution::Args::Type type) + { + // A test ensures that all types are listed here + switch (type) + { + // Args to specify where to get app + case Execution::Args::Type::Query: + return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::MultiQuery: + return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::MultiplePackages }; + case Execution::Args::Type::Manifest: + return { type, "manifest"_liv, 'm', ArgTypeCategory::Manifest }; + + // Query filtering criteria and query behavior + case Execution::Args::Type::Id: + return { type, "id"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Name: + return { type, "name"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Moniker: + return { type, "moniker"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Tag: + return { type, "tag"_liv, ArgTypeCategory::PackageQuery }; + case Execution::Args::Type::Command: + return { type, "command"_liv, "cmd"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Source: + return { type, "source"_liv, 's', ArgTypeCategory::QuerySource }; + case Execution::Args::Type::Count: + return { type, "count"_liv, 'n', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Exact: + return { type, "exact"_liv, 'e', ArgTypeCategory::PackageQuery }; + + // Manifest selection behavior after an app is found + case Execution::Args::Type::Version: + return { type, "version"_liv, 'v', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Channel: + return { type, "channel"_liv, 'c', ArgTypeCategory::PackageQuery }; + + // Install behavior + case Execution::Args::Type::Interactive: + return { type, "interactive"_liv, 'i', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::Silent: + return { type, "silent"_liv, 'h', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::Locale: + return { type, "locale"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::Log: + return { type, "log"_liv, 'o', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::CustomSwitches: + return { type, "custom"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::Override: + return { type, "override"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::InstallLocation: + return { type, "location"_liv, 'l', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::InstallScope: + return { type, "scope"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::InstallArchitecture: + return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::InstallerArchitecture: // Used for input architecture that does not need applicability check. E.g. Download, Show. + return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::InstallerType: + return { type, "installer-type"_liv, ArgTypeCategory::InstallerSelection }; + case Execution::Args::Type::HashOverride: + return { type, "ignore-security-hash"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::IgnoreLocalArchiveMalwareScan: + return { type, "ignore-local-archive-malware-scan"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::AcceptPackageAgreements: + return { type, "accept-package-agreements"_liv, ArgTypeCategory::InstallerBehavior }; + case Execution::Args::Type::Rename: + return { type, "rename"_liv, 'r' }; + case Execution::Args::Type::NoUpgrade: + return { type, "no-upgrade"_liv, ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::SkipDependencies: + return { type, "skip-dependencies"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::DependenciesConflict }; + case Execution::Args::Type::DependenciesOnly: + return { type, "dependencies-only"_liv, ArgTypeCategory::InstallerBehavior, ArgTypeExclusiveSet::DependenciesConflict }; + case Execution::Args::Type::AllowReboot: + return { type, "allow-reboot"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + + // Uninstall behavior + case Execution::Args::Type::Purge: + return { type, "purge"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; + case Execution::Args::Type::Preserve: + return { type, "preserve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; + case Execution::Args::Type::ProductCode: + return { type, "product-code"_liv, ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::AllVersions: + return { type, "all-versions"_liv, "all"_liv, ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::AllAndTargetVersion }; + case Execution::Args::Type::TargetVersion: + return { type, "version"_liv, 'v', ArgTypeCategory::SinglePackageQuery, ArgTypeExclusiveSet::AllAndTargetVersion }; + + // Source Command + case Execution::Args::Type::SourceName: + return { type, "name"_liv, 'n' }; + case Execution::Args::Type::SourceType: + return { type, "type"_liv, 't' }; + case Execution::Args::Type::SourceArg: + return { type, "arg"_liv, 'a' }; + case Execution::Args::Type::ForceSourceReset: + return { type, "force"_liv }; + case Execution::Args::Type::SourceExplicit: + return { type, "explicit"_liv }; + case Execution::Args::Type::SourceTrustLevel: + return { type, "trust-level"_liv }; + case Execution::Args::Type::SourceEditExplicit: + return { type, "explicit"_liv, 'e' }; + case Execution::Args::Type::SourcePriority: + return { type, "priority"_liv, 'p' }; + + // Hash Command + case Execution::Args::Type::HashFile: + return { type, "file"_liv, 'f' }; + case Execution::Args::Type::Msix: + return { type, "msix"_liv, 'm' }; + + // Validate Command + case Execution::Args::Type::ValidateManifest: + return { type, "manifest"_liv }; + case Execution::Args::Type::IgnoreWarnings: + return { type, "ignore-warnings"_liv, "nowarn"_liv}; + + // Complete Command + case Execution::Args::Type::Word: + return { type, "word"_liv }; + case Execution::Args::Type::CommandLine: + return { type, "commandline"_liv }; + case Execution::Args::Type::Position: + return { type, "position"_liv }; + + // Export Command + case Execution::Args::Type::IncludeVersions: + return { type, "include-versions"_liv }; + + // Import Command + case Execution::Args::Type::ImportFile: + return { type, "import-file"_liv, 'i' }; + case Execution::Args::Type::IgnoreUnavailable: + return { type, "ignore-unavailable"_liv }; + case Execution::Args::Type::IgnoreVersions: + return { type, "ignore-versions"_liv }; + + // Setting Command + case Execution::Args::Type::AdminSettingEnable: + return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; + case Execution::Args::Type::AdminSettingDisable: + return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; + case Execution::Args::Type::SettingName: + return { type, "setting"_liv }; + case Execution::Args::Type::SettingValue: + return { type, "value"_liv }; + + // Upgrade command + case Execution::Args::Type::All: + return { type, "all"_liv, 'r', "recurse"_liv, ArgTypeCategory::MultiplePackages }; + case Execution::Args::Type::IncludeUnknown: + return { type, "include-unknown"_liv, 'u', "unknown"_liv, ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::IncludePinned: + return { type, "include-pinned"_liv, "pinned"_liv, ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::UninstallPrevious: + return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + + // Show command + case Execution::Args::Type::ListVersions: + return { type, "versions"_liv }; + + // List command + case Execution::Args::Type::Upgrade: + return { type, "upgrade-available"_liv}; + case Execution::Args::Type::ListDetails: + return { type, "details"_liv }; + case Execution::Args::Type::Sort: + return { type, "sort"_liv }; + case Execution::Args::Type::SortAscending: + return { type, "ascending"_liv, "asc"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::SortDirection }; + case Execution::Args::Type::SortDescending: + return { type, "descending"_liv, "desc"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::SortDirection }; + + // Pin command + case Execution::Args::Type::GatedVersion: + return { type, "version"_liv, 'v', ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; + case Execution::Args::Type::BlockingPin: + return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; + case Execution::Args::Type::PinInstalled: + return { type, "installed"_liv, ArgTypeCategory::None }; + case Execution::Args::Type::PinNote: + return { type, "note"_liv, ArgTypeCategory::None }; + + // Error command + case Execution::Args::Type::ErrorInput: + return { type, "input"_liv, ArgTypeCategory::None }; + + // Resume command + case Execution::Args::Type::ResumeId: + return { type, "resume-id"_liv, 'g', ArgTypeCategory::None }; + case Execution::Args::Type::IgnoreResumeLimit: + return { type, "ignore-resume-limit"_liv, ArgTypeCategory::None }; + + // Font command + case Execution::Args::Type::Family: + return { type, "family"_liv, ArgTypeCategory::None }; + case Execution::Args::Type::Details: + return { type, "details"_liv, ArgTypeCategory::None }; + + // Configuration commands + case Execution::Args::Type::ConfigurationFile: + return { type, "file"_liv, 'f', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; + case Execution::Args::Type::ConfigurationAcceptWarning: + return { type, "accept-configuration-agreements"_liv }; + case Execution::Args::Type::ConfigurationSuppressPrologue: + return { type, "suppress-initial-details"_liv }; + case Execution::Args::Type::ExtendedFeaturesEnable: + return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; + case Execution::Args::Type::ExtendedFeaturesDisable: + return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; + case Execution::Args::Type::ConfigurationModulePath: + return { type, "module-path"_liv }; + case Execution::Args::Type::ConfigurationProcessorPath: + return { type, "processor-path"_liv }; + case Execution::Args::Type::ConfigurationExportPackageId: + return { type, "package-id"_liv }; + case Execution::Args::Type::ConfigurationExportModule: + return { type, "module"_liv }; + case Execution::Args::Type::ConfigurationExportResource: + return { type, "resource"_liv }; + case Execution::Args::Type::ConfigurationExportAll: + return { type, "all"_liv, 'r', "recurse"_liv }; + case Execution::Args::Type::ConfigurationHistoryItem: + return { type, "history"_liv, 'h', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; + case Execution::Args::Type::ConfigurationHistoryRemove: + return { type, "remove"_liv }; + case Execution::Args::Type::ConfigurationStatusWatch: + return { type, "live"_liv }; + + // DSCv3 resources + case Execution::Args::Type::DscResourceFunctionGet: + return { type, "get"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionSet: + return { type, "set"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionWhatIf: + return { type, "whatIf"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionTest: + return { type, "test"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionDelete: + return { type, "delete"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionExport: + return { type, "export"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionValidate: + return { type, "validate"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionResolve: + return { type, "resolve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionAdapter: + return { type, "adapter"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionSchema: + return { type, "schema"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionManifest: + return { type, "manifest"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + + // Download command + case Execution::Args::Type::DownloadDirectory: + return { type, "download-directory"_liv, 'd', ArgTypeCategory::None }; + case Execution::Args::Type::Platform: + return { type, "platform"_liv, ArgTypeCategory::None }; + case Execution::Args::Type::SkipMicrosoftStorePackageLicense: + return { type, "skip-microsoft-store-package-license"_liv, "skip-license"_liv, ArgTypeCategory::None }; + case Execution::Args::Type::OSVersion: + return { type, "os-version"_liv, ArgTypeCategory::None }; + + // Common arguments + case Execution::Args::Type::NoVT: + return { type, "no-vt"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::RetroStyle: + return { type, "retro"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::RainbowStyle: + return { type, "rainbow"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::NoProgress: + return { type, "no-progress"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::Help: + return { type, "help"_liv, APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR }; + case Execution::Args::Type::Info: + return { type, "info"_liv }; + case Execution::Args::Type::VerboseLogs: + return { type, "verbose-logs"_liv, "verbose"_liv }; + case Execution::Args::Type::DisableInteractivity: + return { type, "disable-interactivity"_liv }; + case Execution::Args::Type::Wait: + return { type, "wait"_liv }; + case Execution::Args::Type::OpenLogs: + return { type, "open-logs"_liv, "logs"_liv }; + case Execution::Args::Type::Force: + return { type, "force"_liv, ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::OutputFile: + return { type, "output"_liv, 'o' }; + case Execution::Args::Type::Correlation: + return { type, "correlation"_liv }; + + case Execution::Args::Type::DependencySource: + return { type, "dependency-source"_liv, ArgTypeCategory::ExtendedSource }; + case Execution::Args::Type::CustomHeader: + return { type, "header"_liv, ArgTypeCategory::ExtendedSource }; + case Execution::Args::Type::AcceptSourceAgreements: + return { type, "accept-source-agreements"_liv, ArgTypeCategory::ExtendedSource }; + + case Execution::Args::Type::Proxy: + return { type, "proxy"_liv, ArgTypeCategory::CopyValueToSubContext, ArgTypeExclusiveSet::Proxy }; + case Execution::Args::Type::NoProxy: + return { type, "no-proxy"_liv, ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::Proxy }; + + case Execution::Args::Type::ToolVersion: + return { type, "version"_liv, 'v' }; + + // Authentication arguments + case Execution::Args::Type::AuthenticationMode: + return { type, "authentication-mode"_liv, ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::AuthenticationAccount: + return { type, "authentication-account"_liv, ArgTypeCategory::CopyValueToSubContext }; + + // Used for demonstration purposes + case Execution::Args::Type::ExperimentalArg: + return { type, "arg"_liv }; + + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::vector ArgumentCommon::GetFromExecArgs(const Execution::Args& execArgs) + { + auto argTypes = execArgs.GetTypes(); + std::vector result; + std::transform(argTypes.begin(), argTypes.end(), std::back_inserter(result), ArgumentCommon::ForType); + return result; + } + + Argument Argument::ForType(Execution::Args::Type type) + { + switch (type) + { + case Args::Type::Query: + return Argument{ type, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; + case Args::Type::MultiQuery: + return Argument{ type, Resource::String::MultiQueryArgumentDescription, ArgumentType::Positional }.SetCountLimit(128); + case Args::Type::Manifest: + return Argument{ type, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles, Settings::BoolAdminSetting::LocalManifestFiles }; + case Args::Type::Id: + return Argument{ type, Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Name: + return Argument{ type, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Moniker: + return Argument{ type, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Tag: + return Argument{ type, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Command: + return Argument{ type, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Source: + return Argument{ type, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; + case Args::Type::DependencySource: + return Argument{ type, Resource::String::DependencySourceArgumentDescription, ArgumentType::Standard }; + case Args::Type::Count: + return Argument{ type, Resource::String::CountArgumentDescription, ArgumentType::Standard }; + case Args::Type::Exact: + return Argument{ type, Resource::String::ExactArgumentDescription, ArgumentType::Flag }; + case Args::Type::Version: + return Argument{ type, Resource::String::VersionArgumentDescription, ArgumentType::Standard }; + case Args::Type::Channel: + return Argument{ type, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; + case Args::Type::Interactive: + return Argument{ type, Resource::String::InteractiveArgumentDescription, ArgumentType::Flag }; + case Args::Type::Silent: + return Argument{ type, Resource::String::SilentArgumentDescription, ArgumentType::Flag }; + case Args::Type::Locale: + return Argument{ type, Resource::String::LocaleArgumentDescription, ArgumentType::Standard }; + case Args::Type::InstallArchitecture: + return Argument{ type, Resource::String::ArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::InstallerArchitecture: + return Argument{ type, Resource::String::ArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Log: + return Argument{ type, Resource::String::LogArgumentDescription, ArgumentType::Standard }; + case Args::Type::CustomSwitches: + return Argument{ type, Resource::String::CustomSwitchesArgumentDescription, ArgumentType::Standard }; + case Args::Type::Override: + return Argument{ type, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::InstallLocation: + return Argument{ type, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; + case Args::Type::HashOverride: + return Argument{ type, Resource::String::HashOverrideArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::HashOverride, Settings::BoolAdminSetting::InstallerHashOverride }; + case Args::Type::AcceptPackageAgreements: + return Argument{ type, Resource::String::AcceptPackageAgreementsArgumentDescription, ArgumentType::Flag }; + case Args::Type::NoUpgrade: + return Argument{ type, Resource::String::NoUpgradeArgumentDescription, ArgumentType::Flag }; + case Args::Type::HashFile: + return Argument{ type, Resource::String::FileArgumentDescription, ArgumentType::Positional, true }; + case Args::Type::Msix: + return Argument{ type, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; + case Args::Type::ListVersions: + return Argument{ type, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; + case Args::Type::Help: + return Argument{ type, Resource::String::HelpArgumentDescription, ArgumentType::Flag }; + case Args::Type::SkipDependencies: + return Argument{ type, Resource::String::SkipDependenciesArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::DependenciesOnly: + return Argument{ type, Resource::String::DependenciesOnlyArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::IgnoreLocalArchiveMalwareScan: + return Argument{ type, Resource::String::IgnoreLocalArchiveMalwareScanArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::LocalArchiveMalwareScanOverride, Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride }; + case Args::Type::SourceName: + return Argument{ type, Resource::String::SourceNameArgumentDescription, ArgumentType::Positional, false }; + case Args::Type::SourceArg: + return Argument{ type, Resource::String::SourceArgArgumentDescription, ArgumentType::Positional, true }; + case Args::Type::SourceType: + return Argument{ type, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional }; + case Args::Type::SourceExplicit: + return Argument{ type, Resource::String::SourceExplicitArgumentDescription, ArgumentType::Flag }; + case Args::Type::SourceEditExplicit: + return Argument{ type, Resource::String::SourceEditExplicitArgumentDescription, ArgumentType::Standard }; + case Args::Type::SourcePriority: + return Argument{ type, Resource::String::SourcePriorityArgumentDescription, ArgumentType::Standard, ExperimentalFeature::Feature::SourcePriority }; + case Args::Type::SourceTrustLevel: + return Argument{ type, Resource::String::SourceTrustLevelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::ValidateManifest: + return Argument{ type, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; + case Args::Type::IgnoreWarnings: + return Argument{ type, Resource::String::IgnoreWarningsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + case Args::Type::NoVT: + return Argument{ type, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + case Args::Type::RainbowStyle: + return Argument{ type, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + case Args::Type::RetroStyle: + return Argument{ type, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + case Args::Type::NoProgress: + return Argument{ type, Resource::String::NoProgressArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + case Args::Type::VerboseLogs: + return Argument{ type, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; + case Args::Type::CustomHeader: + return Argument{ type, Resource::String::HeaderArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::AcceptSourceAgreements: + return Argument{ type, Resource::String::AcceptSourceAgreementsArgumentDescription, ArgumentType::Flag }; + case Args::Type::AuthenticationMode: + return Argument{ type, Resource::String::AuthenticationModeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::AuthenticationAccount: + return Argument{ type, Resource::String::AuthenticationAccountArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::ExperimentalArg: + return Argument{ type, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; + case Args::Type::Rename: + return Argument{ type, Resource::String::RenameArgumentDescription, ArgumentType::Standard, false }; + case Args::Type::Purge: + return Argument{ type, Resource::String::PurgeArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::Preserve: + return Argument{ type, Resource::String::PreserveArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::Wait: + return Argument{ type, Resource::String::WaitArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::ProductCode: + return Argument{ type, Resource::String::ProductCodeArgumentDescription, ArgumentType::Standard, false }; + case Args::Type::OpenLogs: + return Argument{ type, Resource::String::OpenLogsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + case Args::Type::UninstallPrevious: + return Argument{ type, Resource::String::UninstallPreviousArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + case Args::Type::Force: + return Argument{ type, Resource::String::ForceArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::DownloadDirectory: + return Argument{ type, Resource::String::DownloadDirectoryArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; + case Args::Type::SkipMicrosoftStorePackageLicense: + return Argument{ type, Resource::String::SkipMicrosoftStorePackageLicenseArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help, false }; + case Args::Type::Platform: + return Argument{ type, Resource::String::PlatformArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; + case Args::Type::InstallerType: + return Argument{ type, Resource::String::InstallerTypeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; + case Args::Type::ResumeId: + return Argument{ type, Resource::String::ResumeIdArgumentDescription, ArgumentType::Standard, true }; + case Args::Type::AllowReboot: + return Argument{ type, Resource::String::AllowRebootArgumentDescription, ArgumentType::Flag }; + case Args::Type::IgnoreResumeLimit: + return Argument{ type, Resource::String::IgnoreResumeLimitArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::Resume }; + case Args::Type::AllVersions: + return Argument{ type, Resource::String::UninstallAllVersionsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + case Args::Type::TargetVersion: + return Argument{ type, Resource::String::TargetVersionArgumentDescription, ArgumentType::Standard }; + case Args::Type::Proxy: + return Argument{ type, Resource::String::ProxyArgumentDescription, ArgumentType::Standard, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; + case Args::Type::NoProxy: + return Argument{ type, Resource::String::NoProxyArgumentDescription, ArgumentType::Flag, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; + case Args::Type::ConfigurationProcessorPath: + return Argument{ type, Resource::String::ConfigurationProcessorPath, ArgumentType::Standard, Argument::Visibility::Help, TogglePolicy::Policy::ConfigurationProcessorPath, BoolAdminSetting::ConfigurationProcessorPath }; + case Args::Type::Family: + return Argument{ type, Resource::String::FontFamilyNameArgumentDescription, ArgumentType::Positional, false }; + case Args::Type::Details: + return Argument{ type, Resource::String::FontDetailsArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::Correlation: + return Argument{ type, Resource::String::CorrelationArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; + case Args::Type::ListDetails: + return Argument{ type, Resource::String::ListDetailsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + default: + THROW_HR(E_UNEXPECTED); + } + } + + void Argument::GetCommon(std::vector& args) + { + args.push_back(ForType(Args::Type::Help)); + args.push_back(ForType(Args::Type::Wait)); + args.push_back(ForType(Args::Type::OpenLogs)); + args.push_back(ForType(Args::Type::NoVT)); + args.push_back(ForType(Args::Type::RainbowStyle)); + args.push_back(ForType(Args::Type::RetroStyle)); + args.push_back(ForType(Args::Type::NoProgress)); + args.push_back(ForType(Args::Type::VerboseLogs)); + args.push_back(ForType(Args::Type::IgnoreWarnings)); + args.emplace_back(Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Flag, false); + args.push_back(ForType(Args::Type::Proxy)); + args.push_back(ForType(Args::Type::NoProxy)); + args.push_back(ForType(Args::Type::Correlation)); + } + + std::string Argument::GetUsageString() const + { + std::ostringstream strstr; + if (Alias() != ArgumentCommon::NoAlias) + { + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Alias() << ','; + } + if (AlternateName() != Argument::NoAlternateName) + { + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << AlternateName() << ','; + } + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Name(); + return strstr.str(); + } + + void Argument::ValidateExclusiveArguments(const Execution::Args& args) + { + auto argProperties = ArgumentCommon::GetFromExecArgs(args); + + using ExclusiveSet_t = std::underlying_type_t; + for (ExclusiveSet_t i = 1 + static_cast(ArgTypeExclusiveSet::None); i < static_cast(ArgTypeExclusiveSet::Max); i <<= 1) + { + std::vector argsFromSet; + std::copy_if( + argProperties.begin(), + argProperties.end(), + std::back_inserter(argsFromSet), + [=](const ArgumentCommon& arg) { return static_cast(arg.ExclusiveSet) & i; }); + + if (argsFromSet.size() > 1) + { + // Create a string showing the exclusive args. + std::string argsString; + for (const auto& arg : argsFromSet) + { + if (!argsString.empty()) + { + argsString += '|'; + + } + + argsString += arg.Name; + } + + throw CommandException(Resource::String::MultipleExclusiveArgumentsProvided(Utility::LocIndString{ argsString })); + } + } + } + + void Argument::ValidateArgumentDependency(const Execution::Args& args, Execution::Args::Type type, Execution::Args::Type dependencyArgType) + { + if (args.Contains(type) && !args.Contains(dependencyArgType)) + { + throw CommandException(Resource::String::DependencyArgumentMissing( + Utility::LocIndString{ ArgumentCommon::ForType(type).Name }, + Utility::LocIndString{ ArgumentCommon::ForType(dependencyArgType).Name })); + } + } + + ArgTypeCategory Argument::GetCategoriesPresent(const Execution::Args& args) + { + auto argProperties = ArgumentCommon::GetFromExecArgs(args); + + ArgTypeCategory result = ArgTypeCategory::None; + for (const auto& arg : argProperties) + { + result |= arg.TypeCategory; + } + + return result; + } + + ArgTypeCategory Argument::GetCategoriesAndValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg) + { + const auto categories = GetCategoriesPresent(args); + + // Commands like install require some argument to select a package + if (requirePackageSelectionArg) + { + if (WI_AreAllFlagsClear(categories, ArgTypeCategory::Manifest | ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery)) + { + throw CommandException(Resource::String::NoPackageSelectionArgumentProvided); + } + } + + // If a manifest is specified, we cannot also have arguments for searching + if (WI_IsFlagSet(categories, ArgTypeCategory::Manifest) && + WI_IsAnyFlagSet(categories, ArgTypeCategory::PackageQuery | ArgTypeCategory::QuerySource)) + { + throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); + } + + // If we have multiple packages, we cannot have arguments that only make sense for a single package + if (WI_IsFlagSet(categories, ArgTypeCategory::MultiplePackages) && + WI_IsAnyFlagSet(categories, ArgTypeCategory::SinglePackageQuery | ArgTypeCategory::SingleInstallerBehavior)) + { + throw CommandException(Resource::String::ArgumentForSinglePackageProvidedWithMultipleQueries); + } + + return categories; + } + + Argument::Visibility Argument::GetVisibility() const + { + if (!ExperimentalFeature::IsEnabled(m_feature)) + { + return Argument::Visibility::Hidden; + } + + if (!GroupPolicies().IsEnabled(m_groupPolicy)) + { + return Argument::Visibility::Hidden; + } + + return m_visibility; + } +} diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index 5ad615ebfc..c96a180ded 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -1,349 +1,349 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PinCommand.h" -#include "Workflows/CompletionFlow.h" -#include "Workflows/PinFlow.h" -#include "Workflows/WorkflowBase.h" -#include "Resources.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::CLI::Workflow; - using namespace AppInstaller::Utility::literals; - using namespace std::string_view_literals; - - Utility::LocIndView s_PinCommand_HelpLink = "https://aka.ms/winget-command-pin"_liv; - - std::vector> PinCommand::GetCommands() const - { - return InitializeFromMoveOnly>>({ - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - }); - } - - Resource::LocString PinCommand::ShortDescription() const - { - return { Resource::String::PinCommandShortDescription }; - } - - Resource::LocString PinCommand::LongDescription() const - { - return { Resource::String::PinCommandLongDescription }; - } - - Utility::LocIndView PinCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinCommand::ExecuteInternal(Execution::Context& context) const - { - OutputHelp(context.Reporter); - } - - std::vector PinAddCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Tag), - Argument::ForType(Args::Type::Command), - Argument::ForType(Args::Type::Exact), - Argument{ Args::Type::GatedVersion, Resource::String::GatedVersionArgumentDescription, ArgumentType::Standard }, - Argument::ForType(Args::Type::Source), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument::ForType(Args::Type::Force), - Argument{ Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag }, - Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, - Argument{ Args::Type::PinNote, Resource::String::PinNoteArgumentDescription, ArgumentType::Standard }, - }; - } - - Resource::LocString PinAddCommand::ShortDescription() const - { - return { Resource::String::PinAddCommandShortDescription }; - } - - Resource::LocString PinAddCommand::LongDescription() const - { - return { Resource::String::PinAddCommandLongDescription }; - } - - void PinAddCommand::Complete(Execution::Context& context, Args::Type valueType) const - { - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::Query: - context << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForManyCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Source: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - context << - Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - } - } - - Utility::LocIndView PinAddCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinAddCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - Argument::ValidateCommonArguments(execArgs); - } - - void PinAddCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::Id)) - { - // When we are given an ID, just pin that available package without checking for installed. - // This helps when there are matching issues, for example due to multiple side-by-side installs. - context << - Workflow::OpenSource(); - } - else - { - // If not working from just ID, try matching a single installed package - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - } - - context << - Workflow::SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Pin) << - Workflow::GetInstalledPackageVersion << - Workflow::ReportPackageIdentity << - Workflow::OpenPinningIndex() << - Workflow::AddPin; - } - - std::vector PinRemoveCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Source), - Argument::ForType(Args::Type::Tag), - Argument::ForType(Args::Type::Command), - Argument::ForType(Args::Type::Exact), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, - }; - } - - Resource::LocString PinRemoveCommand::ShortDescription() const - { - return { Resource::String::PinRemoveCommandShortDescription }; - } - - Resource::LocString PinRemoveCommand::LongDescription() const - { - return { Resource::String::PinRemoveCommandLongDescription }; - } - - void PinRemoveCommand::Complete(Execution::Context& context, Args::Type valueType) const - { - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::Query: - context << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForManyCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Source: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - context << - Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - } - } - - Utility::LocIndView PinRemoveCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinRemoveCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::Id)) - { - // When we are given an ID, just un-pin that available package without checking for installed. - // This helps when there are matching issues, for example due to multiple side-by-side installs. - context << - Workflow::OpenSource(); - } - else - { - // If not working from just ID, try matching a single installed package - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - } - - context << - Workflow::SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Pin) << - Workflow::GetInstalledPackageVersion << - Workflow::ReportPackageIdentity << - Workflow::OpenPinningIndex() << - Workflow::SearchPin << - Workflow::RemovePin; - } - - std::vector PinListCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Source), - Argument::ForType(Args::Type::Tag), - Argument::ForType(Args::Type::Command), - Argument::ForType(Args::Type::Exact), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument::ForType(Args::Type::ListDetails), - }; - } - - Resource::LocString PinListCommand::ShortDescription() const - { - return { Resource::String::PinListCommandShortDescription }; - } - - Resource::LocString PinListCommand::LongDescription() const - { - return { Resource::String::PinListCommandLongDescription }; - } - - void PinListCommand::Complete(Execution::Context& context, Args::Type valueType) const - { - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::Query: - context << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForManyCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Source: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - context << - Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - } - } - - Utility::LocIndView PinListCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinListCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::OpenPinningIndex(/* readOnly */ true) << - Workflow::GetAllPins << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, false, Repository::CompositeSearchBehavior::AllPackages) << - Workflow::ReportPins; - } - - std::vector PinResetCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Force), - Argument::ForType(Args::Type::Source), - }; - } - - Resource::LocString PinResetCommand::ShortDescription() const - { - return { Resource::String::PinResetCommandShortDescription }; - } - - Resource::LocString PinResetCommand::LongDescription() const - { - return { Resource::String::PinResetCommandLongDescription }; - } - - Utility::LocIndView PinResetCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinResetCommand::ExecuteInternal(Execution::Context& context) const - { - - if (context.Args.Contains(Execution::Args::Type::Force)) - { - context << - Workflow::OpenPinningIndex() << - Workflow::ResetAllPins; - } - else - { - AICLI_LOG(CLI, Info, << "--force argument is not present"); - context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; - - context << - Workflow::OpenPinningIndex(/* readOnly */ true) << - Workflow::GetAllPins << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << - Workflow::ReportPins; - } - } - -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinCommand.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/PinFlow.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Workflow; + using namespace AppInstaller::Utility::literals; + using namespace std::string_view_literals; + + Utility::LocIndView s_PinCommand_HelpLink = "https://aka.ms/winget-command-pin"_liv; + + std::vector> PinCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + }); + } + + Resource::LocString PinCommand::ShortDescription() const + { + return { Resource::String::PinCommandShortDescription }; + } + + Resource::LocString PinCommand::LongDescription() const + { + return { Resource::String::PinCommandLongDescription }; + } + + Utility::LocIndView PinCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinCommand::ExecuteInternal(Execution::Context& context) const + { + OutputHelp(context.Reporter); + } + + std::vector PinAddCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument{ Args::Type::GatedVersion, Resource::String::GatedVersionArgumentDescription, ArgumentType::Standard }, + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::Force), + Argument{ Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::PinNote, Resource::String::PinNoteArgumentDescription, ArgumentType::Standard }, + }; + } + + Resource::LocString PinAddCommand::ShortDescription() const + { + return { Resource::String::PinAddCommandShortDescription }; + } + + Resource::LocString PinAddCommand::LongDescription() const + { + return { Resource::String::PinAddCommandLongDescription }; + } + + void PinAddCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView PinAddCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinAddCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void PinAddCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::Id)) + { + // When we are given an ID, just pin that available package without checking for installed. + // This helps when there are matching issues, for example due to multiple side-by-side installs. + context << + Workflow::OpenSource(); + } + else + { + // If not working from just ID, try matching a single installed package + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + } + + context << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Pin) << + Workflow::GetInstalledPackageVersion << + Workflow::ReportPackageIdentity << + Workflow::OpenPinningIndex() << + Workflow::AddPin; + } + + std::vector PinRemoveCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString PinRemoveCommand::ShortDescription() const + { + return { Resource::String::PinRemoveCommandShortDescription }; + } + + Resource::LocString PinRemoveCommand::LongDescription() const + { + return { Resource::String::PinRemoveCommandLongDescription }; + } + + void PinRemoveCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView PinRemoveCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinRemoveCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::Id)) + { + // When we are given an ID, just un-pin that available package without checking for installed. + // This helps when there are matching issues, for example due to multiple side-by-side installs. + context << + Workflow::OpenSource(); + } + else + { + // If not working from just ID, try matching a single installed package + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + } + + context << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Pin) << + Workflow::GetInstalledPackageVersion << + Workflow::ReportPackageIdentity << + Workflow::OpenPinningIndex() << + Workflow::SearchPin << + Workflow::RemovePin; + } + + std::vector PinListCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::ListDetails), + }; + } + + Resource::LocString PinListCommand::ShortDescription() const + { + return { Resource::String::PinListCommandShortDescription }; + } + + Resource::LocString PinListCommand::LongDescription() const + { + return { Resource::String::PinListCommandLongDescription }; + } + + void PinListCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView PinListCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinListCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::OpenPinningIndex(/* readOnly */ true) << + Workflow::GetAllPins << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, false, Repository::CompositeSearchBehavior::AllPackages) << + Workflow::ReportPins; + } + + std::vector PinResetCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Force), + Argument::ForType(Args::Type::Source), + }; + } + + Resource::LocString PinResetCommand::ShortDescription() const + { + return { Resource::String::PinResetCommandShortDescription }; + } + + Resource::LocString PinResetCommand::LongDescription() const + { + return { Resource::String::PinResetCommandLongDescription }; + } + + Utility::LocIndView PinResetCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinResetCommand::ExecuteInternal(Execution::Context& context) const + { + + if (context.Args.Contains(Execution::Args::Type::Force)) + { + context << + Workflow::OpenPinningIndex() << + Workflow::ResetAllPins; + } + else + { + AICLI_LOG(CLI, Info, << "--force argument is not present"); + context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; + + context << + Workflow::OpenPinningIndex(/* readOnly */ true) << + Workflow::GetAllPins << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << + Workflow::ReportPins; + } + } + +} diff --git a/src/AppInstallerCLICore/Commands/PinCommand.h b/src/AppInstallerCLICore/Commands/PinCommand.h index 88f74b8caa..d591e8f149 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.h +++ b/src/AppInstallerCLICore/Commands/PinCommand.h @@ -1,91 +1,91 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" -#include - -namespace AppInstaller::CLI -{ - struct PinCommand final : public Command - { - PinCommand(std::string_view parent) : Command("pin", {} /* aliases */, parent) {} - - std::vector> GetCommands() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct PinAddCommand final : public Command - { - PinAddCommand(std::string_view parent) : Command("add", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct PinRemoveCommand final : public Command - { - PinRemoveCommand(std::string_view parent) : Command("remove", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct PinListCommand final : public Command - { - PinListCommand(std::string_view parent) : Command("list", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct PinResetCommand final : public Command - { - PinResetCommand(std::string_view parent) : Command("reset", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include + +namespace AppInstaller::CLI +{ + struct PinCommand final : public Command + { + PinCommand(std::string_view parent) : Command("pin", {} /* aliases */, parent) {} + + std::vector> GetCommands() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinAddCommand final : public Command + { + PinAddCommand(std::string_view parent) : Command("add", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinRemoveCommand final : public Command + { + PinRemoveCommand(std::string_view parent) : Command("remove", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinListCommand final : public Command + { + PinListCommand(std::string_view parent) : Command("list", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinResetCommand final : public Command + { + PinResetCommand(std::string_view parent) : Command("reset", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + +} diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index 947aa7c6fd..3b15e86db0 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -1,308 +1,308 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace AppInstaller::CLI::Execution -{ - struct Args - { - enum class Type : uint32_t - { - // Args to specify where to get app - Query, // Query to be performed against index - MultiQuery, // Like query, but can take multiple values - Manifest, // Provide the app manifest directly - - // Query filtering criteria and query behavior - Id, - Name, - Moniker, - Tag, - Command, - Source, // Index source to be queried against - Count, // Maximum query results - Exact, // Exact match required - - // Manifest selection behavior after an app is found - Version, - Channel, - - // Install behavior - Interactive, - Silent, - Locale, - Log, - CustomSwitches, // CustomSwitches args are args passed to the installer in addition to any defined in the manifest - Override, // Override args are (and the only args) directly passed to installer - InstallLocation, - InstallScope, - InstallArchitecture, - InstallerArchitecture, - InstallerType, - HashOverride, // Ignore hash mismatches - SkipDependencies, // Skip dependencies - DependenciesOnly, // Install only dependencies, not the target package - IgnoreLocalArchiveMalwareScan, // Ignore the local malware scan on archive files - AcceptPackageAgreements, // Accept all license agreements for packages - Rename, // Renames the file of the executable. Only applies to the portable installerType - NoUpgrade, // Install flow should not try to convert to upgrade flow upon finding existing installed version - AllowReboot, // Allows the reboot flow to proceed if applicable - - // Uninstall behavior - Purge, // Removes all files and directories related to a package during an uninstall. Only applies to the portable installerType. - Preserve, // Retains any files and directories created by the portable exe. - ProductCode, // Uninstalls using the product code as the identifier. - AllVersions, // Uninstall all versions of the package - TargetVersion, // The specific version to target - - //Source Command - SourceName, - SourceType, - SourceArg, - ForceSourceReset, - SourceExplicit, - SourceTrustLevel, - SourcePriority, - SourceEditExplicit, - - //Hash Command - HashFile, - Msix, // Flag to indicate the input file is msix - - //Validate Command - ValidateManifest, - IgnoreWarnings, - - // Complete Command - Word, - CommandLine, - Position, - - // Export Command - IncludeVersions, - - // Import Command - ImportFile, - IgnoreUnavailable, - IgnoreVersions, - - // Download Command - DownloadDirectory, - SkipMicrosoftStorePackageLicense, - Platform, - OSVersion, - - // Setting Command - AdminSettingEnable, - AdminSettingDisable, - SettingName, - SettingValue, - - // Upgrade command - All, // Used in Update command to update all installed packages to latest - IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions - IncludePinned, // Used in Upgrade command to allow upgrades to pinned packages (only for pinning type of pins) - UninstallPrevious, // Used in Upgrade command to override the default manifest behavior to UninstallPrevious - - // Show command - ListVersions, // Used in Show command to list all available versions of an app - - // List Command - Upgrade, // Used in List command to only show versions with upgrades - ListDetails, - Sort, // Sort output by field (repeatable: --sort name --sort id) - SortAscending, // Sort output in ascending order - SortDescending, // Sort output in descending order - - // Pin command - GatedVersion, // Differs from Version in that this supports wildcards - BlockingPin, - PinInstalled, - PinNote, // User-provided note to attach to a pin - - // Error command - ErrorInput, - - // Resume Command - ResumeId, - IgnoreResumeLimit, - - // Font Command - Family, - Details, - - // Stub package (extended features) - ExtendedFeaturesEnable, - ExtendedFeaturesDisable, - - // Configuration - ConfigurationFile, - ConfigurationAcceptWarning, - ConfigurationSuppressPrologue, - ConfigurationProcessorPath, - ConfigurationModulePath, - ConfigurationExportPackageId, - ConfigurationExportModule, - ConfigurationExportResource, - ConfigurationExportAll, - ConfigurationHistoryItem, - ConfigurationHistoryRemove, - ConfigurationStatusWatch, - - // DSCv3 resources - DscResourceFunctionGet, - DscResourceFunctionSet, - DscResourceFunctionWhatIf, - DscResourceFunctionTest, - DscResourceFunctionDelete, - DscResourceFunctionExport, - DscResourceFunctionValidate, - DscResourceFunctionResolve, - DscResourceFunctionAdapter, - DscResourceFunctionSchema, - DscResourceFunctionManifest, - - // Common arguments - NoVT, // Disable VirtualTerminal outputs - RetroStyle, // Makes progress display as retro - RainbowStyle, // Makes progress display as a rainbow - NoProgress, // Disables progress bar and spinner - Help, // Show command usage - Info, // Show general info about WinGet - VerboseLogs, // Increases winget logging level to verbose - DisableInteractivity, // Disable interactive prompts - Wait, // Prompts the user to press any key before exiting - OpenLogs, // Opens the default logs directory after executing the command - Force, // Forces the execution of the workflow with non security related issues - OutputFile, - Correlation, - - DependencySource, // Index source to be queried against for finding dependencies - CustomHeader, // Optional Rest source header - AcceptSourceAgreements, // Accept all source agreements - - AuthenticationMode, // Authentication mode (silent, silentPreferred or interactive) - AuthenticationAccount, // Authentication account to be used - - // Network Behavior - Proxy, // Set a proxy to use in this execution - NoProxy, // Do not use the default proxy - - ToolVersion, - - // Used for demonstration purposes - ExperimentalArg, - - // This should always be at the end - Max - }; - - template), bool> = true> - bool Contains(T... arg) const - { - return (... && (m_parsedArgs.count(arg) != 0)); - } - - const std::vector* GetArgs(Type arg) const - { - auto itr = m_parsedArgs.find(arg); - return (itr == m_parsedArgs.end() ? nullptr : &(itr->second)); - } - - std::string_view GetArg(Type arg) const - { - auto itr = m_parsedArgs.find(arg); - - if (itr == m_parsedArgs.end()) - { - return {}; - } - - return itr->second[0]; - } - - size_t GetCount(Type arg) const - { - auto args = GetArgs(arg); - return (args ? args->size() : 0); - } - - bool AddArg(Type arg) - { - return m_parsedArgs[arg].empty(); - } - - void AddArg(Type arg, std::string value) - { - m_parsedArgs[arg].emplace_back(std::move(value)); - } - - void AddArg(Type arg, std::string_view value) - { - m_parsedArgs[arg].emplace_back(value); - } - - bool Empty() - { - return m_parsedArgs.empty(); - } - - size_t GetArgsCount() const - { - return m_parsedArgs.size(); - } - - std::vector GetTypes() const - { - std::vector types; - - for (auto const& i : m_parsedArgs) - { - types.emplace_back(i.first); - } - - return types; - } - - // If the user passes the same value multiple times inside a MultiQuery, operations will be repeated - // Since there currently is not a way to include search options within a MultiQuery, processing duplicates - // does not make sense within a single invocation - void MakeMultiQueryContainUniqueValues() - { - auto itr = m_parsedArgs.find(Type::MultiQuery); - - // If there is not a value in MultiQuery, or there is only one value, it is presumed to be unique - if (itr == m_parsedArgs.end() || itr->second.size() == 1) - { - return; - } - - std::set querySet; - std::vector& queryStrings = itr->second; - - queryStrings.erase(std::remove_if(queryStrings.begin(), queryStrings.end(), [&](const std::string value) { return !querySet.insert(value).second; }), queryStrings.end()); - } - - // If we get a single value for multi-query, we remove the argument and add it back as a single query. - // This way the rest of the code can assume that if there is a MultiQuery we will always have multiple values, - // and if there is a single one it will be in the Query type. - void MoveMultiQueryToSingleQueryIfNeeded() - { - auto itr = m_parsedArgs.find(Type::MultiQuery); - if (itr != m_parsedArgs.end() && itr->second.size() == 1) - { - // A test ensures that commands don't have both Query and MultiQuery arguments, - // so if we had a MultiQuery value, we can be sure there is no Query value - m_parsedArgs[Type::Query].emplace_back(std::move(itr->second[0])); - m_parsedArgs.erase(itr); - } - } - - private: - std::map> m_parsedArgs; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace AppInstaller::CLI::Execution +{ + struct Args + { + enum class Type : uint32_t + { + // Args to specify where to get app + Query, // Query to be performed against index + MultiQuery, // Like query, but can take multiple values + Manifest, // Provide the app manifest directly + + // Query filtering criteria and query behavior + Id, + Name, + Moniker, + Tag, + Command, + Source, // Index source to be queried against + Count, // Maximum query results + Exact, // Exact match required + + // Manifest selection behavior after an app is found + Version, + Channel, + + // Install behavior + Interactive, + Silent, + Locale, + Log, + CustomSwitches, // CustomSwitches args are args passed to the installer in addition to any defined in the manifest + Override, // Override args are (and the only args) directly passed to installer + InstallLocation, + InstallScope, + InstallArchitecture, + InstallerArchitecture, + InstallerType, + HashOverride, // Ignore hash mismatches + SkipDependencies, // Skip dependencies + DependenciesOnly, // Install only dependencies, not the target package + IgnoreLocalArchiveMalwareScan, // Ignore the local malware scan on archive files + AcceptPackageAgreements, // Accept all license agreements for packages + Rename, // Renames the file of the executable. Only applies to the portable installerType + NoUpgrade, // Install flow should not try to convert to upgrade flow upon finding existing installed version + AllowReboot, // Allows the reboot flow to proceed if applicable + + // Uninstall behavior + Purge, // Removes all files and directories related to a package during an uninstall. Only applies to the portable installerType. + Preserve, // Retains any files and directories created by the portable exe. + ProductCode, // Uninstalls using the product code as the identifier. + AllVersions, // Uninstall all versions of the package + TargetVersion, // The specific version to target + + //Source Command + SourceName, + SourceType, + SourceArg, + ForceSourceReset, + SourceExplicit, + SourceTrustLevel, + SourcePriority, + SourceEditExplicit, + + //Hash Command + HashFile, + Msix, // Flag to indicate the input file is msix + + //Validate Command + ValidateManifest, + IgnoreWarnings, + + // Complete Command + Word, + CommandLine, + Position, + + // Export Command + IncludeVersions, + + // Import Command + ImportFile, + IgnoreUnavailable, + IgnoreVersions, + + // Download Command + DownloadDirectory, + SkipMicrosoftStorePackageLicense, + Platform, + OSVersion, + + // Setting Command + AdminSettingEnable, + AdminSettingDisable, + SettingName, + SettingValue, + + // Upgrade command + All, // Used in Update command to update all installed packages to latest + IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions + IncludePinned, // Used in Upgrade command to allow upgrades to pinned packages (only for pinning type of pins) + UninstallPrevious, // Used in Upgrade command to override the default manifest behavior to UninstallPrevious + + // Show command + ListVersions, // Used in Show command to list all available versions of an app + + // List Command + Upgrade, // Used in List command to only show versions with upgrades + ListDetails, + Sort, // Sort output by field (repeatable: --sort name --sort id) + SortAscending, // Sort output in ascending order + SortDescending, // Sort output in descending order + + // Pin command + GatedVersion, // Differs from Version in that this supports wildcards + BlockingPin, + PinInstalled, + PinNote, // User-provided note to attach to a pin + + // Error command + ErrorInput, + + // Resume Command + ResumeId, + IgnoreResumeLimit, + + // Font Command + Family, + Details, + + // Stub package (extended features) + ExtendedFeaturesEnable, + ExtendedFeaturesDisable, + + // Configuration + ConfigurationFile, + ConfigurationAcceptWarning, + ConfigurationSuppressPrologue, + ConfigurationProcessorPath, + ConfigurationModulePath, + ConfigurationExportPackageId, + ConfigurationExportModule, + ConfigurationExportResource, + ConfigurationExportAll, + ConfigurationHistoryItem, + ConfigurationHistoryRemove, + ConfigurationStatusWatch, + + // DSCv3 resources + DscResourceFunctionGet, + DscResourceFunctionSet, + DscResourceFunctionWhatIf, + DscResourceFunctionTest, + DscResourceFunctionDelete, + DscResourceFunctionExport, + DscResourceFunctionValidate, + DscResourceFunctionResolve, + DscResourceFunctionAdapter, + DscResourceFunctionSchema, + DscResourceFunctionManifest, + + // Common arguments + NoVT, // Disable VirtualTerminal outputs + RetroStyle, // Makes progress display as retro + RainbowStyle, // Makes progress display as a rainbow + NoProgress, // Disables progress bar and spinner + Help, // Show command usage + Info, // Show general info about WinGet + VerboseLogs, // Increases winget logging level to verbose + DisableInteractivity, // Disable interactive prompts + Wait, // Prompts the user to press any key before exiting + OpenLogs, // Opens the default logs directory after executing the command + Force, // Forces the execution of the workflow with non security related issues + OutputFile, + Correlation, + + DependencySource, // Index source to be queried against for finding dependencies + CustomHeader, // Optional Rest source header + AcceptSourceAgreements, // Accept all source agreements + + AuthenticationMode, // Authentication mode (silent, silentPreferred or interactive) + AuthenticationAccount, // Authentication account to be used + + // Network Behavior + Proxy, // Set a proxy to use in this execution + NoProxy, // Do not use the default proxy + + ToolVersion, + + // Used for demonstration purposes + ExperimentalArg, + + // This should always be at the end + Max + }; + + template), bool> = true> + bool Contains(T... arg) const + { + return (... && (m_parsedArgs.count(arg) != 0)); + } + + const std::vector* GetArgs(Type arg) const + { + auto itr = m_parsedArgs.find(arg); + return (itr == m_parsedArgs.end() ? nullptr : &(itr->second)); + } + + std::string_view GetArg(Type arg) const + { + auto itr = m_parsedArgs.find(arg); + + if (itr == m_parsedArgs.end()) + { + return {}; + } + + return itr->second[0]; + } + + size_t GetCount(Type arg) const + { + auto args = GetArgs(arg); + return (args ? args->size() : 0); + } + + bool AddArg(Type arg) + { + return m_parsedArgs[arg].empty(); + } + + void AddArg(Type arg, std::string value) + { + m_parsedArgs[arg].emplace_back(std::move(value)); + } + + void AddArg(Type arg, std::string_view value) + { + m_parsedArgs[arg].emplace_back(value); + } + + bool Empty() + { + return m_parsedArgs.empty(); + } + + size_t GetArgsCount() const + { + return m_parsedArgs.size(); + } + + std::vector GetTypes() const + { + std::vector types; + + for (auto const& i : m_parsedArgs) + { + types.emplace_back(i.first); + } + + return types; + } + + // If the user passes the same value multiple times inside a MultiQuery, operations will be repeated + // Since there currently is not a way to include search options within a MultiQuery, processing duplicates + // does not make sense within a single invocation + void MakeMultiQueryContainUniqueValues() + { + auto itr = m_parsedArgs.find(Type::MultiQuery); + + // If there is not a value in MultiQuery, or there is only one value, it is presumed to be unique + if (itr == m_parsedArgs.end() || itr->second.size() == 1) + { + return; + } + + std::set querySet; + std::vector& queryStrings = itr->second; + + queryStrings.erase(std::remove_if(queryStrings.begin(), queryStrings.end(), [&](const std::string value) { return !querySet.insert(value).second; }), queryStrings.end()); + } + + // If we get a single value for multi-query, we remove the argument and add it back as a single query. + // This way the rest of the code can assume that if there is a MultiQuery we will always have multiple values, + // and if there is a single one it will be in the Query type. + void MoveMultiQueryToSingleQueryIfNeeded() + { + auto itr = m_parsedArgs.find(Type::MultiQuery); + if (itr != m_parsedArgs.end() && itr->second.size() == 1) + { + // A test ensures that commands don't have both Query and MultiQuery arguments, + // so if we had a MultiQuery value, we can be sure there is no Query value + m_parsedArgs[Type::Query].emplace_back(std::move(itr->second[0])); + m_parsedArgs.erase(itr); + } + } + + private: + std::map> m_parsedArgs; + }; +} diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 8633ef57b6..092dc5f989 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -1,874 +1,874 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include - -#include - -namespace AppInstaller::CLI::Resource -{ - using AppInstaller::StringResource::StringId; - using AppInstaller::Resource::LocString; - - // Resource string identifiers. - // This list can mostly be generated by the following PowerShell: - // > [xml]$res = Get-Content - // > $res.root.data.name | % { "WINGET_DEFINE_RESOURCE_STRINGID($_);" } - // - struct String - { - WINGET_DEFINE_RESOURCE_STRINGID(AcceptPackageAgreementsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AcceptSourceAgreementsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFlagError); - WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFoundError); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisabled); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisableDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnabled); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnableDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingHeader); - WINGET_DEFINE_RESOURCE_STRINGID(AllowRebootArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ArchitectureArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScan); - WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScanOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(ArgumentForSinglePackageProvidedWithMultipleQueries); - WINGET_DEFINE_RESOURCE_STRINGID(AuthenticationAccountArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AuthenticationModeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguments); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommandAliases); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableHeader); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableSubcommands); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableUpgrades); - WINGET_DEFINE_RESOURCE_STRINGID(BothManifestAndSearchQueryProvided); - WINGET_DEFINE_RESOURCE_STRINGID(Cancelled); - WINGET_DEFINE_RESOURCE_STRINGID(CancellingOperation); - WINGET_DEFINE_RESOURCE_STRINGID(ChannelArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ClientVersionMismatchError); - WINGET_DEFINE_RESOURCE_STRINGID(Command); - WINGET_DEFINE_RESOURCE_STRINGID(CommandArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CommandDoesNotSupportResumeMessage); - WINGET_DEFINE_RESOURCE_STRINGID(CommandLineArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CommandRequiresAdmin); - WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAcceptWarningArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAllUsersElevated); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApply); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApplyingUnit); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAssert); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDescriptionWasTruncated); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportAddingToFile); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportFailedToGetUnitProcessors); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportingUnit); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportInstallRequiredModule); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportInstallRequiredModuleFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportSuccessful); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportUnitStart); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportUnitFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToApply); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToGetDetails); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToTest); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidType); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidValue); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldMissing); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileEmpty); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalid); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalidYAML); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileVersionUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingDetails); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingResourceSettings); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingUnitProcessors); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryEmpty); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryItemArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryItemNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryRemoveArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInDesiredState); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInform); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInitializing); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInstallDscPackage); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInstallDscPackageFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationLocal); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleNameOnly); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModulePath); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModulePathArgError); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModules); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleWithDetails); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNoTestRun); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotInDesiredState); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPath); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAudit); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditHash); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditIsAlias); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditPath); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditSignature); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditUnsigned); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathHashVerificationFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationReadingConfigFile); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateCompleted); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateInProgress); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStatePending); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSettings); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationStatusWatchArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuccessfullyApplied); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSuccessfullyApplied); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuppressPrologueArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnexpectedTestResult); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitAssertHadNegativeResult); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedConfigSet); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringGet); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringSet); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringTest); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedInternal); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedPrecondition); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedSystemState); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedUnitProcessing); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasDuplicateIdentifier); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasMissingDependency); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitImportModuleAdmin); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitIsPartOfDependencyCycle); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitManuallySkipped); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleConflict); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleImportFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleNotProvidedWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitMultipleMatches); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNeedsPrereleaseWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFoundInModule); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotInCatalogWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotPublicWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToDependency); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToFailedAssert); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitReturnedInvalidResult); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSettingConfigRoot); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSkipped); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateCompleted); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateInProgress); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStatePending); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateSkipped); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationValidationFoundNoIssues); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWaitingOnAnother); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptApply); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptTest); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningSetViewTruncated); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningValueTruncated); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportAll); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportArgumentConflictWithAllError); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportArgumentRequiredError); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportModule); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportPackageId); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportResource); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitInstallDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyBegun); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyEnded); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListFirstApplied); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListIdentifier); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListName); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListOrigin); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListPath); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResult); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResultDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListState); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListUnit); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConvertInstallFlowToUpgrade); - WINGET_DEFINE_RESOURCE_STRINGID(CorrelationArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CountOutOfBoundsError); - WINGET_DEFINE_RESOURCE_STRINGID(CustomSwitchesArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowContainsLoop); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowDownload); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowInstall); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoInstallerFound); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoMatches); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoMinVersion); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoSuitableInstallerFound); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowPackageVersionNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceTooManyMatches); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementError); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementExitMessage); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesSkippedMessage); - WINGET_DEFINE_RESOURCE_STRINGID(DependencyArgumentMissing); - WINGET_DEFINE_RESOURCE_STRINGID(DependencySourceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DisableAdminSettingFailed); - WINGET_DEFINE_RESOURCE_STRINGID(DisabledByGroupPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(DisableInteractivityArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(Done); - WINGET_DEFINE_RESOURCE_STRINGID(DownloadCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DownloadCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DownloadDirectoryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(Downloading); - WINGET_DEFINE_RESOURCE_STRINGID(DscCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionGet); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSet); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionWhatIf); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionTest); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionDelete); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionExport); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionValidate); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionResolve); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionAdapter); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSchema); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionManifest); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionExist); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionInDesiredState); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAcceptAgreements); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAdminSettingsSettings); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageId); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageSource); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageVersion); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageScope); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageMatchOption); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageUseLatest); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageInstallMode); - WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileSettings); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileAction); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceName); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceArgument); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceType); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceTrustLevel); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceExplicit); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourcePriority); - WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(EnableAdminSettingFailed); - WINGET_DEFINE_RESOURCE_STRINGID(EnableWindowsFeaturesSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(EnablingWindowsFeature); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorInputArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorNumberIsTooLarge); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileConflictsWithInput); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorRequiresInputOrOutputFile); - WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExportedPackageRequiresLicenseAgreement); - WINGET_DEFINE_RESOURCE_STRINGID(ExportIncludeVersionsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExportSourceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisabledMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisableMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisablingMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnableArgumentError); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnabledMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnableMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnablingMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesNotEnabledMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExternalDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ExtractArchiveFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ExtractArchiveSucceeded); - WINGET_DEFINE_RESOURCE_STRINGID(ExtractingArchive); - WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeature); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeatureOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeatureOverrideRequired); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToInitiateReboot); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToRefreshPathWarning); - WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledByAdminSettingMessage); - WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesDisabled); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesEnabled); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesFeature); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesLink); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessage); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessageDisabledByBuild); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessageDisabledByPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesProperty); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesStatus); - WINGET_DEFINE_RESOURCE_STRINGID(FileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FileNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(FilesRemainInInstallDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); - WINGET_DEFINE_RESOURCE_STRINGID(FontAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(FontCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontFace); - WINGET_DEFINE_RESOURCE_STRINGID(FontFaces); - WINGET_DEFINE_RESOURCE_STRINGID(FontFamily); - WINGET_DEFINE_RESOURCE_STRINGID(FontFamilyNameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontFileNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(FontDetailsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontFilePaths); - WINGET_DEFINE_RESOURCE_STRINGID(FontInstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontPackage); - WINGET_DEFINE_RESOURCE_STRINGID(FontRollbackFailed); - WINGET_DEFINE_RESOURCE_STRINGID(FontStatus); - WINGET_DEFINE_RESOURCE_STRINGID(FontStatusCorrupt); - WINGET_DEFINE_RESOURCE_STRINGID(FontStatusOK); - WINGET_DEFINE_RESOURCE_STRINGID(FontStatusUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(FontTitle); - WINGET_DEFINE_RESOURCE_STRINGID(FontUninstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(FontValidationFailed); - WINGET_DEFINE_RESOURCE_STRINGID(FontVersion); - WINGET_DEFINE_RESOURCE_STRINGID(FontWinGetSupported); - WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(GatedVersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(GetManifestResultVersionNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HashOverrideArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentNotApplicableForNonRestSourceWarning); - WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentNotApplicableWithoutSource); - WINGET_DEFINE_RESOURCE_STRINGID(HelpArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HelpForDetails); - WINGET_DEFINE_RESOURCE_STRINGID(HelpLinkPreamble); - WINGET_DEFINE_RESOURCE_STRINGID(IdArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IgnoreLocalArchiveMalwareScanArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IgnoreResumeLimitArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IgnoreWarningsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandReportDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportFileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportFileHasInvalidSchema); - WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnorePackageVersionsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnoreUnavailableArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportInstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ImportSourceNotInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedInListArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownInListArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IncompatibleArgumentsProvided); - WINGET_DEFINE_RESOURCE_STRINGID(InitiatingReboot); - WINGET_DEFINE_RESOURCE_STRINGID(InstallAbandoned); - WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer1); - WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer2); - WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimerMSStore); - WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable); - WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable); - WINGET_DEFINE_RESOURCE_STRINGID(InstalledScopeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerAbortsTerminal); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadAuthenticationFailed); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadAuthenticationNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadCommandProhibited); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloaded); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadRequiresAuthentication); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloads); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerElevationExpected); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedSecurityCheck); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedVirusScan); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedWithCode); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchAdminBlock); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchError); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerProhibitsElevation); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerRequiresInstallLocation); - WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal); - WINGET_DEFINE_RESOURCE_STRINGID(InstallersRequireInstallLocation); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerZeroByteFile); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeBlockedByPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCancelledByUser); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeContactSupport); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCustomError); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDiskFull); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDowngrade); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeFileInUse); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInstallInProgress); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInsufficientMemory); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInvalidParameter); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeMissingDependency); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeNoNetwork); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUse); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUseByApplication); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootInitiated); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredForInstall); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredToFinish); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeSystemNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowStartingPackageInstall); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFullPackageDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallLocationNotProvided); - WINGET_DEFINE_RESOURCE_STRINGID(InstallScopeDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallStubPackageDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallWaitingOnAnother); - WINGET_DEFINE_RESOURCE_STRINGID(InteractiveArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidAliasError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentSpecifierError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentValueError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentValueErrorWithoutValidValues); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentWithoutQueryError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidJsonFile); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidNameError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidPathToNestedInstaller); - WINGET_DEFINE_RESOURCE_STRINGID(KeyDirectoriesHeader); - WINGET_DEFINE_RESOURCE_STRINGID(LicenseAgreement); - WINGET_DEFINE_RESOURCE_STRINGID(Links); - WINGET_DEFINE_RESOURCE_STRINGID(ListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ListDetailsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(LocaleArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(LocationArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(LogArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(Logs); - WINGET_DEFINE_RESOURCE_STRINGID(MainCopyrightNotice); - WINGET_DEFINE_RESOURCE_STRINGID(MainHomepage); - WINGET_DEFINE_RESOURCE_STRINGID(ManifestArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationFail); - WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationWarning); - WINGET_DEFINE_RESOURCE_STRINGID(McpCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(McpCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(McpConfigurationPreamble); - WINGET_DEFINE_RESOURCE_STRINGID(MissingArgumentError); - WINGET_DEFINE_RESOURCE_STRINGID(ModifiedPathRequiresShellRestart); - WINGET_DEFINE_RESOURCE_STRINGID(MonikerArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(MsixArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreAppBlocked); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadAuthenticationNotice); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadDependencyPackages); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetDownloadInfo); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetDownloadInfoFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicense); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseForbidden); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadMainPackages); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadMultiplePackagesNotice); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadNoApplicablePackageFound); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloaded); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageHashMismatch); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageHashVerified); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadRenameNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallOrUpdateFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallTryGetEntitlement); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreRepairFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreStoreClientBlocked); - WINGET_DEFINE_RESOURCE_STRINGID(MultipleExclusiveArgumentsProvided); - WINGET_DEFINE_RESOURCE_STRINGID(MultipleInstalledPackagesFound); - WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFound); - WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFoundFilteredBySourcePriority); - WINGET_DEFINE_RESOURCE_STRINGID(MultipleUnsupportedNestedInstallersSpecified); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFoundMultiple); - WINGET_DEFINE_RESOURCE_STRINGID(NameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSpecified); - WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(NoAdminRepairForUserScopePackage); - WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); - WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage); - WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledFontFound); - WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledPackageFound); - WINGET_DEFINE_RESOURCE_STRINGID(NoPackageFound); - WINGET_DEFINE_RESOURCE_STRINGID(NoPackageSelectionArgumentProvided); - WINGET_DEFINE_RESOURCE_STRINGID(NoPackagesFoundInImportFile); - WINGET_DEFINE_RESOURCE_STRINGID(NoProxyArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(NoProgressArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(NoRepairInfoFound); - WINGET_DEFINE_RESOURCE_STRINGID(Notes); - WINGET_DEFINE_RESOURCE_STRINGID(NoUninstallInfoFound); - WINGET_DEFINE_RESOURCE_STRINGID(NoUpgradeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(NoVTArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OpenLogsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatch); - WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatchHelp); - WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoSourceDefined); - WINGET_DEFINE_RESOURCE_STRINGID(Options); - WINGET_DEFINE_RESOURCE_STRINGID(OSVersionDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OutputDirectoryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OutputFileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OverwritingExistingFileAtMessage); - WINGET_DEFINE_RESOURCE_STRINGID(Package); - WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsNotAgreedTo); - WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsPrompt); - WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(PackageIsPinned); - WINGET_DEFINE_RESOURCE_STRINGID(PackageRequiresDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); - WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinAdded); - WINGET_DEFINE_RESOURCE_STRINGID(PinAlreadyExists); - WINGET_DEFINE_RESOURCE_STRINGID(PinCannotOpenIndex); - WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinDateAdded); - WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist); - WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting); - WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg); - WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledSource); - WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinNote); - WINGET_DEFINE_RESOURCE_STRINGID(PinNoteArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist); - WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinRemovedSuccessfully); - WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful); - WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); - WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg); - WINGET_DEFINE_RESOURCE_STRINGID(PinShowNoMatchFound); - WINGET_DEFINE_RESOURCE_STRINGID(PinType); - WINGET_DEFINE_RESOURCE_STRINGID(PinVersion); - WINGET_DEFINE_RESOURCE_STRINGID(PlatformArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(PortableAliasAdded); - WINGET_DEFINE_RESOURCE_STRINGID(PortableHashMismatchOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(PortableHashMismatchOverrideRequired); - WINGET_DEFINE_RESOURCE_STRINGID(PortableInstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(PortableLinksMachine); - WINGET_DEFINE_RESOURCE_STRINGID(PortableLinksUser); - WINGET_DEFINE_RESOURCE_STRINGID(PortablePackageAlreadyExists); - WINGET_DEFINE_RESOURCE_STRINGID(PortableRegistryCollisionOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(PortableRoot); - WINGET_DEFINE_RESOURCE_STRINGID(PortableRoot86); - WINGET_DEFINE_RESOURCE_STRINGID(PortableRootUser); - WINGET_DEFINE_RESOURCE_STRINGID(PositionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PreserveArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PressEnterToContinue); - WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); - WINGET_DEFINE_RESOURCE_STRINGID(ProductCodeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PromptForInstallRoot); - WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); - WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); - WINGET_DEFINE_RESOURCE_STRINGID(PromptToProceed); - WINGET_DEFINE_RESOURCE_STRINGID(ProxyArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PurgeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PurgeInstallDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverrideRequired); - WINGET_DEFINE_RESOURCE_STRINGID(RelatedLink); - WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RepairAbandoned); - WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RepairDifferentInstallTechnology); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFailedWithCode); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowNoMatchingVersion); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowRepairSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowReturnCodeSystemNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowStartingPackageRepair); - WINGET_DEFINE_RESOURCE_STRINGID(RepairOperationNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(ReparsePointsNotSupportedError); - WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityForAgreements); - WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityFound); - WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); - WINGET_DEFINE_RESOURCE_STRINGID(ReservedFilenameError); - WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingSucceeded); - WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsSucceeded); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdNotFoundError); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeLimitExceeded); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeStateDataNotFoundError); - WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureError); - WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureErrorListMatches); - WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureErrorNoMatches); - WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureWarning); - WINGET_DEFINE_RESOURCE_STRINGID(SearchId); - WINGET_DEFINE_RESOURCE_STRINGID(SearchMatch); - WINGET_DEFINE_RESOURCE_STRINGID(SearchName); - WINGET_DEFINE_RESOURCE_STRINGID(SearchSource); - WINGET_DEFINE_RESOURCE_STRINGID(SearchTruncated); - WINGET_DEFINE_RESOURCE_STRINGID(SearchVersion); - WINGET_DEFINE_RESOURCE_STRINGID(SeeLineAndColumn); - WINGET_DEFINE_RESOURCE_STRINGID(SetAdminSettingFailed); - WINGET_DEFINE_RESOURCE_STRINGID(SetAdminSettingSucceeded); - WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); - WINGET_DEFINE_RESOURCE_STRINGID(SettingNameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsResetCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsResetCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsSetCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsSetCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningField); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningValue); - WINGET_DEFINE_RESOURCE_STRINGID(SettingValueArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ShowChannel); - WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelAgreements); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelAuthor); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelChannel); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelCopyright); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelCopyrightUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDocumentation); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelExternalDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallationNotes); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstaller); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerLocale); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerOfflineDistributionSupported); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerProductId); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerReleaseDate); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerSha256); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerType); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicense); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicenseUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelMoniker); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPackageDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPackageUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPrivacyUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisher); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisherSupportUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisherUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPurchaseUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelReleaseNotes); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelReleaseNotesUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelTags); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelVersion); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelWindowsFeaturesDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelWindowsLibrariesDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListAvailableUpgrades); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledArchitecture); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledLocale); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledLocation); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledScope); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledSource); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstallerCategory); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListLocalIdentifier); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListPackageFamilyName); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListProductCode); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListUpgradeCode); - WINGET_DEFINE_RESOURCE_STRINGID(ShowVersion); - WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SingleCharAfterDashError); - WINGET_DEFINE_RESOURCE_STRINGID(SortArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SortAscendingArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SortDescendingArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SkipDependenciesArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyMessage); - WINGET_DEFINE_RESOURCE_STRINGID(SkipMicrosoftStorePackageLicenseArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentArg); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentName); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsMatch); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddBegin); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddFailedAuthenticationNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddOpenSourceFailed); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsMarketMessage); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsNotAgreedTo); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsPrompt); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsTitle); - WINGET_DEFINE_RESOURCE_STRINGID(SourceArgArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditExplicitArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNewValue); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNoChanges); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOldValue); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOne); - WINGET_DEFINE_RESOURCE_STRINGID(SourceExplicitArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListAdditionalSource); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListAllowedSource); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListArg); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListData); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListExplicit); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListField); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListIdentifier); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListName); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoneFound); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoSources); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListPriority); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListTrustLevel); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListType); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdated); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdatedNever); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListValue); - WINGET_DEFINE_RESOURCE_STRINGID(SourceNameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenFailedSuggestion); - WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenPredefinedFailedSuggestion); - WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenWithFailedUpdate); - WINGET_DEFINE_RESOURCE_STRINGID(SourcePriorityArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveAll); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveOne); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRequiresAuthentication); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetAll); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetForceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetListAndOverridePreamble); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetOne); - WINGET_DEFINE_RESOURCE_STRINGID(SourceTrustLevelArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceTypeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateAll); - WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateOne); - WINGET_DEFINE_RESOURCE_STRINGID(StateDisabled); - WINGET_DEFINE_RESOURCE_STRINGID(StateEnabled); - WINGET_DEFINE_RESOURCE_STRINGID(StateHeader); - WINGET_DEFINE_RESOURCE_STRINGID(SystemArchitecture); - WINGET_DEFINE_RESOURCE_STRINGID(TagArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(TargetVersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ThankYou); - WINGET_DEFINE_RESOURCE_STRINGID(ThirdPartSoftwareNotices); - WINGET_DEFINE_RESOURCE_STRINGID(ToolDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ToolInfoArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ToolVersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(TooManyArgError); - WINGET_DEFINE_RESOURCE_STRINGID(TooManyBehaviorsError); - WINGET_DEFINE_RESOURCE_STRINGID(UnableToPurgeInstallDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(Unavailable); - WINGET_DEFINE_RESOURCE_STRINGID(UnexpectedErrorExecutingCommand); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallAbandoned); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallAllVersionsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandReportDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedDueToMultipleVersions); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedWithCode); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowStartingPackageUninstall); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowUninstallSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallPreviousArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UnrecognizedCommand); - WINGET_DEFINE_RESOURCE_STRINGID(UnsupportedArgument); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateAllArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateNoPackagesFound); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateNoPackagesFoundReason); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicable); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicableReason); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeAvailableForPinned); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByManifest); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByPinCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeInstallTechnologyMismatchCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); - WINGET_DEFINE_RESOURCE_STRINGID(UriNotWellFormed); - WINGET_DEFINE_RESOURCE_STRINGID(UriSchemeNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(Usage); - WINGET_DEFINE_RESOURCE_STRINGID(UserSettings); - WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandReportDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ValidateManifestArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(VerboseLogsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedIsDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedNotExist); - WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileSignedMsix); - WINGET_DEFINE_RESOURCE_STRINGID(VerifyPathFailedNotExist); - WINGET_DEFINE_RESOURCE_STRINGID(VersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(VersionsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(WaitArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeatureNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeaturesDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsLibrariesDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsPackageManager); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsPackageManagerPreview); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsStoreTerms); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitBothPackageVersionAndUseLatest); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitDependencySourceNotConfigured); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitDependencySourceNotDeclaredAsDependency); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitEmptyContent); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackage); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageMultipleFound); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageSourceOpenFailed); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageVersionNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitKnownSourceConfliction); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitMissingRecommendedArg); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitMissingRequiredArg); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitPackageVersionSpecifiedWithOnlyOnePackageVersion); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitThirdPartySourceAssertion); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitThirdPartySourceAssertionForPackage); - WINGET_DEFINE_RESOURCE_STRINGID(WordArgumentDescription); - }; - - // Fixed strings are not localized, but we use a similar system to prevent duplication - enum class FixedString - { - ProductName, - }; - - Utility::LocIndView GetFixedString(FixedString fs); -} - -inline std::ostream& operator<<(std::ostream& out, AppInstaller::CLI::Resource::FixedString fs) -{ - return (out << GetFixedString(fs)); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include + +#include + +namespace AppInstaller::CLI::Resource +{ + using AppInstaller::StringResource::StringId; + using AppInstaller::Resource::LocString; + + // Resource string identifiers. + // This list can mostly be generated by the following PowerShell: + // > [xml]$res = Get-Content + // > $res.root.data.name | % { "WINGET_DEFINE_RESOURCE_STRINGID($_);" } + // + struct String + { + WINGET_DEFINE_RESOURCE_STRINGID(AcceptPackageAgreementsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AcceptSourceAgreementsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFlagError); + WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFoundError); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisabled); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisableDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnabled); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnableDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingHeader); + WINGET_DEFINE_RESOURCE_STRINGID(AllowRebootArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ArchitectureArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScan); + WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScanOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(ArgumentForSinglePackageProvidedWithMultipleQueries); + WINGET_DEFINE_RESOURCE_STRINGID(AuthenticationAccountArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AuthenticationModeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguments); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommandAliases); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableHeader); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableSubcommands); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableUpgrades); + WINGET_DEFINE_RESOURCE_STRINGID(BothManifestAndSearchQueryProvided); + WINGET_DEFINE_RESOURCE_STRINGID(Cancelled); + WINGET_DEFINE_RESOURCE_STRINGID(CancellingOperation); + WINGET_DEFINE_RESOURCE_STRINGID(ChannelArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ClientVersionMismatchError); + WINGET_DEFINE_RESOURCE_STRINGID(Command); + WINGET_DEFINE_RESOURCE_STRINGID(CommandArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CommandDoesNotSupportResumeMessage); + WINGET_DEFINE_RESOURCE_STRINGID(CommandLineArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CommandRequiresAdmin); + WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAcceptWarningArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAllUsersElevated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApplyingUnit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAssert); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDescriptionWasTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportAddingToFile); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportFailedToGetUnitProcessors); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportingUnit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportInstallRequiredModule); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportInstallRequiredModuleFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportSuccessful); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportUnitStart); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportUnitFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToGetDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToTest); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidType); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidValue); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldMissing); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileEmpty); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalid); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalidYAML); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileVersionUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingResourceSettings); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingUnitProcessors); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryEmpty); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryItemArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryItemNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryRemoveArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInDesiredState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInform); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInitializing); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInstallDscPackage); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInstallDscPackageFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationLocal); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleNameOnly); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModulePath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModulePathArgError); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModules); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleWithDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNoTestRun); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotInDesiredState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAudit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditHash); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditIsAlias); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditSignature); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditUnsigned); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathHashVerificationFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationReadingConfigFile); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateCompleted); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateInProgress); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStatePending); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSettings); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationStatusWatchArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuccessfullyApplied); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSuccessfullyApplied); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuppressPrologueArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnexpectedTestResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitAssertHadNegativeResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedConfigSet); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringGet); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringSet); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringTest); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedInternal); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedPrecondition); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedSystemState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedUnitProcessing); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasDuplicateIdentifier); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasMissingDependency); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitImportModuleAdmin); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitIsPartOfDependencyCycle); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitManuallySkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleConflict); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleImportFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleNotProvidedWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitMultipleMatches); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNeedsPrereleaseWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFoundInModule); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotInCatalogWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotPublicWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToDependency); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToFailedAssert); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitReturnedInvalidResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSettingConfigRoot); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateCompleted); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateInProgress); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStatePending); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateSkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationValidationFoundNoIssues); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWaitingOnAnother); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptTest); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningSetViewTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningValueTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportAll); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportArgumentConflictWithAllError); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportArgumentRequiredError); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportModule); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportPackageId); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportResource); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitInstallDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyBegun); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyEnded); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListFirstApplied); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListIdentifier); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListName); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListOrigin); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResultDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListUnit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConvertInstallFlowToUpgrade); + WINGET_DEFINE_RESOURCE_STRINGID(CorrelationArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CountOutOfBoundsError); + WINGET_DEFINE_RESOURCE_STRINGID(CustomSwitchesArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowContainsLoop); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowDownload); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowInstall); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoInstallerFound); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoMatches); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoMinVersion); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoSuitableInstallerFound); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowPackageVersionNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceTooManyMatches); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementError); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementExitMessage); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesSkippedMessage); + WINGET_DEFINE_RESOURCE_STRINGID(DependencyArgumentMissing); + WINGET_DEFINE_RESOURCE_STRINGID(DependencySourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DisableAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(DisabledByGroupPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(DisableInteractivityArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Done); + WINGET_DEFINE_RESOURCE_STRINGID(DownloadCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DownloadCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DownloadDirectoryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Downloading); + WINGET_DEFINE_RESOURCE_STRINGID(DscCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionGet); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSet); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionWhatIf); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionTest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionDelete); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionExport); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionValidate); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionResolve); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionAdapter); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSchema); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionManifest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionExist); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionInDesiredState); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAcceptAgreements); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAdminSettingsSettings); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageId); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageSource); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageVersion); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageScope); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageMatchOption); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageUseLatest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageInstallMode); + WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileSettings); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileAction); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceName); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceArgument); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceType); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceTrustLevel); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceExplicit); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourcePriority); + WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(EnableAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(EnableWindowsFeaturesSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(EnablingWindowsFeature); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorInputArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorNumberIsTooLarge); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileConflictsWithInput); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorRequiresInputOrOutputFile); + WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExportedPackageRequiresLicenseAgreement); + WINGET_DEFINE_RESOURCE_STRINGID(ExportIncludeVersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExportSourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisableMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisablingMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnableArgumentError); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnableMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnablingMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesNotEnabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExternalDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ExtractArchiveFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ExtractArchiveSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ExtractingArchive); + WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeature); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeatureOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeatureOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToInitiateReboot); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToRefreshPathWarning); + WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledByAdminSettingMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesDisabled); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesEnabled); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesFeature); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesLink); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessageDisabledByBuild); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessageDisabledByPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesProperty); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesStatus); + WINGET_DEFINE_RESOURCE_STRINGID(FileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FileNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(FilesRemainInInstallDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); + WINGET_DEFINE_RESOURCE_STRINGID(FontAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(FontCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontFace); + WINGET_DEFINE_RESOURCE_STRINGID(FontFaces); + WINGET_DEFINE_RESOURCE_STRINGID(FontFamily); + WINGET_DEFINE_RESOURCE_STRINGID(FontFamilyNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontFileNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(FontDetailsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontFilePaths); + WINGET_DEFINE_RESOURCE_STRINGID(FontInstallFailed); + WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontPackage); + WINGET_DEFINE_RESOURCE_STRINGID(FontRollbackFailed); + WINGET_DEFINE_RESOURCE_STRINGID(FontStatus); + WINGET_DEFINE_RESOURCE_STRINGID(FontStatusCorrupt); + WINGET_DEFINE_RESOURCE_STRINGID(FontStatusOK); + WINGET_DEFINE_RESOURCE_STRINGID(FontStatusUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(FontTitle); + WINGET_DEFINE_RESOURCE_STRINGID(FontUninstallFailed); + WINGET_DEFINE_RESOURCE_STRINGID(FontValidationFailed); + WINGET_DEFINE_RESOURCE_STRINGID(FontVersion); + WINGET_DEFINE_RESOURCE_STRINGID(FontWinGetSupported); + WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(GatedVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(GetManifestResultVersionNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HashOverrideArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentNotApplicableForNonRestSourceWarning); + WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentNotApplicableWithoutSource); + WINGET_DEFINE_RESOURCE_STRINGID(HelpArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HelpForDetails); + WINGET_DEFINE_RESOURCE_STRINGID(HelpLinkPreamble); + WINGET_DEFINE_RESOURCE_STRINGID(IdArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IgnoreLocalArchiveMalwareScanArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IgnoreResumeLimitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IgnoreWarningsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandReportDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportFileHasInvalidSchema); + WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnorePackageVersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnoreUnavailableArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportInstallFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ImportSourceNotInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedInListArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownInListArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IncompatibleArgumentsProvided); + WINGET_DEFINE_RESOURCE_STRINGID(InitiatingReboot); + WINGET_DEFINE_RESOURCE_STRINGID(InstallAbandoned); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer1); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer2); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimerMSStore); + WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstalledScopeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerAbortsTerminal); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadAuthenticationFailed); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadAuthenticationNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadCommandProhibited); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloaded); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadRequiresAuthentication); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloads); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerElevationExpected); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedSecurityCheck); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedVirusScan); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedWithCode); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchAdminBlock); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchError); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerProhibitsElevation); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerRequiresInstallLocation); + WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal); + WINGET_DEFINE_RESOURCE_STRINGID(InstallersRequireInstallLocation); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerZeroByteFile); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeBlockedByPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCancelledByUser); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeContactSupport); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCustomError); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDiskFull); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDowngrade); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeFileInUse); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInstallInProgress); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInsufficientMemory); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInvalidParameter); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeMissingDependency); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeNoNetwork); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUse); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUseByApplication); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootInitiated); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredForInstall); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredToFinish); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeSystemNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowStartingPackageInstall); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFullPackageDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallLocationNotProvided); + WINGET_DEFINE_RESOURCE_STRINGID(InstallScopeDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallStubPackageDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallWaitingOnAnother); + WINGET_DEFINE_RESOURCE_STRINGID(InteractiveArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidAliasError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentSpecifierError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentValueError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentValueErrorWithoutValidValues); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentWithoutQueryError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidJsonFile); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidNameError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidPathToNestedInstaller); + WINGET_DEFINE_RESOURCE_STRINGID(KeyDirectoriesHeader); + WINGET_DEFINE_RESOURCE_STRINGID(LicenseAgreement); + WINGET_DEFINE_RESOURCE_STRINGID(Links); + WINGET_DEFINE_RESOURCE_STRINGID(ListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ListDetailsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(LocaleArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(LocationArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(LogArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Logs); + WINGET_DEFINE_RESOURCE_STRINGID(MainCopyrightNotice); + WINGET_DEFINE_RESOURCE_STRINGID(MainHomepage); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationFail); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationWarning); + WINGET_DEFINE_RESOURCE_STRINGID(McpCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(McpCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(McpConfigurationPreamble); + WINGET_DEFINE_RESOURCE_STRINGID(MissingArgumentError); + WINGET_DEFINE_RESOURCE_STRINGID(ModifiedPathRequiresShellRestart); + WINGET_DEFINE_RESOURCE_STRINGID(MonikerArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MsixArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreAppBlocked); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadAuthenticationNotice); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadDependencyPackages); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetDownloadInfo); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetDownloadInfoFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicense); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseForbidden); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadMainPackages); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadMultiplePackagesNotice); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadNoApplicablePackageFound); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloaded); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageHashMismatch); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageHashVerified); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadRenameNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallOrUpdateFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallTryGetEntitlement); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreRepairFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreStoreClientBlocked); + WINGET_DEFINE_RESOURCE_STRINGID(MultipleExclusiveArgumentsProvided); + WINGET_DEFINE_RESOURCE_STRINGID(MultipleInstalledPackagesFound); + WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFound); + WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFoundFilteredBySourcePriority); + WINGET_DEFINE_RESOURCE_STRINGID(MultipleUnsupportedNestedInstallersSpecified); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFoundMultiple); + WINGET_DEFINE_RESOURCE_STRINGID(NameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSpecified); + WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(NoAdminRepairForUserScopePackage); + WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); + WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage); + WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledFontFound); + WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledPackageFound); + WINGET_DEFINE_RESOURCE_STRINGID(NoPackageFound); + WINGET_DEFINE_RESOURCE_STRINGID(NoPackageSelectionArgumentProvided); + WINGET_DEFINE_RESOURCE_STRINGID(NoPackagesFoundInImportFile); + WINGET_DEFINE_RESOURCE_STRINGID(NoProxyArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NoProgressArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NoRepairInfoFound); + WINGET_DEFINE_RESOURCE_STRINGID(Notes); + WINGET_DEFINE_RESOURCE_STRINGID(NoUninstallInfoFound); + WINGET_DEFINE_RESOURCE_STRINGID(NoUpgradeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NoVTArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OpenLogsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatch); + WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatchHelp); + WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoSourceDefined); + WINGET_DEFINE_RESOURCE_STRINGID(Options); + WINGET_DEFINE_RESOURCE_STRINGID(OSVersionDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OutputDirectoryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OutputFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OverwritingExistingFileAtMessage); + WINGET_DEFINE_RESOURCE_STRINGID(Package); + WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsNotAgreedTo); + WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsPrompt); + WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(PackageIsPinned); + WINGET_DEFINE_RESOURCE_STRINGID(PackageRequiresDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAdded); + WINGET_DEFINE_RESOURCE_STRINGID(PinAlreadyExists); + WINGET_DEFINE_RESOURCE_STRINGID(PinCannotOpenIndex); + WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinDateAdded); + WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist); + WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting); + WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg); + WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledSource); + WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinNote); + WINGET_DEFINE_RESOURCE_STRINGID(PinNoteArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemovedSuccessfully); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful); + WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg); + WINGET_DEFINE_RESOURCE_STRINGID(PinShowNoMatchFound); + WINGET_DEFINE_RESOURCE_STRINGID(PinType); + WINGET_DEFINE_RESOURCE_STRINGID(PinVersion); + WINGET_DEFINE_RESOURCE_STRINGID(PlatformArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(PortableAliasAdded); + WINGET_DEFINE_RESOURCE_STRINGID(PortableHashMismatchOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(PortableHashMismatchOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(PortableInstallFailed); + WINGET_DEFINE_RESOURCE_STRINGID(PortableLinksMachine); + WINGET_DEFINE_RESOURCE_STRINGID(PortableLinksUser); + WINGET_DEFINE_RESOURCE_STRINGID(PortablePackageAlreadyExists); + WINGET_DEFINE_RESOURCE_STRINGID(PortableRegistryCollisionOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(PortableRoot); + WINGET_DEFINE_RESOURCE_STRINGID(PortableRoot86); + WINGET_DEFINE_RESOURCE_STRINGID(PortableRootUser); + WINGET_DEFINE_RESOURCE_STRINGID(PositionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PreserveArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PressEnterToContinue); + WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); + WINGET_DEFINE_RESOURCE_STRINGID(ProductCodeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PromptForInstallRoot); + WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); + WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); + WINGET_DEFINE_RESOURCE_STRINGID(PromptToProceed); + WINGET_DEFINE_RESOURCE_STRINGID(ProxyArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PurgeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PurgeInstallDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(RelatedLink); + WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairAbandoned); + WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairDifferentInstallTechnology); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFailedWithCode); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowNoMatchingVersion); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowRepairSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowReturnCodeSystemNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowStartingPackageRepair); + WINGET_DEFINE_RESOURCE_STRINGID(RepairOperationNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(ReparsePointsNotSupportedError); + WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityForAgreements); + WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityFound); + WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); + WINGET_DEFINE_RESOURCE_STRINGID(ReservedFilenameError); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdNotFoundError); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeLimitExceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeStateDataNotFoundError); + WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureError); + WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureErrorListMatches); + WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureErrorNoMatches); + WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureWarning); + WINGET_DEFINE_RESOURCE_STRINGID(SearchId); + WINGET_DEFINE_RESOURCE_STRINGID(SearchMatch); + WINGET_DEFINE_RESOURCE_STRINGID(SearchName); + WINGET_DEFINE_RESOURCE_STRINGID(SearchSource); + WINGET_DEFINE_RESOURCE_STRINGID(SearchTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(SearchVersion); + WINGET_DEFINE_RESOURCE_STRINGID(SeeLineAndColumn); + WINGET_DEFINE_RESOURCE_STRINGID(SetAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(SetAdminSettingSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); + WINGET_DEFINE_RESOURCE_STRINGID(SettingNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsResetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsSetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsSetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningField); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningValue); + WINGET_DEFINE_RESOURCE_STRINGID(SettingValueArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowChannel); + WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelAgreements); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelAuthor); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelChannel); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelCopyright); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelCopyrightUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDocumentation); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelExternalDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallationNotes); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstaller); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerLocale); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerOfflineDistributionSupported); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerProductId); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerReleaseDate); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerSha256); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerType); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicense); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicenseUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelMoniker); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPackageDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPackageUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPrivacyUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisher); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisherSupportUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisherUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPurchaseUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelReleaseNotes); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelReleaseNotesUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelTags); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelVersion); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelWindowsFeaturesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelWindowsLibrariesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListAvailableUpgrades); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledArchitecture); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledLocale); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledLocation); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledScope); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledSource); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstallerCategory); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListLocalIdentifier); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListPackageFamilyName); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListProductCode); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListUpgradeCode); + WINGET_DEFINE_RESOURCE_STRINGID(ShowVersion); + WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SingleCharAfterDashError); + WINGET_DEFINE_RESOURCE_STRINGID(SortArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SortAscendingArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SortDescendingArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SkipDependenciesArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyMessage); + WINGET_DEFINE_RESOURCE_STRINGID(SkipMicrosoftStorePackageLicenseArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentArg); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentName); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsMatch); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddBegin); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddFailedAuthenticationNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddOpenSourceFailed); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsMarketMessage); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsNotAgreedTo); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsPrompt); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsTitle); + WINGET_DEFINE_RESOURCE_STRINGID(SourceArgArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditExplicitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNewValue); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNoChanges); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOldValue); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOne); + WINGET_DEFINE_RESOURCE_STRINGID(SourceExplicitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListAdditionalSource); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListAllowedSource); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListArg); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListData); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListExplicit); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListField); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListIdentifier); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListName); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoneFound); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoSources); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListPriority); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListTrustLevel); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListType); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdated); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdatedNever); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListValue); + WINGET_DEFINE_RESOURCE_STRINGID(SourceNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenFailedSuggestion); + WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenPredefinedFailedSuggestion); + WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenWithFailedUpdate); + WINGET_DEFINE_RESOURCE_STRINGID(SourcePriorityArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveAll); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveOne); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRequiresAuthentication); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetAll); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetForceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetListAndOverridePreamble); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetOne); + WINGET_DEFINE_RESOURCE_STRINGID(SourceTrustLevelArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceTypeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateAll); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateOne); + WINGET_DEFINE_RESOURCE_STRINGID(StateDisabled); + WINGET_DEFINE_RESOURCE_STRINGID(StateEnabled); + WINGET_DEFINE_RESOURCE_STRINGID(StateHeader); + WINGET_DEFINE_RESOURCE_STRINGID(SystemArchitecture); + WINGET_DEFINE_RESOURCE_STRINGID(TagArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(TargetVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ThankYou); + WINGET_DEFINE_RESOURCE_STRINGID(ThirdPartSoftwareNotices); + WINGET_DEFINE_RESOURCE_STRINGID(ToolDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ToolInfoArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ToolVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(TooManyArgError); + WINGET_DEFINE_RESOURCE_STRINGID(TooManyBehaviorsError); + WINGET_DEFINE_RESOURCE_STRINGID(UnableToPurgeInstallDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(Unavailable); + WINGET_DEFINE_RESOURCE_STRINGID(UnexpectedErrorExecutingCommand); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallAbandoned); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallAllVersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandReportDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedDueToMultipleVersions); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedWithCode); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowStartingPackageUninstall); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowUninstallSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallPreviousArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UnrecognizedCommand); + WINGET_DEFINE_RESOURCE_STRINGID(UnsupportedArgument); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateAllArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateNoPackagesFound); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateNoPackagesFoundReason); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicable); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicableReason); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeAvailableForPinned); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByManifest); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByPinCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeInstallTechnologyMismatchCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); + WINGET_DEFINE_RESOURCE_STRINGID(UriNotWellFormed); + WINGET_DEFINE_RESOURCE_STRINGID(UriSchemeNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(Usage); + WINGET_DEFINE_RESOURCE_STRINGID(UserSettings); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandReportDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateManifestArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VerboseLogsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedIsDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedNotExist); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileSignedMsix); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyPathFailedNotExist); + WINGET_DEFINE_RESOURCE_STRINGID(VersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(WaitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeatureNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeaturesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsLibrariesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsPackageManager); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsPackageManagerPreview); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsStoreTerms); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitBothPackageVersionAndUseLatest); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitDependencySourceNotConfigured); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitDependencySourceNotDeclaredAsDependency); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitEmptyContent); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackage); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageMultipleFound); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageSourceOpenFailed); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageVersionNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitKnownSourceConfliction); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitMissingRecommendedArg); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitMissingRequiredArg); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitPackageVersionSpecifiedWithOnlyOnePackageVersion); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitThirdPartySourceAssertion); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitThirdPartySourceAssertionForPackage); + WINGET_DEFINE_RESOURCE_STRINGID(WordArgumentDescription); + }; + + // Fixed strings are not localized, but we use a similar system to prevent duplication + enum class FixedString + { + ProductName, + }; + + Utility::LocIndView GetFixedString(FixedString fs); +} + +inline std::ostream& operator<<(std::ostream& out, AppInstaller::CLI::Resource::FixedString fs) +{ + return (out << GetFixedString(fs)); +} diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index 7bc9b84e8a..96ac8e168f 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -1,464 +1,464 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Resources.h" -#include "PinFlow.h" -#include "TableOutput.h" -#include -#include -#include -#include - -using namespace AppInstaller::Repository; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - struct PinRowData - { - Utility::LocIndString PackageName; - Utility::LocIndString PackageId; - Utility::LocIndString Version; - Utility::LocIndString SourceName; - Utility::LocIndString PinType; - Utility::LocIndString PinnedVersion; - Utility::LocIndString DateAdded; - Utility::LocIndString Note; - }; - - // Creates a Pin appropriate for the context based on the arguments provided - Pinning::Pin CreatePin(Execution::Context& context, const Pinning::PinKey& pinKey) - { - if (context.Args.Contains(Execution::Args::Type::GatedVersion)) - { - return Pinning::Pin::CreateGatingPin(pinKey, context.Args.GetArg(Execution::Args::Type::GatedVersion)); - } - else if (context.Args.Contains(Execution::Args::Type::BlockingPin)) - { - return Pinning::Pin::CreateBlockingPin(pinKey); - } - else - { - return Pinning::Pin::CreatePinningPin(pinKey); - } - } - - void GetPinKeysForInstalled(const std::shared_ptr& installedVersion, std::set& pinKeys) - { - auto installedType = Manifest::ConvertToInstallerTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); - std::vector propertyStrings; - - if (Manifest::DoesInstallerTypeUsePackageFamilyName(installedType)) - { - propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); - } - else if (Manifest::DoesInstallerTypeUseProductCode(installedType)) - { - propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); - } - - for (const auto& value : propertyStrings) - { - pinKeys.emplace(Pinning::PinKey::GetPinKeyForInstalled(value)); - } - } - - std::set GetPinKeysForPackage(Execution::Context& context) - { - auto package = context.Get(); - - std::set pinKeys; - - if (context.Args.Contains(Execution::Args::Type::PinInstalled)) - { - auto installedVersion = GetInstalledVersion(package); - if (installedVersion) - { - GetPinKeysForInstalled(installedVersion, pinKeys); - } - } - else - { - auto availablePackages = package->GetAvailable(); - for (const auto& availablePackage : availablePackages) - { - pinKeys.emplace( - availablePackage->GetProperty(PackageProperty::Id).get(), - availablePackage->GetSource().GetIdentifier()); - } - } - - return pinKeys; - } - - // Gets a search request that can be used to find the installed package that corresponds with a pin. - SearchRequest GetSearchRequestForPin(const Pinning::PinKey& pinKey) - { - SearchRequest searchRequest; - if (pinKey.IsForInstalled()) - { - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pinKey.PackageId)); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pinKey.PackageId)); - } - else - { - searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pinKey.PackageId); - } - - return searchRequest; - } - - bool IsMatchForListQuery(const Pinning::Pin& pin, Execution::Context& context) - { - const auto& packageId = pin.GetKey().PackageId; - - bool hasId = context.Args.Contains(Execution::Args::Type::Id); - bool hasName = context.Args.Contains(Execution::Args::Type::Name); - bool hasQuery = context.Args.Contains(Execution::Args::Type::Query); - bool exactMatch = context.Args.Contains(Execution::Args::Type::Exact); - - if (hasId) - { - std::string_view idArg = context.Args.GetArg(Execution::Args::Type::Id); - return exactMatch - ? Utility::CaseInsensitiveEquals(packageId, idArg) - : Utility::CaseInsensitiveContainsSubstring(packageId, idArg); - } - - if (hasName || hasQuery) - { - std::string_view queryArg = hasName - ? context.Args.GetArg(Execution::Args::Type::Name) - : context.Args.GetArg(Execution::Args::Type::Query); - - return exactMatch - ? Utility::CaseInsensitiveEquals(packageId, queryArg) - : Utility::CaseInsensitiveContainsSubstring(packageId, queryArg); - } - - return true; - } - } - - void OpenPinningIndex::operator()(Execution::Context& context) const - { - auto pinningData = Pinning::PinningData{ m_readOnly ? Pinning::PinningData::Disposition::ReadOnly : Pinning::PinningData::Disposition::ReadWrite }; - if (!m_readOnly && !pinningData) - { - AICLI_LOG(CLI, Error, << "Unable to open pinning index."); - context.Reporter.Error() << Resource::String::PinCannotOpenIndex << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX); - } - - context.Add(std::move(pinningData)); - } - - void GetAllPins(Execution::Context& context) - { - AICLI_LOG(CLI, Info, << "Getting all existing pins"); - context.Add(context.Get().GetAllPins()); - } - - void SearchPin(Execution::Context& context) - { - auto pinKeys = GetPinKeysForPackage(context); - - auto package = context.Get(); - auto pinningData = context.Get(); - - std::vector pins; - for (const auto& pinKey : pinKeys) - { - auto pin = pinningData.GetPin(pinKey); - if (pin) - { - pins.emplace_back(std::move(pin.value())); - } - } - - context.Add(std::move(pins)); - } - - void AddPin(Execution::Context& context) - { - auto pinKeys = GetPinKeysForPackage(context); - - auto package = context.Get(); - auto pinningData = context.Get(); - auto installedVersion = context.Get(); - - std::vector pinsToAddOrUpdate; - for (const auto& pinKey : pinKeys) - { - auto pin = CreatePin(context, pinKey); - AICLI_LOG(CLI, Info, << "Evaluating Pin " << pin.ToString()); - - auto existingPin = pinningData.GetPin(pinKey); - if (existingPin) - { - Utility::LocIndString packageNameToReport; - if (pinKey.IsForInstalled() && installedVersion) - { - packageNameToReport = installedVersion->GetProperty(PackageVersionProperty::Name); - } - else - { - auto availableVersion = GetAvailablePackageFromSource(package, pinKey.SourceId)->GetLatestVersion(); - if (availableVersion) - { - packageNameToReport = availableVersion->GetProperty(PackageVersionProperty::Name); - } - } - - // Pin already exists. - // If it is the same, we do nothing. If it is different, check for the --force arg - if (pin == existingPin) - { - AICLI_LOG(CLI, Info, << "Pin already exists"); - context.Reporter.Info() << Resource::String::PinAlreadyExists(packageNameToReport) << std::endl; - continue; - } - - AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId); - if (context.Args.Contains(Execution::Args::Type::Force)) - { - AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); - context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageNameToReport) << std::endl; - pinsToAddOrUpdate.push_back(std::move(pin)); - } - else - { - context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageNameToReport) << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); - } - } - else - { - pinsToAddOrUpdate.push_back(std::move(pin)); - } - } - - if (!pinsToAddOrUpdate.empty()) - { - auto pinTime = std::chrono::system_clock::now(); - - std::optional note; - if (context.Args.Contains(Execution::Args::Type::PinNote)) - { - note = std::string{ context.Args.GetArg(Execution::Args::Type::PinNote) }; - } - - for (auto& pin : pinsToAddOrUpdate) - { - pin.SetDateAdded(pinTime); - pin.SetNote(note); - pinningData.AddOrUpdatePin(pin); - } - - context.Reporter.Info() << Resource::String::PinAdded << std::endl; - } - } - - void RemovePin(Execution::Context& context) - { - auto package = context.Get(); - auto pins = context.Get(); - - auto pinningData = context.Get(); - bool pinExists = false; - - // Note that if a source was specified in the command line, - // that will be the only one we get version keys from. - // So, we remove pins from all sources unless one was provided. - for (const auto& pin : pins) - { - AICLI_LOG(CLI, Info, << "Removing Pin " << pin.GetKey().ToString()); - pinningData.RemovePin(pin.GetKey()); - pinExists = true; - } - - if (!pinExists) - { - AICLI_LOG(CLI, Warning, << "Pin does not exist"); - context.Reporter.Warn() << Resource::String::PinDoesNotExist(package->GetProperty(PackageProperty::Name)) << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); - } - - context.Reporter.Info() << Resource::String::PinRemovedSuccessfully << std::endl; - } - - void ReportPins(Execution::Context& context) - { - const auto& pins = context.Get(); - bool hasListQuery = context.Args.Contains(Execution::Args::Type::Query) || - context.Args.Contains(Execution::Args::Type::Id) || - context.Args.Contains(Execution::Args::Type::Name); - std::vector filteredPins; - filteredPins.reserve(pins.size()); - - for (const auto& pin : pins) - { - if (IsMatchForListQuery(pin, context)) - { - filteredPins.push_back(pin); - } - } - - if (filteredPins.empty()) - { - if (hasListQuery) - { - context.Reporter.Info() << Resource::String::PinShowNoMatchFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); - } - else - { - context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; - } - - return; - } - - std::vector rowData; - rowData.reserve(filteredPins.size()); - const auto& source = context.Get(); - for (const auto& pin : filteredPins) - { - const auto& pinKey = pin.GetKey(); - auto searchRequest = GetSearchRequestForPin(pin.GetKey()); - auto searchResult = source.Search(searchRequest); - for (const auto& match : searchResult.Matches) - { - Utility::LocIndString packageName; - Utility::LocIndString sourceName; - Utility::LocIndString version; - - if (pinKey.IsForInstalled()) - { - sourceName = Resource::LocString{ Resource::String::PinInstalledSource }; - } - else - { - // This ensures we get the info from the right source if it exists on multiple - auto availablePackage = GetAvailablePackageFromSource(match.Package, pinKey.SourceId); - if (availablePackage) - { - auto availableVersion = availablePackage->GetLatestVersion(); - if (availableVersion) - { - packageName = availableVersion->GetProperty(PackageVersionProperty::Name); - sourceName = availableVersion->GetProperty(PackageVersionProperty::SourceName); - } - } - } - - auto installedVersion = GetInstalledVersion(match.Package); - if (installedVersion) - { - packageName = installedVersion->GetProperty(PackageVersionProperty::Name); - version = installedVersion->GetProperty(PackageVersionProperty::Version); - } - - Utility::LocIndString dateAdded; - const auto& pinDateAdded = pin.GetDateAdded(); - if (pinDateAdded.has_value()) - { - dateAdded = Utility::LocIndString{ Utility::TimePointToString(*pinDateAdded, - Utility::TimeFacet::Year | Utility::TimeFacet::Month | Utility::TimeFacet::Day | - Utility::TimeFacet::Hour | Utility::TimeFacet::Minute | Utility::TimeFacet::Second) }; - } - - Utility::LocIndString note; - const auto& pinNote = pin.GetNote(); - if (pinNote.has_value() && !pinNote->empty()) - { - note = Utility::LocIndString{ *pinNote }; - } - - rowData.push_back(PinRowData{ - std::move(packageName), - Utility::LocIndString{ pinKey.PackageId }, - std::move(version), - std::move(sourceName), - Utility::LocIndString{ std::string{ ToString(pin.GetType()) } }, - Utility::LocIndString{ pin.GetGatedVersion().ToString() }, - std::move(dateAdded), - std::move(note), - }); - } - } - - bool showDetails = context.Args.Contains(Execution::Args::Type::ListDetails); - if (!showDetails) - { - Execution::TableOutput<6> table(context.Reporter, - { - Resource::String::SearchName, - Resource::String::SearchId, - Resource::String::SearchVersion, - Resource::String::SearchSource, - Resource::String::PinType, - Resource::String::PinVersion, - }); - - for (const auto& row : rowData) - { - table.OutputLine({ row.PackageName, row.PackageId, row.Version, row.SourceName, row.PinType, row.PinnedVersion }); - } - - table.Complete(); - } - else - { - Execution::TableOutput<8> table(context.Reporter, - { - Resource::String::SearchName, - Resource::String::SearchId, - Resource::String::SearchVersion, - Resource::String::SearchSource, - Resource::String::PinType, - Resource::String::PinVersion, - Resource::String::PinDateAdded, - Resource::String::PinNote, - }); - - for (const auto& row : rowData) - { - table.OutputLine({ row.PackageName, row.PackageId, row.Version, row.SourceName, row.PinType, row.PinnedVersion, row.DateAdded, row.Note }); - } - - table.Complete(); - } - } - - void ResetAllPins(Execution::Context& context) - { - AICLI_LOG(CLI, Info, << "Resetting all pins"); - context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; - - std::string sourceId; - if (context.Args.Contains(Execution::Args::Type::Source)) - { - auto sourceName = context.Args.GetArg(Execution::Args::Type::Source); - auto sources = Source::GetCurrentSources(); - for (const auto& source : sources) - { - if (Utility::CaseInsensitiveEquals(source.Name, sourceName)) - { - sourceId = source.Identifier; - break; - } - } - } - - if (context.Get().ResetAllPins(sourceId)) - { - context.Reporter.Info() << Resource::String::PinResetSuccessful << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; - } - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Resources.h" +#include "PinFlow.h" +#include "TableOutput.h" +#include +#include +#include +#include + +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + struct PinRowData + { + Utility::LocIndString PackageName; + Utility::LocIndString PackageId; + Utility::LocIndString Version; + Utility::LocIndString SourceName; + Utility::LocIndString PinType; + Utility::LocIndString PinnedVersion; + Utility::LocIndString DateAdded; + Utility::LocIndString Note; + }; + + // Creates a Pin appropriate for the context based on the arguments provided + Pinning::Pin CreatePin(Execution::Context& context, const Pinning::PinKey& pinKey) + { + if (context.Args.Contains(Execution::Args::Type::GatedVersion)) + { + return Pinning::Pin::CreateGatingPin(pinKey, context.Args.GetArg(Execution::Args::Type::GatedVersion)); + } + else if (context.Args.Contains(Execution::Args::Type::BlockingPin)) + { + return Pinning::Pin::CreateBlockingPin(pinKey); + } + else + { + return Pinning::Pin::CreatePinningPin(pinKey); + } + } + + void GetPinKeysForInstalled(const std::shared_ptr& installedVersion, std::set& pinKeys) + { + auto installedType = Manifest::ConvertToInstallerTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); + std::vector propertyStrings; + + if (Manifest::DoesInstallerTypeUsePackageFamilyName(installedType)) + { + propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); + } + else if (Manifest::DoesInstallerTypeUseProductCode(installedType)) + { + propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + } + + for (const auto& value : propertyStrings) + { + pinKeys.emplace(Pinning::PinKey::GetPinKeyForInstalled(value)); + } + } + + std::set GetPinKeysForPackage(Execution::Context& context) + { + auto package = context.Get(); + + std::set pinKeys; + + if (context.Args.Contains(Execution::Args::Type::PinInstalled)) + { + auto installedVersion = GetInstalledVersion(package); + if (installedVersion) + { + GetPinKeysForInstalled(installedVersion, pinKeys); + } + } + else + { + auto availablePackages = package->GetAvailable(); + for (const auto& availablePackage : availablePackages) + { + pinKeys.emplace( + availablePackage->GetProperty(PackageProperty::Id).get(), + availablePackage->GetSource().GetIdentifier()); + } + } + + return pinKeys; + } + + // Gets a search request that can be used to find the installed package that corresponds with a pin. + SearchRequest GetSearchRequestForPin(const Pinning::PinKey& pinKey) + { + SearchRequest searchRequest; + if (pinKey.IsForInstalled()) + { + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pinKey.PackageId)); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pinKey.PackageId)); + } + else + { + searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pinKey.PackageId); + } + + return searchRequest; + } + + bool IsMatchForListQuery(const Pinning::Pin& pin, Execution::Context& context) + { + const auto& packageId = pin.GetKey().PackageId; + + bool hasId = context.Args.Contains(Execution::Args::Type::Id); + bool hasName = context.Args.Contains(Execution::Args::Type::Name); + bool hasQuery = context.Args.Contains(Execution::Args::Type::Query); + bool exactMatch = context.Args.Contains(Execution::Args::Type::Exact); + + if (hasId) + { + std::string_view idArg = context.Args.GetArg(Execution::Args::Type::Id); + return exactMatch + ? Utility::CaseInsensitiveEquals(packageId, idArg) + : Utility::CaseInsensitiveContainsSubstring(packageId, idArg); + } + + if (hasName || hasQuery) + { + std::string_view queryArg = hasName + ? context.Args.GetArg(Execution::Args::Type::Name) + : context.Args.GetArg(Execution::Args::Type::Query); + + return exactMatch + ? Utility::CaseInsensitiveEquals(packageId, queryArg) + : Utility::CaseInsensitiveContainsSubstring(packageId, queryArg); + } + + return true; + } + } + + void OpenPinningIndex::operator()(Execution::Context& context) const + { + auto pinningData = Pinning::PinningData{ m_readOnly ? Pinning::PinningData::Disposition::ReadOnly : Pinning::PinningData::Disposition::ReadWrite }; + if (!m_readOnly && !pinningData) + { + AICLI_LOG(CLI, Error, << "Unable to open pinning index."); + context.Reporter.Error() << Resource::String::PinCannotOpenIndex << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX); + } + + context.Add(std::move(pinningData)); + } + + void GetAllPins(Execution::Context& context) + { + AICLI_LOG(CLI, Info, << "Getting all existing pins"); + context.Add(context.Get().GetAllPins()); + } + + void SearchPin(Execution::Context& context) + { + auto pinKeys = GetPinKeysForPackage(context); + + auto package = context.Get(); + auto pinningData = context.Get(); + + std::vector pins; + for (const auto& pinKey : pinKeys) + { + auto pin = pinningData.GetPin(pinKey); + if (pin) + { + pins.emplace_back(std::move(pin.value())); + } + } + + context.Add(std::move(pins)); + } + + void AddPin(Execution::Context& context) + { + auto pinKeys = GetPinKeysForPackage(context); + + auto package = context.Get(); + auto pinningData = context.Get(); + auto installedVersion = context.Get(); + + std::vector pinsToAddOrUpdate; + for (const auto& pinKey : pinKeys) + { + auto pin = CreatePin(context, pinKey); + AICLI_LOG(CLI, Info, << "Evaluating Pin " << pin.ToString()); + + auto existingPin = pinningData.GetPin(pinKey); + if (existingPin) + { + Utility::LocIndString packageNameToReport; + if (pinKey.IsForInstalled() && installedVersion) + { + packageNameToReport = installedVersion->GetProperty(PackageVersionProperty::Name); + } + else + { + auto availableVersion = GetAvailablePackageFromSource(package, pinKey.SourceId)->GetLatestVersion(); + if (availableVersion) + { + packageNameToReport = availableVersion->GetProperty(PackageVersionProperty::Name); + } + } + + // Pin already exists. + // If it is the same, we do nothing. If it is different, check for the --force arg + if (pin == existingPin) + { + AICLI_LOG(CLI, Info, << "Pin already exists"); + context.Reporter.Info() << Resource::String::PinAlreadyExists(packageNameToReport) << std::endl; + continue; + } + + AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId); + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); + context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageNameToReport) << std::endl; + pinsToAddOrUpdate.push_back(std::move(pin)); + } + else + { + context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageNameToReport) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + } + } + else + { + pinsToAddOrUpdate.push_back(std::move(pin)); + } + } + + if (!pinsToAddOrUpdate.empty()) + { + auto pinTime = std::chrono::system_clock::now(); + + std::optional note; + if (context.Args.Contains(Execution::Args::Type::PinNote)) + { + note = std::string{ context.Args.GetArg(Execution::Args::Type::PinNote) }; + } + + for (auto& pin : pinsToAddOrUpdate) + { + pin.SetDateAdded(pinTime); + pin.SetNote(note); + pinningData.AddOrUpdatePin(pin); + } + + context.Reporter.Info() << Resource::String::PinAdded << std::endl; + } + } + + void RemovePin(Execution::Context& context) + { + auto package = context.Get(); + auto pins = context.Get(); + + auto pinningData = context.Get(); + bool pinExists = false; + + // Note that if a source was specified in the command line, + // that will be the only one we get version keys from. + // So, we remove pins from all sources unless one was provided. + for (const auto& pin : pins) + { + AICLI_LOG(CLI, Info, << "Removing Pin " << pin.GetKey().ToString()); + pinningData.RemovePin(pin.GetKey()); + pinExists = true; + } + + if (!pinExists) + { + AICLI_LOG(CLI, Warning, << "Pin does not exist"); + context.Reporter.Warn() << Resource::String::PinDoesNotExist(package->GetProperty(PackageProperty::Name)) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + } + + context.Reporter.Info() << Resource::String::PinRemovedSuccessfully << std::endl; + } + + void ReportPins(Execution::Context& context) + { + const auto& pins = context.Get(); + bool hasListQuery = context.Args.Contains(Execution::Args::Type::Query) || + context.Args.Contains(Execution::Args::Type::Id) || + context.Args.Contains(Execution::Args::Type::Name); + std::vector filteredPins; + filteredPins.reserve(pins.size()); + + for (const auto& pin : pins) + { + if (IsMatchForListQuery(pin, context)) + { + filteredPins.push_back(pin); + } + } + + if (filteredPins.empty()) + { + if (hasListQuery) + { + context.Reporter.Info() << Resource::String::PinShowNoMatchFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + } + else + { + context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; + } + + return; + } + + std::vector rowData; + rowData.reserve(filteredPins.size()); + const auto& source = context.Get(); + for (const auto& pin : filteredPins) + { + const auto& pinKey = pin.GetKey(); + auto searchRequest = GetSearchRequestForPin(pin.GetKey()); + auto searchResult = source.Search(searchRequest); + for (const auto& match : searchResult.Matches) + { + Utility::LocIndString packageName; + Utility::LocIndString sourceName; + Utility::LocIndString version; + + if (pinKey.IsForInstalled()) + { + sourceName = Resource::LocString{ Resource::String::PinInstalledSource }; + } + else + { + // This ensures we get the info from the right source if it exists on multiple + auto availablePackage = GetAvailablePackageFromSource(match.Package, pinKey.SourceId); + if (availablePackage) + { + auto availableVersion = availablePackage->GetLatestVersion(); + if (availableVersion) + { + packageName = availableVersion->GetProperty(PackageVersionProperty::Name); + sourceName = availableVersion->GetProperty(PackageVersionProperty::SourceName); + } + } + } + + auto installedVersion = GetInstalledVersion(match.Package); + if (installedVersion) + { + packageName = installedVersion->GetProperty(PackageVersionProperty::Name); + version = installedVersion->GetProperty(PackageVersionProperty::Version); + } + + Utility::LocIndString dateAdded; + const auto& pinDateAdded = pin.GetDateAdded(); + if (pinDateAdded.has_value()) + { + dateAdded = Utility::LocIndString{ Utility::TimePointToString(*pinDateAdded, + Utility::TimeFacet::Year | Utility::TimeFacet::Month | Utility::TimeFacet::Day | + Utility::TimeFacet::Hour | Utility::TimeFacet::Minute | Utility::TimeFacet::Second) }; + } + + Utility::LocIndString note; + const auto& pinNote = pin.GetNote(); + if (pinNote.has_value() && !pinNote->empty()) + { + note = Utility::LocIndString{ *pinNote }; + } + + rowData.push_back(PinRowData{ + std::move(packageName), + Utility::LocIndString{ pinKey.PackageId }, + std::move(version), + std::move(sourceName), + Utility::LocIndString{ std::string{ ToString(pin.GetType()) } }, + Utility::LocIndString{ pin.GetGatedVersion().ToString() }, + std::move(dateAdded), + std::move(note), + }); + } + } + + bool showDetails = context.Args.Contains(Execution::Args::Type::ListDetails); + if (!showDetails) + { + Execution::TableOutput<6> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId, + Resource::String::SearchVersion, + Resource::String::SearchSource, + Resource::String::PinType, + Resource::String::PinVersion, + }); + + for (const auto& row : rowData) + { + table.OutputLine({ row.PackageName, row.PackageId, row.Version, row.SourceName, row.PinType, row.PinnedVersion }); + } + + table.Complete(); + } + else + { + Execution::TableOutput<8> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId, + Resource::String::SearchVersion, + Resource::String::SearchSource, + Resource::String::PinType, + Resource::String::PinVersion, + Resource::String::PinDateAdded, + Resource::String::PinNote, + }); + + for (const auto& row : rowData) + { + table.OutputLine({ row.PackageName, row.PackageId, row.Version, row.SourceName, row.PinType, row.PinnedVersion, row.DateAdded, row.Note }); + } + + table.Complete(); + } + } + + void ResetAllPins(Execution::Context& context) + { + AICLI_LOG(CLI, Info, << "Resetting all pins"); + context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; + + std::string sourceId; + if (context.Args.Contains(Execution::Args::Type::Source)) + { + auto sourceName = context.Args.GetArg(Execution::Args::Type::Source); + auto sources = Source::GetCurrentSources(); + for (const auto& source : sources) + { + if (Utility::CaseInsensitiveEquals(source.Name, sourceName)) + { + sourceId = source.Identifier; + break; + } + } + } + + if (context.Get().ResetAllPins(sourceId)) + { + context.Reporter.Info() << Resource::String::PinResetSuccessful << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; + } + } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index 2e0e55a5db..e22f49f7e7 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -1,62 +1,62 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Opens the pinning index for use in future operations. - // Required Args: None - // Inputs: None - // Outputs: PinningIndex - struct OpenPinningIndex : public WorkflowTask - { - OpenPinningIndex(bool readOnly = false) : WorkflowTask("OpenPinningIndex"), m_readOnly(readOnly) {} - - void operator()(Execution::Context& context) const override; - - private: - bool m_readOnly; - }; - - // Gets all the pins from the index. - // Required Args: None - // Inputs: PinningIndex - // Outputs: Pins - void GetAllPins(Execution::Context& context); - - // Searches for all the pins associated with a package. - // There may be several if a package is available from multiple sources - // or if the pin is for the installed package. - // Required Args: None - // Inputs: PinningIndex, Package - // Outputs: Pins - void SearchPin(Execution::Context& context); - - // Adds a pin for the current package. - // A separate pin will be added for each source. - // Required Args: None - // Inputs: PinningIndex, Package, InstalledVersion? - // Outputs: None - void AddPin(Execution::Context& context); - - // Removes all the pins associated with a package. - // Required Args: None - // Inputs: PinningIndex, Package, InstalledPackageVersion - // Outputs: None - void RemovePin(Execution::Context& context); - - // Report the pins in a table. - // This includes searching for the corresponding installed packages - // to be able to show more info, like the package name. - // Required Args: None - // Inputs: Pins - // Outputs: None - void ReportPins(Execution::Context& context); - - // Resets all the existing pins. - // Required Args: None - // Inputs: None - // Outputs: None - void ResetAllPins(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Opens the pinning index for use in future operations. + // Required Args: None + // Inputs: None + // Outputs: PinningIndex + struct OpenPinningIndex : public WorkflowTask + { + OpenPinningIndex(bool readOnly = false) : WorkflowTask("OpenPinningIndex"), m_readOnly(readOnly) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_readOnly; + }; + + // Gets all the pins from the index. + // Required Args: None + // Inputs: PinningIndex + // Outputs: Pins + void GetAllPins(Execution::Context& context); + + // Searches for all the pins associated with a package. + // There may be several if a package is available from multiple sources + // or if the pin is for the installed package. + // Required Args: None + // Inputs: PinningIndex, Package + // Outputs: Pins + void SearchPin(Execution::Context& context); + + // Adds a pin for the current package. + // A separate pin will be added for each source. + // Required Args: None + // Inputs: PinningIndex, Package, InstalledVersion? + // Outputs: None + void AddPin(Execution::Context& context); + + // Removes all the pins associated with a package. + // Required Args: None + // Inputs: PinningIndex, Package, InstalledPackageVersion + // Outputs: None + void RemovePin(Execution::Context& context); + + // Report the pins in a table. + // This includes searching for the corresponding installed packages + // to be able to show more info, like the package name. + // Required Args: None + // Inputs: Pins + // Outputs: None + void ReportPins(Execution::Context& context); + + // Resets all the existing pins. + // Required Args: None + // Inputs: None + // Outputs: None + void ResetAllPins(Execution::Context& context); +} diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest index 0fac2aad64..78de4d13b7 100644 --- a/src/AppInstallerCLIPackage/Package.appxmanifest +++ b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -1,160 +1,160 @@ - - - - - WinGet Dev CLI - Microsoft Corporation - Images\StoreLogo.png - disabled - disabled - - - - - - - - - - - - - - - - - com.microsoft.winget.source - - - - - - - - - - - .wingetdev - - WinGetDev configuration file - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Microsoft.Management.Configuration.winmd - - - - - - - - - - - - - - - - - - - - - - - - - - Microsoft.Management.Configuration.dll - - - - - - - - - - - - - - + + + + + WinGet Dev CLI + Microsoft Corporation + Images\StoreLogo.png + disabled + disabled + + + + + + + + + + + + + + + + + com.microsoft.winget.source + + + + + + + + + + + .wingetdev + + WinGetDev configuration file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft.Management.Configuration.winmd + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft.Management.Configuration.dll + + + + + + + + + + + + + + diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index e375e36b36..6f7518d653 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1,3668 +1,3668 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Adjoined alias is not a flag: '{0}' - {Locked="{0}"} Error message displayed when the user provides an adjoined alias that is not a flag argument. {0} is a placeholder replaced by the user input argument (e.g. '-ab'). - - - Adjoined flag alias not found: '{0}' - {Locked="{0}"} Error message displayed when the user provides an adjoined flag alias argument that was not found. {0} is a placeholder replaced by the user input argument (e.g. '-ab'). - - - The following arguments are available: - Message displayed to inform the user about the available command line arguments. - - - The following command aliases are available: - Message displayed to inform the user about the available command line alias arguments. - - - The following commands are available: - Title displayed to inform the user about the available commands. - - - Available - As in "a new version is available to upgrade to". - - - The following options are available: - Message displayed to inform the user about the available options. - - - The following sub-commands are available: - Message displayed to inform the user about the available nested commands that run in context of the selected command. - - - {0} upgrades available. - {Locked="{0}"} Message displayed to inform the user about available package upgrades. {0} is a placeholder replaced by the number of package upgrades. - - - Use the specified channel; default is general audience - - - command - Label displayed for a command to give the software. - - - Filter results by command - Description message displayed to inform the user about filtering the search results by a package command. - - - The full command line for completion - - - This command requires administrator privileges to execute. - - - This command can be used to request context sensitive command line completion. The command line, cursor position, and word to be completed are passed in. The output is a set of potential values based on the inputs, with one possible value per line. - - - Enables context sensitive command line completion - - - Show no more than specified number of results (between 1 and 1000) - - - Done - Label displayed when an operation completes or is done executing. - - - Find package using exact match - Description message displayed to inform the user about finding an application package using an exact matching criteria. - - - Experimental argument for demonstration purposes - - - This command is an example on how to implement an experimental feature. To turn on go to 'winget settings' and enable experimentalCmd or experimentalArg features. - {Locked="winget settings"} - - - Experimental feature example - - - Found a positional argument when none was expected: '{0}' - {Locked="{0}"} Error message displayed when the user provides an extra positional argument when none was expected. {0} is a placeholder replaced by the user's extra argument input. - - - This feature is a work in progress, and may be changed dramatically or removed altogether in the future. To enable it, edit your settings ('winget settings') to include the experimental feature: '{0}' - {Locked="winget settings","{0}"}. Error message displayed when the user uses an experimental feature that is disabled. {0} is a placeholder replaced by the experimental feature name. - - - Shows the status of experimental features. Experimental features can be turned on via 'winget settings'. - {Locked="winget settings"} - - - Shows the status of experimental features - - - Disabled - Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. - - - Enabled - Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. - - - Feature - Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. - - - Link - Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. - - - The following experimental features are in progress. -They can be configured through the settings file 'winget settings'. - {Locked="winget settings"} - - - Property - Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. - - - Status - Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. - - - File to be hashed - - - Flag argument cannot contain adjoined value: '{0}' - {Locked="{0}"} Error message displayed when the user provides a flag argument containing an unexpected adjoined value. {0} is a placeholder replaced by the user input. - - - Computes the hash of a local file, appropriate for entry into a manifest. It can also compute the hash of the signature file of an MSIX package to enable streaming installations. - - - Helper to hash installer files - - - Shows help about the selected command - - - For more details on a specific command, pass it the help argument. - - - More help can be found at: {0} - {Locked="{0}"} Message displayed to inform the user about a link where they can learn more about the subject context. {0} is a placeholder replaced by a website address. - - - Filter results by id - - - Suppresses warning outputs - - - This application is licensed to you by its owner. - - - Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. - - - This package is provided through Microsoft Store. WinGet may need to acquire the package from Microsoft Store on behalf of the current user. - {Locked="WinGet"} - - - Installs the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, install command will check package installed status and try to perform an upgrade if applicable. Override with --force to perform a direct install. - {Locked="--force"}; id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Installs the given package - - - Installer hash does not match; this cannot be overridden when running as admin - - - Installer hash does not match; proceeding due to --ignore-security-hash - {Locked="--ignore-security-hash"} - - - Installer hash does not match; to override this check use --ignore-security-hash - {Locked="--ignore-security-hash"} - - - Successfully verified installer hash - - - Successfully installed - - - Starting package install... - - - Ignore the installer hash check failure - - - Ignore the malware scan performed as part of installing an archive type package from local manifest - - - Request interactive installation; user input may be needed - - - Argument alias was not recognized for the current command: '{0}' - {Locked="{0}"} Error message displayed when the user provides a command line argument alias that was not recognized for a selected command. {0} is a placeholder replaced by the user's argument alias input (e.g. '-a'). - - - Invalid argument specifier: '{0}' - {Locked="{0}"} Error message displayed when the user provides an invalid argument specifier. {0} is a placeholder replaced by an argument specifier (e.g. '-'). - - - Argument name was not recognized for the current command: '{0}' - {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). - - - WinGet Directories - {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs - - - Locale to use (BCP47 format) - {Locked="BCP47"} - - - License Agreement - - - Links - Links to different webpages - - - The list command displays the packages installed on the system, as well as whether an upgrade is available. Additional options can be provided to filter the output, much like the search command. - {Locked="list","search"} - - - Display installed packages - - - Location to install to (if supported) - - - Log location (if supported) - - - © 2026 Microsoft. All rights reserved. - - - Homepage - The primary webpage for the software - - - The path to the manifest of the package - - - Manifest validation failed. - - - Manifest validation succeeded. - - - Manifest validation succeeded with warnings. - - - Argument value required, but none found: '{0}' - {Locked="{0}"} Error message displayed when the user does not provide a required command line argument value. {0} is a placeholder replaced by the argument name. - - - Filter results by moniker - - - Input file will be treated as msix; signature hash will be provided if signed - - - Failed to calculate MSIX signature hash. - - - Failed to install or upgrade Microsoft Store package because the specific app is blocked by policy - - - Failed to install or upgrade Microsoft Store package. Error code: {0} - {Locked="{0}"} Error message displayed when a Microsoft Store application package fails to install or upgrade. {0} is a placeholder replaced by an error code. - - - Failed to install or upgrade Microsoft Store package because Microsoft Store client is blocked by policy - - - Verifying/Requesting package acquisition... - - - Multiple installed packages found matching input criteria. Please refine the input. - - - Multiple packages found matching input criteria. Please refine the input. - - - Filter results by name - - - No applicable installer found; see logs for more details. - - - There are currently no experimental features available. - - - No installed package found matching input criteria. - - - No package found matching input criteria. - - - Disables VirtualTerminal display - {Locked="VirtualTerminal"} - - - Open the default logs location - - - options - Options to change how a command works - - - Override arguments to be passed on to the installer - - - Package: {0} - {Locked="{0}"} Label displayed for a software package. {0} is a placeholder replaced by the software package name. - - - Oops, we forgot to do this... - - - The position of the cursor within the command line - - - Privacy Statement - - - The query used to search for a package - - - Progress display a rainbow of colors - - - Required argument not provided: '{0}' - {Locked="{0}"} Error message displayed when the user does not provide a required command line argument. {0} is a placeholder replaced by an argument name. - - - Progress display as the default color - - - Searches for packages from configured sources. - - - Find and show basic info of packages - - - Id - Abbreviation of Identifier. - - - Match - Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. - - - Name - - - Source - Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. - - - additional entries truncated due to result limit - - - Version - - - The following failures were found validating the settings: - - - Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings This command can also be used to set administrator settings by providing the --enable or --disable arguments - {Locked="--enable"} {Locked="--disable"} - - - Open settings or set administrator settings - - - Unexpected error while loading settings. Please verify your settings by running the 'settings' command. - {Locked="'settings'"} - - - Channel - Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. - - - Shows information on a specific package. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. - id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Shows information about a package - - - Version - - - Request silent installation - - - Only the single character alias can occur after a single -: '{0}' - {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. - - - Sort results by a property (can be repeated) - Description for the --sort argument used to sort output by field name. - - - Sort results in ascending order - Description for the --ascending (--asc) flag that sets ascending sort direction for output. - - - Sort results in descending order - Description for the --descending (--desc) flag that sets descending sort direction for output. - - - A source with the given name already exists and refers to a different location: - - - A source with a different name already refers to this location: - - - A source with the given name already exists and refers to the same location: - - - Adding source: - - - Add a new source. A source provides the data for you to discover and install packages. Only add a new source if you trust it as a secure location. - - - Add a new source - - - Argument given to the source - - - Find package using the specified source - - - Manage sources with the sub-commands. A source provides the data for you to discover and install packages. Only add a new source if you trust it as a secure location. - - - Manage sources of packages - - - Edit properties of an existing source. A source provides the data for you to discover and install packages. - - - Edit properties of a source - - - Editing source: {0} - {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being edited. {0} is a placeholder replaced by the repository source name. - - - The source named '{0}' is already in the desired state. - {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being edited. {0} is a placeholder replaced by the repository source name. - - - Argument - Value given to source. - - - List all current sources, or full details of a specific source. - - - List current sources - - - Data - Data stored by the source. - - - Field - The name of a piece of information about a source. - - - Name - The name of the source. - - - Identifier - The source's unique identifier. - - - Did not find a source named: {0} - Error message displayed when the user provides a repository source name that was not found. {0} is a placeholder replaced by the user input. - - - There are no sources configured. - - - Type - The kind of source. - - - Updated - The last time the source was updated. - - - never - The source has never been updated. - - - Value - The value of information about a source. - - - Name of the source - - - Failed when opening source(s); try the 'source reset' command if the problem persists. - {Locked="source reset"} - - - Failed to open the predefined source; please report to WinGet maintainers. - {Locked="WinGet"} - - - Removing all sources... - - - Remove a specific source. - - - Remove current sources - - - Removing source: {0}... - {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being removed. {0} is a placeholder replaced by the repository source name. - - - Resetting all sources... - - - This command drops existing sources, potentially leaving any local data behind. Without any argument, it will drop all sources and add the defaults. If a named source is provided, only that source will be dropped. - - - Reset sources - - - Forces the reset of the sources - - - The following sources will be reset if the --force option is given: - {Locked="--force"} - - - Resetting source: {0}... - {Locked="{0}"} Message displayed to inform the user about a repository source that is currently being reset. {0} is a placeholder replaced by the repository source name. - - - Type of the source - - - Updating all sources... - - - Update all sources, or only a specific source. - - - Update current sources - - - Updating source: {0}... - {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being updated. {0} is a placeholder replaced by the repository source name. - - - Filter results by tag - - - Thank you for using WinGet - {Locked="WinGet"} - - - Third Party Notices - - - The winget command line utility enables installing applications and other packages from the command line. - - - Display general info of the tool - - - Display the version of the tool - - - Argument provided more times than allowed: '{0}' - {Locked="{0}"} Error message displayed when the user provides a command line argument more times than it is allowed. {0} is a placeholder replaced by the user's argument name input. - - - More than one execution behavior argument provided: '{0}' - {Locked="{0}"} Error message displayed when the user provides more than one execution behavior argument when installing an application package. {0} is a placeholder replaced by the user specified execution behaviors (e.g. 'silent|interactive'). - - - An unexpected error occurred while executing the command: - - - Uninstall the previous version of the package during upgrade - - - Unrecognized command: '{0}' - {Locked="{0}"} Error message displayed when the user provides an unrecognized command. {0} is a placeholder replaced by the user input. - - - Upgrade all installed packages to latest if available - - - No applicable upgrade found. - - - A newer package version is available in a configured source, but it does not apply to your system or requirements. - - - No available upgrade found. - - - No newer package versions are available from the configured sources. - - - Upgrades the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. When no arguments are given, shows the packages with upgrades available - id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Shows and performs available upgrades - - - usage: {0} {1} - {Locked="{0} {1}"} Message displayed to provide the user with instructions on how to use a command. {0} is a placeholder replaced by the program name (e.g. 'winget'). {1} is a placeholder replaced by the pattern for using the selected command. - - - Validates a manifest using a strict set of guidelines. This is intended to enable you to check your manifest before submitting to a repo. - - - Validates a manifest file - - - The path to the manifest to be validated - - - Enables verbose logging for winget - - - Please verify that the input file is a valid, signed MSIX. - - - Use the specified version; default is the latest version - - - Show available versions of the package - - - The value provided before completion is requested - - - No version found matching: {0} - {Locked="{0}"} Error message displayed when the user attempts to upgrade an application package to a version that was not found. {0} is a placeholder replaced by the user's provided upgrade package version. - - - No sources match the given value: {0} - {Locked="{0}"} Error message displayed when the user attempts to install or upgrade an application package from a repository source that was not found. {0} is a placeholder replaced by the user's repository source name input. - - - The configured sources are: - - - No sources defined; add one with 'source add' or reset to defaults with 'source reset' - {Locked="source add","source reset"} - - - Found - Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. - - - Path is a directory: {0} - {Locked="{0}"} Error message displayed when the user provides a system path that is a directory. {0} is a placeholder replaced by the provided directory path. - - - File does not exist: {0} - {Locked="{0}"} Error message displayed when the user provides a system file that does not exist. {0} is a placeholder replaced by the provided file path. - - - Both local manifest and search query arguments are provided - - - Logs - Label displayed for diagnostic files containing information about the application use. - - - The installer is blocked by policy - - - The installer failed security check - - - An anti-virus product reports an infection in the installer - - - Failed in attempting to update the source: {0} - {Locked="{0}"} Error message displayed when an attempt to update the repository source fails. {0} is a placeholder replaced by the repository source name. - - - Uninstalls the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. - id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Uninstalls the given package - - - Starting package uninstall... - - - Successfully uninstalled - - - WinGet cannot locate the uninstall command for this package. Please reach out to the package publisher for support. - {Locked="WinGet"} - - - Uninstallation abandoned - - - Uninstall failed with exit code: {0} - {Locked="{0}"} Error message displayed when an attempt to uninstall an application package fails. {0} is a placeholder replaced by an error code. - - - Exports a list of the installed packages - - - Installs all the packages listed in a file. - - - Installs all the packages in a file - - - File where the result is to be written - - - File describing the packages to install - - - Export packages from the specified source - - - Writes a list of the installed packages to a file. The packages can then be installed with the import command. - {Locked="import"} - - - One or more imported packages failed to install - - - Source required for import is not installed: {0} - {Locked="{0}"} Error message displayed when the user attempts to import application package(s) from a repository source that is not installed. {0} is a placeholder replaced by the repository source name. - - - Installed package is not available from any source: {0} - {Locked="{0}"} Warning message displayed when the user attempts to export an installed application package that is not available from any repository source. {0} is a placeholder replaced by the installed package name. - - - Installed version of package is not available from any source: {0} {1} {2} - {Locked="{0} {1} {2}"} Warning message displayed when the user attempts to export an installed application package with a version that is not available from any repository source. {0} is a placeholder replaced by the installed package identifier. {1} is a placeholder replaced by the installed package version. {2} is a placeholder replaced by the installed package channel. - - - No packages found in import file - - - JSON file is not valid - - - Package is already installed: {0} - {Locked="{0}"} Message displayed to inform the user that an application package is already installed. {0} is a placeholder replaced by the package identifier or search query. - - - Ignore unavailable packages - - - Include package versions in export file - - - Ignore package versions from import file - - - Path does not exist: {0} - {Locked="{0}"} Error message displayed when the user provides a system path argument value that does not exist. {0} is a placeholder replaced by the user's provided path. - - - The JSON file does not specify a recognized schema. - - - Select install scope (user or machine) - {Locked="user","machine"} This argument allows the user to select between installing for just the user or for the entire machine. - - - The value provided for the `{0}` argument is invalid; valid values are: {1} - {Locked="{0}","{1}"} Error message displayed when the user provides an invalid command line argument value. {0} is a placeholder replaced by the argument name. {1} is a placeholder replaced by a list of valid options. - - - This operation is disabled by Group Policy: {0} - {Locked="{0}"} Error message displayed when the user performs a command operation that is disabled by a group policy. {0} is a placeholder replaced by a group policy description. - - - Enable Additional Windows App Installer Sources - - - Enable Windows App Installer Allowed Sources - - - Enable Windows App Installer Default Source - - - Enable Windows App Installer Experimental Features - - - Enable Windows App Installer Microsoft Store Source - - - Enable Windows App Installer Font Source - - - Enable Windows Package Manager Settings - - - Enable Windows Package Manager - - - Enable Windows Package Manager command line interfaces - - - Set Windows Package Manager Source Auto Update Interval In Minutes - - - Group Policy - Header for a table listing active Group Policies - - - Enable Windows App Installer Local Manifest Files - - - Enable Windows App Installer Microsoft Store Source Pinned Certificate Bypass - - - Enable Windows App Installer Local Archive Malware Scan Override - - - Field: {0} - {Locked="{0}"} Warning message displayed when a user setting field has invalid syntax or semantics. {0} is a placeholder replaced by the setting field path. - - - Invalid field format. - - - Invalid field value. - - - Invalid setting from Group Policy. - - - Loaded settings from backup file. - - - Error parsing file: - - - Value: {0} - {Locked="{0}"} Warning message displayed when a user setting value has invalid syntax or semantics. {0} is a placeholder replaced by the setting data value. - - - The following experimental features are in progress. -Configuration is disabled due to Group Policy. - - - Installer hash does not match. - - - Enable Windows App Installer Hash Override - - - Export current sources as JSON for Group Policy. - - - Export current sources - - - Additional source - An additional source required by policy. - - - Allowed source - A source that the user is allowed to add. - - - The value provided for the `{0}` argument is invalid - {Locked="{0}"} Error message displayed when the user provides an invalid command line argument value. {0} is a placeholder replaced by the argument name. - - - Cancelled - - - External - Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". - - - The packages found in this import have the following dependencies: - Import command sentence showed before reporting dependencies - - - This package requires the following dependencies: - Message shown before reporting dependencies - - - Installing dependencies: - - - Dependency source not found - - - Package search yield more than one result: {0} - {Locked="{0}"} Error message displayed when application packages search yield more than one result. {0} is a placeholder replaced by the dependency package identifier. - - - Latest version not found for package: {0} - {Locked="{0}"} Error message displayed when no suitable version found for the specific application package. {0} is a placeholder replaced by the package identifier. - - - No installers found: {0} - {Locked="{0}"} Error message displayed when no installer found for a manifest. {0} is a placeholder replaced by the manifest identifier. - - - Minimum required version not available for package: {0} - {Locked="{0}"} Error message displayed when the minimum required version is not available for an application package. {0} is a placeholder replaced by the package identifier. - - - No matches - When package search yields no matches - - - Has loop - Dependency graph has loop - - - No suitable installer found for manifest: {0} version {1} - {Locked="{0}","{1}"} Error message displayed when an attempt to get a preferred installer for a manifest fails. {0} is a placeholder replaced by the manifest identifier. {1} is a placeholder replaced by the manifest version. - - - Error processing package dependencies. Do you wish to continue installation? - Prompt message shown when dependencies processing yields errors. - - - Error processing package dependencies. Exiting... - - - Packages - - - This package had dependencies that may not be needed anymore: - Uninstall command sentence showed before reporting dependencies - - - Manifest has the following dependencies that were not validated; ensure that they are valid: - Validate command sentence showed before reporting dependencies - - - Windows Features - - - Windows Libraries - - - Find package dependencies using the specified source - For getting package type dependencies when installing from a local manifest - - - Windows Store Terms - - - Accept all license agreements for packages - - - Exported package requires license agreement to install: {0} - {Locked="{0}"} Warning message displayed when an exported application package requires license agreement to install. {0} is a placeholder replaced by the package name. - - - The publisher requires that you view the above information and accept the agreements before installing. -Do you agree to the terms? - - - Package agreements were not agreed to. Operation cancelled. - - - Agreements: - - - Author: - - - Description: - - - Installer: - - - Installer Locale: - - - Store Product Id: - - - Installer SHA256: - - - Installer Type: - - - Installer Url: - - - License: - - - License Url: - - - Moniker: - - - Homepage: - - - Publisher: - - - Tags: - - - Version: - - - Dependencies: - - - Windows Features: - - - Windows Libraries: - - - Package Dependencies: - - - External Dependencies: - - - No - - - Yes - - - Failed to open the added source. - - - Accept all source agreements during source operations - - - The `{0}` source requires that you view the following agreements before using. - {Locked="{0}"} Message displayed to inform the user that a repository source requires viewing agreements before using. {0} is a placeholder replaced by the repository source name. - - - Do you agree to all the source agreements terms? - - - One or more of the source agreements were not agreed to. Operation cancelled. Please accept the source agreements or remove the corresponding sources. - - - The source requires the current machine's 2-letter geographic region to be sent to the backend service to function properly (ex. "US"). - - - Successfully installed. Restart the application to complete the upgrade. - - - Optional Windows-Package-Manager REST source HTTP header - - - Ignoring the optional header as it is not applicable for this source. - - - The optional header is not applicable without specifying a source: '{0}' - {Locked="{0}"} Error message displayed when the user performs an operation (e.g install) and provides the HTTP 'header' argument without specifying the repository source. {0} is a placeholder replaced by the header argument name. - - - Release Date: - - - Offline Distribution Supported: - - - Publisher Url: - - - Purchase Url: - - - Publisher Support Url: - - - Privacy Url: - - - Copyright: - - - Copyright Url: - - - Release Notes: - - - Release Notes Url: - - - Failed when searching source; results will not be included: {0} - {Locked="{0}"} Warning message displayed when searching a repository source fails. {0} is a placeholder replaced by the repository source name. - - - Failed when searching source: {0} - {Locked="{0}"} Error message displayed when searching a repository source fails. {0} is a placeholder replaced by the repository source name. - - - This feature needs to be enabled by administrators. To enable it, run 'winget settings --enable {0}' as administrator. - {Locked="winget settings --enable", "{0}"}. Error message displayed when the user uses a feature that needs to be enabled by administrators. {0} is a placeholder replaced by the admin setting. - - - Enables the specific administrator setting - - - Disables the specific administrator setting - - - Enabled admin setting '{0}'. - {Locked="{0}"} Message displayed when the user enables an admin setting. {0} is a placeholder replaced by the setting name. - - - Disabled admin setting '{0}'. - {Locked="{0}"} Message displayed when the user disables an admin setting. {0} is a placeholder replaced by the setting name. - - - Admin Setting - Header for a table displaying admin settings. - - - Application is currently running. Exit the application then try again. - - - Files modified by the installer are currently in use by a different application. Exit the applications then try again. - - - Another installation is already in progress. Try again later. - - - One or more files are being used. Exit the application then try again. - - - This package has a dependency missing from your system. - - - There's no more space on your PC. Make space, then try again. - - - There's not enough memory available to install. Close other applications then try again. - - - One of the installation parameters is invalid. The package installation log may have additional details. - - - This application requires internet connectivity. Connect to a network then try again. - - - This application encountered an error during installation. Contact support. - - - Restart your PC to finish installation. - - - Your PC will restart to finish installation. - - - Installation failed. Restart your PC then try again. - - - You cancelled the installation. - - - Another version of this application is already installed. - - - A higher version of this application is already installed. - - - Organization policies are preventing installation. Contact your admin. - - - The current system configuration does not support the installation of this package. - - - Installation failed with a custom installer error. Contact package support. - - - Installation abandoned - - - Installer failed with exit code: {0} - {Locked="{0}"} Error message displayed when the application installer fails. {0} is a placeholder replaced by an error code. - - - Installer log is available at: {0} - {Locked="{0}"} Message displayed to inform the user about the system path of a diagnostic files containing information about the installer. {0} is a placeholder replaced by the diagnostic file system path. - - - The following packages were found among the working sources. -Please specify one of them using the --source option to proceed. - {Locked="--source"} "working sources" as in "sources that are working correctly" - - - No packages were found among the working sources. - "working sources" as in "sources that are working correctly" - - - This is a stable release of the Windows Package Manager. If you would like to try experimental features, please install a pre-release build. Instructions are available on GitHub at https://github.com/microsoft/winget-cli. - {Locked="https://github.com/microsoft/winget-cli"} - - - Windows Package Manager v{0} - {Locked="{0}"} Label displaying the product name and version. {0} is a placeholder replaced by the product version. - - - Ignore package versions in import file - - - The requested number of results must be between 1 and 1000. - - - Arguments to be passed on to the installer in addition to the defaults - - - A newer version was found, but the install technology is different from the current version installed. Please uninstall the package and install the newer version. - - - {0} package(s) have upgrades blocked because newer versions use a different install technology than the current installation. Uninstall each package, then install the newer version. - {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. - - - The install technology of the newer version specified is different from the current version installed. Please uninstall the package and install the newer version. - - - Windows Package Manager (Preview) v{0} - {Locked="{0}"} Label displaying the preview product name and pre-release version. {0} is a placeholder replaced by the product version. - - - Select the architecture - - - Upgrade packages even if their current version cannot be determined - - - List packages even if their current version cannot be determined. Can only be used with the --upgrade-available argument - {Locked="--upgrade-available"} - - - This package's version number cannot be determined. To upgrade it anyway, add the argument --include-unknown to your previous command. - {Locked="--include-unknown"} - - - {0} package(s) have version numbers that cannot be determined. Use --include-unknown to see all results. - {Locked="{0}","--include-unknown"} {0} is a placeholder that is replaced by an integer number of packages that do not have notated versions. - - - {0} package(s) are pinned and need to be explicitly upgraded. - {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages that require explicit upgrades. - - - The arguments provided can only be used with a query. - - - System Architecture: {0} - {Locked="{0}"} Label displayed for the system architecture. {0} is a placeholder replaced by the value of the system architecture (e.g. X64). - - - Retains all files and directories created by the package (portable) - - - Deletes all files and directories in the package directory (portable) - - - Press Enter to continue . . . - - - The value to rename the executable file (portable) - - - Prompts the user to press any key before exiting - - - The installer will request to run as administrator. Expect a prompt. - - - The installer cannot be run from an administrator context. - - - Path environment variable modified; restart your shell to use the new value. - - - Filters using the product code - - - A portable package with the same name but from a different source already exists; proceeding due to --force - {Locked="--force"} - - - The volume does not support reparse points - - - The specified filename is not a valid filename - - - Overwriting existing file: {0} - {Locked="{0}"} Warning message displayed to inform the user that an existing file is being overwritten. {0} is a placeholder replaced by the file system path. - - - No package selection argument was provided; see the help for details about finding a package. - - - Command line alias added: - - - Portable Links Directory (Machine) - - - Portable Links Directory (User) - - - Portable Package Root (User) - - - Portable Package Root - - - Portable Package Root (x86) - - - Portable install failed; Cleaning up... - - - Portable package has been modified; proceeding due to --force - {Locked="--force"} - - - Unable to remove Portable package as it has been modified; to override this check use --force - {Locked="--force"} - - - Files remain in install directory: {0} - {Locked="{0}"} Warning message displayed when files remain in install directory. {0} is a placeholder replaced by the directory path. - - - Purging install directory... - - - Cannot purge install directory as it was not created by WinGet - {Locked="WinGet"} - - - Related Link - - - Documentation: - - - Notes: {0} - {Locked="{0}"} Label displayed for installation notes. {0} is a placeholder replaced by installation notes. - - - Installation Notes: - - - A provided argument is not supported for this package - - - Failed to extract the contents of the archive - - - Nested installer file does not exist. Ensure the specified relative path of the nested installer matches: {0} - {Locked="{0}"} Error message displayed when nested installer file does not exist. {0} is a placeholder replaced by the nested installer file path. - - - Invalid relative file path to the nested installer; path points to a location outside of the install directory - - - No nested installers specified for this package - - - Only one nested installer can be specified for an archive installer unless it is a portable or font nested installer - - - Incompatible command line arguments provided - - - Lists only packages which have an upgrade available - - - The following packages have an upgrade available, but require explicit targeting for upgrade: - "require explicit targeting for upgrade" means that the package will not be upgraded with all others unless an extra flag is added, or the package is mentioned explicitly - - - Downloading - Label displayed while downloading an application installer. - - - The nested installer type is not supported - - - This installer is known to restart the terminal or shell - - - This package requires an install location - - - The following installers are known to restart the terminal or shell: - - - The following installers require an install location: - - - Specify the install root: - - - Do you want to proceed? - - - Agreements for - This will be followed by a package name, and then a list of license agreements - - - Install location is required by the package but it was not provided - - - Disable interactive prompts - Description for a command line argument, shown next to it in the help - - - Found an existing package already installed. Trying to upgrade the installed package... - - - Direct run the command and continue with non security related issues - Description for a command line argument, shown next to it in the help - - - Portable package from a different source already exists - - - Successfully extracted archive - - - Extracting archive... - - - Archive scan detected malware. To override this check use --ignore-local-archive-malware-scan - {Locked="--ignore-local-archive-malware-scan"} - - - Archive scan detected malware. Proceeding due to --ignore-local-archive-malware-scan - {Locked="--ignore-local-archive-malware-scan"} - - - Skips upgrade if an installed version already exists - - - A package version is already installed. Installation cancelled. - - - Cannot enable {0}. This setting is controlled by policy. For more information contact your system administrator. - {Locked="{0}"} The value will be replaced with the feature name - - - Cannot disable {0}. This setting is controlled by policy. For more information contact your system administrator. - {Locked="{0}"} The value will be replaced with the feature name - - - Add a new pin. A pin can limit the Windows Package Manager from upgrade a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. By default, a pinned package can be upgraded by mentioning it explicitly in the 'upgrade' command or by adding the '--include-pinned' flag to 'winget upgrade --all'. - {Locked{"'upgrade'"} Locked{"--include-pinned"} Locked{"winget upgrade --all"} - - - Add a new pin - - - Manage package pins with the sub-commands. A pin can limit the Windows Package Manager from upgrading a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. - - - Manage package pins - - - List all current pins, or full details of a specific pin. - - - List current pins - - - Remove a specific package pin. - - - Remove a package pin - - - Reset all existing pins. - - - Reset pins - - - Version to which to pin the package. The wildcard '*' can be used as the last version part - - - Block from upgrading until the pin is removed, preventing override arguments - - - Pin a specific installed version - - - Optional note to store with the pin - Description for the --note argument used with `winget pin add` - - - Installed - Value used in a table to indicate that a package comes from the list of packages installed in the machine - - - Export settings as JSON - - - Export settings - - - User Settings - Label displayed for the file containing the user settings. - - - Settings file couldn't load. Using default values. - - - Select installed package scope filter (user or machine) - {Locked="user","machine"} This argument allows the user to select installed packages for just the user or for the entire machine. - - - Pin added successfully - - - There is already a pin for package {0} - {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to add a pin for a package that is already pinned. - - - A pin already exists for package {0}. Overwriting due to the --force argument. - {Locked="--force"}{Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. - - - A pin already exists for package {0}. Use the --force argument to overwrite it. - {Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. - - - Resetting all current pins. - - - Use the --force argument to reset all pins. The following pins would be removed: - {Locked="--force"} - - - Pin type - - - There is no pin for package {0} - {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to delete a pin for a package that is not pinned. - - - There are no pins configured. - Shown when listing or modifying existing pins if there are none. - - - Pins reset successfully - Shown after resetting (deleting) all the pins - - - Unable to open pin database. - Error message for when we cannot open the database containing package pins. - - - An argument was provided that can only be used for single package - - - Multiple mutually exclusive arguments provided: {0} - {Locked="{0}"} Error message shown when mutually incompatible command line arguments are used. {0} is a placeholder replaced by the arguments that cannot be specified together - - - Argument {0} can only be used with {1} - {Locked="{0}","{1}"} Error message shown when having an argument needs another to be present, but that is missing. {0} and {1} are replaced by the arguments. For example "Argument --include-unknown can only be used with --upgrade" - - - Disabled - As in enabled/disabled - - - Enabled - As in enabled/disabled - - - State - Header for a table listing the state (enabled/disabled) of Group Policies and Settings - - - The query used to search for a package - - - Package not found: {0} - {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns no results. {0} is a placeholder replaced by the package name or query. - - - Multiple packages found for: {0} - {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns more than one result. {0} is a placeholder replaced by the package name or query. - - - Search failed for: {0} - {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches fails. This message is for generic failures, we have more specific messages for when the search returns no results, or when it returns more than one result. {0} is a placeholder replaced by the package name or query. - - - {0} package(s) have a pin that needs to be removed before upgrade - {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - - - The package cannot be upgraded using WinGet. Please use the method provided by the publisher for upgrading this package. - {Locked="WinGet"} - - - Upgrade packages even if they have a non-blocking pin - - - List packages even if they have a pin that prevents upgrade. Can only be used with the --upgrade-available argument - {Locked="--upgrade-available"} - - - Pin removed successfully - - - A newer version was found, but the package has a pin that prevents from upgrading it. - - - The package is pinned and cannot be upgraded. Use the 'winget pin' command to view and edit pins. Some pin types can be bypassed with the --include-pinned argument. - {Locked="winget pin","--include-pinned"} Error shown when we block an upgrade due to the package being pinned - - - {0} package(s) have pins that prevent upgrade. Use the 'winget pin' command to view and edit pins. Using the '--include-pinned' argument may show more results. - {Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages - - - Ensures that the system matches the desired state as described by the provided configuration. May download/execute processors in order to achieve the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before applying them. - - - Configures the system into a desired state - - - Shows details of the provided configuration. By default, will not modify the system, but some options will cause files to be downloaded and/or loaded. - - - Shows details of a configuration - - - Checks that the system matches the desired state as described by the provided configuration. May download/execute processors in order to test the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before executing them. - - - Checks the system against a desired state - - - Validates a configuration file for correctness. - - - Validates a configuration file - - - The field '{0}' in the configuration file is the wrong type. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. - - - The path to the configuration file - - - The configuration file is invalid. - - - Configuration file version {0} is not known. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the version of the configuration file. - - - Accepts the configuration warning, preventing an interactive prompt - - - Apply - Indicates that this item is used to write state - - - Assert - Indicates that this item is used to check/assert the state rather than write to it - - - Dependencies:{0} - {Locked="{0}"} Label displaying a list of dependencies. {0} is replaced with a space separated list of identifiers referencing other items. - - - Some of the configuration was not applied successfully. - - - Failed to get detailed information about the configuration. - - - Inform - Indicates that this item is used to retrieve values for future use rather than writing them - - - Local - Used to indicate that the item is present on the device. - - - Module: {0} - {Locked="{0}"} Label displaying a module name. {0} is replaced with the name of the module from the user input file. - - - Module: {0} by {1} [{2}] - {Locked="{0}","{1}","{2}"} Label displaying module information. {0} is replaced by the module name. {1} is replaced by the module author. {2} is replaced by a string indicating the source of the module. - - - Settings: - Label for the values that are used as inputs for this item when applying state - - - Configuration successfully applied. - - - Unit successfully applied. - - - Another configuration is being applied to the system. This configuration will continue as soon as is possible... - - - You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applications installed are licensed to you by their owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages or services. - Legal approved. Do not change without approval. - - - Have you reviewed the configuration and would you like to proceed applying it to the system? - PM approved. - - - The configuration is empty. - - - The feature [{0}] was not found. - {Locked="{0}"} Error message displayed when a Windows feature was not found on the system. - - - Failed to enable Windows Feature dependencies. To proceed with installation, use '--force'. - {Locked="--force"} - - - Reboot required to fully enable the Windows Feature(s); to override this check use '--force'. - "Windows Feature(s)" is the name of the options Windows features setting. - - - Failed to enable Windows Feature dependencies; proceeding due to --force - "Windows Feature(s)" is the name of the options Windows features setting. -{Locked="--force"} - - - Enabling [{0}]... - {Locked="{0}"} Message displayed to the user regarding which Windows Feature is being enabled. - - - Failed to enable [{0}] feature: {1} - {Locked="{0}","{1}"} An error when enabling a Windows Feature. {0} is a placeholder for the name of the Windows Feature. -{1} is a placeholder for the unrecognized error code. - - - Reboot required to fully enable the Windows Feature(s); proceeding due to --force - "Windows Feature(s)" is the name of the options Windows features setting. - - - Waiting for another install/uninstall to complete... - - - Pinned version - Table header for the version to which a package is pinned; meaning it should not update from that version. - - - Time Pinned - Table header shown with `winget pin list --details` for when the pin was added or last updated. - - - Note - Table header shown with `winget pin list --details` for the user-provided note stored with the pin. - - - No pin found matching the specified criteria. - Shown when a filtered `winget pin list` command finds no matching pin. - - - <See the log file for additional details> - The brackets are intended to make the value stand out from other text which it will follow. Any locale appropriate mechanism that achieves this is acceptable. - - - Retrieving configuration details - - - Initializing configuration system - - - Reading configuration file - - - The system is not in the desired state asserted by the configuration. - - - This configuration unit failed for an unknown reason: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to the configuration: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed while attempting to get the current system state. - - - The configuration unit failed while attempting to apply the desired state. - - - The configuration unit failed while attempting to test the current system state. - - - The configuration unit failed due to an internal error: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to a precondition not being valid: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to the system state: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed while attempting to run: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration contains the identifier `{0}` multiple times. - {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. - - - The dependency `{0}` was not found within the configuration. - {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. - - - This configuration unit was manually skipped. - - - The module for the configuration unit is available in multiple locations with the same version. - - - Loading the module for the configuration unit failed. - - - Multiple matches were found for the configuration unit; specify the module to select the correct one. - - - The configuration unit could not be found. - - - The configuration unit was not in the module as expected. - - - This configuration unit was not run because a dependency failed or was not run. - - - This configuration unit was not run because an assert failed or was false. - - - The configuration unit returned an unexpected result during execution. - - - This configuration unit was not run for an unknown reason: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The field '{0}' has an invalid value: {1} - {Locked="{0}","{1}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. {1} is a placeholder for the invalid value. - - - The field '{0}' is missing or empty. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the expected field name from the file. - - - See line {0}, column {1} in the file. - {Locked="{0}","{1}"} Indicates the file location of the error, {0} and {1} are placeholders for numbers of the line and column, respectively. - - - Cancelling operation - - - Install the stub package for AppInstaller - - - Install the full package for AppInstaller - - - Enable extended features. Requires store access. - - - Option '--enable' and '--disable' cannot be used with other arguments. - {Locked="--enable", "--disable"} - - - Enabling extended features. Requires store access. - - - Extended features are not enabled. Run `winget configure --enable` to enable them. - {Locked="winget configure --enable"} - - - Extended features are enabled. - - - Disable extended features. Requires store access. - - - Disabling extended features. Requires store access. - - - Extended features are disabled. - - - Skips processing package dependencies and Windows features - - - Installs only the dependencies of the package - - - Installing dependencies only. The package itself will not be installed. - - - Dependencies skipped. - - - Failed to refresh PATH variable for process. Subsequent installs that depend on changes to the PATH variable may fail. - {Locked="PATH"} - - - Some of the configuration units failed while testing their state. - - - System is in the described configuration state. - - - Configuration state was not tested. - - - System is not in the described configuration state. - - - Unexpected test result: {0} - {Locked="{0}"} Error message. {0} will be replaced with the unexpected value (a number). - - - Have you reviewed the configuration and would you like to proceed verifying it against the system? - - - The configuration file is not a valid YAML file. - {Locked="YAML"} YAML is a file format name. - - - This configuration unit is part of a dependency cycle. - - - Validation found no issues. - - - The configuration unit is only available as a prerelease, but it is not marked that way in the configuration. Add `allowPrerelease: true` to the `directives`. - {Locked="allowPrerelease: true","directives"} These are values in the configuration file that are not localized. - - - The configuration unit was found locally, but could not be found in any configured catalog. Ensure that it is present on any system before applying the configuration. - - - The configuration unit is not available publicly; ensure that anyone who will use this configuration has access to it. - - - The module was not provided. Specifying the module improves performance and prevents future name collisions. - - - Downloads the installer from the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, download command will download the appropriate installer to the user's Downloads folder. - - - Downloads the installer from a given package - - - Directory where the installers are downloaded to - - - Downloading dependencies: - - - Installer downloaded: {0} - {Locked="{0}"} Full path of the downloaded installer. - - - Select the installer type - - - Installer Downloads - - - Installer is prohibited from being downloaded for later offline installation. - - - `--module-path allusers` requires administrator privileges to execute. - {Locked="--module-path allusers"} - - - Specifies the location on the local computer to store modules. Default %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules - {Locked="%LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules"} - - - `--module-path` value must be `currentuser`, `allusers`, `default`, or an absolute path. - {Locked="{--module-path}, {currentuser}, {allusers}, {default}} - - - Enable Windows Package Manager Configuration - - - Retrieve information about errors. Given a number, the output will contain details about the error, including the symbol name if it is a WinGet specific error. Given a string, the WinGet specific errors are searched for this value. - {Locked="WinGet"} - - - Get information on errors - - - A value to search within the error information - - - Generate error code Markdown file - Description of an argument that causes a Markdown file to be generated for all winget error codes. - - - The --output argument cannot be combined with an input value. - {Locked="--output"} - - - An input value or --output must be provided. - {Locked="--output"} - - - The given number is too large to be an HRESULT. - - - Unknown error code - - - Internal Error - - - Invalid command line arguments - - - Executing command failed - - - Opening manifest failed - - - Cancellation signal received - - - Running ShellExecute failed - - - Cannot process manifest. The manifest version is higher than supported. Please update the client. - - - Downloading installer failed - - - Cannot write to index; it is a higher schema version - - - The index is corrupt - - - The configured source information is corrupt - - - The source name is already configured - - - The source type is invalid - - - The MSIX file is a bundle, not a package - - - Data required by the source is missing - - - None of the installers are applicable for the current system - - - The installer file's hash does not match the manifest - - - The source name does not exist - - - The source location is already configured under another name - - - No packages found - - - No sources are configured - - - Multiple packages found matching the criteria - - - No manifest found matching the criteria - - - Failed to get Public folder from source package - - - Command requires administrator privileges to run - - - The source location is not secure - - - The Microsoft Store client is blocked by policy - - - The Microsoft Store app is blocked by policy - - - The feature is currently under development. It can be enabled using `winget settings`. - {Locked="winget settings"} - - - Failed to install the Microsoft Store app - - - Failed to perform auto complete - - - Failed to initialize YAML parser - - - Encountered an invalid YAML key - - - Encountered a duplicate YAML key - - - Invalid YAML operation - - - Failed to build YAML doc - - - Invalid YAML emitter state - - - Invalid YAML data - - - LibYAML error - - - Manifest validation succeeded with warning - - - Manifest validation failed - - - Manifest is invalid - - - No applicable update found - - - An upgrade is available but uses a different install technology than the current installation - - - winget upgrade --all completed with failures - - - Installer failed security check - - - Download size does not match expected content length - - - Uninstall command not found - - - Running uninstall command failed - - - ICU break iterator error - - - ICU casemap error - - - ICU regex error - - - Failed to install one or more imported packages - - - Could not find one or more requested packages - - - Json file is invalid - - - The source location is not remote - - - The configured rest source is not supported - - - Invalid data returned by rest source - - - Operation is blocked by Group Policy - - - Rest API internal error - - - Invalid rest source url - - - Unsupported MIME type returned by rest API - - - Invalid rest source contract version - - - The source data is corrupted or tampered - - - Error reading from the stream - - - Package agreements were not agreed to - - - Error reading input in prompt - - - The search request is not supported by one or more sources - - - The rest API endpoint is not found. - - - Failed to open the source. - - - Source agreements were not agreed to - - - Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again. - - - Missing resource file - - - Running MSI install failed - - - Arguments for msiexec are invalid - - - Failed to open one or more sources - - - Failed to validate dependencies - - - One or more package is missing - - - Invalid table column - - - The upgrade version is not newer than the installed version - - - Upgrade version is unknown and override is not specified - - - ICU conversion error - - - Failed to install portable package - - - Volume does not support reparse points - - - Portable package from a different source already exists. - - - Unable to create symlink. Path points to a directory. - - - The installer cannot be run from an administrator context. - - - Failed to uninstall portable package - - - Failed to validate DisplayVersion values against index. - - - One or more arguments are not supported. - - - Embedded null characters are disallowed for SQLite - - - Failed to find the nested installer in the archive. - - - Failed to extract archive. - - - Invalid relative file path to nested installer provided. - - - The server certificate did not match any of the expected values. - - - Install location must be provided. - - - Archive malware scan failed. - - - Found at least one version of the package installed. - - - A pin already exists for the package. - - - There is no pin for the package. - - - Unable to open the pin database. - - - One or more applications failed to install - - - One or more applications failed to uninstall - - - One or more queries did not return exactly one match - - - The package has a pin that prevents upgrade. - - - The package currently installed is the stub package - - - Application shutdown signal received - - - Failed to download package dependencies. - - - Failed to download package. Download for offline installation is prohibited. - - - A required service is busy or unavailable. Try again later. - - - The guid provided does not correspond to a valid resume state. - - - The current client version did not match the client version of the saved state. - - - The resume state data is invalid. - - - Unable to open the checkpoint database. - - - Exceeded max resume limit. - - - Invalid authentication info. - - - Authentication method not supported. - - - Authentication failed. - - - Authentication failed. Interactive authentication required. - - - Authentication failed. User cancelled. - - - Authentication failed. Authenticated account is not the desired account. - - - Application is currently running. Exit the application then try again. - - - Another installation is already in progress. Try again later. - - - One or more file is being used. Exit the application then try again. - - - This package has a dependency missing from your system. - - - There's no more space on your PC. Make space, then try again. - - - There's not enough memory available to install. Close other applications then try again. - - - This application requires internet connectivity. Connect to a network then try again. - - - This application encountered an error during installation. Contact support. - - - Restart your PC to finish installation. - - - Installation failed. Restart your PC then try again. - - - Your PC will restart to finish installation. - - - You cancelled the installation. - - - Another version of this application is already installed. - - - A higher version of this application is already installed. - - - Organization policies are preventing installation. Contact your admin. - - - Failed to install package dependencies. - - - Application is currently in use by another application. - - - Invalid parameter. - - - Package not supported by the system. - - - The installer does not support upgrading an existing package. - - - Installation failed with a custom installer error. - - - The Apps and Features Entry for the package could not be found. - - - The install location is not applicable. - - - The install location could not be found. - - - The hash of the existing file did not match. - - - File not found. - - - The file was found but the hash was not checked. - - - The file could not be accessed. - - - The configuration file is invalid. - - - The YAML syntax is invalid. - - - A configuration field has an invalid type. - - - The configuration has an unknown version. - - - An error occurred while applying the configuration. - - - The configuration contains a duplicate identifier. - - - The configuration is missing a dependency. - - - The configuration has an unsatisfied dependency. - - - An assertion for the configuration unit failed. - - - The configuration was manually skipped. - - - The user declined to continue execution. - - - The dependency graph contains a cycle which cannot be resolved. - - - The configuration has an invalid field value. - - - The configuration is missing a field. - - - Some of the configuration units failed while testing their state. - - - Configuration state was not tested. - - - The configuration unit was not installed. - - - The configuration unit could not be found. - - - Multiple matches were found for the configuration unit; specify the module to select the correct one. - - - The configuration unit failed while attempting to get the current system state. - - - The configuration unit failed while attempting to test the current system state. - - - The configuration unit failed while attempting to apply the desired state. - - - The module for the configuration unit is available in multiple locations with the same version. - - - Loading the module for the configuration unit failed. - - - The configuration unit returned an unexpected result during execution. - - - A unit contains a setting that requires the config root. - - - Operation is not supported by the configuration processor. - - - Unavailable - - - Successfully enabled Windows Features dependencies - - - Loading the module for the configuration unit failed because it requires administrator privileges to run. - - - A unit contains a setting that requires the config root. - - - Loading the module for the configuration unit failed because it requires administrator privileges to run. - - - Resumes execution of a previously saved command by passing in the unique identifier of the saved command. This is used to resume an executed command that may have been terminated due to a reboot. - - - Resumes execution of a previously saved command. - - - The unique identifier of the saved state to resume - - - Resuming the state from a different client version is not supported: {0} - {Locked= "{0}"} Message displayed to inform the user that the client version of the resume state does not match the current client version. {0} is a placeholder for the client version that created the resume state. - - - The resume state does not exist: {0} - {Locked="{0}"} Error message displayed when the user provides a guid that does not correspond to a valid saved state. {0} is a placeholder replaced by the provided guid string. - - - No data found in the resume state. - - - This command does not support resuming. - - - Allows a reboot if applicable - - - Initiating reboot to complete operation... - - - Failed to initiate a reboot. - - - Resume operation exceeds the allowable limit of {0} resume(s). To manually resume, run the command `{1}`. - {Locked="{0}", "{1}"} {0} is a placeholder that is replaced by an integer number of the number of allowed resumes. {1} is a placeholder for the command to run to perform a manual resume. - - - Ignore the limit on resuming a saved state - - - Uri scheme not supported: {0} - {Locked="{0}"} Error message displayed when the provided uri is not supported. {0} is a placeholder replaced by the provided uri. - - - Uri not well formed: {0} - {Locked="{0}"} Error message displayed when the provided uri is not well formed. {0} is a placeholder replaced by the provided uri. - - - Failed to parse {0} configuration unit settings content or settings content is empty. - {Locked="{0}"} {0} is a placeholder replaced by the input winget configure resource unit type. - - - {0} configuration unit is missing required argument: {1} - {Locked="{0},{1}"} {0} is a placeholder for the input winget configure resource unit type. {1} is placeholder for the missing arg. - - - {0} configuration unit is missing recommended argument: {1} - {Locked="{0},{1}"} {0} is a placeholder for the input winget configure resource unit type. {1} is placeholder for the missing arg. - - - WinGetSource configuration unit conflicts with a known source: {0} - {Locked="WinGetSource,{0}"} {0} is a placeholder for the input winget source in the configuration unit settings. - - - WinGetSource configuration unit asserts on a third-party source: {0} - {Locked="WinGetSource,{0}"} {0} is a placeholder for the input winget source in the configuration unit settings. - - - WinGetPackage declares both UseLatest and Version. Package Id: {0} - {Locked="WinGetPackage,UseLatest,Version,{0}"} - - - WinGetPackage configuration unit asserts on a package from third-party source. Package Id: {0}; Source: {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package depends on a third-party source not previously configured. Package Id: {0}; Source: {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package depends on a 3rd party source. It is recommended to declare the dependency in uni dependsOn section. Package Id: {0}; Source: {1} - {Locked="WinGetPackage,dependsOn,{0},{1}"} - - - WinGetPackage configuration unit package cannot be validated. Source open failed. Package Id: {0}; Source: {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package cannot be validated. Package not found. Package Id: {0} - {Locked="WinGetPackage,{0}"} - - - WinGetPackage configuration unit package cannot be validated. More than one package found. Package Id: {0} - {Locked="WinGetPackage,{0}"} - - - WinGetPackage configuration unit package cannot be validated. Package version not found. Package Id: {0}; Version {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package specified with a specific version while only one package version is available. Package Id: {0}; Version: {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package cannot be validated. Package Id: {0} - {Locked="WinGetPackage,{0}"} - - - Specify authentication window preference (silent, silentPreferred, or interactive) - {Locked="silent","silentPreferred","interactive"} This argument allows the user to select authentication window popup behavior. - - - Specify the account to be used for authentication - - - Failed to add source. This WinGet version does not support the source's authentication method. Try upgrading to latest WinGet version. - {Locked="WinGet"} - - - The {0} source requires authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with the source for access authorization. - {Locked="{0}"} - - - Repairs the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. - id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Repairs the selected package - - - The repair command for this package cannot be found. Please reach out to the package publisher for support. - - - The installer technology in use does not match the version currently installed. - - - Repair command not found. - - - The installer technology in use doesn't support repair. - - - Repair operation completed successfully. - - - Repair abandoned - - - Starting package repair... - - - Failed to repair Microsoft Store package. Error code: {0} - {Locked="{0}"} Error message displayed when a Microsoft Store application package fails to repair. {0} is a placeholder replaced by an error code. - - - Repair operation failed. - - - Repair operation is not applicable. - - - No matching package versions are available from the configured sources. - - - The current system configuration does not support the repair of this package. - - - The installer technology in use does not support repair. - - - The package installed for user scope cannot be repaired when running with administrator privileges. - - - Repair operations involving administrator privileges are not permitted on packages installed within the user scope. - - - Repair failed with exit code: {0} - {Locked="{0}"} Error message displayed when an attempt to repair an application package fails. {0} is a placeholder replaced by an error code. - - - The SQLite connection was terminated to prevent corruption. - - - Uninstall all versions - - - The version to act upon - - - Multiple versions of this package are installed. Either refine the search, pass the `--version` argument to select one, or pass the `--all-versions` flag to uninstall all of them. - {Locked="--version,--all-versions"} - - - Enable Windows Package Manager proxy command line options - Describes a Group Policy that can enable the use of the --proxy option to set a proxy - - - Enable Windows Package Manager Configuration processor path - Describes a Group Policy that can enable the use of the --processor-path option in configuration commands - - - Set a proxy to use for this execution - - - Disable the use of proxy for this execution - - - Disables the progress bar and spinner - - - Cannot reset {0}. This setting is controlled by policy. For more information contact your system administrator. - {Locked="{0}"} The value will be replaced with the feature name - - - Reset admin setting '{0}'. - {Locked="{0}"} Message displayed after the user resets an admin setting to its default value. Reset is used as verb in past tense. {0} is a placeholder replaced by the setting name. - - - Cannot set {0}. This setting is controlled by policy. For more information contact your system administrator. - {Locked="{0}"} The value will be replaced with the feature name - - - Set admin setting '{0}' to '{1}'. - {Locked="{0}"} Message displayed after the user sets the value of an admin setting. Set is used as a verb in past tense. {0} is a placeholder replaced by the setting name. {1} is a placeholder replaced - - - Name of the setting to modify - - - Value to set for the setting. - - - Resets an admin setting to its default value. - - - Resets an admin setting to its default value. - - - Sets the value of an admin setting. - - - Sets the value of an admin setting. - - - Excludes a source from discovery unless specified - - - Excludes a source from discovery (true or false) - - - Explicit - Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. - - - Trust level of the source (none or trusted) - - - Trust Level - - - Failed to get Microsoft Store package catalog. - - - No applicable Microsoft Store package found from Microsoft Store package catalog. - - - Failed to get Microsoft Store package download information. - - - No applicable Microsoft Store package found for download. - - - Failed to retrieve Microsoft Store package license. - - - No applicable Microsoft Store package found. - - - Successfully verified Microsoft Store package hash - - - Microsoft Store package hash mismatch - - - Microsoft Store package downloaded: {0} - {Locked="{0}"} Full path of the downloaded package. - - - Microsoft Store package download failed: {0} - {Locked="{0}"} Package name. - - - Microsoft Store package download completed - - - Downloading main packages from Microsoft Store... - - - Downloading dependency packages from Microsoft Store... - - - Retrieving Microsoft Store package download information - - - Failed to retrieve Microsoft Store package download information - - - Retrieving Microsoft Store package license - - - Microsoft Store package license saved: {0} - {Locked="{0}"} License file full path. - - - Failed to retrieve Microsoft Store package license - - - Microsoft Store package download does not support --rename argument. Microsoft Store package will use names provided by Microsoft Store catalog. - {Locked="--rename"} - - - Microsoft Store package download requires Microsoft Entra Id authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with Microsoft services for access authorization. For Microsoft Store package licensing, the Microsoft Entra Id account needs to be a member of Global Administrator, User Administrator, or License Administrator. - {Locked="Global Administrator,User Administrator,License Administrator"} - - - Skips retrieving Microsoft Store package offline license - - - Select the target platform - - - Adding configuration file: {0} - {Locked="{0}"} - - - Successfully exported - - - Getting configuration settings... - - - At least --packageId and/or --module with --resource must be provided. Or use --all to export all package configurations. - {Locked="--packageId,--module, --resource, --all"} - - - Arguments --packageId, --module and --resource cannot be used with --all. - {Locked="--packageId,--module, --resource, --all"} - - - Exports configuration resources to a configuration file. When used with --all, exports all package configurations. When used with --packageId, exports a WinGetPackage resource of the given package id. When used with --module and --resource, gets the settings of the resource and exports it to the configuration file. If the output configuration file already exists, appends the exported configuration resources. - {Locked="WinGetPackage,--packageId,--module, --resource"} - - - Exports configuration resources to a configuration file. - - - The module of the resource to export. - - - The package identifier to export. - - - The configuration resource to export. - - - Exports all package configurations. - - - The configuration unit failed getting its properties. - - - Failed exporting configuration. - - - Configure {0} - {Locked="{0}"} - - - Install {0} - {Locked="{0}"} - - - Some of the data present in the configuration file was truncated for this output; inspect the file contents for the complete content. - - - <this value has been truncated; inspect the file contents for the complete text> - Keep some form of separator like the "<>" around the text so that it stands out from the preceding text. - - - Shows the high level details for configurations that have been applied to the system. This data can then be used with `configure` commands to get more details. - {Locked="`configure`"} - - - Shows configuration history - - - There are no configurations in the history. - - - Select items from history - - - No single configuration could be found that matched the provided data. Provide either the full name or part of the identifier that unambiguously matches the desired configuration. - - - Remove the item from history - - - First Applied - Column header for date values indicating when a configuration was first applied to the system. - - - Identifier - - - Name - - - Origin - - - Path - - - The specified configuration could not be found. - - - Completed - The state of processing an item. - - - In progress - The state of processing an item. - - - Pending - The state of processing an item. - - - Unknown - The state of processing an item. - - - Monitor configuration status. - As in "to monitor the status of a configuration being applied". - - - Completed - The state of processing an item. - - - In progress - The state of processing an item. - - - Pending - The state of processing an item. - - - Skipped - The state of processing an item. - - - Unknown - The state of processing an item. - - - Apply Started - When the configuration application started. - - - Apply Ended - When the configuration application ended. - - - Result - - - Details - - - State - The state of processing an item. - - - Unit - - - The package is not compatible with the current Windows version or platform. - - - Suppress showing initial configuration details when possible - - - Multiple Microsoft Store packages may be downloaded targeting different platforms and architectures. --platform, --architecture can be used to filter the packages. - {Locked="--platform--architecture"} - - - Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account is not a member of Global Administrator, User Administrator, or License Administrator. Use --skip-license to skip retrieving Microsoft Store package license. - {Locked="Global Administrator,User Administrator,License Administrator,--skip-license"} - - - The Microsoft Store package does not support download. - - - The Microsoft Store package does not support download. - - - Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have required privilege. - - - Parameter cannot be passed across integrity boundary. - - - Downloaded zero byte installer; ensure that your network connection is working properly. - - - Downloaded zero byte installer; ensure that your network connection is working properly. - - - Manage fonts - - - Manage fonts with sub-commands. Fonts can be installed, upgraded, or uninstalled similar to packages. - - - Family - Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - - - Faces - Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - - - Filter results by family name - 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - - - Face - A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. - - - Paths - - - No installed font found matching input criteria. - - - List installed fonts - - - List all installed fonts, all font files, or full details of a specific font. - - - Version - - - Configuration Modules - PowerShell Modules that are used for the Configuration feature - - - The package installer requires authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with the installer download url. - - - Failed to download installer. This WinGet version does not support the installer download authentication method. Try upgrading to latest WinGet version. - {Locked="WinGet"} - - - Failed to download installer. Authentication failed. - - - Specify the path to the configuration processor - - - Custom processor path: - Displayed as a warning header when a custom --processor-path argument is provided. - - - Hash: {0} - {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. - - - Type: App execution alias - Indicates the processor path is an app execution alias (MSIX package link). - - - Path: {0} - {Locked="{0}"} {0} is the file system path provided by the user. - - - Signed By: {0} - {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. - - - Signed By: (unsigned) - Indicates the custom processor executable does not have an Authenticode signature. - - - The integrity check failed for the custom DSC processor path. The path may have been modified. - Error when the server-side hash of the processor path does not match the client-computed hash. - - - DSC v3 resource commands - DSC stands for "Desired State Configuration". It should already have a locked translation. - - - The sub-commands here implement Desired State Configuration (DSC) v3 resources for configuring WinGet and packages. - {Locked="WinGet"} - - - Get the resource state - - - Set the resource state - - - Describe required state changes - - - Test the resource state - - - Delete the resource state - - - Get all state instances - - - Validate group contents - - - Resolve external state - - - Run the adapter - - - Get the resource schema - - - Get the resource manifest - - - Manage package state - - - Manage packages through WinGet. - {Locked="WinGet"} - - - Indicates whether an instance should or does exist. - - - Indicates whether an instance is in the desired state. - - - The identifier of the package. - - - The source of the package. - - - The version of the package. - - - The method for matching the identifier with a package. - - - Indicate that the latest available version of the package should be installed. - - - The install mode to use if needed. - - - The target scope of the package. - - - Indicates whether to accept agreements for sources and packages. - - - Manage user settings file - - - Manage the user settings of WinGet. - {Locked="WinGet"} - - - The settings json file content. - - - The action used to apply the settings. - - - Value is logged for correlation - - - Manage source configuration - - - Manage the sources of WinGet. - {Locked="WinGet"} - - - The name to use for the source. - - - The argument for the source. - - - The type of the source. - - - The trust level of the source. - - - Whether the source is included when calls don't specify a source. - - - Applying configuration unit... - - - Exporting configuration unit... - - - Getting configuration unit processors... - - - Ensure required module for export [{0}] - {Locked="{0}"} - - - Failed to test or acquire required module. Related settings will not be exported. - - - Export [{0}] - {Locked="{0}"} - - - Failed to export the resource. - - - Failed to get unit processors. Individual package settings will not be exported. - - - Directory where the results are to be written - - - Desired State Configuration package not found on the system. Installing the package... - - - Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through --processor-path argument. - {Locked="dsc.exe","--processor-path"} - - - An object containing the administrator settings and their values. - - - Manage administrator settings - - - Manage the administrator settings of WinGet. - {Locked="WinGet"} - - - Resets all admin settings - - - All admin settings reset. - - - MCP information - MCP stands for Model Context Protocol and should probably remain as-is - - - MCP (Model Context Protocol) information for the Windows Package Manager. - - - To manually configure the Windows Package Manager MCP server with your MCP client, use the following JSON fragment in the `servers` object: - {Locked="`servers`"} -MCP stands for Model Context Protocol and should probably remain as-is. -An unlocalized JSON fragment will follow on another line. - - - Enable Windows Package Manager MCP Server - - - Target OS version - - - Failed installing one or more fonts. - - - Font file is not supported and cannot be installed. - - - One or more fonts in the font package is not supported and cannot be installed. - - - Font install failed; cleaning up. - - - Font already installed. - - - Package Id - - - WinGet Supported - - - Title - - - Font file not found. - - - Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. - - - Font validation failed. - - - Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. - - - Show font file detailed information. - - - Validation of the font package failed. - - - Rollback attempt for failed font install was unsuccessful. A restart may be required to successfully uninstall the font. - - - Font package uninstall failed. This is often due to the fonts being in use by the system or an application. Uninstall may be successful after restarting your computer. - - - OK - "OK" means the font is in a good healthy state - - - Corrupt - "Corrupt" refers to an install that is in a corrupted or bad state, and needs repair. - - - Status - - - Unknown - - - Font package is already installed. - - - Show detailed information about packages - Providing this argument causes the CLI to output additional details about installed application packages. - - - Channel: - Precedes a string value that names the delivery channel for the software package (ex. stable, beta). - - - Local Identifier: - Precedes a value that is the unique identifier for the installed package on the local system. - - - Package Family Name: - Precedes a value that is the APPX/MSIX package family name of the installed package. - - - Product Code: - Precedes a value that is the Add/Remove Programs identifier in the registry. This is also the Product Code value as defined in MSI installers. - - - Upgrade Code: - Precedes a value that is the MSI Upgrade Code for the installed package. - - - Installed Scope: - Precedes a value that is the scope of the installation of the package (ex. user, machine). - - - Installed Architecture: - Precedes a value that is the installed architecture of the package (ex. x86, x64, ARM64). - - - Installed Locale: - Precedes a value that is the locale of the installed package. - - - Installed Location: - Precedes a value that is the directory path to the installed package. - - - Origin Source: - Precedes a value that names the package source where the installed package originated from. - - - Available Upgrades: - Precedes a list of package upgrades available for the installed package. - - - Installer Category: - Precedes a value that indicates the category of the installer for the installed package (ex. exe, msi, msix). - - - Old Value - Column title for listing edit changes. - - - New Value - Column title for listing the new value. - - - Priority; higher numbers first - This argument sets the numerical priority of the source. Higher values will be first in priority. - - - Priority - Label for the priority of the source with respect to other sources. Higher values come first in the order. - - - The priority of the source. Higher values are sorted first in the order. - - - Results have been filtered to the highest matched source priority. - A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. - - - The DSC processor hash provided does not match hash of the target file. - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Adjoined alias is not a flag: '{0}' + {Locked="{0}"} Error message displayed when the user provides an adjoined alias that is not a flag argument. {0} is a placeholder replaced by the user input argument (e.g. '-ab'). + + + Adjoined flag alias not found: '{0}' + {Locked="{0}"} Error message displayed when the user provides an adjoined flag alias argument that was not found. {0} is a placeholder replaced by the user input argument (e.g. '-ab'). + + + The following arguments are available: + Message displayed to inform the user about the available command line arguments. + + + The following command aliases are available: + Message displayed to inform the user about the available command line alias arguments. + + + The following commands are available: + Title displayed to inform the user about the available commands. + + + Available + As in "a new version is available to upgrade to". + + + The following options are available: + Message displayed to inform the user about the available options. + + + The following sub-commands are available: + Message displayed to inform the user about the available nested commands that run in context of the selected command. + + + {0} upgrades available. + {Locked="{0}"} Message displayed to inform the user about available package upgrades. {0} is a placeholder replaced by the number of package upgrades. + + + Use the specified channel; default is general audience + + + command + Label displayed for a command to give the software. + + + Filter results by command + Description message displayed to inform the user about filtering the search results by a package command. + + + The full command line for completion + + + This command requires administrator privileges to execute. + + + This command can be used to request context sensitive command line completion. The command line, cursor position, and word to be completed are passed in. The output is a set of potential values based on the inputs, with one possible value per line. + + + Enables context sensitive command line completion + + + Show no more than specified number of results (between 1 and 1000) + + + Done + Label displayed when an operation completes or is done executing. + + + Find package using exact match + Description message displayed to inform the user about finding an application package using an exact matching criteria. + + + Experimental argument for demonstration purposes + + + This command is an example on how to implement an experimental feature. To turn on go to 'winget settings' and enable experimentalCmd or experimentalArg features. + {Locked="winget settings"} + + + Experimental feature example + + + Found a positional argument when none was expected: '{0}' + {Locked="{0}"} Error message displayed when the user provides an extra positional argument when none was expected. {0} is a placeholder replaced by the user's extra argument input. + + + This feature is a work in progress, and may be changed dramatically or removed altogether in the future. To enable it, edit your settings ('winget settings') to include the experimental feature: '{0}' + {Locked="winget settings","{0}"}. Error message displayed when the user uses an experimental feature that is disabled. {0} is a placeholder replaced by the experimental feature name. + + + Shows the status of experimental features. Experimental features can be turned on via 'winget settings'. + {Locked="winget settings"} + + + Shows the status of experimental features + + + Disabled + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. + + + Enabled + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. + + + Feature + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. + + + Link + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. + + + The following experimental features are in progress. +They can be configured through the settings file 'winget settings'. + {Locked="winget settings"} + + + Property + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. + + + Status + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. + + + File to be hashed + + + Flag argument cannot contain adjoined value: '{0}' + {Locked="{0}"} Error message displayed when the user provides a flag argument containing an unexpected adjoined value. {0} is a placeholder replaced by the user input. + + + Computes the hash of a local file, appropriate for entry into a manifest. It can also compute the hash of the signature file of an MSIX package to enable streaming installations. + + + Helper to hash installer files + + + Shows help about the selected command + + + For more details on a specific command, pass it the help argument. + + + More help can be found at: {0} + {Locked="{0}"} Message displayed to inform the user about a link where they can learn more about the subject context. {0} is a placeholder replaced by a website address. + + + Filter results by id + + + Suppresses warning outputs + + + This application is licensed to you by its owner. + + + Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. + + + This package is provided through Microsoft Store. WinGet may need to acquire the package from Microsoft Store on behalf of the current user. + {Locked="WinGet"} + + + Installs the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, install command will check package installed status and try to perform an upgrade if applicable. Override with --force to perform a direct install. + {Locked="--force"}; id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Installs the given package + + + Installer hash does not match; this cannot be overridden when running as admin + + + Installer hash does not match; proceeding due to --ignore-security-hash + {Locked="--ignore-security-hash"} + + + Installer hash does not match; to override this check use --ignore-security-hash + {Locked="--ignore-security-hash"} + + + Successfully verified installer hash + + + Successfully installed + + + Starting package install... + + + Ignore the installer hash check failure + + + Ignore the malware scan performed as part of installing an archive type package from local manifest + + + Request interactive installation; user input may be needed + + + Argument alias was not recognized for the current command: '{0}' + {Locked="{0}"} Error message displayed when the user provides a command line argument alias that was not recognized for a selected command. {0} is a placeholder replaced by the user's argument alias input (e.g. '-a'). + + + Invalid argument specifier: '{0}' + {Locked="{0}"} Error message displayed when the user provides an invalid argument specifier. {0} is a placeholder replaced by an argument specifier (e.g. '-'). + + + Argument name was not recognized for the current command: '{0}' + {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). + + + WinGet Directories + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs + + + Locale to use (BCP47 format) + {Locked="BCP47"} + + + License Agreement + + + Links + Links to different webpages + + + The list command displays the packages installed on the system, as well as whether an upgrade is available. Additional options can be provided to filter the output, much like the search command. + {Locked="list","search"} + + + Display installed packages + + + Location to install to (if supported) + + + Log location (if supported) + + + © 2026 Microsoft. All rights reserved. + + + Homepage + The primary webpage for the software + + + The path to the manifest of the package + + + Manifest validation failed. + + + Manifest validation succeeded. + + + Manifest validation succeeded with warnings. + + + Argument value required, but none found: '{0}' + {Locked="{0}"} Error message displayed when the user does not provide a required command line argument value. {0} is a placeholder replaced by the argument name. + + + Filter results by moniker + + + Input file will be treated as msix; signature hash will be provided if signed + + + Failed to calculate MSIX signature hash. + + + Failed to install or upgrade Microsoft Store package because the specific app is blocked by policy + + + Failed to install or upgrade Microsoft Store package. Error code: {0} + {Locked="{0}"} Error message displayed when a Microsoft Store application package fails to install or upgrade. {0} is a placeholder replaced by an error code. + + + Failed to install or upgrade Microsoft Store package because Microsoft Store client is blocked by policy + + + Verifying/Requesting package acquisition... + + + Multiple installed packages found matching input criteria. Please refine the input. + + + Multiple packages found matching input criteria. Please refine the input. + + + Filter results by name + + + No applicable installer found; see logs for more details. + + + There are currently no experimental features available. + + + No installed package found matching input criteria. + + + No package found matching input criteria. + + + Disables VirtualTerminal display + {Locked="VirtualTerminal"} + + + Open the default logs location + + + options + Options to change how a command works + + + Override arguments to be passed on to the installer + + + Package: {0} + {Locked="{0}"} Label displayed for a software package. {0} is a placeholder replaced by the software package name. + + + Oops, we forgot to do this... + + + The position of the cursor within the command line + + + Privacy Statement + + + The query used to search for a package + + + Progress display a rainbow of colors + + + Required argument not provided: '{0}' + {Locked="{0}"} Error message displayed when the user does not provide a required command line argument. {0} is a placeholder replaced by an argument name. + + + Progress display as the default color + + + Searches for packages from configured sources. + + + Find and show basic info of packages + + + Id + Abbreviation of Identifier. + + + Match + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. + + + Name + + + Source + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. + + + additional entries truncated due to result limit + + + Version + + + The following failures were found validating the settings: + + + Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings This command can also be used to set administrator settings by providing the --enable or --disable arguments + {Locked="--enable"} {Locked="--disable"} + + + Open settings or set administrator settings + + + Unexpected error while loading settings. Please verify your settings by running the 'settings' command. + {Locked="'settings'"} + + + Channel + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. + + + Shows information on a specific package. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. + id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Shows information about a package + + + Version + + + Request silent installation + + + Only the single character alias can occur after a single -: '{0}' + {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + + Sort results by a property (can be repeated) + Description for the --sort argument used to sort output by field name. + + + Sort results in ascending order + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + Sort results in descending order + Description for the --descending (--desc) flag that sets descending sort direction for output. + + + A source with the given name already exists and refers to a different location: + + + A source with a different name already refers to this location: + + + A source with the given name already exists and refers to the same location: + + + Adding source: + + + Add a new source. A source provides the data for you to discover and install packages. Only add a new source if you trust it as a secure location. + + + Add a new source + + + Argument given to the source + + + Find package using the specified source + + + Manage sources with the sub-commands. A source provides the data for you to discover and install packages. Only add a new source if you trust it as a secure location. + + + Manage sources of packages + + + Edit properties of an existing source. A source provides the data for you to discover and install packages. + + + Edit properties of a source + + + Editing source: {0} + {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being edited. {0} is a placeholder replaced by the repository source name. + + + The source named '{0}' is already in the desired state. + {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being edited. {0} is a placeholder replaced by the repository source name. + + + Argument + Value given to source. + + + List all current sources, or full details of a specific source. + + + List current sources + + + Data + Data stored by the source. + + + Field + The name of a piece of information about a source. + + + Name + The name of the source. + + + Identifier + The source's unique identifier. + + + Did not find a source named: {0} + Error message displayed when the user provides a repository source name that was not found. {0} is a placeholder replaced by the user input. + + + There are no sources configured. + + + Type + The kind of source. + + + Updated + The last time the source was updated. + + + never + The source has never been updated. + + + Value + The value of information about a source. + + + Name of the source + + + Failed when opening source(s); try the 'source reset' command if the problem persists. + {Locked="source reset"} + + + Failed to open the predefined source; please report to WinGet maintainers. + {Locked="WinGet"} + + + Removing all sources... + + + Remove a specific source. + + + Remove current sources + + + Removing source: {0}... + {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being removed. {0} is a placeholder replaced by the repository source name. + + + Resetting all sources... + + + This command drops existing sources, potentially leaving any local data behind. Without any argument, it will drop all sources and add the defaults. If a named source is provided, only that source will be dropped. + + + Reset sources + + + Forces the reset of the sources + + + The following sources will be reset if the --force option is given: + {Locked="--force"} + + + Resetting source: {0}... + {Locked="{0}"} Message displayed to inform the user about a repository source that is currently being reset. {0} is a placeholder replaced by the repository source name. + + + Type of the source + + + Updating all sources... + + + Update all sources, or only a specific source. + + + Update current sources + + + Updating source: {0}... + {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being updated. {0} is a placeholder replaced by the repository source name. + + + Filter results by tag + + + Thank you for using WinGet + {Locked="WinGet"} + + + Third Party Notices + + + The winget command line utility enables installing applications and other packages from the command line. + + + Display general info of the tool + + + Display the version of the tool + + + Argument provided more times than allowed: '{0}' + {Locked="{0}"} Error message displayed when the user provides a command line argument more times than it is allowed. {0} is a placeholder replaced by the user's argument name input. + + + More than one execution behavior argument provided: '{0}' + {Locked="{0}"} Error message displayed when the user provides more than one execution behavior argument when installing an application package. {0} is a placeholder replaced by the user specified execution behaviors (e.g. 'silent|interactive'). + + + An unexpected error occurred while executing the command: + + + Uninstall the previous version of the package during upgrade + + + Unrecognized command: '{0}' + {Locked="{0}"} Error message displayed when the user provides an unrecognized command. {0} is a placeholder replaced by the user input. + + + Upgrade all installed packages to latest if available + + + No applicable upgrade found. + + + A newer package version is available in a configured source, but it does not apply to your system or requirements. + + + No available upgrade found. + + + No newer package versions are available from the configured sources. + + + Upgrades the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. When no arguments are given, shows the packages with upgrades available + id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Shows and performs available upgrades + + + usage: {0} {1} + {Locked="{0} {1}"} Message displayed to provide the user with instructions on how to use a command. {0} is a placeholder replaced by the program name (e.g. 'winget'). {1} is a placeholder replaced by the pattern for using the selected command. + + + Validates a manifest using a strict set of guidelines. This is intended to enable you to check your manifest before submitting to a repo. + + + Validates a manifest file + + + The path to the manifest to be validated + + + Enables verbose logging for winget + + + Please verify that the input file is a valid, signed MSIX. + + + Use the specified version; default is the latest version + + + Show available versions of the package + + + The value provided before completion is requested + + + No version found matching: {0} + {Locked="{0}"} Error message displayed when the user attempts to upgrade an application package to a version that was not found. {0} is a placeholder replaced by the user's provided upgrade package version. + + + No sources match the given value: {0} + {Locked="{0}"} Error message displayed when the user attempts to install or upgrade an application package from a repository source that was not found. {0} is a placeholder replaced by the user's repository source name input. + + + The configured sources are: + + + No sources defined; add one with 'source add' or reset to defaults with 'source reset' + {Locked="source add","source reset"} + + + Found + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. + + + Path is a directory: {0} + {Locked="{0}"} Error message displayed when the user provides a system path that is a directory. {0} is a placeholder replaced by the provided directory path. + + + File does not exist: {0} + {Locked="{0}"} Error message displayed when the user provides a system file that does not exist. {0} is a placeholder replaced by the provided file path. + + + Both local manifest and search query arguments are provided + + + Logs + Label displayed for diagnostic files containing information about the application use. + + + The installer is blocked by policy + + + The installer failed security check + + + An anti-virus product reports an infection in the installer + + + Failed in attempting to update the source: {0} + {Locked="{0}"} Error message displayed when an attempt to update the repository source fails. {0} is a placeholder replaced by the repository source name. + + + Uninstalls the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. + id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Uninstalls the given package + + + Starting package uninstall... + + + Successfully uninstalled + + + WinGet cannot locate the uninstall command for this package. Please reach out to the package publisher for support. + {Locked="WinGet"} + + + Uninstallation abandoned + + + Uninstall failed with exit code: {0} + {Locked="{0}"} Error message displayed when an attempt to uninstall an application package fails. {0} is a placeholder replaced by an error code. + + + Exports a list of the installed packages + + + Installs all the packages listed in a file. + + + Installs all the packages in a file + + + File where the result is to be written + + + File describing the packages to install + + + Export packages from the specified source + + + Writes a list of the installed packages to a file. The packages can then be installed with the import command. + {Locked="import"} + + + One or more imported packages failed to install + + + Source required for import is not installed: {0} + {Locked="{0}"} Error message displayed when the user attempts to import application package(s) from a repository source that is not installed. {0} is a placeholder replaced by the repository source name. + + + Installed package is not available from any source: {0} + {Locked="{0}"} Warning message displayed when the user attempts to export an installed application package that is not available from any repository source. {0} is a placeholder replaced by the installed package name. + + + Installed version of package is not available from any source: {0} {1} {2} + {Locked="{0} {1} {2}"} Warning message displayed when the user attempts to export an installed application package with a version that is not available from any repository source. {0} is a placeholder replaced by the installed package identifier. {1} is a placeholder replaced by the installed package version. {2} is a placeholder replaced by the installed package channel. + + + No packages found in import file + + + JSON file is not valid + + + Package is already installed: {0} + {Locked="{0}"} Message displayed to inform the user that an application package is already installed. {0} is a placeholder replaced by the package identifier or search query. + + + Ignore unavailable packages + + + Include package versions in export file + + + Ignore package versions from import file + + + Path does not exist: {0} + {Locked="{0}"} Error message displayed when the user provides a system path argument value that does not exist. {0} is a placeholder replaced by the user's provided path. + + + The JSON file does not specify a recognized schema. + + + Select install scope (user or machine) + {Locked="user","machine"} This argument allows the user to select between installing for just the user or for the entire machine. + + + The value provided for the `{0}` argument is invalid; valid values are: {1} + {Locked="{0}","{1}"} Error message displayed when the user provides an invalid command line argument value. {0} is a placeholder replaced by the argument name. {1} is a placeholder replaced by a list of valid options. + + + This operation is disabled by Group Policy: {0} + {Locked="{0}"} Error message displayed when the user performs a command operation that is disabled by a group policy. {0} is a placeholder replaced by a group policy description. + + + Enable Additional Windows App Installer Sources + + + Enable Windows App Installer Allowed Sources + + + Enable Windows App Installer Default Source + + + Enable Windows App Installer Experimental Features + + + Enable Windows App Installer Microsoft Store Source + + + Enable Windows App Installer Font Source + + + Enable Windows Package Manager Settings + + + Enable Windows Package Manager + + + Enable Windows Package Manager command line interfaces + + + Set Windows Package Manager Source Auto Update Interval In Minutes + + + Group Policy + Header for a table listing active Group Policies + + + Enable Windows App Installer Local Manifest Files + + + Enable Windows App Installer Microsoft Store Source Pinned Certificate Bypass + + + Enable Windows App Installer Local Archive Malware Scan Override + + + Field: {0} + {Locked="{0}"} Warning message displayed when a user setting field has invalid syntax or semantics. {0} is a placeholder replaced by the setting field path. + + + Invalid field format. + + + Invalid field value. + + + Invalid setting from Group Policy. + + + Loaded settings from backup file. + + + Error parsing file: + + + Value: {0} + {Locked="{0}"} Warning message displayed when a user setting value has invalid syntax or semantics. {0} is a placeholder replaced by the setting data value. + + + The following experimental features are in progress. +Configuration is disabled due to Group Policy. + + + Installer hash does not match. + + + Enable Windows App Installer Hash Override + + + Export current sources as JSON for Group Policy. + + + Export current sources + + + Additional source + An additional source required by policy. + + + Allowed source + A source that the user is allowed to add. + + + The value provided for the `{0}` argument is invalid + {Locked="{0}"} Error message displayed when the user provides an invalid command line argument value. {0} is a placeholder replaced by the argument name. + + + Cancelled + + + External + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". + + + The packages found in this import have the following dependencies: + Import command sentence showed before reporting dependencies + + + This package requires the following dependencies: + Message shown before reporting dependencies + + + Installing dependencies: + + + Dependency source not found + + + Package search yield more than one result: {0} + {Locked="{0}"} Error message displayed when application packages search yield more than one result. {0} is a placeholder replaced by the dependency package identifier. + + + Latest version not found for package: {0} + {Locked="{0}"} Error message displayed when no suitable version found for the specific application package. {0} is a placeholder replaced by the package identifier. + + + No installers found: {0} + {Locked="{0}"} Error message displayed when no installer found for a manifest. {0} is a placeholder replaced by the manifest identifier. + + + Minimum required version not available for package: {0} + {Locked="{0}"} Error message displayed when the minimum required version is not available for an application package. {0} is a placeholder replaced by the package identifier. + + + No matches + When package search yields no matches + + + Has loop + Dependency graph has loop + + + No suitable installer found for manifest: {0} version {1} + {Locked="{0}","{1}"} Error message displayed when an attempt to get a preferred installer for a manifest fails. {0} is a placeholder replaced by the manifest identifier. {1} is a placeholder replaced by the manifest version. + + + Error processing package dependencies. Do you wish to continue installation? + Prompt message shown when dependencies processing yields errors. + + + Error processing package dependencies. Exiting... + + + Packages + + + This package had dependencies that may not be needed anymore: + Uninstall command sentence showed before reporting dependencies + + + Manifest has the following dependencies that were not validated; ensure that they are valid: + Validate command sentence showed before reporting dependencies + + + Windows Features + + + Windows Libraries + + + Find package dependencies using the specified source + For getting package type dependencies when installing from a local manifest + + + Windows Store Terms + + + Accept all license agreements for packages + + + Exported package requires license agreement to install: {0} + {Locked="{0}"} Warning message displayed when an exported application package requires license agreement to install. {0} is a placeholder replaced by the package name. + + + The publisher requires that you view the above information and accept the agreements before installing. +Do you agree to the terms? + + + Package agreements were not agreed to. Operation cancelled. + + + Agreements: + + + Author: + + + Description: + + + Installer: + + + Installer Locale: + + + Store Product Id: + + + Installer SHA256: + + + Installer Type: + + + Installer Url: + + + License: + + + License Url: + + + Moniker: + + + Homepage: + + + Publisher: + + + Tags: + + + Version: + + + Dependencies: + + + Windows Features: + + + Windows Libraries: + + + Package Dependencies: + + + External Dependencies: + + + No + + + Yes + + + Failed to open the added source. + + + Accept all source agreements during source operations + + + The `{0}` source requires that you view the following agreements before using. + {Locked="{0}"} Message displayed to inform the user that a repository source requires viewing agreements before using. {0} is a placeholder replaced by the repository source name. + + + Do you agree to all the source agreements terms? + + + One or more of the source agreements were not agreed to. Operation cancelled. Please accept the source agreements or remove the corresponding sources. + + + The source requires the current machine's 2-letter geographic region to be sent to the backend service to function properly (ex. "US"). + + + Successfully installed. Restart the application to complete the upgrade. + + + Optional Windows-Package-Manager REST source HTTP header + + + Ignoring the optional header as it is not applicable for this source. + + + The optional header is not applicable without specifying a source: '{0}' + {Locked="{0}"} Error message displayed when the user performs an operation (e.g install) and provides the HTTP 'header' argument without specifying the repository source. {0} is a placeholder replaced by the header argument name. + + + Release Date: + + + Offline Distribution Supported: + + + Publisher Url: + + + Purchase Url: + + + Publisher Support Url: + + + Privacy Url: + + + Copyright: + + + Copyright Url: + + + Release Notes: + + + Release Notes Url: + + + Failed when searching source; results will not be included: {0} + {Locked="{0}"} Warning message displayed when searching a repository source fails. {0} is a placeholder replaced by the repository source name. + + + Failed when searching source: {0} + {Locked="{0}"} Error message displayed when searching a repository source fails. {0} is a placeholder replaced by the repository source name. + + + This feature needs to be enabled by administrators. To enable it, run 'winget settings --enable {0}' as administrator. + {Locked="winget settings --enable", "{0}"}. Error message displayed when the user uses a feature that needs to be enabled by administrators. {0} is a placeholder replaced by the admin setting. + + + Enables the specific administrator setting + + + Disables the specific administrator setting + + + Enabled admin setting '{0}'. + {Locked="{0}"} Message displayed when the user enables an admin setting. {0} is a placeholder replaced by the setting name. + + + Disabled admin setting '{0}'. + {Locked="{0}"} Message displayed when the user disables an admin setting. {0} is a placeholder replaced by the setting name. + + + Admin Setting + Header for a table displaying admin settings. + + + Application is currently running. Exit the application then try again. + + + Files modified by the installer are currently in use by a different application. Exit the applications then try again. + + + Another installation is already in progress. Try again later. + + + One or more files are being used. Exit the application then try again. + + + This package has a dependency missing from your system. + + + There's no more space on your PC. Make space, then try again. + + + There's not enough memory available to install. Close other applications then try again. + + + One of the installation parameters is invalid. The package installation log may have additional details. + + + This application requires internet connectivity. Connect to a network then try again. + + + This application encountered an error during installation. Contact support. + + + Restart your PC to finish installation. + + + Your PC will restart to finish installation. + + + Installation failed. Restart your PC then try again. + + + You cancelled the installation. + + + Another version of this application is already installed. + + + A higher version of this application is already installed. + + + Organization policies are preventing installation. Contact your admin. + + + The current system configuration does not support the installation of this package. + + + Installation failed with a custom installer error. Contact package support. + + + Installation abandoned + + + Installer failed with exit code: {0} + {Locked="{0}"} Error message displayed when the application installer fails. {0} is a placeholder replaced by an error code. + + + Installer log is available at: {0} + {Locked="{0}"} Message displayed to inform the user about the system path of a diagnostic files containing information about the installer. {0} is a placeholder replaced by the diagnostic file system path. + + + The following packages were found among the working sources. +Please specify one of them using the --source option to proceed. + {Locked="--source"} "working sources" as in "sources that are working correctly" + + + No packages were found among the working sources. + "working sources" as in "sources that are working correctly" + + + This is a stable release of the Windows Package Manager. If you would like to try experimental features, please install a pre-release build. Instructions are available on GitHub at https://github.com/microsoft/winget-cli. + {Locked="https://github.com/microsoft/winget-cli"} + + + Windows Package Manager v{0} + {Locked="{0}"} Label displaying the product name and version. {0} is a placeholder replaced by the product version. + + + Ignore package versions in import file + + + The requested number of results must be between 1 and 1000. + + + Arguments to be passed on to the installer in addition to the defaults + + + A newer version was found, but the install technology is different from the current version installed. Please uninstall the package and install the newer version. + + + {0} package(s) have upgrades blocked because newer versions use a different install technology than the current installation. Uninstall each package, then install the newer version. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + + + The install technology of the newer version specified is different from the current version installed. Please uninstall the package and install the newer version. + + + Windows Package Manager (Preview) v{0} + {Locked="{0}"} Label displaying the preview product name and pre-release version. {0} is a placeholder replaced by the product version. + + + Select the architecture + + + Upgrade packages even if their current version cannot be determined + + + List packages even if their current version cannot be determined. Can only be used with the --upgrade-available argument + {Locked="--upgrade-available"} + + + This package's version number cannot be determined. To upgrade it anyway, add the argument --include-unknown to your previous command. + {Locked="--include-unknown"} + + + {0} package(s) have version numbers that cannot be determined. Use --include-unknown to see all results. + {Locked="{0}","--include-unknown"} {0} is a placeholder that is replaced by an integer number of packages that do not have notated versions. + + + {0} package(s) are pinned and need to be explicitly upgraded. + {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages that require explicit upgrades. + + + The arguments provided can only be used with a query. + + + System Architecture: {0} + {Locked="{0}"} Label displayed for the system architecture. {0} is a placeholder replaced by the value of the system architecture (e.g. X64). + + + Retains all files and directories created by the package (portable) + + + Deletes all files and directories in the package directory (portable) + + + Press Enter to continue . . . + + + The value to rename the executable file (portable) + + + Prompts the user to press any key before exiting + + + The installer will request to run as administrator. Expect a prompt. + + + The installer cannot be run from an administrator context. + + + Path environment variable modified; restart your shell to use the new value. + + + Filters using the product code + + + A portable package with the same name but from a different source already exists; proceeding due to --force + {Locked="--force"} + + + The volume does not support reparse points + + + The specified filename is not a valid filename + + + Overwriting existing file: {0} + {Locked="{0}"} Warning message displayed to inform the user that an existing file is being overwritten. {0} is a placeholder replaced by the file system path. + + + No package selection argument was provided; see the help for details about finding a package. + + + Command line alias added: + + + Portable Links Directory (Machine) + + + Portable Links Directory (User) + + + Portable Package Root (User) + + + Portable Package Root + + + Portable Package Root (x86) + + + Portable install failed; Cleaning up... + + + Portable package has been modified; proceeding due to --force + {Locked="--force"} + + + Unable to remove Portable package as it has been modified; to override this check use --force + {Locked="--force"} + + + Files remain in install directory: {0} + {Locked="{0}"} Warning message displayed when files remain in install directory. {0} is a placeholder replaced by the directory path. + + + Purging install directory... + + + Cannot purge install directory as it was not created by WinGet + {Locked="WinGet"} + + + Related Link + + + Documentation: + + + Notes: {0} + {Locked="{0}"} Label displayed for installation notes. {0} is a placeholder replaced by installation notes. + + + Installation Notes: + + + A provided argument is not supported for this package + + + Failed to extract the contents of the archive + + + Nested installer file does not exist. Ensure the specified relative path of the nested installer matches: {0} + {Locked="{0}"} Error message displayed when nested installer file does not exist. {0} is a placeholder replaced by the nested installer file path. + + + Invalid relative file path to the nested installer; path points to a location outside of the install directory + + + No nested installers specified for this package + + + Only one nested installer can be specified for an archive installer unless it is a portable or font nested installer + + + Incompatible command line arguments provided + + + Lists only packages which have an upgrade available + + + The following packages have an upgrade available, but require explicit targeting for upgrade: + "require explicit targeting for upgrade" means that the package will not be upgraded with all others unless an extra flag is added, or the package is mentioned explicitly + + + Downloading + Label displayed while downloading an application installer. + + + The nested installer type is not supported + + + This installer is known to restart the terminal or shell + + + This package requires an install location + + + The following installers are known to restart the terminal or shell: + + + The following installers require an install location: + + + Specify the install root: + + + Do you want to proceed? + + + Agreements for + This will be followed by a package name, and then a list of license agreements + + + Install location is required by the package but it was not provided + + + Disable interactive prompts + Description for a command line argument, shown next to it in the help + + + Found an existing package already installed. Trying to upgrade the installed package... + + + Direct run the command and continue with non security related issues + Description for a command line argument, shown next to it in the help + + + Portable package from a different source already exists + + + Successfully extracted archive + + + Extracting archive... + + + Archive scan detected malware. To override this check use --ignore-local-archive-malware-scan + {Locked="--ignore-local-archive-malware-scan"} + + + Archive scan detected malware. Proceeding due to --ignore-local-archive-malware-scan + {Locked="--ignore-local-archive-malware-scan"} + + + Skips upgrade if an installed version already exists + + + A package version is already installed. Installation cancelled. + + + Cannot enable {0}. This setting is controlled by policy. For more information contact your system administrator. + {Locked="{0}"} The value will be replaced with the feature name + + + Cannot disable {0}. This setting is controlled by policy. For more information contact your system administrator. + {Locked="{0}"} The value will be replaced with the feature name + + + Add a new pin. A pin can limit the Windows Package Manager from upgrade a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. By default, a pinned package can be upgraded by mentioning it explicitly in the 'upgrade' command or by adding the '--include-pinned' flag to 'winget upgrade --all'. + {Locked{"'upgrade'"} Locked{"--include-pinned"} Locked{"winget upgrade --all"} + + + Add a new pin + + + Manage package pins with the sub-commands. A pin can limit the Windows Package Manager from upgrading a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. + + + Manage package pins + + + List all current pins, or full details of a specific pin. + + + List current pins + + + Remove a specific package pin. + + + Remove a package pin + + + Reset all existing pins. + + + Reset pins + + + Version to which to pin the package. The wildcard '*' can be used as the last version part + + + Block from upgrading until the pin is removed, preventing override arguments + + + Pin a specific installed version + + + Optional note to store with the pin + Description for the --note argument used with `winget pin add` + + + Installed + Value used in a table to indicate that a package comes from the list of packages installed in the machine + + + Export settings as JSON + + + Export settings + + + User Settings + Label displayed for the file containing the user settings. + + + Settings file couldn't load. Using default values. + + + Select installed package scope filter (user or machine) + {Locked="user","machine"} This argument allows the user to select installed packages for just the user or for the entire machine. + + + Pin added successfully + + + There is already a pin for package {0} + {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to add a pin for a package that is already pinned. + + + A pin already exists for package {0}. Overwriting due to the --force argument. + {Locked="--force"}{Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. + + + A pin already exists for package {0}. Use the --force argument to overwrite it. + {Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. + + + Resetting all current pins. + + + Use the --force argument to reset all pins. The following pins would be removed: + {Locked="--force"} + + + Pin type + + + There is no pin for package {0} + {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to delete a pin for a package that is not pinned. + + + There are no pins configured. + Shown when listing or modifying existing pins if there are none. + + + Pins reset successfully + Shown after resetting (deleting) all the pins + + + Unable to open pin database. + Error message for when we cannot open the database containing package pins. + + + An argument was provided that can only be used for single package + + + Multiple mutually exclusive arguments provided: {0} + {Locked="{0}"} Error message shown when mutually incompatible command line arguments are used. {0} is a placeholder replaced by the arguments that cannot be specified together + + + Argument {0} can only be used with {1} + {Locked="{0}","{1}"} Error message shown when having an argument needs another to be present, but that is missing. {0} and {1} are replaced by the arguments. For example "Argument --include-unknown can only be used with --upgrade" + + + Disabled + As in enabled/disabled + + + Enabled + As in enabled/disabled + + + State + Header for a table listing the state (enabled/disabled) of Group Policies and Settings + + + The query used to search for a package + + + Package not found: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns no results. {0} is a placeholder replaced by the package name or query. + + + Multiple packages found for: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns more than one result. {0} is a placeholder replaced by the package name or query. + + + Search failed for: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches fails. This message is for generic failures, we have more specific messages for when the search returns no results, or when it returns more than one result. {0} is a placeholder replaced by the package name or query. + + + {0} package(s) have a pin that needs to be removed before upgrade + {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade + + + The package cannot be upgraded using WinGet. Please use the method provided by the publisher for upgrading this package. + {Locked="WinGet"} + + + Upgrade packages even if they have a non-blocking pin + + + List packages even if they have a pin that prevents upgrade. Can only be used with the --upgrade-available argument + {Locked="--upgrade-available"} + + + Pin removed successfully + + + A newer version was found, but the package has a pin that prevents from upgrading it. + + + The package is pinned and cannot be upgraded. Use the 'winget pin' command to view and edit pins. Some pin types can be bypassed with the --include-pinned argument. + {Locked="winget pin","--include-pinned"} Error shown when we block an upgrade due to the package being pinned + + + {0} package(s) have pins that prevent upgrade. Use the 'winget pin' command to view and edit pins. Using the '--include-pinned' argument may show more results. + {Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages + + + Ensures that the system matches the desired state as described by the provided configuration. May download/execute processors in order to achieve the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before applying them. + + + Configures the system into a desired state + + + Shows details of the provided configuration. By default, will not modify the system, but some options will cause files to be downloaded and/or loaded. + + + Shows details of a configuration + + + Checks that the system matches the desired state as described by the provided configuration. May download/execute processors in order to test the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before executing them. + + + Checks the system against a desired state + + + Validates a configuration file for correctness. + + + Validates a configuration file + + + The field '{0}' in the configuration file is the wrong type. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. + + + The path to the configuration file + + + The configuration file is invalid. + + + Configuration file version {0} is not known. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the version of the configuration file. + + + Accepts the configuration warning, preventing an interactive prompt + + + Apply + Indicates that this item is used to write state + + + Assert + Indicates that this item is used to check/assert the state rather than write to it + + + Dependencies:{0} + {Locked="{0}"} Label displaying a list of dependencies. {0} is replaced with a space separated list of identifiers referencing other items. + + + Some of the configuration was not applied successfully. + + + Failed to get detailed information about the configuration. + + + Inform + Indicates that this item is used to retrieve values for future use rather than writing them + + + Local + Used to indicate that the item is present on the device. + + + Module: {0} + {Locked="{0}"} Label displaying a module name. {0} is replaced with the name of the module from the user input file. + + + Module: {0} by {1} [{2}] + {Locked="{0}","{1}","{2}"} Label displaying module information. {0} is replaced by the module name. {1} is replaced by the module author. {2} is replaced by a string indicating the source of the module. + + + Settings: + Label for the values that are used as inputs for this item when applying state + + + Configuration successfully applied. + + + Unit successfully applied. + + + Another configuration is being applied to the system. This configuration will continue as soon as is possible... + + + You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applications installed are licensed to you by their owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages or services. + Legal approved. Do not change without approval. + + + Have you reviewed the configuration and would you like to proceed applying it to the system? + PM approved. + + + The configuration is empty. + + + The feature [{0}] was not found. + {Locked="{0}"} Error message displayed when a Windows feature was not found on the system. + + + Failed to enable Windows Feature dependencies. To proceed with installation, use '--force'. + {Locked="--force"} + + + Reboot required to fully enable the Windows Feature(s); to override this check use '--force'. + "Windows Feature(s)" is the name of the options Windows features setting. + + + Failed to enable Windows Feature dependencies; proceeding due to --force + "Windows Feature(s)" is the name of the options Windows features setting. +{Locked="--force"} + + + Enabling [{0}]... + {Locked="{0}"} Message displayed to the user regarding which Windows Feature is being enabled. + + + Failed to enable [{0}] feature: {1} + {Locked="{0}","{1}"} An error when enabling a Windows Feature. {0} is a placeholder for the name of the Windows Feature. +{1} is a placeholder for the unrecognized error code. + + + Reboot required to fully enable the Windows Feature(s); proceeding due to --force + "Windows Feature(s)" is the name of the options Windows features setting. + + + Waiting for another install/uninstall to complete... + + + Pinned version + Table header for the version to which a package is pinned; meaning it should not update from that version. + + + Time Pinned + Table header shown with `winget pin list --details` for when the pin was added or last updated. + + + Note + Table header shown with `winget pin list --details` for the user-provided note stored with the pin. + + + No pin found matching the specified criteria. + Shown when a filtered `winget pin list` command finds no matching pin. + + + <See the log file for additional details> + The brackets are intended to make the value stand out from other text which it will follow. Any locale appropriate mechanism that achieves this is acceptable. + + + Retrieving configuration details + + + Initializing configuration system + + + Reading configuration file + + + The system is not in the desired state asserted by the configuration. + + + This configuration unit failed for an unknown reason: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to the configuration: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed while attempting to get the current system state. + + + The configuration unit failed while attempting to apply the desired state. + + + The configuration unit failed while attempting to test the current system state. + + + The configuration unit failed due to an internal error: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to a precondition not being valid: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to the system state: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed while attempting to run: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration contains the identifier `{0}` multiple times. + {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. + + + The dependency `{0}` was not found within the configuration. + {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. + + + This configuration unit was manually skipped. + + + The module for the configuration unit is available in multiple locations with the same version. + + + Loading the module for the configuration unit failed. + + + Multiple matches were found for the configuration unit; specify the module to select the correct one. + + + The configuration unit could not be found. + + + The configuration unit was not in the module as expected. + + + This configuration unit was not run because a dependency failed or was not run. + + + This configuration unit was not run because an assert failed or was false. + + + The configuration unit returned an unexpected result during execution. + + + This configuration unit was not run for an unknown reason: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The field '{0}' has an invalid value: {1} + {Locked="{0}","{1}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. {1} is a placeholder for the invalid value. + + + The field '{0}' is missing or empty. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the expected field name from the file. + + + See line {0}, column {1} in the file. + {Locked="{0}","{1}"} Indicates the file location of the error, {0} and {1} are placeholders for numbers of the line and column, respectively. + + + Cancelling operation + + + Install the stub package for AppInstaller + + + Install the full package for AppInstaller + + + Enable extended features. Requires store access. + + + Option '--enable' and '--disable' cannot be used with other arguments. + {Locked="--enable", "--disable"} + + + Enabling extended features. Requires store access. + + + Extended features are not enabled. Run `winget configure --enable` to enable them. + {Locked="winget configure --enable"} + + + Extended features are enabled. + + + Disable extended features. Requires store access. + + + Disabling extended features. Requires store access. + + + Extended features are disabled. + + + Skips processing package dependencies and Windows features + + + Installs only the dependencies of the package + + + Installing dependencies only. The package itself will not be installed. + + + Dependencies skipped. + + + Failed to refresh PATH variable for process. Subsequent installs that depend on changes to the PATH variable may fail. + {Locked="PATH"} + + + Some of the configuration units failed while testing their state. + + + System is in the described configuration state. + + + Configuration state was not tested. + + + System is not in the described configuration state. + + + Unexpected test result: {0} + {Locked="{0}"} Error message. {0} will be replaced with the unexpected value (a number). + + + Have you reviewed the configuration and would you like to proceed verifying it against the system? + + + The configuration file is not a valid YAML file. + {Locked="YAML"} YAML is a file format name. + + + This configuration unit is part of a dependency cycle. + + + Validation found no issues. + + + The configuration unit is only available as a prerelease, but it is not marked that way in the configuration. Add `allowPrerelease: true` to the `directives`. + {Locked="allowPrerelease: true","directives"} These are values in the configuration file that are not localized. + + + The configuration unit was found locally, but could not be found in any configured catalog. Ensure that it is present on any system before applying the configuration. + + + The configuration unit is not available publicly; ensure that anyone who will use this configuration has access to it. + + + The module was not provided. Specifying the module improves performance and prevents future name collisions. + + + Downloads the installer from the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, download command will download the appropriate installer to the user's Downloads folder. + + + Downloads the installer from a given package + + + Directory where the installers are downloaded to + + + Downloading dependencies: + + + Installer downloaded: {0} + {Locked="{0}"} Full path of the downloaded installer. + + + Select the installer type + + + Installer Downloads + + + Installer is prohibited from being downloaded for later offline installation. + + + `--module-path allusers` requires administrator privileges to execute. + {Locked="--module-path allusers"} + + + Specifies the location on the local computer to store modules. Default %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules + {Locked="%LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules"} + + + `--module-path` value must be `currentuser`, `allusers`, `default`, or an absolute path. + {Locked="{--module-path}, {currentuser}, {allusers}, {default}} + + + Enable Windows Package Manager Configuration + + + Retrieve information about errors. Given a number, the output will contain details about the error, including the symbol name if it is a WinGet specific error. Given a string, the WinGet specific errors are searched for this value. + {Locked="WinGet"} + + + Get information on errors + + + A value to search within the error information + + + Generate error code Markdown file + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + The --output argument cannot be combined with an input value. + {Locked="--output"} + + + An input value or --output must be provided. + {Locked="--output"} + + + The given number is too large to be an HRESULT. + + + Unknown error code + + + Internal Error + + + Invalid command line arguments + + + Executing command failed + + + Opening manifest failed + + + Cancellation signal received + + + Running ShellExecute failed + + + Cannot process manifest. The manifest version is higher than supported. Please update the client. + + + Downloading installer failed + + + Cannot write to index; it is a higher schema version + + + The index is corrupt + + + The configured source information is corrupt + + + The source name is already configured + + + The source type is invalid + + + The MSIX file is a bundle, not a package + + + Data required by the source is missing + + + None of the installers are applicable for the current system + + + The installer file's hash does not match the manifest + + + The source name does not exist + + + The source location is already configured under another name + + + No packages found + + + No sources are configured + + + Multiple packages found matching the criteria + + + No manifest found matching the criteria + + + Failed to get Public folder from source package + + + Command requires administrator privileges to run + + + The source location is not secure + + + The Microsoft Store client is blocked by policy + + + The Microsoft Store app is blocked by policy + + + The feature is currently under development. It can be enabled using `winget settings`. + {Locked="winget settings"} + + + Failed to install the Microsoft Store app + + + Failed to perform auto complete + + + Failed to initialize YAML parser + + + Encountered an invalid YAML key + + + Encountered a duplicate YAML key + + + Invalid YAML operation + + + Failed to build YAML doc + + + Invalid YAML emitter state + + + Invalid YAML data + + + LibYAML error + + + Manifest validation succeeded with warning + + + Manifest validation failed + + + Manifest is invalid + + + No applicable update found + + + An upgrade is available but uses a different install technology than the current installation + + + winget upgrade --all completed with failures + + + Installer failed security check + + + Download size does not match expected content length + + + Uninstall command not found + + + Running uninstall command failed + + + ICU break iterator error + + + ICU casemap error + + + ICU regex error + + + Failed to install one or more imported packages + + + Could not find one or more requested packages + + + Json file is invalid + + + The source location is not remote + + + The configured rest source is not supported + + + Invalid data returned by rest source + + + Operation is blocked by Group Policy + + + Rest API internal error + + + Invalid rest source url + + + Unsupported MIME type returned by rest API + + + Invalid rest source contract version + + + The source data is corrupted or tampered + + + Error reading from the stream + + + Package agreements were not agreed to + + + Error reading input in prompt + + + The search request is not supported by one or more sources + + + The rest API endpoint is not found. + + + Failed to open the source. + + + Source agreements were not agreed to + + + Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again. + + + Missing resource file + + + Running MSI install failed + + + Arguments for msiexec are invalid + + + Failed to open one or more sources + + + Failed to validate dependencies + + + One or more package is missing + + + Invalid table column + + + The upgrade version is not newer than the installed version + + + Upgrade version is unknown and override is not specified + + + ICU conversion error + + + Failed to install portable package + + + Volume does not support reparse points + + + Portable package from a different source already exists. + + + Unable to create symlink. Path points to a directory. + + + The installer cannot be run from an administrator context. + + + Failed to uninstall portable package + + + Failed to validate DisplayVersion values against index. + + + One or more arguments are not supported. + + + Embedded null characters are disallowed for SQLite + + + Failed to find the nested installer in the archive. + + + Failed to extract archive. + + + Invalid relative file path to nested installer provided. + + + The server certificate did not match any of the expected values. + + + Install location must be provided. + + + Archive malware scan failed. + + + Found at least one version of the package installed. + + + A pin already exists for the package. + + + There is no pin for the package. + + + Unable to open the pin database. + + + One or more applications failed to install + + + One or more applications failed to uninstall + + + One or more queries did not return exactly one match + + + The package has a pin that prevents upgrade. + + + The package currently installed is the stub package + + + Application shutdown signal received + + + Failed to download package dependencies. + + + Failed to download package. Download for offline installation is prohibited. + + + A required service is busy or unavailable. Try again later. + + + The guid provided does not correspond to a valid resume state. + + + The current client version did not match the client version of the saved state. + + + The resume state data is invalid. + + + Unable to open the checkpoint database. + + + Exceeded max resume limit. + + + Invalid authentication info. + + + Authentication method not supported. + + + Authentication failed. + + + Authentication failed. Interactive authentication required. + + + Authentication failed. User cancelled. + + + Authentication failed. Authenticated account is not the desired account. + + + Application is currently running. Exit the application then try again. + + + Another installation is already in progress. Try again later. + + + One or more file is being used. Exit the application then try again. + + + This package has a dependency missing from your system. + + + There's no more space on your PC. Make space, then try again. + + + There's not enough memory available to install. Close other applications then try again. + + + This application requires internet connectivity. Connect to a network then try again. + + + This application encountered an error during installation. Contact support. + + + Restart your PC to finish installation. + + + Installation failed. Restart your PC then try again. + + + Your PC will restart to finish installation. + + + You cancelled the installation. + + + Another version of this application is already installed. + + + A higher version of this application is already installed. + + + Organization policies are preventing installation. Contact your admin. + + + Failed to install package dependencies. + + + Application is currently in use by another application. + + + Invalid parameter. + + + Package not supported by the system. + + + The installer does not support upgrading an existing package. + + + Installation failed with a custom installer error. + + + The Apps and Features Entry for the package could not be found. + + + The install location is not applicable. + + + The install location could not be found. + + + The hash of the existing file did not match. + + + File not found. + + + The file was found but the hash was not checked. + + + The file could not be accessed. + + + The configuration file is invalid. + + + The YAML syntax is invalid. + + + A configuration field has an invalid type. + + + The configuration has an unknown version. + + + An error occurred while applying the configuration. + + + The configuration contains a duplicate identifier. + + + The configuration is missing a dependency. + + + The configuration has an unsatisfied dependency. + + + An assertion for the configuration unit failed. + + + The configuration was manually skipped. + + + The user declined to continue execution. + + + The dependency graph contains a cycle which cannot be resolved. + + + The configuration has an invalid field value. + + + The configuration is missing a field. + + + Some of the configuration units failed while testing their state. + + + Configuration state was not tested. + + + The configuration unit was not installed. + + + The configuration unit could not be found. + + + Multiple matches were found for the configuration unit; specify the module to select the correct one. + + + The configuration unit failed while attempting to get the current system state. + + + The configuration unit failed while attempting to test the current system state. + + + The configuration unit failed while attempting to apply the desired state. + + + The module for the configuration unit is available in multiple locations with the same version. + + + Loading the module for the configuration unit failed. + + + The configuration unit returned an unexpected result during execution. + + + A unit contains a setting that requires the config root. + + + Operation is not supported by the configuration processor. + + + Unavailable + + + Successfully enabled Windows Features dependencies + + + Loading the module for the configuration unit failed because it requires administrator privileges to run. + + + A unit contains a setting that requires the config root. + + + Loading the module for the configuration unit failed because it requires administrator privileges to run. + + + Resumes execution of a previously saved command by passing in the unique identifier of the saved command. This is used to resume an executed command that may have been terminated due to a reboot. + + + Resumes execution of a previously saved command. + + + The unique identifier of the saved state to resume + + + Resuming the state from a different client version is not supported: {0} + {Locked= "{0}"} Message displayed to inform the user that the client version of the resume state does not match the current client version. {0} is a placeholder for the client version that created the resume state. + + + The resume state does not exist: {0} + {Locked="{0}"} Error message displayed when the user provides a guid that does not correspond to a valid saved state. {0} is a placeholder replaced by the provided guid string. + + + No data found in the resume state. + + + This command does not support resuming. + + + Allows a reboot if applicable + + + Initiating reboot to complete operation... + + + Failed to initiate a reboot. + + + Resume operation exceeds the allowable limit of {0} resume(s). To manually resume, run the command `{1}`. + {Locked="{0}", "{1}"} {0} is a placeholder that is replaced by an integer number of the number of allowed resumes. {1} is a placeholder for the command to run to perform a manual resume. + + + Ignore the limit on resuming a saved state + + + Uri scheme not supported: {0} + {Locked="{0}"} Error message displayed when the provided uri is not supported. {0} is a placeholder replaced by the provided uri. + + + Uri not well formed: {0} + {Locked="{0}"} Error message displayed when the provided uri is not well formed. {0} is a placeholder replaced by the provided uri. + + + Failed to parse {0} configuration unit settings content or settings content is empty. + {Locked="{0}"} {0} is a placeholder replaced by the input winget configure resource unit type. + + + {0} configuration unit is missing required argument: {1} + {Locked="{0},{1}"} {0} is a placeholder for the input winget configure resource unit type. {1} is placeholder for the missing arg. + + + {0} configuration unit is missing recommended argument: {1} + {Locked="{0},{1}"} {0} is a placeholder for the input winget configure resource unit type. {1} is placeholder for the missing arg. + + + WinGetSource configuration unit conflicts with a known source: {0} + {Locked="WinGetSource,{0}"} {0} is a placeholder for the input winget source in the configuration unit settings. + + + WinGetSource configuration unit asserts on a third-party source: {0} + {Locked="WinGetSource,{0}"} {0} is a placeholder for the input winget source in the configuration unit settings. + + + WinGetPackage declares both UseLatest and Version. Package Id: {0} + {Locked="WinGetPackage,UseLatest,Version,{0}"} + + + WinGetPackage configuration unit asserts on a package from third-party source. Package Id: {0}; Source: {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package depends on a third-party source not previously configured. Package Id: {0}; Source: {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package depends on a 3rd party source. It is recommended to declare the dependency in uni dependsOn section. Package Id: {0}; Source: {1} + {Locked="WinGetPackage,dependsOn,{0},{1}"} + + + WinGetPackage configuration unit package cannot be validated. Source open failed. Package Id: {0}; Source: {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package cannot be validated. Package not found. Package Id: {0} + {Locked="WinGetPackage,{0}"} + + + WinGetPackage configuration unit package cannot be validated. More than one package found. Package Id: {0} + {Locked="WinGetPackage,{0}"} + + + WinGetPackage configuration unit package cannot be validated. Package version not found. Package Id: {0}; Version {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package specified with a specific version while only one package version is available. Package Id: {0}; Version: {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package cannot be validated. Package Id: {0} + {Locked="WinGetPackage,{0}"} + + + Specify authentication window preference (silent, silentPreferred, or interactive) + {Locked="silent","silentPreferred","interactive"} This argument allows the user to select authentication window popup behavior. + + + Specify the account to be used for authentication + + + Failed to add source. This WinGet version does not support the source's authentication method. Try upgrading to latest WinGet version. + {Locked="WinGet"} + + + The {0} source requires authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with the source for access authorization. + {Locked="{0}"} + + + Repairs the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. + id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Repairs the selected package + + + The repair command for this package cannot be found. Please reach out to the package publisher for support. + + + The installer technology in use does not match the version currently installed. + + + Repair command not found. + + + The installer technology in use doesn't support repair. + + + Repair operation completed successfully. + + + Repair abandoned + + + Starting package repair... + + + Failed to repair Microsoft Store package. Error code: {0} + {Locked="{0}"} Error message displayed when a Microsoft Store application package fails to repair. {0} is a placeholder replaced by an error code. + + + Repair operation failed. + + + Repair operation is not applicable. + + + No matching package versions are available from the configured sources. + + + The current system configuration does not support the repair of this package. + + + The installer technology in use does not support repair. + + + The package installed for user scope cannot be repaired when running with administrator privileges. + + + Repair operations involving administrator privileges are not permitted on packages installed within the user scope. + + + Repair failed with exit code: {0} + {Locked="{0}"} Error message displayed when an attempt to repair an application package fails. {0} is a placeholder replaced by an error code. + + + The SQLite connection was terminated to prevent corruption. + + + Uninstall all versions + + + The version to act upon + + + Multiple versions of this package are installed. Either refine the search, pass the `--version` argument to select one, or pass the `--all-versions` flag to uninstall all of them. + {Locked="--version,--all-versions"} + + + Enable Windows Package Manager proxy command line options + Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + + Enable Windows Package Manager Configuration processor path + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + + + Set a proxy to use for this execution + + + Disable the use of proxy for this execution + + + Disables the progress bar and spinner + + + Cannot reset {0}. This setting is controlled by policy. For more information contact your system administrator. + {Locked="{0}"} The value will be replaced with the feature name + + + Reset admin setting '{0}'. + {Locked="{0}"} Message displayed after the user resets an admin setting to its default value. Reset is used as verb in past tense. {0} is a placeholder replaced by the setting name. + + + Cannot set {0}. This setting is controlled by policy. For more information contact your system administrator. + {Locked="{0}"} The value will be replaced with the feature name + + + Set admin setting '{0}' to '{1}'. + {Locked="{0}"} Message displayed after the user sets the value of an admin setting. Set is used as a verb in past tense. {0} is a placeholder replaced by the setting name. {1} is a placeholder replaced + + + Name of the setting to modify + + + Value to set for the setting. + + + Resets an admin setting to its default value. + + + Resets an admin setting to its default value. + + + Sets the value of an admin setting. + + + Sets the value of an admin setting. + + + Excludes a source from discovery unless specified + + + Excludes a source from discovery (true or false) + + + Explicit + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. + + + Trust level of the source (none or trusted) + + + Trust Level + + + Failed to get Microsoft Store package catalog. + + + No applicable Microsoft Store package found from Microsoft Store package catalog. + + + Failed to get Microsoft Store package download information. + + + No applicable Microsoft Store package found for download. + + + Failed to retrieve Microsoft Store package license. + + + No applicable Microsoft Store package found. + + + Successfully verified Microsoft Store package hash + + + Microsoft Store package hash mismatch + + + Microsoft Store package downloaded: {0} + {Locked="{0}"} Full path of the downloaded package. + + + Microsoft Store package download failed: {0} + {Locked="{0}"} Package name. + + + Microsoft Store package download completed + + + Downloading main packages from Microsoft Store... + + + Downloading dependency packages from Microsoft Store... + + + Retrieving Microsoft Store package download information + + + Failed to retrieve Microsoft Store package download information + + + Retrieving Microsoft Store package license + + + Microsoft Store package license saved: {0} + {Locked="{0}"} License file full path. + + + Failed to retrieve Microsoft Store package license + + + Microsoft Store package download does not support --rename argument. Microsoft Store package will use names provided by Microsoft Store catalog. + {Locked="--rename"} + + + Microsoft Store package download requires Microsoft Entra Id authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with Microsoft services for access authorization. For Microsoft Store package licensing, the Microsoft Entra Id account needs to be a member of Global Administrator, User Administrator, or License Administrator. + {Locked="Global Administrator,User Administrator,License Administrator"} + + + Skips retrieving Microsoft Store package offline license + + + Select the target platform + + + Adding configuration file: {0} + {Locked="{0}"} + + + Successfully exported + + + Getting configuration settings... + + + At least --packageId and/or --module with --resource must be provided. Or use --all to export all package configurations. + {Locked="--packageId,--module, --resource, --all"} + + + Arguments --packageId, --module and --resource cannot be used with --all. + {Locked="--packageId,--module, --resource, --all"} + + + Exports configuration resources to a configuration file. When used with --all, exports all package configurations. When used with --packageId, exports a WinGetPackage resource of the given package id. When used with --module and --resource, gets the settings of the resource and exports it to the configuration file. If the output configuration file already exists, appends the exported configuration resources. + {Locked="WinGetPackage,--packageId,--module, --resource"} + + + Exports configuration resources to a configuration file. + + + The module of the resource to export. + + + The package identifier to export. + + + The configuration resource to export. + + + Exports all package configurations. + + + The configuration unit failed getting its properties. + + + Failed exporting configuration. + + + Configure {0} + {Locked="{0}"} + + + Install {0} + {Locked="{0}"} + + + Some of the data present in the configuration file was truncated for this output; inspect the file contents for the complete content. + + + <this value has been truncated; inspect the file contents for the complete text> + Keep some form of separator like the "<>" around the text so that it stands out from the preceding text. + + + Shows the high level details for configurations that have been applied to the system. This data can then be used with `configure` commands to get more details. + {Locked="`configure`"} + + + Shows configuration history + + + There are no configurations in the history. + + + Select items from history + + + No single configuration could be found that matched the provided data. Provide either the full name or part of the identifier that unambiguously matches the desired configuration. + + + Remove the item from history + + + First Applied + Column header for date values indicating when a configuration was first applied to the system. + + + Identifier + + + Name + + + Origin + + + Path + + + The specified configuration could not be found. + + + Completed + The state of processing an item. + + + In progress + The state of processing an item. + + + Pending + The state of processing an item. + + + Unknown + The state of processing an item. + + + Monitor configuration status. + As in "to monitor the status of a configuration being applied". + + + Completed + The state of processing an item. + + + In progress + The state of processing an item. + + + Pending + The state of processing an item. + + + Skipped + The state of processing an item. + + + Unknown + The state of processing an item. + + + Apply Started + When the configuration application started. + + + Apply Ended + When the configuration application ended. + + + Result + + + Details + + + State + The state of processing an item. + + + Unit + + + The package is not compatible with the current Windows version or platform. + + + Suppress showing initial configuration details when possible + + + Multiple Microsoft Store packages may be downloaded targeting different platforms and architectures. --platform, --architecture can be used to filter the packages. + {Locked="--platform--architecture"} + + + Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account is not a member of Global Administrator, User Administrator, or License Administrator. Use --skip-license to skip retrieving Microsoft Store package license. + {Locked="Global Administrator,User Administrator,License Administrator,--skip-license"} + + + The Microsoft Store package does not support download. + + + The Microsoft Store package does not support download. + + + Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have required privilege. + + + Parameter cannot be passed across integrity boundary. + + + Downloaded zero byte installer; ensure that your network connection is working properly. + + + Downloaded zero byte installer; ensure that your network connection is working properly. + + + Manage fonts + + + Manage fonts with sub-commands. Fonts can be installed, upgraded, or uninstalled similar to packages. + + + Family + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. + + + Faces + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. + + + Filter results by family name + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. + + + Face + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. + + + Paths + + + No installed font found matching input criteria. + + + List installed fonts + + + List all installed fonts, all font files, or full details of a specific font. + + + Version + + + Configuration Modules + PowerShell Modules that are used for the Configuration feature + + + The package installer requires authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with the installer download url. + + + Failed to download installer. This WinGet version does not support the installer download authentication method. Try upgrading to latest WinGet version. + {Locked="WinGet"} + + + Failed to download installer. Authentication failed. + + + Specify the path to the configuration processor + + + Custom processor path: + Displayed as a warning header when a custom --processor-path argument is provided. + + + Hash: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Type: App execution alias + Indicates the processor path is an app execution alias (MSIX package link). + + + Path: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Signed By: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Signed By: (unsigned) + Indicates the custom processor executable does not have an Authenticode signature. + + + The integrity check failed for the custom DSC processor path. The path may have been modified. + Error when the server-side hash of the processor path does not match the client-computed hash. + + + DSC v3 resource commands + DSC stands for "Desired State Configuration". It should already have a locked translation. + + + The sub-commands here implement Desired State Configuration (DSC) v3 resources for configuring WinGet and packages. + {Locked="WinGet"} + + + Get the resource state + + + Set the resource state + + + Describe required state changes + + + Test the resource state + + + Delete the resource state + + + Get all state instances + + + Validate group contents + + + Resolve external state + + + Run the adapter + + + Get the resource schema + + + Get the resource manifest + + + Manage package state + + + Manage packages through WinGet. + {Locked="WinGet"} + + + Indicates whether an instance should or does exist. + + + Indicates whether an instance is in the desired state. + + + The identifier of the package. + + + The source of the package. + + + The version of the package. + + + The method for matching the identifier with a package. + + + Indicate that the latest available version of the package should be installed. + + + The install mode to use if needed. + + + The target scope of the package. + + + Indicates whether to accept agreements for sources and packages. + + + Manage user settings file + + + Manage the user settings of WinGet. + {Locked="WinGet"} + + + The settings json file content. + + + The action used to apply the settings. + + + Value is logged for correlation + + + Manage source configuration + + + Manage the sources of WinGet. + {Locked="WinGet"} + + + The name to use for the source. + + + The argument for the source. + + + The type of the source. + + + The trust level of the source. + + + Whether the source is included when calls don't specify a source. + + + Applying configuration unit... + + + Exporting configuration unit... + + + Getting configuration unit processors... + + + Ensure required module for export [{0}] + {Locked="{0}"} + + + Failed to test or acquire required module. Related settings will not be exported. + + + Export [{0}] + {Locked="{0}"} + + + Failed to export the resource. + + + Failed to get unit processors. Individual package settings will not be exported. + + + Directory where the results are to be written + + + Desired State Configuration package not found on the system. Installing the package... + + + Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through --processor-path argument. + {Locked="dsc.exe","--processor-path"} + + + An object containing the administrator settings and their values. + + + Manage administrator settings + + + Manage the administrator settings of WinGet. + {Locked="WinGet"} + + + Resets all admin settings + + + All admin settings reset. + + + MCP information + MCP stands for Model Context Protocol and should probably remain as-is + + + MCP (Model Context Protocol) information for the Windows Package Manager. + + + To manually configure the Windows Package Manager MCP server with your MCP client, use the following JSON fragment in the `servers` object: + {Locked="`servers`"} +MCP stands for Model Context Protocol and should probably remain as-is. +An unlocalized JSON fragment will follow on another line. + + + Enable Windows Package Manager MCP Server + + + Target OS version + + + Failed installing one or more fonts. + + + Font file is not supported and cannot be installed. + + + One or more fonts in the font package is not supported and cannot be installed. + + + Font install failed; cleaning up. + + + Font already installed. + + + Package Id + + + WinGet Supported + + + Title + + + Font file not found. + + + Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. + + + Font validation failed. + + + Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. + + + Show font file detailed information. + + + Validation of the font package failed. + + + Rollback attempt for failed font install was unsuccessful. A restart may be required to successfully uninstall the font. + + + Font package uninstall failed. This is often due to the fonts being in use by the system or an application. Uninstall may be successful after restarting your computer. + + + OK + "OK" means the font is in a good healthy state + + + Corrupt + "Corrupt" refers to an install that is in a corrupted or bad state, and needs repair. + + + Status + + + Unknown + + + Font package is already installed. + + + Show detailed information about packages + Providing this argument causes the CLI to output additional details about installed application packages. + + + Channel: + Precedes a string value that names the delivery channel for the software package (ex. stable, beta). + + + Local Identifier: + Precedes a value that is the unique identifier for the installed package on the local system. + + + Package Family Name: + Precedes a value that is the APPX/MSIX package family name of the installed package. + + + Product Code: + Precedes a value that is the Add/Remove Programs identifier in the registry. This is also the Product Code value as defined in MSI installers. + + + Upgrade Code: + Precedes a value that is the MSI Upgrade Code for the installed package. + + + Installed Scope: + Precedes a value that is the scope of the installation of the package (ex. user, machine). + + + Installed Architecture: + Precedes a value that is the installed architecture of the package (ex. x86, x64, ARM64). + + + Installed Locale: + Precedes a value that is the locale of the installed package. + + + Installed Location: + Precedes a value that is the directory path to the installed package. + + + Origin Source: + Precedes a value that names the package source where the installed package originated from. + + + Available Upgrades: + Precedes a list of package upgrades available for the installed package. + + + Installer Category: + Precedes a value that indicates the category of the installer for the installed package (ex. exe, msi, msix). + + + Old Value + Column title for listing edit changes. + + + New Value + Column title for listing the new value. + + + Priority; higher numbers first + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + Priority + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + The priority of the source. Higher values are sorted first in the order. + + + Results have been filtered to the highest matched source priority. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + The DSC processor hash provided does not match hash of the target file. + + diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index 739077c3c6..90bc4bc27e 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -1,480 +1,480 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include "TestHooks.h" -#include "PinTestCommon.h" -#include "TestSource.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::Pinning; -using namespace AppInstaller::SQLite; - -TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinAddOutput; - TestContext addContext{ pinAddOutput, std::cin }; - OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); - addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - addContext.Args.AddArg(Execution::Args::Type::BlockingPin); - - PinAddCommand pinAdd({}); - pinAdd.Execute(addContext); - INFO(pinAddOutput.str()); - - SECTION("Pin is saved") - { - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Blocking); - REQUIRE(pins[0].GetGatedVersion().ToString() == ""); - REQUIRE(pins[0].GetKey().PackageId == "AppInstallerCliTest.TestExeInstaller"); - REQUIRE(pins[0].GetKey().SourceId == "*TestSource"); - REQUIRE(pins[0].GetDateAdded().has_value()); - REQUIRE_FALSE(pins[0].GetNote().has_value()); - - std::ostringstream pinListOutput; - TestContext listContext{ pinListOutput, std::cin }; - OverrideForCompositeInstalledSource(listContext, CreateTestSource({ TSR::TestInstaller_Exe })); - listContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - PinListCommand pinList({}); - pinList.Execute(listContext); - - INFO(pinListOutput.str()); - REQUIRE(pinListOutput.str().find("AppInstallerCliTest.TestExeInstaller")); - REQUIRE(pinListOutput.str().find("Blocking")); - } - SECTION("Remove pin") - { - std::ostringstream pinRemoveOutput; - TestContext removeContext{ pinRemoveOutput, std::cin }; - OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); - removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - PinRemoveCommand pinRemove({}); - pinRemove.Execute(removeContext); - INFO(pinRemoveOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.empty()); - } - SECTION("Reset pins") - { - std::ostringstream pinResetOutput; - TestContext resetContext{ pinResetOutput, std::cin }; - - SECTION("Without --force") - { - OverrideForCompositeInstalledSource(resetContext, CreateTestSource({ TSR::TestInstaller_Exe })); - PinResetCommand pinReset({}); - pinReset.Execute(resetContext); - INFO(pinResetOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - } - SECTION("With --force") - { - resetContext.Args.AddArg(Execution::Args::Type::Force); - - PinResetCommand pinReset({}); - pinReset.Execute(resetContext); - INFO(pinResetOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.empty()); - } - } - SECTION("Update pin") - { - std::ostringstream pinUpdateOutput; - TestContext updateContext{ pinUpdateOutput, std::cin }; - OverrideForCompositeInstalledSource(updateContext, CreateTestSource({ TSR::TestInstaller_Exe })); - updateContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - SECTION("Without --force") - { - PinAddCommand pinUpdate({}); - pinUpdate.Execute(updateContext); - INFO(pinUpdateOutput.str()); - REQUIRE_TERMINATED_WITH(updateContext, APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Blocking); - } - SECTION("With --force") - { - updateContext.Args.AddArg(Execution::Args::Type::Force); - - PinAddCommand pinUpdate({}); - pinUpdate.Execute(updateContext); - INFO(pinUpdateOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Pinning); - } - } -} - -TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinAddOutput; - TestContext addContext{ pinAddOutput, std::cin }; - OverrideForCompositeInstalledSource(addContext, CreateTestSource({})); - addContext.Args.AddArg(Execution::Args::Type::Query, "This package doesn't exist"sv); - - PinAddCommand pinAdd({}); - pinAdd.Execute(addContext); - INFO(pinAddOutput.str()); - - REQUIRE_TERMINATED_WITH(addContext, APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); -} - -TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinListOutput; - TestContext listContext{ pinListOutput, std::cin }; - OverrideForCompositeInstalledSource(listContext, CreateTestSource({})); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(pinListOutput.str()); - - REQUIRE(pinListOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); -} - -TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinRemoveOutput; - TestContext removeContext{ pinRemoveOutput, std::cin }; - OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); - removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - PinRemoveCommand pinRemove({}); - pinRemove.Execute(removeContext); - INFO(pinRemoveOutput.str()); - - REQUIRE_TERMINATED_WITH(removeContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); -} - -TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinResetOutput; - TestContext resetContext{ pinResetOutput, std::cin }; - resetContext.Args.AddArg(Execution::Args::Type::Force); - - PinResetCommand pinReset({}); - pinReset.Execute(resetContext); - INFO(pinResetOutput.str()); - - REQUIRE(pinResetOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); -} - -TEST_CASE("PinFlow_Add_WithNote", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinAddOutput; - TestContext addContext{ pinAddOutput, std::cin }; - OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); - addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - addContext.Args.AddArg(Execution::Args::Type::PinNote, "my test note"sv); - - PinAddCommand pinAdd({}); - pinAdd.Execute(addContext); - INFO(pinAddOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetNote().has_value()); - REQUIRE(pins[0].GetNote().value() == "my test note"); -} - -// Helper: Creates a pinning index at the given path and adds the provided pins directly. -// Each pin should already have date_added and note set as desired. -namespace -{ - AppInstaller::Manifest::Manifest MakeListTestManifest(std::string_view id) - { - AppInstaller::Manifest::Manifest result; - result.Id = std::string{ id }; - result.DefaultLocalization.Add(std::string{ id }); - result.DefaultLocalization.Add("TestPublisher"); - result.Version = "1.0.0"; - result.Installers.push_back({}); - return result; - } - - TestSourceResult MakeListTestSourceResult(std::string_view id) - { - std::string packageId{ id }; - return TestSourceResult( - packageId, - [packageId](std::vector& matches, std::weak_ptr source) - { - auto manifest = MakeListTestManifest(packageId); - matches.emplace_back( - AppInstaller::Repository::ResultMatch( - TestCompositePackage::Make(std::vector{ manifest }, source), - AppInstaller::Repository::PackageMatchFilter( - AppInstaller::Repository::PackageMatchField::Id, - AppInstaller::Repository::MatchType::Exact, - packageId))); - }); - } - - std::shared_ptr CreateListTestSource(std::initializer_list ids) - { - std::vector results; - results.reserve(ids.size()); - for (auto id : ids) - { - results.emplace_back(MakeListTestSourceResult(id)); - } - - return CreateTestSource(std::move(results)); - } - - void PopulatePinIndexForList(const std::filesystem::path& indexPath, const std::vector& pins, AppInstaller::SQLite::Version version = AppInstaller::SQLite::Version::Latest()) - { - PinningIndex index = PinningIndex::CreateNew(indexPath.u8string(), version); - for (const auto& pin : pins) - { - index.AddPin(pin); - } - } -} - -TEST_CASE("PinFlow_List_Filter_NoMatch", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - Pin existingPin = Pin::CreateBlockingPin({ "SomePackage.Id", "sourceId" }); - existingPin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1000)); - PopulatePinIndexForList(indexFile.GetPath(), { existingPin }); - - std::ostringstream listOutput; - TestContext listContext{ listOutput, std::cin }; - listContext.Args.AddArg(Execution::Args::Type::Query, "ThisQueryMatchesNothing"sv); - OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "SomePackage.Id" })); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(listOutput.str()); - - REQUIRE_TERMINATED_WITH(listContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); - REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinShowNoMatchFound)) != std::string::npos); -} - -TEST_CASE("PinFlow_List_Filter_MatchById_WithDetails", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - Pin pin = Pin::CreateBlockingPin({ "MyApp.Package", "sourceId" }); - pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jun2026_01_0900)); - pin.SetNote(std::string{ "keep this one" }); - PopulatePinIndexForList(indexFile.GetPath(), { pin }); - - std::ostringstream listOutput; - TestContext listContext{ listOutput, std::cin }; - listContext.Args.AddArg(Execution::Args::Type::Id, "MyApp.Package"sv); - listContext.Args.AddArg(Execution::Args::Type::ListDetails); - OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "MyApp.Package" })); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(listOutput.str()); - - REQUIRE_FALSE(listContext.IsTerminated()); - REQUIRE(listOutput.str().find("MyApp.Package") != std::string::npos); - REQUIRE(listOutput.str().find("Blocking") != std::string::npos); - REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) != std::string::npos); - REQUIRE(listOutput.str().find("keep this one") != std::string::npos); -} - -TEST_CASE("PinFlow_List_Filter_MatchByQuery", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - Pin pin = Pin::CreatePinningPin({ "Contoso.AppOne", "sourceId" }); - pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Mar2026_10_1200)); - PopulatePinIndexForList(indexFile.GetPath(), { pin }); - - std::ostringstream listOutput; - TestContext listContext{ listOutput, std::cin }; - // Partial, case-insensitive match on the package ID - listContext.Args.AddArg(Execution::Args::Type::Query, "appone"sv); - OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Contoso.AppOne" })); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(listOutput.str()); - - REQUIRE_FALSE(listContext.IsTerminated()); - REQUIRE(listOutput.str().find("Contoso.AppOne") != std::string::npos); -} - -TEST_CASE("PinFlow_List_Filter_ExactMatch", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - // Two pins sharing a prefix - Pin pinA = Pin::CreateBlockingPin({ "Vendor.App", "src" }); - pinA.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_01_0000)); - - Pin pinB = Pin::CreateBlockingPin({ "Vendor.AppExtra", "src" }); - pinB.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_01_0000)); - - PopulatePinIndexForList(indexFile.GetPath(), { pinA, pinB }); - - std::ostringstream listOutput; - TestContext listContext{ listOutput, std::cin }; - listContext.Args.AddArg(Execution::Args::Type::Id, "Vendor.App"sv); - listContext.Args.AddArg(Execution::Args::Type::Exact); - OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Vendor.App", "Vendor.AppExtra" })); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(listOutput.str()); - - REQUIRE_FALSE(listContext.IsTerminated()); - // Only the exact-match pin should appear - REQUIRE(listOutput.str().find("Vendor.App") != std::string::npos); - // The inexact match should NOT appear - REQUIRE(listOutput.str().find("Vendor.AppExtra") == std::string::npos); -} - -TEST_CASE("PinFlow_List_DetailsWithoutNotes_ShowsEmptyNoteColumn", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - Pin pin = Pin::CreatePinningPin({ "NoNote.Package", "src" }); - pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::May2026_01_0800)); - // note intentionally not set - PopulatePinIndexForList(indexFile.GetPath(), { pin }); - - std::ostringstream listOutput; - TestContext listContext{ listOutput, std::cin }; - listContext.Args.AddArg(Execution::Args::Type::Query, "NoNote.Package"sv); - listContext.Args.AddArg(Execution::Args::Type::ListDetails); - OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "NoNote.Package" })); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(listOutput.str()); - - REQUIRE_FALSE(listContext.IsTerminated()); - REQUIRE(listOutput.str().find("NoNote.Package") != std::string::npos); - REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) != std::string::npos); - REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinNote)) != std::string::npos); -} - -TEST_CASE("PinFlow_List_V1_0Index_NoMigrationRequired", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - Pin pin = Pin::CreateBlockingPin({ "Legacy.Package", "src" }); - PopulatePinIndexForList(indexFile.GetPath(), { pin }, { 1, 0 }); - - std::ostringstream listOutput; - TestContext listContext{ listOutput, std::cin }; - listContext.Args.AddArg(Execution::Args::Type::Query, "Legacy.Package"sv); - listContext.Args.AddArg(Execution::Args::Type::ListDetails); - OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Legacy.Package" })); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(listOutput.str()); - - REQUIRE_FALSE(listContext.IsTerminated()); - REQUIRE(listOutput.str().find("Legacy.Package") != std::string::npos); - REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) == std::string::npos); - REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinNote)) == std::string::npos); -} - -TEST_CASE("PinFlow_List_Filter_EmptyIndex_NoMatch", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - // Create an empty index (no pins) - { PinningIndex::CreateNew(indexFile.GetPath().u8string(), AppInstaller::SQLite::Version::Latest()); } - - std::ostringstream listOutput; - TestContext listContext{ listOutput, std::cin }; - listContext.Args.AddArg(Execution::Args::Type::Query, "AnyQuery"sv); - OverrideForCompositeInstalledSource(listContext, CreateListTestSource({})); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(listOutput.str()); - - REQUIRE_TERMINATED_WITH(listContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); -} - -TEST_CASE("PinFlow_List_DefaultOutput_DoesNotShowDetailsColumns", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - Pin pin = Pin::CreateBlockingPin({ "Has.Note.Package", "sourceId" }); - pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jun2026_01_0900)); - pin.SetNote(std::string{ "some note" }); - PopulatePinIndexForList(indexFile.GetPath(), { pin }); - - std::ostringstream listOutput; - TestContext listContext{ listOutput, std::cin }; - listContext.Args.AddArg(Execution::Args::Type::Query, "Has.Note.Package"sv); - OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Has.Note.Package" })); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(listOutput.str()); - - REQUIRE_FALSE(listContext.IsTerminated()); - REQUIRE(listOutput.str().find("Has.Note.Package") != std::string::npos); - REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) == std::string::npos); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "TestHooks.h" +#include "PinTestCommon.h" +#include "TestSource.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Pinning; +using namespace AppInstaller::SQLite; + +TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); + addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + addContext.Args.AddArg(Execution::Args::Type::BlockingPin); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + SECTION("Pin is saved") + { + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Blocking); + REQUIRE(pins[0].GetGatedVersion().ToString() == ""); + REQUIRE(pins[0].GetKey().PackageId == "AppInstallerCliTest.TestExeInstaller"); + REQUIRE(pins[0].GetKey().SourceId == "*TestSource"); + REQUIRE(pins[0].GetDateAdded().has_value()); + REQUIRE_FALSE(pins[0].GetNote().has_value()); + + std::ostringstream pinListOutput; + TestContext listContext{ pinListOutput, std::cin }; + OverrideForCompositeInstalledSource(listContext, CreateTestSource({ TSR::TestInstaller_Exe })); + listContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + PinListCommand pinList({}); + pinList.Execute(listContext); + + INFO(pinListOutput.str()); + REQUIRE(pinListOutput.str().find("AppInstallerCliTest.TestExeInstaller")); + REQUIRE(pinListOutput.str().find("Blocking")); + } + SECTION("Remove pin") + { + std::ostringstream pinRemoveOutput; + TestContext removeContext{ pinRemoveOutput, std::cin }; + OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); + removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + PinRemoveCommand pinRemove({}); + pinRemove.Execute(removeContext); + INFO(pinRemoveOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.empty()); + } + SECTION("Reset pins") + { + std::ostringstream pinResetOutput; + TestContext resetContext{ pinResetOutput, std::cin }; + + SECTION("Without --force") + { + OverrideForCompositeInstalledSource(resetContext, CreateTestSource({ TSR::TestInstaller_Exe })); + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + } + SECTION("With --force") + { + resetContext.Args.AddArg(Execution::Args::Type::Force); + + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.empty()); + } + } + SECTION("Update pin") + { + std::ostringstream pinUpdateOutput; + TestContext updateContext{ pinUpdateOutput, std::cin }; + OverrideForCompositeInstalledSource(updateContext, CreateTestSource({ TSR::TestInstaller_Exe })); + updateContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + SECTION("Without --force") + { + PinAddCommand pinUpdate({}); + pinUpdate.Execute(updateContext); + INFO(pinUpdateOutput.str()); + REQUIRE_TERMINATED_WITH(updateContext, APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Blocking); + } + SECTION("With --force") + { + updateContext.Args.AddArg(Execution::Args::Type::Force); + + PinAddCommand pinUpdate({}); + pinUpdate.Execute(updateContext); + INFO(pinUpdateOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Pinning); + } + } +} + +TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + OverrideForCompositeInstalledSource(addContext, CreateTestSource({})); + addContext.Args.AddArg(Execution::Args::Type::Query, "This package doesn't exist"sv); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + REQUIRE_TERMINATED_WITH(addContext, APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); +} + +TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinListOutput; + TestContext listContext{ pinListOutput, std::cin }; + OverrideForCompositeInstalledSource(listContext, CreateTestSource({})); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(pinListOutput.str()); + + REQUIRE(pinListOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); +} + +TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinRemoveOutput; + TestContext removeContext{ pinRemoveOutput, std::cin }; + OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); + removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + PinRemoveCommand pinRemove({}); + pinRemove.Execute(removeContext); + INFO(pinRemoveOutput.str()); + + REQUIRE_TERMINATED_WITH(removeContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); +} + +TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinResetOutput; + TestContext resetContext{ pinResetOutput, std::cin }; + resetContext.Args.AddArg(Execution::Args::Type::Force); + + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + REQUIRE(pinResetOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); +} + +TEST_CASE("PinFlow_Add_WithNote", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); + addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + addContext.Args.AddArg(Execution::Args::Type::PinNote, "my test note"sv); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetNote().has_value()); + REQUIRE(pins[0].GetNote().value() == "my test note"); +} + +// Helper: Creates a pinning index at the given path and adds the provided pins directly. +// Each pin should already have date_added and note set as desired. +namespace +{ + AppInstaller::Manifest::Manifest MakeListTestManifest(std::string_view id) + { + AppInstaller::Manifest::Manifest result; + result.Id = std::string{ id }; + result.DefaultLocalization.Add(std::string{ id }); + result.DefaultLocalization.Add("TestPublisher"); + result.Version = "1.0.0"; + result.Installers.push_back({}); + return result; + } + + TestSourceResult MakeListTestSourceResult(std::string_view id) + { + std::string packageId{ id }; + return TestSourceResult( + packageId, + [packageId](std::vector& matches, std::weak_ptr source) + { + auto manifest = MakeListTestManifest(packageId); + matches.emplace_back( + AppInstaller::Repository::ResultMatch( + TestCompositePackage::Make(std::vector{ manifest }, source), + AppInstaller::Repository::PackageMatchFilter( + AppInstaller::Repository::PackageMatchField::Id, + AppInstaller::Repository::MatchType::Exact, + packageId))); + }); + } + + std::shared_ptr CreateListTestSource(std::initializer_list ids) + { + std::vector results; + results.reserve(ids.size()); + for (auto id : ids) + { + results.emplace_back(MakeListTestSourceResult(id)); + } + + return CreateTestSource(std::move(results)); + } + + void PopulatePinIndexForList(const std::filesystem::path& indexPath, const std::vector& pins, AppInstaller::SQLite::Version version = AppInstaller::SQLite::Version::Latest()) + { + PinningIndex index = PinningIndex::CreateNew(indexPath.u8string(), version); + for (const auto& pin : pins) + { + index.AddPin(pin); + } + } +} + +TEST_CASE("PinFlow_List_Filter_NoMatch", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin existingPin = Pin::CreateBlockingPin({ "SomePackage.Id", "sourceId" }); + existingPin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_15_1000)); + PopulatePinIndexForList(indexFile.GetPath(), { existingPin }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "ThisQueryMatchesNothing"sv); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "SomePackage.Id" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_TERMINATED_WITH(listContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinShowNoMatchFound)) != std::string::npos); +} + +TEST_CASE("PinFlow_List_Filter_MatchById_WithDetails", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "MyApp.Package", "sourceId" }); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jun2026_01_0900)); + pin.SetNote(std::string{ "keep this one" }); + PopulatePinIndexForList(indexFile.GetPath(), { pin }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Id, "MyApp.Package"sv); + listContext.Args.AddArg(Execution::Args::Type::ListDetails); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "MyApp.Package" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("MyApp.Package") != std::string::npos); + REQUIRE(listOutput.str().find("Blocking") != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) != std::string::npos); + REQUIRE(listOutput.str().find("keep this one") != std::string::npos); +} + +TEST_CASE("PinFlow_List_Filter_MatchByQuery", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreatePinningPin({ "Contoso.AppOne", "sourceId" }); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Mar2026_10_1200)); + PopulatePinIndexForList(indexFile.GetPath(), { pin }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + // Partial, case-insensitive match on the package ID + listContext.Args.AddArg(Execution::Args::Type::Query, "appone"sv); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Contoso.AppOne" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("Contoso.AppOne") != std::string::npos); +} + +TEST_CASE("PinFlow_List_Filter_ExactMatch", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + // Two pins sharing a prefix + Pin pinA = Pin::CreateBlockingPin({ "Vendor.App", "src" }); + pinA.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_01_0000)); + + Pin pinB = Pin::CreateBlockingPin({ "Vendor.AppExtra", "src" }); + pinB.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jan2026_01_0000)); + + PopulatePinIndexForList(indexFile.GetPath(), { pinA, pinB }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Id, "Vendor.App"sv); + listContext.Args.AddArg(Execution::Args::Type::Exact); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Vendor.App", "Vendor.AppExtra" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_FALSE(listContext.IsTerminated()); + // Only the exact-match pin should appear + REQUIRE(listOutput.str().find("Vendor.App") != std::string::npos); + // The inexact match should NOT appear + REQUIRE(listOutput.str().find("Vendor.AppExtra") == std::string::npos); +} + +TEST_CASE("PinFlow_List_DetailsWithoutNotes_ShowsEmptyNoteColumn", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreatePinningPin({ "NoNote.Package", "src" }); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::May2026_01_0800)); + // note intentionally not set + PopulatePinIndexForList(indexFile.GetPath(), { pin }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "NoNote.Package"sv); + listContext.Args.AddArg(Execution::Args::Type::ListDetails); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "NoNote.Package" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("NoNote.Package") != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinNote)) != std::string::npos); +} + +TEST_CASE("PinFlow_List_V1_0Index_NoMigrationRequired", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "Legacy.Package", "src" }); + PopulatePinIndexForList(indexFile.GetPath(), { pin }, { 1, 0 }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "Legacy.Package"sv); + listContext.Args.AddArg(Execution::Args::Type::ListDetails); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Legacy.Package" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("Legacy.Package") != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) == std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinNote)) == std::string::npos); +} + +TEST_CASE("PinFlow_List_Filter_EmptyIndex_NoMatch", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + // Create an empty index (no pins) + { PinningIndex::CreateNew(indexFile.GetPath().u8string(), AppInstaller::SQLite::Version::Latest()); } + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "AnyQuery"sv); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({})); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_TERMINATED_WITH(listContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); +} + +TEST_CASE("PinFlow_List_DefaultOutput_DoesNotShowDetailsColumns", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "Has.Note.Package", "sourceId" }); + pin.SetDateAdded(AppInstaller::Utility::ConvertUnixEpochToSystemClock(PinTestEpoch::Jun2026_01_0900)); + pin.SetNote(std::string{ "some note" }); + PopulatePinIndexForList(indexFile.GetPath(), { pin }); + + std::ostringstream listOutput; + TestContext listContext{ listOutput, std::cin }; + listContext.Args.AddArg(Execution::Args::Type::Query, "Has.Note.Package"sv); + OverrideForCompositeInstalledSource(listContext, CreateListTestSource({ "Has.Note.Package" })); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(listOutput.str()); + + REQUIRE_FALSE(listContext.IsTerminated()); + REQUIRE(listOutput.str().find("Has.Note.Package") != std::string::npos); + REQUIRE(listOutput.str().find(Resource::LocString(Resource::String::PinDateAdded)) == std::string::npos); +} diff --git a/src/AppInstallerCLITests/SQLiteWrapper.cpp b/src/AppInstallerCLITests/SQLiteWrapper.cpp index 7aae4c75be..cecdc51b6a 100644 --- a/src/AppInstallerCLITests/SQLiteWrapper.cpp +++ b/src/AppInstallerCLITests/SQLiteWrapper.cpp @@ -1,889 +1,889 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include - -using namespace AppInstaller::SQLite; -using namespace std::string_literals; - -static const char* s_firstColumn = "first"; -static const char* s_secondColumn = "second"; -static const char* s_tableName = "simpletest"; -static const char* s_savepoint = "simplesave"; - -static const char* s_CreateSimpleTestTableSQL = R"( -CREATE TABLE [main].[simpletest]( - [first] INT, - [second] TEXT); -)"; - -static const char* s_insertToSimpleTestTableSQL = R"( -insert into simpletest (first, second) values (?, ?) -)"; - -static const char* s_selectFromSimpleTestTableSQL = R"( -select first, second from simpletest -)"; - -void CreateSimpleTestTable(Connection& connection) -{ - Builder::StatementBuilder builder; - builder.CreateTable(s_tableName).Columns({ - Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int), - Builder::ColumnBuilder(s_secondColumn, Builder::Type::Text), - }); - - Statement createTable = builder.Prepare(connection); - REQUIRE_FALSE(createTable.Step()); - REQUIRE(createTable.GetState() == Statement::State::Completed); -} - -void InsertIntoSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) -{ - Builder::StatementBuilder builder; - builder.InsertInto(s_tableName).Columns({ s_firstColumn, s_secondColumn }).Values(firstVal, secondVal); - Statement insert = builder.Prepare(connection); - - REQUIRE_FALSE(insert.Step()); - REQUIRE(insert.GetState() == Statement::State::Completed); -} - -void UpdateSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) -{ - Builder::StatementBuilder update; - update.Update(s_tableName).Set().Column(s_firstColumn).AssignValue(firstVal).Column(s_secondColumn).AssignValue(secondVal); - update.Execute(connection); -} - -void InsertIntoSimpleTestTableWithNull(Connection& connection, int firstVal) -{ - Builder::StatementBuilder builder; - builder.InsertInto(s_tableName).Columns({ s_firstColumn, s_secondColumn }).Values(firstVal, nullptr); - Statement insert = builder.Prepare(connection); - - REQUIRE_FALSE(insert.Step()); - REQUIRE(insert.GetState() == Statement::State::Completed); -} - -void SelectFromSimpleTestTableOnlyOneRow(Connection& connection, int firstVal, const std::string& secondVal) -{ - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName); - Statement select = builder.Prepare(connection); - - REQUIRE(select.Step()); - REQUIRE(select.GetState() == Statement::State::HasRow); - - int firstRead = select.GetColumn(0); - std::string secondRead = select.GetColumn(1); - - REQUIRE(firstVal == firstRead); - REQUIRE(secondVal == secondRead); - - auto tuple = select.GetRow(); - - REQUIRE(firstVal == std::get<0>(tuple)); - REQUIRE(secondVal == std::get<1>(tuple)); - - REQUIRE_FALSE(select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); - - select.Reset(); - REQUIRE(select.GetState() == Statement::State::Prepared); - - REQUIRE(select.Step()); - REQUIRE(select.GetState() == Statement::State::HasRow); -} - -TEST_CASE("SQLiteWrapperMemoryCreate", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - int firstVal = 1; - std::string secondVal = "test"; - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLiteWrapperFileCreateAndReopen", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - int firstVal = 1; - std::string secondVal = "test"; - - // Create the DB and some data - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - } - - // Reopen the DB and read data - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); - } -} - -TEST_CASE("SQLiteWrapperSavepointRollback", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - savepoint.Rollback(); - - Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); - REQUIRE(!select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); -} - -TEST_CASE("SQLiteWrapperSavepointRollbackOnDestruct", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - } - - Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); - REQUIRE(!select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); -} - -TEST_CASE("SQLiteWrapperSavepointCommit", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - savepoint.Commit(); - } - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLiteWrapperSavepointReuse", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - int firstVal = 1; - std::string secondVal = "test"; - - // Create the DB and some data - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - } - - // Reopen the DB and update with a single savepoint - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - Savepoint savepoint = Savepoint::Create(connection, s_savepoint); - - firstVal = 2; - secondVal = "test2"; - UpdateSimpleTestTable(connection, firstVal, secondVal); - - savepoint.Commit(); - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); - } - - // Reopen the DB and update with a multiple savepoint - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - { - Savepoint savepoint = Savepoint::Create(connection, s_savepoint); - - firstVal = 3; - secondVal = "test3"; - UpdateSimpleTestTable(connection, firstVal, secondVal); - } - - { - Savepoint savepoint = Savepoint::Create(connection, s_savepoint); - - firstVal = 4; - secondVal = "test4"; - UpdateSimpleTestTable(connection, firstVal, secondVal); - - savepoint.Commit(); - } - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); - } -} - -TEST_CASE("SQLiteWrapper_EscapeStringForLike", "[sqlitewrapper]") -{ - std::string escape(EscapeCharForLike); - - std::string input = "test"; - std::string output = EscapeStringForLike(input); - REQUIRE(input == output); - - input = EscapeCharForLike; - output = EscapeStringForLike(input); - REQUIRE((input + input) == output); - - input = "%"; - output = EscapeStringForLike(input); - REQUIRE((escape + input) == output); - - input = "_"; - output = EscapeStringForLike(input); - REQUIRE((escape + input) == output); - - input = "%_A_%"; - std::string expected = escape + "%" + escape + "_A" + escape + "_" + escape + "%"; - output = EscapeStringForLike(input); - REQUIRE(expected == output); -} - -TEST_CASE("SQLiteWrapper_BindWithEmbeddedNull", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - int firstVal = 1; - std::string secondVal = "test"; - secondVal[1] = '\0'; - - REQUIRE_THROWS_HR(InsertIntoSimpleTestTable(connection, firstVal, secondVal), APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL); -} - -TEST_CASE("SQLiteWrapper_PrepareFailure", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(std::string{ s_tableName } + "2").Where(s_firstColumn).Equals(2); - - REQUIRE_THROWS_HR(builder.Prepare(connection), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_ERROR)); -} - -TEST_CASE("SQLiteWrapper_BusyTimeout_None", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - wil::unique_event busy, done; - busy.create(); - done.create(); - - std::thread busyThread([&]() - { - Connection threadConnection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - Statement threadStatement = Statement::Create(threadConnection, "BEGIN EXCLUSIVE TRANSACTION"); - threadStatement.Execute(); - busy.SetEvent(); - done.wait(500); - }); - busyThread.detach(); - - busy.wait(500); - - Connection testConnection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - testConnection.SetBusyTimeout(0ms); - Statement testStatement = Statement::Create(testConnection, "BEGIN EXCLUSIVE TRANSACTION"); - REQUIRE_THROWS_HR(testStatement.Execute(), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_BUSY)); - - done.SetEvent(); -} - -TEST_CASE("SQLiteWrapper_BusyTimeout_Some", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - wil::unique_event busy, ready, done; - busy.create(); - ready.create(); - done.create(); - - std::thread busyThread([&]() - { - Connection threadConnection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - Statement threadBeginStatement = Statement::Create(threadConnection, "BEGIN EXCLUSIVE TRANSACTION"); - Statement threadCommitStatement = Statement::Create(threadConnection, "COMMIT"); - threadBeginStatement.Execute(); - busy.SetEvent(); - ready.wait(500); - done.wait(100); - threadCommitStatement.Execute(); - }); - busyThread.detach(); - - busy.wait(500); - - Connection testConnection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - testConnection.SetBusyTimeout(500ms); - Statement testStatement = Statement::Create(testConnection, "BEGIN EXCLUSIVE TRANSACTION"); - ready.SetEvent(); - testStatement.Execute(); - - done.SetEvent(); -} - -TEST_CASE("SQLiteWrapper_CloseConnectionOnError", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - Builder::StatementBuilder builder; - builder.CreateTable(s_tableName).Columns({ - Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int), - Builder::ColumnBuilder(s_secondColumn, Builder::Type::Text), - }); - - Statement createTable = builder.Prepare(connection); - REQUIRE_FALSE(createTable.Step()); - REQUIRE(createTable.GetState() == Statement::State::Completed); - - createTable.Reset(); - REQUIRE_THROWS(createTable.Step(true)); - - // Do anything that needs the connection - REQUIRE_THROWS_HR(connection.GetLastInsertRowID(), APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED); -} - -TEST_CASE("SQLBuilder_SimpleSelectBind", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, 1, "1"); - InsertIntoSimpleTestTable(connection, 2, "2"); - InsertIntoSimpleTestTable(connection, 3, "3"); - - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(2); - - auto statement = builder.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 2); - REQUIRE(statement.GetColumn(0) == "2"); - - REQUIRE(!statement.Step()); - - Builder::StatementBuilder buildCount; - buildCount.Select(Builder::RowCount).From(s_tableName); - - auto rows = buildCount.Prepare(connection); - - REQUIRE(rows.Step()); - REQUIRE(rows.GetColumn(0) == 3); - - REQUIRE(!rows.Step()); -} - -TEST_CASE("SQLBuilder_SimpleSelectUnbound", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, 1, "1"); - InsertIntoSimpleTestTable(connection, 2, "2"); - InsertIntoSimpleTestTable(connection, 3, "3"); - - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(Builder::Unbound); - - auto statement = builder.Prepare(connection); - - statement.Bind(1, 2); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 2); - REQUIRE(statement.GetColumn(0) == "2"); - - REQUIRE(!statement.Step()); -} - -TEST_CASE("SQLBuilder_SimpleSelectNull", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, 1, "1"); - InsertIntoSimpleTestTable(connection, 2, "2"); - InsertIntoSimpleTestTableWithNull(connection, 3); - - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).IsNull(); - - auto statement = builder.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 3); - REQUIRE(statement.GetColumnIsNull(1)); - - REQUIRE(!statement.Step()); -} - -TEST_CASE("SQLBuilder_SimpleSelectOptional", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, 1, "1"); - InsertIntoSimpleTestTable(connection, 2, "2"); - InsertIntoSimpleTestTableWithNull(connection, 3); - - std::optional secondValue; - - { - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).Equals(secondValue); - - auto statement = builder.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 3); - REQUIRE(statement.GetColumnIsNull(1)); - - REQUIRE(!statement.Step()); - } - - { - secondValue = "2"; - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).Equals(secondValue); - - auto statement = builder.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 2); - REQUIRE(statement.GetColumn(1) == "2"); - - REQUIRE(!statement.Step()); - } -} - -TEST_CASE("SQLBuilder_Update", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - int firstVal = 1; - std::string secondVal = "test"; - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); - - firstVal = 2; - secondVal = "testing"; - - UpdateSimpleTestTable(connection, firstVal, secondVal); - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLBuilder_UpdateEmptyOptional", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - int firstVal = 1; - std::string secondVal = "test"; - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - std::optional emptySecondVal; - - Builder::StatementBuilder update; - update.Update(s_tableName) - .Set() - .Column(s_secondColumn).AssignValue(emptySecondVal) - .Where(s_firstColumn).Equals(firstVal); - - update.Execute(connection); - - Builder::StatementBuilder select; - select.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(firstVal); - - auto statement = select.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == firstVal); - REQUIRE(statement.GetColumnIsNull(1)); - - REQUIRE(!statement.Step()); -} - -TEST_CASE("SQLBuilder_CaseInsensitive", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - Builder::StatementBuilder createTable; - createTable.CreateTable(s_tableName).Columns({ - Builder::ColumnBuilder(s_firstColumn, Builder::Type::Text).CollateNoCase() - }); - - createTable.Execute(connection); - - std::string upperCaseVal = "TEST"; - std::string lowerCaseVal = "test"; - - { - INFO("Insert initial value"); - Builder::StatementBuilder builder; - builder.InsertInto(s_tableName) - .Columns({ s_firstColumn }) - .Values(upperCaseVal); - - builder.Execute(connection); - } - - { - INFO("Retrieve using case-insensitive value"); - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn }).From(s_tableName).Where(s_firstColumn).Equals(lowerCaseVal); - - auto statement = builder.Prepare(connection); - REQUIRE(statement.Step()); - } -} - -TEST_CASE("SQLBuilder_CreateTable", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int testRun = GENERATE(0, 1, 2, 3, 4, 5, 6, 7); - - bool notNull = ((testRun & 1) != 0); - bool unique = ((testRun & 2) != 0); - bool pk = ((testRun & 4) != 0); - CAPTURE(notNull, unique, pk); - - Builder::StatementBuilder createTable; - createTable.CreateTable(s_tableName).Columns({ - Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int).NotNull(notNull).Unique(unique).PrimaryKey(pk) - }); - - createTable.Execute(connection); - - Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_tableName).Columns(s_firstColumn).Values(Builder::Unbound); - - Statement insertStatement = insertBuilder.Prepare(connection); - - { - INFO("Insert NULL"); - insertStatement.Bind(1, nullptr); - - if (notNull) - { - REQUIRE_THROWS_HR(insertStatement.Execute(), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_NOTNULL)); - } - else - { - insertStatement.Execute(); - } - } - - { - INFO("Insert unique values"); - insertStatement.Reset(); - insertStatement.Bind(1, 1); - insertStatement.Execute(); - - insertStatement.Reset(); - insertStatement.Bind(1, 2); - insertStatement.Execute(); - } - - { - INFO("Insert duplicate values"); - insertStatement.Reset(); - insertStatement.Bind(1, 1); - - if (unique || pk) - { - HRESULT expectedHR = S_OK; - if (pk) - { - expectedHR = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_PRIMARYKEY); - } - else - { - expectedHR = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_UNIQUE); - } - REQUIRE_THROWS_HR(insertStatement.Execute(), expectedHR); - } - else - { - insertStatement.Execute(); - } - } -} - -TEST_CASE("SQLBuilder_InsertValueBinding", "[sqlbuilder]") -{ - char const* const columns[] = { "a", "b", "c", "d", "e", "f" }; - - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - - { - INFO("Create table"); - Builder::StatementBuilder createTable; - createTable.CreateTable(s_tableName).BeginColumns(); - for (const auto c : columns) - { - createTable.Column(Builder::ColumnBuilder(c, Builder::Type::Int)); - } - createTable.EndColumns(); - createTable.Execute(connection); - } - - { - INFO("Insert values"); - Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_tableName).BeginColumns(); - for (const auto c : columns) - { - insertBuilder.Column(c); - } - insertBuilder.EndColumns().Values(0, 1, 2, 3, 4, 5); - insertBuilder.Execute(connection); - } - - { - INFO("Insert values"); - Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_tableName).BeginColumns(); - for (const auto c : columns) - { - insertBuilder.Column(c); - } - insertBuilder.EndColumns().BeginValues(); - insertBuilder.Value(5); - insertBuilder.Value(nullptr); - insertBuilder.Value(3); - insertBuilder.Value(std::optional{}); - insertBuilder.Value(std::optional{ 1 }); - insertBuilder.Value(Builder::Unbound); - insertBuilder.EndValues(); - insertBuilder.Execute(connection); - } - - { - INFO("Select values"); - Builder::StatementBuilder selectBuilder; - selectBuilder.Select(); - for (const auto c : columns) - { - selectBuilder.Column(c); - } - selectBuilder.From(s_tableName); - - Statement select = selectBuilder.Prepare(connection); - REQUIRE(select.Step()); - - for (int i = 0; i < ARRAYSIZE(columns); ++i) - { - REQUIRE(i == select.GetColumn(i)); - } - - REQUIRE(select.Step()); - - for (int i = 0; i < ARRAYSIZE(columns); ++i) - { - if (i & 1) - { - REQUIRE(select.GetColumnIsNull(i)); - } - else - { - REQUIRE((5 - i) == select.GetColumn(i)); - } - } - - REQUIRE(!select.Step()); - } -} - -TEST_CASE("SQLiteWrapperTransactionRollback", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - Transaction transaction = Transaction::Create(connection, "test_transaction", false); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - transaction.Rollback(); - - Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); - REQUIRE(!select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); -} - -TEST_CASE("SQLiteWrapperTransactionRollbackOnDestruct", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Transaction transaction = Transaction::Create(connection, "test_transaction", false); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - } - - Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); - REQUIRE(!select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); -} - -TEST_CASE("SQLiteWrapperTransactionCommit", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Transaction transaction = Transaction::Create(connection, "test_transaction", false); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - transaction.Commit(); - } - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLiteWrapperTransactionImmediate", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Transaction transaction = Transaction::Create(connection, "test_transaction", true); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - transaction.Commit(); - } - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLiteWrapperTransactionWriteConflict", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - connection.SetJournalMode("WAL"); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - Connection connection2 = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - std::chrono::milliseconds busyWait = 250ms; - connection2.SetBusyTimeout(busyWait); - - { - Transaction transaction = Transaction::Create(connection, "test_transaction", true); - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - // Start second transaction - std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); - std::chrono::system_clock::time_point end = start; - try - { - Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); - } - catch (...) - { - end = std::chrono::system_clock::now(); - } - - std::chrono::milliseconds duration = std::chrono::duration_cast(end - start); - REQUIRE(duration >= busyWait); - - transaction.Commit(); - - Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); - InsertIntoSimpleTestTable(connection2, firstVal, secondVal); - } - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include + +using namespace AppInstaller::SQLite; +using namespace std::string_literals; + +static const char* s_firstColumn = "first"; +static const char* s_secondColumn = "second"; +static const char* s_tableName = "simpletest"; +static const char* s_savepoint = "simplesave"; + +static const char* s_CreateSimpleTestTableSQL = R"( +CREATE TABLE [main].[simpletest]( + [first] INT, + [second] TEXT); +)"; + +static const char* s_insertToSimpleTestTableSQL = R"( +insert into simpletest (first, second) values (?, ?) +)"; + +static const char* s_selectFromSimpleTestTableSQL = R"( +select first, second from simpletest +)"; + +void CreateSimpleTestTable(Connection& connection) +{ + Builder::StatementBuilder builder; + builder.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int), + Builder::ColumnBuilder(s_secondColumn, Builder::Type::Text), + }); + + Statement createTable = builder.Prepare(connection); + REQUIRE_FALSE(createTable.Step()); + REQUIRE(createTable.GetState() == Statement::State::Completed); +} + +void InsertIntoSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) +{ + Builder::StatementBuilder builder; + builder.InsertInto(s_tableName).Columns({ s_firstColumn, s_secondColumn }).Values(firstVal, secondVal); + Statement insert = builder.Prepare(connection); + + REQUIRE_FALSE(insert.Step()); + REQUIRE(insert.GetState() == Statement::State::Completed); +} + +void UpdateSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) +{ + Builder::StatementBuilder update; + update.Update(s_tableName).Set().Column(s_firstColumn).AssignValue(firstVal).Column(s_secondColumn).AssignValue(secondVal); + update.Execute(connection); +} + +void InsertIntoSimpleTestTableWithNull(Connection& connection, int firstVal) +{ + Builder::StatementBuilder builder; + builder.InsertInto(s_tableName).Columns({ s_firstColumn, s_secondColumn }).Values(firstVal, nullptr); + Statement insert = builder.Prepare(connection); + + REQUIRE_FALSE(insert.Step()); + REQUIRE(insert.GetState() == Statement::State::Completed); +} + +void SelectFromSimpleTestTableOnlyOneRow(Connection& connection, int firstVal, const std::string& secondVal) +{ + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName); + Statement select = builder.Prepare(connection); + + REQUIRE(select.Step()); + REQUIRE(select.GetState() == Statement::State::HasRow); + + int firstRead = select.GetColumn(0); + std::string secondRead = select.GetColumn(1); + + REQUIRE(firstVal == firstRead); + REQUIRE(secondVal == secondRead); + + auto tuple = select.GetRow(); + + REQUIRE(firstVal == std::get<0>(tuple)); + REQUIRE(secondVal == std::get<1>(tuple)); + + REQUIRE_FALSE(select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); + + select.Reset(); + REQUIRE(select.GetState() == Statement::State::Prepared); + + REQUIRE(select.Step()); + REQUIRE(select.GetState() == Statement::State::HasRow); +} + +TEST_CASE("SQLiteWrapperMemoryCreate", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + int firstVal = 1; + std::string secondVal = "test"; + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperFileCreateAndReopen", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + int firstVal = 1; + std::string secondVal = "test"; + + // Create the DB and some data + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + // Reopen the DB and read data + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); + } +} + +TEST_CASE("SQLiteWrapperSavepointRollback", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + savepoint.Rollback(); + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperSavepointRollbackOnDestruct", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperSavepointCommit", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + savepoint.Commit(); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperSavepointReuse", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + int firstVal = 1; + std::string secondVal = "test"; + + // Create the DB and some data + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + // Reopen the DB and update with a single savepoint + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + Savepoint savepoint = Savepoint::Create(connection, s_savepoint); + + firstVal = 2; + secondVal = "test2"; + UpdateSimpleTestTable(connection, firstVal, secondVal); + + savepoint.Commit(); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); + } + + // Reopen the DB and update with a multiple savepoint + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + { + Savepoint savepoint = Savepoint::Create(connection, s_savepoint); + + firstVal = 3; + secondVal = "test3"; + UpdateSimpleTestTable(connection, firstVal, secondVal); + } + + { + Savepoint savepoint = Savepoint::Create(connection, s_savepoint); + + firstVal = 4; + secondVal = "test4"; + UpdateSimpleTestTable(connection, firstVal, secondVal); + + savepoint.Commit(); + } + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); + } +} + +TEST_CASE("SQLiteWrapper_EscapeStringForLike", "[sqlitewrapper]") +{ + std::string escape(EscapeCharForLike); + + std::string input = "test"; + std::string output = EscapeStringForLike(input); + REQUIRE(input == output); + + input = EscapeCharForLike; + output = EscapeStringForLike(input); + REQUIRE((input + input) == output); + + input = "%"; + output = EscapeStringForLike(input); + REQUIRE((escape + input) == output); + + input = "_"; + output = EscapeStringForLike(input); + REQUIRE((escape + input) == output); + + input = "%_A_%"; + std::string expected = escape + "%" + escape + "_A" + escape + "_" + escape + "%"; + output = EscapeStringForLike(input); + REQUIRE(expected == output); +} + +TEST_CASE("SQLiteWrapper_BindWithEmbeddedNull", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + int firstVal = 1; + std::string secondVal = "test"; + secondVal[1] = '\0'; + + REQUIRE_THROWS_HR(InsertIntoSimpleTestTable(connection, firstVal, secondVal), APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL); +} + +TEST_CASE("SQLiteWrapper_PrepareFailure", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(std::string{ s_tableName } + "2").Where(s_firstColumn).Equals(2); + + REQUIRE_THROWS_HR(builder.Prepare(connection), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_ERROR)); +} + +TEST_CASE("SQLiteWrapper_BusyTimeout_None", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + wil::unique_event busy, done; + busy.create(); + done.create(); + + std::thread busyThread([&]() + { + Connection threadConnection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + Statement threadStatement = Statement::Create(threadConnection, "BEGIN EXCLUSIVE TRANSACTION"); + threadStatement.Execute(); + busy.SetEvent(); + done.wait(500); + }); + busyThread.detach(); + + busy.wait(500); + + Connection testConnection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + testConnection.SetBusyTimeout(0ms); + Statement testStatement = Statement::Create(testConnection, "BEGIN EXCLUSIVE TRANSACTION"); + REQUIRE_THROWS_HR(testStatement.Execute(), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_BUSY)); + + done.SetEvent(); +} + +TEST_CASE("SQLiteWrapper_BusyTimeout_Some", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + wil::unique_event busy, ready, done; + busy.create(); + ready.create(); + done.create(); + + std::thread busyThread([&]() + { + Connection threadConnection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + Statement threadBeginStatement = Statement::Create(threadConnection, "BEGIN EXCLUSIVE TRANSACTION"); + Statement threadCommitStatement = Statement::Create(threadConnection, "COMMIT"); + threadBeginStatement.Execute(); + busy.SetEvent(); + ready.wait(500); + done.wait(100); + threadCommitStatement.Execute(); + }); + busyThread.detach(); + + busy.wait(500); + + Connection testConnection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + testConnection.SetBusyTimeout(500ms); + Statement testStatement = Statement::Create(testConnection, "BEGIN EXCLUSIVE TRANSACTION"); + ready.SetEvent(); + testStatement.Execute(); + + done.SetEvent(); +} + +TEST_CASE("SQLiteWrapper_CloseConnectionOnError", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + Builder::StatementBuilder builder; + builder.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int), + Builder::ColumnBuilder(s_secondColumn, Builder::Type::Text), + }); + + Statement createTable = builder.Prepare(connection); + REQUIRE_FALSE(createTable.Step()); + REQUIRE(createTable.GetState() == Statement::State::Completed); + + createTable.Reset(); + REQUIRE_THROWS(createTable.Step(true)); + + // Do anything that needs the connection + REQUIRE_THROWS_HR(connection.GetLastInsertRowID(), APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED); +} + +TEST_CASE("SQLBuilder_SimpleSelectBind", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, 1, "1"); + InsertIntoSimpleTestTable(connection, 2, "2"); + InsertIntoSimpleTestTable(connection, 3, "3"); + + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(2); + + auto statement = builder.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 2); + REQUIRE(statement.GetColumn(0) == "2"); + + REQUIRE(!statement.Step()); + + Builder::StatementBuilder buildCount; + buildCount.Select(Builder::RowCount).From(s_tableName); + + auto rows = buildCount.Prepare(connection); + + REQUIRE(rows.Step()); + REQUIRE(rows.GetColumn(0) == 3); + + REQUIRE(!rows.Step()); +} + +TEST_CASE("SQLBuilder_SimpleSelectUnbound", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, 1, "1"); + InsertIntoSimpleTestTable(connection, 2, "2"); + InsertIntoSimpleTestTable(connection, 3, "3"); + + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(Builder::Unbound); + + auto statement = builder.Prepare(connection); + + statement.Bind(1, 2); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 2); + REQUIRE(statement.GetColumn(0) == "2"); + + REQUIRE(!statement.Step()); +} + +TEST_CASE("SQLBuilder_SimpleSelectNull", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, 1, "1"); + InsertIntoSimpleTestTable(connection, 2, "2"); + InsertIntoSimpleTestTableWithNull(connection, 3); + + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).IsNull(); + + auto statement = builder.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 3); + REQUIRE(statement.GetColumnIsNull(1)); + + REQUIRE(!statement.Step()); +} + +TEST_CASE("SQLBuilder_SimpleSelectOptional", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, 1, "1"); + InsertIntoSimpleTestTable(connection, 2, "2"); + InsertIntoSimpleTestTableWithNull(connection, 3); + + std::optional secondValue; + + { + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).Equals(secondValue); + + auto statement = builder.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 3); + REQUIRE(statement.GetColumnIsNull(1)); + + REQUIRE(!statement.Step()); + } + + { + secondValue = "2"; + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).Equals(secondValue); + + auto statement = builder.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 2); + REQUIRE(statement.GetColumn(1) == "2"); + + REQUIRE(!statement.Step()); + } +} + +TEST_CASE("SQLBuilder_Update", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + int firstVal = 1; + std::string secondVal = "test"; + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); + + firstVal = 2; + secondVal = "testing"; + + UpdateSimpleTestTable(connection, firstVal, secondVal); + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLBuilder_UpdateEmptyOptional", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + int firstVal = 1; + std::string secondVal = "test"; + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + std::optional emptySecondVal; + + Builder::StatementBuilder update; + update.Update(s_tableName) + .Set() + .Column(s_secondColumn).AssignValue(emptySecondVal) + .Where(s_firstColumn).Equals(firstVal); + + update.Execute(connection); + + Builder::StatementBuilder select; + select.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(firstVal); + + auto statement = select.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == firstVal); + REQUIRE(statement.GetColumnIsNull(1)); + + REQUIRE(!statement.Step()); +} + +TEST_CASE("SQLBuilder_CaseInsensitive", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + Builder::StatementBuilder createTable; + createTable.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Text).CollateNoCase() + }); + + createTable.Execute(connection); + + std::string upperCaseVal = "TEST"; + std::string lowerCaseVal = "test"; + + { + INFO("Insert initial value"); + Builder::StatementBuilder builder; + builder.InsertInto(s_tableName) + .Columns({ s_firstColumn }) + .Values(upperCaseVal); + + builder.Execute(connection); + } + + { + INFO("Retrieve using case-insensitive value"); + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn }).From(s_tableName).Where(s_firstColumn).Equals(lowerCaseVal); + + auto statement = builder.Prepare(connection); + REQUIRE(statement.Step()); + } +} + +TEST_CASE("SQLBuilder_CreateTable", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int testRun = GENERATE(0, 1, 2, 3, 4, 5, 6, 7); + + bool notNull = ((testRun & 1) != 0); + bool unique = ((testRun & 2) != 0); + bool pk = ((testRun & 4) != 0); + CAPTURE(notNull, unique, pk); + + Builder::StatementBuilder createTable; + createTable.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int).NotNull(notNull).Unique(unique).PrimaryKey(pk) + }); + + createTable.Execute(connection); + + Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_tableName).Columns(s_firstColumn).Values(Builder::Unbound); + + Statement insertStatement = insertBuilder.Prepare(connection); + + { + INFO("Insert NULL"); + insertStatement.Bind(1, nullptr); + + if (notNull) + { + REQUIRE_THROWS_HR(insertStatement.Execute(), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_NOTNULL)); + } + else + { + insertStatement.Execute(); + } + } + + { + INFO("Insert unique values"); + insertStatement.Reset(); + insertStatement.Bind(1, 1); + insertStatement.Execute(); + + insertStatement.Reset(); + insertStatement.Bind(1, 2); + insertStatement.Execute(); + } + + { + INFO("Insert duplicate values"); + insertStatement.Reset(); + insertStatement.Bind(1, 1); + + if (unique || pk) + { + HRESULT expectedHR = S_OK; + if (pk) + { + expectedHR = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_PRIMARYKEY); + } + else + { + expectedHR = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_UNIQUE); + } + REQUIRE_THROWS_HR(insertStatement.Execute(), expectedHR); + } + else + { + insertStatement.Execute(); + } + } +} + +TEST_CASE("SQLBuilder_InsertValueBinding", "[sqlbuilder]") +{ + char const* const columns[] = { "a", "b", "c", "d", "e", "f" }; + + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + + { + INFO("Create table"); + Builder::StatementBuilder createTable; + createTable.CreateTable(s_tableName).BeginColumns(); + for (const auto c : columns) + { + createTable.Column(Builder::ColumnBuilder(c, Builder::Type::Int)); + } + createTable.EndColumns(); + createTable.Execute(connection); + } + + { + INFO("Insert values"); + Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_tableName).BeginColumns(); + for (const auto c : columns) + { + insertBuilder.Column(c); + } + insertBuilder.EndColumns().Values(0, 1, 2, 3, 4, 5); + insertBuilder.Execute(connection); + } + + { + INFO("Insert values"); + Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_tableName).BeginColumns(); + for (const auto c : columns) + { + insertBuilder.Column(c); + } + insertBuilder.EndColumns().BeginValues(); + insertBuilder.Value(5); + insertBuilder.Value(nullptr); + insertBuilder.Value(3); + insertBuilder.Value(std::optional{}); + insertBuilder.Value(std::optional{ 1 }); + insertBuilder.Value(Builder::Unbound); + insertBuilder.EndValues(); + insertBuilder.Execute(connection); + } + + { + INFO("Select values"); + Builder::StatementBuilder selectBuilder; + selectBuilder.Select(); + for (const auto c : columns) + { + selectBuilder.Column(c); + } + selectBuilder.From(s_tableName); + + Statement select = selectBuilder.Prepare(connection); + REQUIRE(select.Step()); + + for (int i = 0; i < ARRAYSIZE(columns); ++i) + { + REQUIRE(i == select.GetColumn(i)); + } + + REQUIRE(select.Step()); + + for (int i = 0; i < ARRAYSIZE(columns); ++i) + { + if (i & 1) + { + REQUIRE(select.GetColumnIsNull(i)); + } + else + { + REQUIRE((5 - i) == select.GetColumn(i)); + } + } + + REQUIRE(!select.Step()); + } +} + +TEST_CASE("SQLiteWrapperTransactionRollback", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Rollback(); + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperTransactionRollbackOnDestruct", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperTransactionCommit", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Commit(); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperTransactionImmediate", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", true); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Commit(); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperTransactionWriteConflict", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + connection.SetJournalMode("WAL"); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + Connection connection2 = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + std::chrono::milliseconds busyWait = 250ms; + connection2.SetBusyTimeout(busyWait); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", true); + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + // Start second transaction + std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); + std::chrono::system_clock::time_point end = start; + try + { + Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); + } + catch (...) + { + end = std::chrono::system_clock::now(); + } + + std::chrono::milliseconds duration = std::chrono::duration_cast(end - start); + REQUIRE(duration >= busyWait); + + transaction.Commit(); + + Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); + InsertIntoSimpleTestTable(connection2, firstVal, secondVal); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index 247b9d7176..1df9c5b748 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -1,128 +1,128 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winget/Manifest.h" -#include "AppInstallerVersions.h" -#include -#include -#include - -namespace AppInstaller::Pinning -{ - // The pin types are ordered by how "strict" they are. - // Meaning, the one that is more restrictive goes later. - // This is used to decide which pin to report if there are multiple pins. - enum class PinType : int64_t - { - // Unknown pin type or not pinned - Unknown, - // Pinned by the manifest using the RequiresExplicitUpgrade field. - // Behaves the same as Pinning pins - PinnedByManifest, - // The package is excluded from 'upgrade --all', unless '--include-pinned' is added. - // 'upgrade ' is not blocked. - Pinning, - // The package is pinned to a specific version range. - Gating, - // The package is blocked from 'upgrade --all' and 'upgrade '. - // User has to unblock to allow update. - Blocking, - }; - - std::string_view ToString(PinType type); - PinType ConvertToPinTypeEnum(std::string_view in); - - // Determines which of two pin types is more strict. - bool IsStricter(PinType first, PinType second); - - // Returns the stricter of two pin types. - PinType Stricter(PinType first, PinType second); - - // The set of values needed to uniquely identify a Pin. - // A Pin can apply to an installed package or to an available package. - // Pins on available packages can persist when an app is updated outside of winget, - // but it's hard to have them work when there are multiple installed packages for the same available package. - // Pins on installed packages work fine when there are multiple installed packages for the same available, - // but they break when the package is updated outside of winget. - struct PinKey - { - PinKey() = default; - PinKey(const Manifest::Manifest::string_t& packageId, std::string_view sourceId) - : PackageId(packageId), SourceId(sourceId) {} - - // Gets a pin key that refers to an installed package by its ProductCode or PackageFamilyName. - // The sourceId used is a special string to distinguish from available packages. - static PinKey GetPinKeyForInstalled(std::string_view systemReferenceString); - - bool IsForInstalled() const; - - bool operator==(const PinKey& other) const - { - return PackageId == other.PackageId - && SourceId == other.SourceId; - } - - bool operator!=(const PinKey& other) const - { - return !(*this == other); - } - - bool operator<(const PinKey& other) const - { - // std::tie implements tuple comparison, wherein it checks the first item in the tuple, - // iff the first elements are equal, then the second element is used for comparison, and so on - return std::tie(PackageId, SourceId) < std::tie(other.PackageId, other.SourceId); - } - - // Used for logging - std::string ToString() const; - - std::string PackageId; - std::string SourceId; - }; - - struct Pin - { - Pin(const Pin&) = default; - Pin& operator=(const Pin& other) = default; - - Pin(Pin&&) = default; - Pin& operator=(Pin&&) = default; - - static Pin CreateBlockingPin(PinKey&& pinKey); - static Pin CreatePinningPin(PinKey&& pinKey); - static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion&& gatedVersion); - - static Pin CreateBlockingPin(const PinKey& pinKey) { return CreateBlockingPin(PinKey{ pinKey }); } - static Pin CreatePinningPin(const PinKey& pinKey) { return CreatePinningPin(PinKey{ pinKey }); } - static Pin CreateGatingPin(const PinKey& pinKey, const Utility::GatedVersion& gatedVersion) { return CreateGatingPin(PinKey{ pinKey }, Utility::GatedVersion{ gatedVersion }); } - - PinType GetType() const { return m_type; } - const PinKey& GetKey() const { return m_key; } - const Utility::GatedVersion& GetGatedVersion() const { return m_gatedVersion; } - const std::optional& GetDateAdded() const { return m_dateAdded; } - const std::optional& GetNote() const { return m_note; } - - void SetDateAdded(std::optional dateAdded) { m_dateAdded = std::move(dateAdded); } - void SetNote(std::optional note) { m_note = std::move(note); } - - bool operator==(const Pin& other) const; - bool operator<(const Pin& other) const - { - return std::make_pair(m_type, m_key) < std::make_pair(other.m_type, other.m_key); - } - - // Used for logging - std::string ToString() const; - - private: - Pin(PinType type, PinKey&& pinKey, Utility::GatedVersion&& gatedVersion = {}) - : m_type(type), m_key(std::move(pinKey)), m_gatedVersion(std::move(gatedVersion)) {} - - PinType m_type = PinType::Unknown; - PinKey m_key; - Utility::GatedVersion m_gatedVersion; - std::optional m_dateAdded; - std::optional m_note; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winget/Manifest.h" +#include "AppInstallerVersions.h" +#include +#include +#include + +namespace AppInstaller::Pinning +{ + // The pin types are ordered by how "strict" they are. + // Meaning, the one that is more restrictive goes later. + // This is used to decide which pin to report if there are multiple pins. + enum class PinType : int64_t + { + // Unknown pin type or not pinned + Unknown, + // Pinned by the manifest using the RequiresExplicitUpgrade field. + // Behaves the same as Pinning pins + PinnedByManifest, + // The package is excluded from 'upgrade --all', unless '--include-pinned' is added. + // 'upgrade ' is not blocked. + Pinning, + // The package is pinned to a specific version range. + Gating, + // The package is blocked from 'upgrade --all' and 'upgrade '. + // User has to unblock to allow update. + Blocking, + }; + + std::string_view ToString(PinType type); + PinType ConvertToPinTypeEnum(std::string_view in); + + // Determines which of two pin types is more strict. + bool IsStricter(PinType first, PinType second); + + // Returns the stricter of two pin types. + PinType Stricter(PinType first, PinType second); + + // The set of values needed to uniquely identify a Pin. + // A Pin can apply to an installed package or to an available package. + // Pins on available packages can persist when an app is updated outside of winget, + // but it's hard to have them work when there are multiple installed packages for the same available package. + // Pins on installed packages work fine when there are multiple installed packages for the same available, + // but they break when the package is updated outside of winget. + struct PinKey + { + PinKey() = default; + PinKey(const Manifest::Manifest::string_t& packageId, std::string_view sourceId) + : PackageId(packageId), SourceId(sourceId) {} + + // Gets a pin key that refers to an installed package by its ProductCode or PackageFamilyName. + // The sourceId used is a special string to distinguish from available packages. + static PinKey GetPinKeyForInstalled(std::string_view systemReferenceString); + + bool IsForInstalled() const; + + bool operator==(const PinKey& other) const + { + return PackageId == other.PackageId + && SourceId == other.SourceId; + } + + bool operator!=(const PinKey& other) const + { + return !(*this == other); + } + + bool operator<(const PinKey& other) const + { + // std::tie implements tuple comparison, wherein it checks the first item in the tuple, + // iff the first elements are equal, then the second element is used for comparison, and so on + return std::tie(PackageId, SourceId) < std::tie(other.PackageId, other.SourceId); + } + + // Used for logging + std::string ToString() const; + + std::string PackageId; + std::string SourceId; + }; + + struct Pin + { + Pin(const Pin&) = default; + Pin& operator=(const Pin& other) = default; + + Pin(Pin&&) = default; + Pin& operator=(Pin&&) = default; + + static Pin CreateBlockingPin(PinKey&& pinKey); + static Pin CreatePinningPin(PinKey&& pinKey); + static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion&& gatedVersion); + + static Pin CreateBlockingPin(const PinKey& pinKey) { return CreateBlockingPin(PinKey{ pinKey }); } + static Pin CreatePinningPin(const PinKey& pinKey) { return CreatePinningPin(PinKey{ pinKey }); } + static Pin CreateGatingPin(const PinKey& pinKey, const Utility::GatedVersion& gatedVersion) { return CreateGatingPin(PinKey{ pinKey }, Utility::GatedVersion{ gatedVersion }); } + + PinType GetType() const { return m_type; } + const PinKey& GetKey() const { return m_key; } + const Utility::GatedVersion& GetGatedVersion() const { return m_gatedVersion; } + const std::optional& GetDateAdded() const { return m_dateAdded; } + const std::optional& GetNote() const { return m_note; } + + void SetDateAdded(std::optional dateAdded) { m_dateAdded = std::move(dateAdded); } + void SetNote(std::optional note) { m_note = std::move(note); } + + bool operator==(const Pin& other) const; + bool operator<(const Pin& other) const + { + return std::make_pair(m_type, m_key) < std::make_pair(other.m_type, other.m_key); + } + + // Used for logging + std::string ToString() const; + + private: + Pin(PinType type, PinKey&& pinKey, Utility::GatedVersion&& gatedVersion = {}) + : m_type(type), m_key(std::move(pinKey)), m_gatedVersion(std::move(gatedVersion)) {} + + PinType m_type = PinType::Unknown; + PinKey m_key; + Utility::GatedVersion m_gatedVersion; + std::optional m_dateAdded; + std::optional m_note; + }; +} diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 48f807841f..8a9350d542 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -1,531 +1,531 @@ - - - - - true - true - true - 15.0 - {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} - Win32Proj - AppInstallerRepositoryCore - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - ARM64 - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - StaticLibrary - - - true - true - - - false - true - false - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - - - false - Windows - Windows - - - - - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - false - - - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + true + true + true + 15.0 + {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + Win32Proj + AppInstallerRepositoryCore + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + ARM64 + + + Debug + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + StaticLibrary + + + true + true + + + false + true + false + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + + + false + Windows + Windows + + + + + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + false + + + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index c8eaf55cbc..ae35be7dda 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -1,838 +1,838 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {2d97761a-4967-4ece-b5fd-d96f346731ed} - - - {7996cf09-4e36-4009-b48d-53ffc3bfa956} - - - {cff561cd-211b-4cab-87f5-39359eef1e8d} - - - {69ce2e35-fe7f-41af-bd47-91a70131d167} - - - {aef8989c-44fd-4848-ae2c-c81d3f2e2c72} - - - {dc8b6163-e9b5-4d05-87bc-d6098afe0f92} - - - {7f16f6e0-12c6-4bb9-885e-3f8b666d5f74} - - - {15a1ee83-4118-40fe-ba43-c54d7185d505} - - - {8a93b62f-d065-4048-b43a-a2b14b70c330} - - - {6bcbaf7a-289f-4d0b-b128-67bef903745c} - - - {15639b2c-ce61-4a18-995a-a73cf1a5817e} - - - {9d8095ed-07de-4bc9-bfe7-630b781586d0} - - - {2cc20cdb-dcb2-4e0e-b04f-e2d838146100} - - - {aa7315bc-4eb0-4280-9572-f5a25f6e73ad} - - - {dcae9c55-cdd7-4381-8acd-3554896608a5} - - - {e31c8e5b-ed2c-43c8-b91b-db8ec4c52f71} - - - {84a55def-9fb8-4c90-8d5a-2cedc171940b} - - - {edef5ff7-9bfe-48f8-a179-e343d1a8b57f} - - - {4f5950e2-1713-4c1e-84ce-f868cfcb3a68} - - - {d9d70cf5-ce04-4db2-a0ec-970dd0ad22b6} - - - {f131c993-1136-4f4c-85a9-8606c6d297a8} - - - {78cf34a8-b868-4393-ad9c-630940569186} - - - {f05e19bb-2161-4ab0-9d04-2dfa2d3eb3c6} - - - {a3e2f7c1-5d84-4b9e-8f02-1c6d3a7b0e45} - - - {21da32f5-b918-436e-96a9-465525f90259} - - - {b2e78f3d-931e-432c-8485-255b1dbc9db7} - - - {f610927a-6f1d-42c5-9ad9-b59790091944} - - - {a3f9c7ed-f487-40d6-9ee7-e9a052e55c29} - - - {f42acfce-4fc0-435b-a9a2-bf5528aecbcc} - - - {7fd6c265-81c0-4c6e-87b2-24bef117e21d} - - - {34442899-29e5-4183-96ba-a1e8740146be} - - - {8edd7018-8836-4b15-84c1-998391e19038} - - - {7464e3ff-7a60-4bb6-8806-70562382043b} - - - {da801426-6d3b-40ca-b204-152e7e18067b} - - - {1a1efb9f-7332-4094-bb98-a4c51ea68b24} - - - {c29ae113-5eb6-41af-b64d-fac925dcf2c2} - - - {2075b51e-aa11-473a-bae0-e0d4366f926b} - - - {a3b7c8d1-e2f4-5a6b-9c0d-1e2f3a4b5c6d} - - - {b4c8d9e2-f3a5-6b7c-0d1e-2f3a4b5c6d7e} - - - - - Header Files - - - Microsoft - - - Microsoft\Schema - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Header Files - - - Microsoft - - - Microsoft - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_1 - - - Microsoft\Schema\1_1 - - - Microsoft\Schema\1_1 - - - Microsoft\Schema\1_1 - - - Microsoft - - - Header Files - - - Microsoft\Schema\1_1 - - - Microsoft - - - Microsoft - - - Microsoft\Schema\1_2 - - - Microsoft\Schema\1_2 - - - Microsoft\Schema\1_2 - - - Microsoft\Schema\1_2 - - - Rest\Schema\1_0 - - - Rest\Schema - - - Rest - - - Rest - - - Rest - - - Rest\Schema\1_0\Json - - - Rest\Schema\1_0\Json - - - Rest\Schema\1_0\Json - - - Rest\Schema - - - Microsoft\Schema\1_3 - - - Microsoft\Schema\1_3 - - - Header Files - - - Header Files - - - Microsoft - - - Rest\Schema - - - Rest\Schema\1_1 - - - Rest\Schema\1_1\Json - - - Rest\Schema\1_1\Json - - - Microsoft - - - Public\winget - - - Public\winget - - - Public\winget - - - Header Files - - - Microsoft\Schema\1_4 - - - Microsoft\Schema\1_4 - - - Header Files - - - Header Files - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Microsoft\Schema\1_5 - - - Microsoft\Schema\1_5 - - - Header Files - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_6 - - - Microsoft\Schema\1_6 - - - Microsoft\Schema\1_6 - - - Microsoft\Schema\Portable_1_0 - - - Microsoft\Schema\Portable_1_0 - - - Microsoft\Schema - - - Rest\Schema\1_4\Json - - - Rest\Schema\1_4 - - - Rest\Schema\1_4\Json - - - Rest\Schema\1_5\Json - - - Rest\Schema\1_5 - - - Rest\Schema - - - Rest\Schema - - - Microsoft\Schema - - - Microsoft - - - Microsoft\Schema\Pinning_1_0 - - - Microsoft\Schema\Pinning_1_0 - - - Microsoft\Schema\Pinning_1_1 - - - Microsoft\Schema\Pinning_1_1 - - - Public\winget - - - Header Files - - - Public\winget - - - Rest\Schema\1_6\Json - - - Rest\Schema\1_6 - - - Microsoft\Schema\1_7 - - - Microsoft\Schema - - - Microsoft\Schema\Checkpoint_1_0 - - - Microsoft\Schema\Checkpoint_1_0 - - - Microsoft\Schema\Checkpoint_1_0 - - - Public\winget - - - Header Files - - - Rest\Schema\1_7 - - - Rest\Schema - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Rest\Schema\1_7\Json - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema - - - Microsoft - - - Microsoft - - - Rest\Schema\1_9\Json - - - Rest\Schema\1_9 - - - Rest\Schema\1_10 - - - Rest\Schema\1_10\Json - - - Rest\Schema\1_12 - - - Rest\Schema\1_12\Json - - - Rest\Schema\1_28 - - - Rest\Schema\1_28\Json - - - Rest - - - Header Files - - - - - Source Files - - - Microsoft - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Source Files - - - Microsoft - - - Microsoft - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_1 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_1 - - - Microsoft - - - Source Files - - - Microsoft\Schema\1_1 - - - Microsoft - - - Microsoft - - - Microsoft\Schema\1_2 - - - Microsoft\Schema\1_2 - - - Rest\Schema\1_0 - - - Rest - - - Rest - - - Rest - - - Rest\Schema\1_0\Json - - - Rest\Schema\1_0\Json - - - Rest\Schema\1_0\Json - - - Microsoft\Schema\1_3 - - - Source Files - - - Source Files - - - Rest\Schema - - - Microsoft - - - Rest\Schema\1_1 - - - Rest\Schema\1_1\Json - - - Rest\Schema\1_1\Json - - - Microsoft - - - Source Files - - - Source Files - - - Microsoft\Schema\1_4 - - - Microsoft\Schema\1_4 - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Microsoft\Schema\1_5 - - - Source Files - - - Microsoft\Schema\1_6 - - - Microsoft\Schema\1_6 - - - Microsoft - - - Microsoft\Schema\Portable_1_0 - - - Microsoft\Schema\Portable_1_0 - - - Rest\Schema\1_4\Json - - - Rest\Schema\1_4 - - - Rest\Schema\1_4\Json - - - Rest\Schema\1_5\Json - - - Rest\Schema\1_5 - - - Rest\Schema - - - Rest\Schema - - - Source Files - - - Source Files - - - Microsoft\Schema\Pinning_1_0 - - - Microsoft\Schema\Pinning_1_1 - - - Source Files - - - Source Files - - - Rest\Schema\1_6\Json - - - Rest\Schema\1_6 - - - Microsoft\Schema\1_7 - - - Source Files - - - Microsoft\Schema\Checkpoint_1_0 - - - Microsoft\Schema\Checkpoint_1_0 - - - Source Files - - - Microsoft\Schema\Checkpoint_1_0 - - - Microsoft\Schema\Pinning_1_0 - - - Microsoft\Schema\Pinning_1_1 - - - Rest\Schema\1_7 - - - Rest\Schema - - - Source Files - - - Source Files - - - Rest\Schema\1_7\Json - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft - - - Microsoft - - - Rest\Schema\1_9\Json - - - Rest\Schema\1_9 - - - Rest\Schema\1_10 - - - Rest\Schema\1_10\Json - - - Rest\Schema\1_12 - - - Rest\Schema\1_12\Json - - - Rest\Schema\1_28 - - - Rest\Schema\1_28\Json - - - Rest - - - Source Files - - - - - - - Microsoft - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {2d97761a-4967-4ece-b5fd-d96f346731ed} + + + {7996cf09-4e36-4009-b48d-53ffc3bfa956} + + + {cff561cd-211b-4cab-87f5-39359eef1e8d} + + + {69ce2e35-fe7f-41af-bd47-91a70131d167} + + + {aef8989c-44fd-4848-ae2c-c81d3f2e2c72} + + + {dc8b6163-e9b5-4d05-87bc-d6098afe0f92} + + + {7f16f6e0-12c6-4bb9-885e-3f8b666d5f74} + + + {15a1ee83-4118-40fe-ba43-c54d7185d505} + + + {8a93b62f-d065-4048-b43a-a2b14b70c330} + + + {6bcbaf7a-289f-4d0b-b128-67bef903745c} + + + {15639b2c-ce61-4a18-995a-a73cf1a5817e} + + + {9d8095ed-07de-4bc9-bfe7-630b781586d0} + + + {2cc20cdb-dcb2-4e0e-b04f-e2d838146100} + + + {aa7315bc-4eb0-4280-9572-f5a25f6e73ad} + + + {dcae9c55-cdd7-4381-8acd-3554896608a5} + + + {e31c8e5b-ed2c-43c8-b91b-db8ec4c52f71} + + + {84a55def-9fb8-4c90-8d5a-2cedc171940b} + + + {edef5ff7-9bfe-48f8-a179-e343d1a8b57f} + + + {4f5950e2-1713-4c1e-84ce-f868cfcb3a68} + + + {d9d70cf5-ce04-4db2-a0ec-970dd0ad22b6} + + + {f131c993-1136-4f4c-85a9-8606c6d297a8} + + + {78cf34a8-b868-4393-ad9c-630940569186} + + + {f05e19bb-2161-4ab0-9d04-2dfa2d3eb3c6} + + + {a3e2f7c1-5d84-4b9e-8f02-1c6d3a7b0e45} + + + {21da32f5-b918-436e-96a9-465525f90259} + + + {b2e78f3d-931e-432c-8485-255b1dbc9db7} + + + {f610927a-6f1d-42c5-9ad9-b59790091944} + + + {a3f9c7ed-f487-40d6-9ee7-e9a052e55c29} + + + {f42acfce-4fc0-435b-a9a2-bf5528aecbcc} + + + {7fd6c265-81c0-4c6e-87b2-24bef117e21d} + + + {34442899-29e5-4183-96ba-a1e8740146be} + + + {8edd7018-8836-4b15-84c1-998391e19038} + + + {7464e3ff-7a60-4bb6-8806-70562382043b} + + + {da801426-6d3b-40ca-b204-152e7e18067b} + + + {1a1efb9f-7332-4094-bb98-a4c51ea68b24} + + + {c29ae113-5eb6-41af-b64d-fac925dcf2c2} + + + {2075b51e-aa11-473a-bae0-e0d4366f926b} + + + {a3b7c8d1-e2f4-5a6b-9c0d-1e2f3a4b5c6d} + + + {b4c8d9e2-f3a5-6b7c-0d1e-2f3a4b5c6d7e} + + + + + Header Files + + + Microsoft + + + Microsoft\Schema + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Header Files + + + Microsoft + + + Microsoft + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_1 + + + Microsoft\Schema\1_1 + + + Microsoft\Schema\1_1 + + + Microsoft\Schema\1_1 + + + Microsoft + + + Header Files + + + Microsoft\Schema\1_1 + + + Microsoft + + + Microsoft + + + Microsoft\Schema\1_2 + + + Microsoft\Schema\1_2 + + + Microsoft\Schema\1_2 + + + Microsoft\Schema\1_2 + + + Rest\Schema\1_0 + + + Rest\Schema + + + Rest + + + Rest + + + Rest + + + Rest\Schema\1_0\Json + + + Rest\Schema\1_0\Json + + + Rest\Schema\1_0\Json + + + Rest\Schema + + + Microsoft\Schema\1_3 + + + Microsoft\Schema\1_3 + + + Header Files + + + Header Files + + + Microsoft + + + Rest\Schema + + + Rest\Schema\1_1 + + + Rest\Schema\1_1\Json + + + Rest\Schema\1_1\Json + + + Microsoft + + + Public\winget + + + Public\winget + + + Public\winget + + + Header Files + + + Microsoft\Schema\1_4 + + + Microsoft\Schema\1_4 + + + Header Files + + + Header Files + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Microsoft\Schema\1_5 + + + Microsoft\Schema\1_5 + + + Header Files + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_6 + + + Microsoft\Schema\1_6 + + + Microsoft\Schema\1_6 + + + Microsoft\Schema\Portable_1_0 + + + Microsoft\Schema\Portable_1_0 + + + Microsoft\Schema + + + Rest\Schema\1_4\Json + + + Rest\Schema\1_4 + + + Rest\Schema\1_4\Json + + + Rest\Schema\1_5\Json + + + Rest\Schema\1_5 + + + Rest\Schema + + + Rest\Schema + + + Microsoft\Schema + + + Microsoft + + + Microsoft\Schema\Pinning_1_0 + + + Microsoft\Schema\Pinning_1_0 + + + Microsoft\Schema\Pinning_1_1 + + + Microsoft\Schema\Pinning_1_1 + + + Public\winget + + + Header Files + + + Public\winget + + + Rest\Schema\1_6\Json + + + Rest\Schema\1_6 + + + Microsoft\Schema\1_7 + + + Microsoft\Schema + + + Microsoft\Schema\Checkpoint_1_0 + + + Microsoft\Schema\Checkpoint_1_0 + + + Microsoft\Schema\Checkpoint_1_0 + + + Public\winget + + + Header Files + + + Rest\Schema\1_7 + + + Rest\Schema + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Rest\Schema\1_7\Json + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema + + + Microsoft + + + Microsoft + + + Rest\Schema\1_9\Json + + + Rest\Schema\1_9 + + + Rest\Schema\1_10 + + + Rest\Schema\1_10\Json + + + Rest\Schema\1_12 + + + Rest\Schema\1_12\Json + + + Rest\Schema\1_28 + + + Rest\Schema\1_28\Json + + + Rest + + + Header Files + + + + + Source Files + + + Microsoft + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Source Files + + + Microsoft + + + Microsoft + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_1 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_1 + + + Microsoft + + + Source Files + + + Microsoft\Schema\1_1 + + + Microsoft + + + Microsoft + + + Microsoft\Schema\1_2 + + + Microsoft\Schema\1_2 + + + Rest\Schema\1_0 + + + Rest + + + Rest + + + Rest + + + Rest\Schema\1_0\Json + + + Rest\Schema\1_0\Json + + + Rest\Schema\1_0\Json + + + Microsoft\Schema\1_3 + + + Source Files + + + Source Files + + + Rest\Schema + + + Microsoft + + + Rest\Schema\1_1 + + + Rest\Schema\1_1\Json + + + Rest\Schema\1_1\Json + + + Microsoft + + + Source Files + + + Source Files + + + Microsoft\Schema\1_4 + + + Microsoft\Schema\1_4 + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Microsoft\Schema\1_5 + + + Source Files + + + Microsoft\Schema\1_6 + + + Microsoft\Schema\1_6 + + + Microsoft + + + Microsoft\Schema\Portable_1_0 + + + Microsoft\Schema\Portable_1_0 + + + Rest\Schema\1_4\Json + + + Rest\Schema\1_4 + + + Rest\Schema\1_4\Json + + + Rest\Schema\1_5\Json + + + Rest\Schema\1_5 + + + Rest\Schema + + + Rest\Schema + + + Source Files + + + Source Files + + + Microsoft\Schema\Pinning_1_0 + + + Microsoft\Schema\Pinning_1_1 + + + Source Files + + + Source Files + + + Rest\Schema\1_6\Json + + + Rest\Schema\1_6 + + + Microsoft\Schema\1_7 + + + Source Files + + + Microsoft\Schema\Checkpoint_1_0 + + + Microsoft\Schema\Checkpoint_1_0 + + + Source Files + + + Microsoft\Schema\Checkpoint_1_0 + + + Microsoft\Schema\Pinning_1_0 + + + Microsoft\Schema\Pinning_1_1 + + + Rest\Schema\1_7 + + + Rest\Schema + + + Source Files + + + Source Files + + + Rest\Schema\1_7\Json + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft + + + Microsoft + + + Rest\Schema\1_9\Json + + + Rest\Schema\1_9 + + + Rest\Schema\1_10 + + + Rest\Schema\1_10\Json + + + Rest\Schema\1_12 + + + Rest\Schema\1_12\Json + + + Rest\Schema\1_28 + + + Rest\Schema\1_28\Json + + + Rest + + + Source Files + + + + + + + Microsoft + + \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index 38c801b4c0..c7780ac6fb 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -1,248 +1,248 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PinningIndex.h" -#include -#include "Schema/Pinning_1_0/PinningIndexInterface.h" -#include "Schema/Pinning_1_1/PinningIndexInterface.h" - -namespace AppInstaller::Repository::Microsoft -{ -#ifndef AICLI_DISABLE_TEST_HOOKS - std::optional s_PinningIndexOverride{}; - void TestHook_SetPinningIndex_Override(std::optional&& indexPath) - { - s_PinningIndexOverride = std::move(indexPath); - } -#endif - - namespace - { - std::filesystem::path GetPinningDatabasePath() - { - const auto DefaultPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; - - return -#ifndef AICLI_DISABLE_TEST_HOOKS - s_PinningIndexOverride.has_value() ? s_PinningIndexOverride.value() : -#endif - DefaultPath; - } - - std::shared_ptr OpenDatabaseIfExists(const std::filesystem::path& path, SQLite::SQLiteStorageBase::OpenDisposition openDisposition) - { - AICLI_LOG(Repo, Info, << "Attempting to open pinning database: " << path); - - try - { - if (std::filesystem::exists(path)) - { - if (std::filesystem::is_regular_file(path)) - { - try - { - AICLI_LOG(Repo, Info, << "... opening existing pinning database"); - return std::make_shared(PinningIndex::Open(path.u8string(), openDisposition)); - } - CATCH_LOG(); - - AICLI_LOG(Repo, Info, << "... deleting bad pinning database file"); - std::filesystem::remove_all(path); - } - else - { - AICLI_LOG(Repo, Info, << "... deleting pinning database path that is a directory"); - std::filesystem::remove_all(path); - } - } - } - CATCH_LOG(); - - return {}; - } - } - - PinningIndex PinningIndex::CreateNew(const std::string& filePath, SQLite::Version version) - { - AICLI_LOG(Repo, Info, << "Creating new Pinning Index with version [" << version << "] at '" << filePath << "'"); - PinningIndex result{ filePath, version }; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "pinningindex_createnew"); - - // Use calculated version, as incoming version could be 'latest' - result.m_version.SetSchemaVersion(result.m_dbconn); - - result.m_interface->CreateTables(result.m_dbconn); - - result.SetLastWriteTime(); - - savepoint.Commit(); - - return result; - } - - std::shared_ptr PinningIndex::OpenIfExists(OpenDisposition openDisposition) - { - return OpenDatabaseIfExists(GetPinningDatabasePath(), openDisposition); - } - - std::shared_ptr PinningIndex::OpenOrCreateDefault(OpenDisposition openDisposition) - { - const auto databasePath = GetPinningDatabasePath(); - - std::shared_ptr result = OpenDatabaseIfExists(databasePath, openDisposition); - - if (!result) - { - AICLI_LOG(Repo, Info, << "... creating pinning database"); - - try - { - result = std::make_shared(PinningIndex::CreateNew(databasePath.u8string())); - } - CATCH_LOG(); - } - - return result; - } - - PinningIndex::IdType PinningIndex::AddPin(const Pinning::Pin& pin) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Adding Pin " << pin.ToString()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_addpin"); - - IdType result = m_interface->AddPin(m_dbconn, pin); - - SetLastWriteTime(); - - savepoint.Commit(); - - return result; - } - - bool PinningIndex::UpdatePin(const Pinning::Pin& pin) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Updating Pin " << pin.ToString()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_updatepin"); - - bool result = m_interface->UpdatePin(m_dbconn, pin).first; - - if (result) - { - SetLastWriteTime(); - savepoint.Commit(); - } - - return result; - } - - void PinningIndex::AddOrUpdatePin(const Pinning::Pin& pin) - { - auto existingPin = GetPin(pin.GetKey()); - if (existingPin.has_value()) - { - UpdatePin(pin); - } - else - { - AddPin(pin); - } - } - - void PinningIndex::RemovePin(const Pinning::PinKey& pinKey) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Removing Pin " << pinKey.ToString()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_removePin"); - - m_interface->RemovePin(m_dbconn, pinKey); - - SetLastWriteTime(); - - savepoint.Commit(); - } - - std::optional PinningIndex::GetPin(const Pinning::PinKey& pinKey) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetPin(m_dbconn, pinKey); - } - - std::vector PinningIndex::GetAllPins() - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetAllPins(m_dbconn); - } - - bool PinningIndex::ResetAllPins(std::string_view sourceId) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->ResetAllPins(m_dbconn, sourceId); - } - - std::unique_ptr PinningIndex::CreateIPinningIndex(const SQLite::Version& version) - { - if (version == SQLite::Version{ 1, 0 }) - { - return std::make_unique(); - } - - if (version == SQLite::Version{ 1, 1 } || - version.MajorVersion == 1 || - version.IsLatest()) - { - return std::make_unique(); - } - - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - - PinningIndex::PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : - SQLiteStorageBase(target, disposition, std::move(indexFile)) - { - AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); - - // Create the correct interface for the stored schema version. - m_interface = CreateIPinningIndex(m_version); - - if (disposition == SQLiteStorageBase::OpenDisposition::ReadWrite) - { - // For writable opens, create a latest interface and migrate if the stored version is older. - auto latestInterface = CreateIPinningIndex(SQLite::Version::Latest()); - - if (m_version != latestInterface->GetVersion()) - { - AICLI_LOG(Repo, Info, << "Attempting to migrate Pinning Index from [" << m_version << "] to [" << latestInterface->GetVersion() << "]"); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_migrate"); - bool migrated = latestInterface->MigrateFrom(m_dbconn, m_interface.get()); - - if (migrated) - { - latestInterface->GetVersion().SetSchemaVersion(m_dbconn); - SetLastWriteTime(); - savepoint.Commit(); - m_version = latestInterface->GetVersion(); - m_interface = std::move(latestInterface); - AICLI_LOG(Repo, Info, << "Migration successful"); - } - else - { - AICLI_LOG(Repo, Error, << "Migration failed"); - THROW_HR(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX); - } - } - } - } - - PinningIndex::PinningIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) - { - m_interface = CreateIPinningIndex(version); - m_version = m_interface->GetVersion(); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinningIndex.h" +#include +#include "Schema/Pinning_1_0/PinningIndexInterface.h" +#include "Schema/Pinning_1_1/PinningIndexInterface.h" + +namespace AppInstaller::Repository::Microsoft +{ +#ifndef AICLI_DISABLE_TEST_HOOKS + std::optional s_PinningIndexOverride{}; + void TestHook_SetPinningIndex_Override(std::optional&& indexPath) + { + s_PinningIndexOverride = std::move(indexPath); + } +#endif + + namespace + { + std::filesystem::path GetPinningDatabasePath() + { + const auto DefaultPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; + + return +#ifndef AICLI_DISABLE_TEST_HOOKS + s_PinningIndexOverride.has_value() ? s_PinningIndexOverride.value() : +#endif + DefaultPath; + } + + std::shared_ptr OpenDatabaseIfExists(const std::filesystem::path& path, SQLite::SQLiteStorageBase::OpenDisposition openDisposition) + { + AICLI_LOG(Repo, Info, << "Attempting to open pinning database: " << path); + + try + { + if (std::filesystem::exists(path)) + { + if (std::filesystem::is_regular_file(path)) + { + try + { + AICLI_LOG(Repo, Info, << "... opening existing pinning database"); + return std::make_shared(PinningIndex::Open(path.u8string(), openDisposition)); + } + CATCH_LOG(); + + AICLI_LOG(Repo, Info, << "... deleting bad pinning database file"); + std::filesystem::remove_all(path); + } + else + { + AICLI_LOG(Repo, Info, << "... deleting pinning database path that is a directory"); + std::filesystem::remove_all(path); + } + } + } + CATCH_LOG(); + + return {}; + } + } + + PinningIndex PinningIndex::CreateNew(const std::string& filePath, SQLite::Version version) + { + AICLI_LOG(Repo, Info, << "Creating new Pinning Index with version [" << version << "] at '" << filePath << "'"); + PinningIndex result{ filePath, version }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "pinningindex_createnew"); + + // Use calculated version, as incoming version could be 'latest' + result.m_version.SetSchemaVersion(result.m_dbconn); + + result.m_interface->CreateTables(result.m_dbconn); + + result.SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + std::shared_ptr PinningIndex::OpenIfExists(OpenDisposition openDisposition) + { + return OpenDatabaseIfExists(GetPinningDatabasePath(), openDisposition); + } + + std::shared_ptr PinningIndex::OpenOrCreateDefault(OpenDisposition openDisposition) + { + const auto databasePath = GetPinningDatabasePath(); + + std::shared_ptr result = OpenDatabaseIfExists(databasePath, openDisposition); + + if (!result) + { + AICLI_LOG(Repo, Info, << "... creating pinning database"); + + try + { + result = std::make_shared(PinningIndex::CreateNew(databasePath.u8string())); + } + CATCH_LOG(); + } + + return result; + } + + PinningIndex::IdType PinningIndex::AddPin(const Pinning::Pin& pin) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Adding Pin " << pin.ToString()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_addpin"); + + IdType result = m_interface->AddPin(m_dbconn, pin); + + SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + bool PinningIndex::UpdatePin(const Pinning::Pin& pin) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Updating Pin " << pin.ToString()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_updatepin"); + + bool result = m_interface->UpdatePin(m_dbconn, pin).first; + + if (result) + { + SetLastWriteTime(); + savepoint.Commit(); + } + + return result; + } + + void PinningIndex::AddOrUpdatePin(const Pinning::Pin& pin) + { + auto existingPin = GetPin(pin.GetKey()); + if (existingPin.has_value()) + { + UpdatePin(pin); + } + else + { + AddPin(pin); + } + } + + void PinningIndex::RemovePin(const Pinning::PinKey& pinKey) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Removing Pin " << pinKey.ToString()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_removePin"); + + m_interface->RemovePin(m_dbconn, pinKey); + + SetLastWriteTime(); + + savepoint.Commit(); + } + + std::optional PinningIndex::GetPin(const Pinning::PinKey& pinKey) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetPin(m_dbconn, pinKey); + } + + std::vector PinningIndex::GetAllPins() + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetAllPins(m_dbconn); + } + + bool PinningIndex::ResetAllPins(std::string_view sourceId) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->ResetAllPins(m_dbconn, sourceId); + } + + std::unique_ptr PinningIndex::CreateIPinningIndex(const SQLite::Version& version) + { + if (version == SQLite::Version{ 1, 0 }) + { + return std::make_unique(); + } + + if (version == SQLite::Version{ 1, 1 } || + version.MajorVersion == 1 || + version.IsLatest()) + { + return std::make_unique(); + } + + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + + PinningIndex::PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : + SQLiteStorageBase(target, disposition, std::move(indexFile)) + { + AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); + + // Create the correct interface for the stored schema version. + m_interface = CreateIPinningIndex(m_version); + + if (disposition == SQLiteStorageBase::OpenDisposition::ReadWrite) + { + // For writable opens, create a latest interface and migrate if the stored version is older. + auto latestInterface = CreateIPinningIndex(SQLite::Version::Latest()); + + if (m_version != latestInterface->GetVersion()) + { + AICLI_LOG(Repo, Info, << "Attempting to migrate Pinning Index from [" << m_version << "] to [" << latestInterface->GetVersion() << "]"); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_migrate"); + bool migrated = latestInterface->MigrateFrom(m_dbconn, m_interface.get()); + + if (migrated) + { + latestInterface->GetVersion().SetSchemaVersion(m_dbconn); + SetLastWriteTime(); + savepoint.Commit(); + m_version = latestInterface->GetVersion(); + m_interface = std::move(latestInterface); + AICLI_LOG(Repo, Info, << "Migration successful"); + } + else + { + AICLI_LOG(Repo, Error, << "Migration failed"); + THROW_HR(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX); + } + } + } + } + + PinningIndex::PinningIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) + { + m_interface = CreateIPinningIndex(version); + m_version = m_interface->GetVersion(); + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index 62a92a17e0..feb9bcd4f0 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -1,75 +1,75 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Microsoft/Schema/IPinningIndex.h" -#include -#include -#include - -namespace AppInstaller::Repository::Microsoft -{ - struct PinningIndex : SQLite::SQLiteStorageBase - { - // An id that refers to a specific Pinning file. - using IdType = SQLite::rowid_t; - - PinningIndex(const PinningIndex&) = delete; - PinningIndex& operator=(const PinningIndex&) = delete; - - PinningIndex(PinningIndex&&) = default; - PinningIndex& operator=(PinningIndex&&) = default; - - // Creates a new PinningIndex database of the given version. - static PinningIndex CreateNew(const std::string& filePath, SQLite::Version version = SQLite::Version::Latest()); - - // Opens an existing PinningIndex database. - static PinningIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}) - { - return { filePath, disposition, std::move(indexFile) }; - } - - // Opens the PinningIndex database on the default path if it exists. - // Returns nullptr in case of error. - static std::shared_ptr OpenIfExists(OpenDisposition openDisposition = OpenDisposition::Read); - - // Opens or creates a PinningIndex database on the default path. - // openDisposition is only used when opening an existing database. - // Returns nullptr in case of error. - static std::shared_ptr OpenOrCreateDefault(OpenDisposition openDisposition = OpenDisposition::ReadWrite); - - // Adds a pin to the index. - IdType AddPin(const Pinning::Pin& pin); - - // Updates a pin type, and gated version if needed. - // Return value indicates whether there were any changes. - bool UpdatePin(const Pinning::Pin& pin); - - // Adds a pin or updates it if it already exists. - void AddOrUpdatePin(const Pinning::Pin& pin); - - // Removes a pin from the index. - void RemovePin(const Pinning::PinKey& pinKey); - - // Returns the current pin for a given package if it exists. - std::optional GetPin(const Pinning::PinKey& pinKey); - - // Returns a vector containing all the existing pins. - std::vector GetAllPins(); - - // Deletes all pins from a given source, or from all sources if none is specified - bool ResetAllPins(std::string_view sourceId = {}); - - private: - // Constructor used to open an existing index. - PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); - - // Constructor used to create a new index. - PinningIndex(const std::string& target, SQLite::Version version); - - // Creates an IPinningIndex interface object for a specific version. - static std::unique_ptr CreateIPinningIndex(const SQLite::Version& version); - - std::unique_ptr m_interface; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Microsoft/Schema/IPinningIndex.h" +#include +#include +#include + +namespace AppInstaller::Repository::Microsoft +{ + struct PinningIndex : SQLite::SQLiteStorageBase + { + // An id that refers to a specific Pinning file. + using IdType = SQLite::rowid_t; + + PinningIndex(const PinningIndex&) = delete; + PinningIndex& operator=(const PinningIndex&) = delete; + + PinningIndex(PinningIndex&&) = default; + PinningIndex& operator=(PinningIndex&&) = default; + + // Creates a new PinningIndex database of the given version. + static PinningIndex CreateNew(const std::string& filePath, SQLite::Version version = SQLite::Version::Latest()); + + // Opens an existing PinningIndex database. + static PinningIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}) + { + return { filePath, disposition, std::move(indexFile) }; + } + + // Opens the PinningIndex database on the default path if it exists. + // Returns nullptr in case of error. + static std::shared_ptr OpenIfExists(OpenDisposition openDisposition = OpenDisposition::Read); + + // Opens or creates a PinningIndex database on the default path. + // openDisposition is only used when opening an existing database. + // Returns nullptr in case of error. + static std::shared_ptr OpenOrCreateDefault(OpenDisposition openDisposition = OpenDisposition::ReadWrite); + + // Adds a pin to the index. + IdType AddPin(const Pinning::Pin& pin); + + // Updates a pin type, and gated version if needed. + // Return value indicates whether there were any changes. + bool UpdatePin(const Pinning::Pin& pin); + + // Adds a pin or updates it if it already exists. + void AddOrUpdatePin(const Pinning::Pin& pin); + + // Removes a pin from the index. + void RemovePin(const Pinning::PinKey& pinKey); + + // Returns the current pin for a given package if it exists. + std::optional GetPin(const Pinning::PinKey& pinKey); + + // Returns a vector containing all the existing pins. + std::vector GetAllPins(); + + // Deletes all pins from a given source, or from all sources if none is specified + bool ResetAllPins(std::string_view sourceId = {}); + + private: + // Constructor used to open an existing index. + PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + + // Constructor used to create a new index. + PinningIndex(const std::string& target, SQLite::Version version); + + // Creates an IPinningIndex interface object for a specific version. + static std::unique_ptr CreateIPinningIndex(const SQLite::Version& version); + + std::unique_ptr m_interface; + }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp index ed88f44f17..6d1f104d9d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp @@ -1,217 +1,217 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_0/OneToOneTable.h" -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - static constexpr std::string_view s_OneToOneTable_IndexSuffix = "_pkindex"sv; - - void CreateOneToOneTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool useNamedIndices) - { - using namespace SQLite::Builder; - - // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. - // The resulting database will function the same, but give us control to drop the indices to reduce space. - if (useNamedIndices) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v1_1"); - - StatementBuilder createTableBuilder; - - createTableBuilder.CreateTable(tableName).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(valueName, Type::Text).NotNull() - }); - - createTableBuilder.Execute(connection); - - StatementBuilder indexBuilder; - indexBuilder.CreateUniqueIndex({ tableName, s_OneToOneTable_IndexSuffix }).On(tableName).Columns(valueName); - indexBuilder.Execute(connection); - - savepoint.Commit(); - } - else - { - StatementBuilder createTableBuilder; - - createTableBuilder.CreateTable(tableName).Columns({ - ColumnBuilder(valueName, Type::Text).NotNull().PrimaryKey() - }); - - createTableBuilder.Execute(connection); - } - } - - void DropOneToOneTable(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(tableName); - - dropTableBuilder.Execute(connection); - } - - std::optional OneToOneTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); - - if (useLike) - { - selectBuilder.LikeWithEscape(value); - } - else - { - selectBuilder.Equals(value); - } - - SQLite::Statement select = selectBuilder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - std::optional OneToOneTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement select = selectBuilder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - std::vector OneToOneTableGetAllRowIds(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, size_t limit) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(SQLite::RowIDName).From(tableName).OrderBy(valueName); - - if (limit) - { - selectBuilder.Limit(limit); - } - - SQLite::Statement select = selectBuilder.Prepare(connection); - - std::vector result; - while (select.Step()) - { - result.emplace_back(select.GetColumn(0)); - } - return result; - } - - SQLite::rowid_t OneToOneTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch) - { - auto selectResult = OneToOneTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); - if (selectResult) - { - if (overwriteLikeMatch) - { - // If the value in the table is not an exact match, overwrite it with the incoming value - auto tableValue = OneToOneTableSelectValueById(connection, tableName, valueName, selectResult.value()); - if (tableValue.value() != value) - { - SQLite::Builder::StatementBuilder updateBuilder; - updateBuilder.Update(tableName).Set().Column(valueName).AssignValue(value).Where(SQLite::RowIDName).Equals(selectResult); - - updateBuilder.Execute(connection); - } - } - - return selectResult.value(); - } - - SQLite::Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); - - insertBuilder.Execute(connection); - - return connection.GetLastInsertRowID(); - } - - void OneToOneTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, bool useNamedIndices, bool preserveValuesIndex) - { - if (useNamedIndices && !preserveValuesIndex) - { - SQLite::Builder::StatementBuilder dropIndexBuilder; - dropIndexBuilder.DropIndex({ tableName, s_OneToOneTable_IndexSuffix }); - dropIndexBuilder.Execute(connection); - } - } - - uint64_t OneToOneTableGetCount(const SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(tableName); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return static_cast(countStatement.GetColumn(0)); - } - - bool OneToOneTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) - { - return (OneToOneTableGetCount(connection, tableName) == 0); - } - - void OneToOneTableDeleteById(SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(tableName).Where(SQLite::RowIDName).Equals(id); - - builder.Execute(connection); - } - - bool OneToOneTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) - { - // Build a select statement to find values that contain an embedded null character - // Such as: - // Select count(*) from table where instr(value,char(0))>0 - SQLite::Builder::StatementBuilder builder; - builder. - Select({ SQLite::RowIDName, valueName }). - From(tableName). - WhereValueContainsEmbeddedNullCharacter(valueName); - - SQLite::Statement select = builder.Prepare(connection); - bool result = true; - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); - } - - return result; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_0/OneToOneTable.h" +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + static constexpr std::string_view s_OneToOneTable_IndexSuffix = "_pkindex"sv; + + void CreateOneToOneTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool useNamedIndices) + { + using namespace SQLite::Builder; + + // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. + // The resulting database will function the same, but give us control to drop the indices to reduce space. + if (useNamedIndices) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v1_1"); + + StatementBuilder createTableBuilder; + + createTableBuilder.CreateTable(tableName).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(valueName, Type::Text).NotNull() + }); + + createTableBuilder.Execute(connection); + + StatementBuilder indexBuilder; + indexBuilder.CreateUniqueIndex({ tableName, s_OneToOneTable_IndexSuffix }).On(tableName).Columns(valueName); + indexBuilder.Execute(connection); + + savepoint.Commit(); + } + else + { + StatementBuilder createTableBuilder; + + createTableBuilder.CreateTable(tableName).Columns({ + ColumnBuilder(valueName, Type::Text).NotNull().PrimaryKey() + }); + + createTableBuilder.Execute(connection); + } + } + + void DropOneToOneTable(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(tableName); + + dropTableBuilder.Execute(connection); + } + + std::optional OneToOneTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); + + if (useLike) + { + selectBuilder.LikeWithEscape(value); + } + else + { + selectBuilder.Equals(value); + } + + SQLite::Statement select = selectBuilder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + std::optional OneToOneTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement select = selectBuilder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + std::vector OneToOneTableGetAllRowIds(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, size_t limit) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(SQLite::RowIDName).From(tableName).OrderBy(valueName); + + if (limit) + { + selectBuilder.Limit(limit); + } + + SQLite::Statement select = selectBuilder.Prepare(connection); + + std::vector result; + while (select.Step()) + { + result.emplace_back(select.GetColumn(0)); + } + return result; + } + + SQLite::rowid_t OneToOneTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch) + { + auto selectResult = OneToOneTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); + if (selectResult) + { + if (overwriteLikeMatch) + { + // If the value in the table is not an exact match, overwrite it with the incoming value + auto tableValue = OneToOneTableSelectValueById(connection, tableName, valueName, selectResult.value()); + if (tableValue.value() != value) + { + SQLite::Builder::StatementBuilder updateBuilder; + updateBuilder.Update(tableName).Set().Column(valueName).AssignValue(value).Where(SQLite::RowIDName).Equals(selectResult); + + updateBuilder.Execute(connection); + } + } + + return selectResult.value(); + } + + SQLite::Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); + + insertBuilder.Execute(connection); + + return connection.GetLastInsertRowID(); + } + + void OneToOneTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, bool useNamedIndices, bool preserveValuesIndex) + { + if (useNamedIndices && !preserveValuesIndex) + { + SQLite::Builder::StatementBuilder dropIndexBuilder; + dropIndexBuilder.DropIndex({ tableName, s_OneToOneTable_IndexSuffix }); + dropIndexBuilder.Execute(connection); + } + } + + uint64_t OneToOneTableGetCount(const SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(tableName); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return static_cast(countStatement.GetColumn(0)); + } + + bool OneToOneTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) + { + return (OneToOneTableGetCount(connection, tableName) == 0); + } + + void OneToOneTableDeleteById(SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(tableName).Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + } + + bool OneToOneTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) + { + // Build a select statement to find values that contain an embedded null character + // Such as: + // Select count(*) from table where instr(value,char(0))>0 + SQLite::Builder::StatementBuilder builder; + builder. + Select({ SQLite::RowIDName, valueName }). + From(tableName). + WhereValueContainsEmbeddedNullCharacter(valueName); + + SQLite::Statement select = builder.Prepare(connection); + bool result = true; + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); + } + + return result; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp index d4fd017d64..4ad42c0bad 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp @@ -1,302 +1,302 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResultsTable.h" -#include - -#include "Microsoft/Schema/1_0/IdTable.h" -#include "Microsoft/Schema/1_0/NameTable.h" -#include "Microsoft/Schema/1_0/MonikerTable.h" -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_0/TagsTable.h" -#include "Microsoft/Schema/1_0/CommandsTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace - { - using namespace std::string_literals; - using namespace std::string_view_literals; - - constexpr std::string_view s_SearchResultsTable_Manifest = "manifest"sv; - constexpr std::string_view s_SearchResultsTable_MatchField = "field"sv; - constexpr std::string_view s_SearchResultsTable_MatchType = "match"sv; - constexpr std::string_view s_SearchResultsTable_MatchValue = "value"sv; - constexpr std::string_view s_SearchResultsTable_SortValue = "sort"sv; - constexpr std::string_view s_SearchResultsTable_Filter = "filter"sv; - - constexpr std::string_view s_SearchResultsTable_Index_Suffix = "_i_m"sv; - - constexpr std::string_view s_SearchResultsTable_SubSelect_TableAlias = "valueTable"sv; - constexpr std::string_view s_SearchResultsTable_SubSelect_ManifestAlias = "m"sv; - constexpr std::string_view s_SearchResultsTable_SubSelect_ValueAlias = "v"sv; - } - - SearchResultsTable::SearchResultsTable(const SQLite::Connection& connection) : - m_connection(connection) - { - using namespace SQLite::Builder; - - { - StatementBuilder builder; - builder.CreateTable(GetQualifiedName()).BeginColumns(); - - builder.Column(ColumnBuilder(s_SearchResultsTable_Manifest, Type::RowId).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchField, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchType, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchValue, Type::Text).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_SortValue, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_Filter, Type::Bool).NotNull()); - - builder.EndColumns(); - - builder.Execute(m_connection); - } - - InitDropStatement(m_connection); - - { - SQLite::Builder::QualifiedTable index = GetQualifiedName(); - std::string indexName(index.Table); - indexName += s_SearchResultsTable_Index_Suffix; - index.Table = indexName; - - StatementBuilder builder; - builder.CreateIndex(indexName).On(GetQualifiedName().Table).Columns(s_SearchResultsTable_Manifest); - - builder.Execute(m_connection); - } - } - - void SearchResultsTable::SearchOnField(const PackageMatchFilter& filter) - { - using namespace SQLite::Builder; - - int sortOrdinal = m_sortOrdinalValue++; - - // Create an insert statement to select values into the table as requested. - // The goal is a statement like this: - // INSERT INTO - // SELECT valueTable.m, , , valueTable.v, , FROM - // (SELECT manifest.rowid as m, manifest.id as v from manifest join ids on manifest.id = ids.rowid where ids.id = ) AS valueTable - // Where the subselect is built by the owning table. - StatementBuilder builder; - builder.InsertInto(GetQualifiedName()).Select(). - Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ManifestAlias)). - Value(filter.Field). - Value(filter.Type). - Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ValueAlias)). - Value(sortOrdinal). - Value(false). - From().BeginParenthetical(); - - // Add the field specific portion - std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); - - if (bindIndex.empty()) - { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); - return; - } - - builder.EndParenthetical().As(s_SearchResultsTable_SubSelect_TableAlias); - - SQLite::Statement statement = builder.Prepare(m_connection); - BindStatementForMatchType(statement, filter, bindIndex); - statement.Execute(); - AICLI_LOG(SQL, Verbose, << "Search found " << m_connection.GetChanges() << " rows"); - } - - void SearchResultsTable::RemoveDuplicateManifestRows() - { - using namespace SQLite::Builder; - - // Create a delete statement to leave only one row with a given manifest. - // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. - // The goal is a statement like this: - // DELETE from where rowid not in ( - // SELECT rowid from ( - // SELECT rowid, min(sort) from group by manifest - // ) - // ) - StatementBuilder builder; - builder.DeleteFrom(GetQualifiedName()).Where(SQLite::RowIDName).Not().In().BeginParenthetical(). - Select(SQLite::RowIDName).From().BeginParenthetical(). - Select().Column(SQLite::RowIDName).Column(Aggregate::Min, s_SearchResultsTable_SortValue).From(GetQualifiedName()).GroupBy(s_SearchResultsTable_Manifest). - EndParenthetical(). - EndParenthetical(); - - builder.Execute(m_connection); - AICLI_LOG(SQL, Verbose, << "Removed " << m_connection.GetChanges() << " duplicate rows"); - } - - void SearchResultsTable::PrepareToFilter() - { - // Reset all filter values to unselected - SQLite::Builder::StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(false); - - builder.Execute(m_connection); - } - - void SearchResultsTable::FilterOnField(const PackageMatchFilter& filter) - { - using namespace SQLite::Builder; - - // Create an update statement to mark rows that are found by the search. - // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. - // The goal is a statement like this: - // UPDATE set filter = 1 where manifest in ( - // SELECT m from ( - // SELECT manifest.rowid as m, manifest.id as v from manifest join ids on manifest.id = ids.rowid where ids.id = - // ) - // ) - StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(true).Where(s_SearchResultsTable_Manifest).In().BeginParenthetical(). - Select(s_SearchResultsTable_SubSelect_ManifestAlias).From().BeginParenthetical(); - - // Add the field specific portion - std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); - - if (bindIndex.empty()) - { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); - return; - } - - builder.EndParenthetical().EndParenthetical(); - - SQLite::Statement statement = builder.Prepare(m_connection); - BindStatementForMatchType(statement, filter, bindIndex); - statement.Execute(); - AICLI_LOG(SQL, Verbose, << "Filter kept " << m_connection.GetChanges() << " rows"); - } - - void SearchResultsTable::CompleteFilter() - { - // Delete all unselected values - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(GetQualifiedName()).Where(s_SearchResultsTable_Filter).Equals(false); - - builder.Execute(m_connection); - AICLI_LOG(SQL, Verbose, << "Filter deleted " << m_connection.GetChanges() << " rows"); - } - - ISQLiteIndex::SearchResult SearchResultsTable::GetSearchResults(size_t limit) - { - constexpr std::string_view tempTableAlias = "t"sv; - - using namespace SQLite::Builder; - using QCol = QualifiedColumn; - - // Select all unique ids from the results table, and their highest ordered match. - // The goal is a statement like this: - // SELECT m.id, field, match, value, min(sort) from join manifest on rowid = manifest group by m.id order by t.sort - // Through the "group by m.id", we will only ever have one row per id, and the "min(sort)" returns us one of the rows that matched - // through the earliest search. We also order by the sort value to have the earliest search matches first in the list - StatementBuilder builder; - builder.Select(). - Column(QCol(ManifestTable::TableName(), IdTable::ValueName())). - Column(QCol(tempTableAlias, s_SearchResultsTable_MatchField)). - Column(QCol(tempTableAlias, s_SearchResultsTable_MatchType)). - Column(QCol(tempTableAlias, s_SearchResultsTable_MatchValue)). - Column(Aggregate::Min, QCol(tempTableAlias, s_SearchResultsTable_SortValue)). - From(GetQualifiedName()).As(tempTableAlias). - Join(ManifestTable::TableName()).On(QCol(tempTableAlias, s_SearchResultsTable_Manifest), QCol(ManifestTable::TableName(), SQLite::RowIDName)). - GroupBy(QCol(ManifestTable::TableName(), IdTable::ValueName())).OrderBy(QCol(tempTableAlias, s_SearchResultsTable_SortValue)); - - SQLite::Statement select = builder.Prepare(m_connection); - - ISQLiteIndex::SearchResult result; - while (select.Step()) - { - if (limit && result.Matches.size() >= limit) - { - break; - } - - result.Matches.emplace_back(select.GetColumn(0), - PackageMatchFilter(select.GetColumn(1), select.GetColumn(2), select.GetColumn(3))); - } - - result.Truncated = (select.GetState() != SQLite::Statement::State::Completed); - - return result; - } - - std::vector SearchResultsTable::BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const - { - return BuildSearchStatement(builder, field, s_SearchResultsTable_SubSelect_ManifestAlias, s_SearchResultsTable_SubSelect_ValueAlias, MatchUsesLike(match)); - } - - std::vector SearchResultsTable::BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const - { - switch (field) - { - case PackageMatchField::Id: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::Name: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::Moniker: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::Tag: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::Command: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - default: - return {}; - } - } - - bool SearchResultsTable::MatchUsesLike(MatchType match) - { - return (match != MatchType::Exact); - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value) - { - std::string valueToUse; - - if (MatchUsesLike(match)) - { - valueToUse = SQLite::EscapeStringForLike(value); - } - else - { - valueToUse = value; - } - - switch (match) - { - case AppInstaller::Repository::MatchType::StartsWith: - valueToUse += '%'; - break; - case AppInstaller::Repository::MatchType::Substring: - valueToUse = "%"s + valueToUse + '%'; - break; - default: - // No changes required for others. - break; - } - - statement.Bind(bindIndex, valueToUse); - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) - { - // TODO: Implement these more complex match types - if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) - { - AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); - return; - } - - BindStatementForMatchType(statement, filter.Type, bindIndex[0], filter.Value); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResultsTable.h" +#include + +#include "Microsoft/Schema/1_0/IdTable.h" +#include "Microsoft/Schema/1_0/NameTable.h" +#include "Microsoft/Schema/1_0/MonikerTable.h" +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_0/TagsTable.h" +#include "Microsoft/Schema/1_0/CommandsTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace + { + using namespace std::string_literals; + using namespace std::string_view_literals; + + constexpr std::string_view s_SearchResultsTable_Manifest = "manifest"sv; + constexpr std::string_view s_SearchResultsTable_MatchField = "field"sv; + constexpr std::string_view s_SearchResultsTable_MatchType = "match"sv; + constexpr std::string_view s_SearchResultsTable_MatchValue = "value"sv; + constexpr std::string_view s_SearchResultsTable_SortValue = "sort"sv; + constexpr std::string_view s_SearchResultsTable_Filter = "filter"sv; + + constexpr std::string_view s_SearchResultsTable_Index_Suffix = "_i_m"sv; + + constexpr std::string_view s_SearchResultsTable_SubSelect_TableAlias = "valueTable"sv; + constexpr std::string_view s_SearchResultsTable_SubSelect_ManifestAlias = "m"sv; + constexpr std::string_view s_SearchResultsTable_SubSelect_ValueAlias = "v"sv; + } + + SearchResultsTable::SearchResultsTable(const SQLite::Connection& connection) : + m_connection(connection) + { + using namespace SQLite::Builder; + + { + StatementBuilder builder; + builder.CreateTable(GetQualifiedName()).BeginColumns(); + + builder.Column(ColumnBuilder(s_SearchResultsTable_Manifest, Type::RowId).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchField, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchType, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchValue, Type::Text).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_SortValue, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_Filter, Type::Bool).NotNull()); + + builder.EndColumns(); + + builder.Execute(m_connection); + } + + InitDropStatement(m_connection); + + { + SQLite::Builder::QualifiedTable index = GetQualifiedName(); + std::string indexName(index.Table); + indexName += s_SearchResultsTable_Index_Suffix; + index.Table = indexName; + + StatementBuilder builder; + builder.CreateIndex(indexName).On(GetQualifiedName().Table).Columns(s_SearchResultsTable_Manifest); + + builder.Execute(m_connection); + } + } + + void SearchResultsTable::SearchOnField(const PackageMatchFilter& filter) + { + using namespace SQLite::Builder; + + int sortOrdinal = m_sortOrdinalValue++; + + // Create an insert statement to select values into the table as requested. + // The goal is a statement like this: + // INSERT INTO + // SELECT valueTable.m, , , valueTable.v, , FROM + // (SELECT manifest.rowid as m, manifest.id as v from manifest join ids on manifest.id = ids.rowid where ids.id = ) AS valueTable + // Where the subselect is built by the owning table. + StatementBuilder builder; + builder.InsertInto(GetQualifiedName()).Select(). + Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ManifestAlias)). + Value(filter.Field). + Value(filter.Type). + Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ValueAlias)). + Value(sortOrdinal). + Value(false). + From().BeginParenthetical(); + + // Add the field specific portion + std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); + + if (bindIndex.empty()) + { + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); + return; + } + + builder.EndParenthetical().As(s_SearchResultsTable_SubSelect_TableAlias); + + SQLite::Statement statement = builder.Prepare(m_connection); + BindStatementForMatchType(statement, filter, bindIndex); + statement.Execute(); + AICLI_LOG(SQL, Verbose, << "Search found " << m_connection.GetChanges() << " rows"); + } + + void SearchResultsTable::RemoveDuplicateManifestRows() + { + using namespace SQLite::Builder; + + // Create a delete statement to leave only one row with a given manifest. + // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. + // The goal is a statement like this: + // DELETE from where rowid not in ( + // SELECT rowid from ( + // SELECT rowid, min(sort) from group by manifest + // ) + // ) + StatementBuilder builder; + builder.DeleteFrom(GetQualifiedName()).Where(SQLite::RowIDName).Not().In().BeginParenthetical(). + Select(SQLite::RowIDName).From().BeginParenthetical(). + Select().Column(SQLite::RowIDName).Column(Aggregate::Min, s_SearchResultsTable_SortValue).From(GetQualifiedName()).GroupBy(s_SearchResultsTable_Manifest). + EndParenthetical(). + EndParenthetical(); + + builder.Execute(m_connection); + AICLI_LOG(SQL, Verbose, << "Removed " << m_connection.GetChanges() << " duplicate rows"); + } + + void SearchResultsTable::PrepareToFilter() + { + // Reset all filter values to unselected + SQLite::Builder::StatementBuilder builder; + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(false); + + builder.Execute(m_connection); + } + + void SearchResultsTable::FilterOnField(const PackageMatchFilter& filter) + { + using namespace SQLite::Builder; + + // Create an update statement to mark rows that are found by the search. + // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. + // The goal is a statement like this: + // UPDATE set filter = 1 where manifest in ( + // SELECT m from ( + // SELECT manifest.rowid as m, manifest.id as v from manifest join ids on manifest.id = ids.rowid where ids.id = + // ) + // ) + StatementBuilder builder; + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(true).Where(s_SearchResultsTable_Manifest).In().BeginParenthetical(). + Select(s_SearchResultsTable_SubSelect_ManifestAlias).From().BeginParenthetical(); + + // Add the field specific portion + std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); + + if (bindIndex.empty()) + { + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); + return; + } + + builder.EndParenthetical().EndParenthetical(); + + SQLite::Statement statement = builder.Prepare(m_connection); + BindStatementForMatchType(statement, filter, bindIndex); + statement.Execute(); + AICLI_LOG(SQL, Verbose, << "Filter kept " << m_connection.GetChanges() << " rows"); + } + + void SearchResultsTable::CompleteFilter() + { + // Delete all unselected values + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(GetQualifiedName()).Where(s_SearchResultsTable_Filter).Equals(false); + + builder.Execute(m_connection); + AICLI_LOG(SQL, Verbose, << "Filter deleted " << m_connection.GetChanges() << " rows"); + } + + ISQLiteIndex::SearchResult SearchResultsTable::GetSearchResults(size_t limit) + { + constexpr std::string_view tempTableAlias = "t"sv; + + using namespace SQLite::Builder; + using QCol = QualifiedColumn; + + // Select all unique ids from the results table, and their highest ordered match. + // The goal is a statement like this: + // SELECT m.id, field, match, value, min(sort) from join manifest on rowid = manifest group by m.id order by t.sort + // Through the "group by m.id", we will only ever have one row per id, and the "min(sort)" returns us one of the rows that matched + // through the earliest search. We also order by the sort value to have the earliest search matches first in the list + StatementBuilder builder; + builder.Select(). + Column(QCol(ManifestTable::TableName(), IdTable::ValueName())). + Column(QCol(tempTableAlias, s_SearchResultsTable_MatchField)). + Column(QCol(tempTableAlias, s_SearchResultsTable_MatchType)). + Column(QCol(tempTableAlias, s_SearchResultsTable_MatchValue)). + Column(Aggregate::Min, QCol(tempTableAlias, s_SearchResultsTable_SortValue)). + From(GetQualifiedName()).As(tempTableAlias). + Join(ManifestTable::TableName()).On(QCol(tempTableAlias, s_SearchResultsTable_Manifest), QCol(ManifestTable::TableName(), SQLite::RowIDName)). + GroupBy(QCol(ManifestTable::TableName(), IdTable::ValueName())).OrderBy(QCol(tempTableAlias, s_SearchResultsTable_SortValue)); + + SQLite::Statement select = builder.Prepare(m_connection); + + ISQLiteIndex::SearchResult result; + while (select.Step()) + { + if (limit && result.Matches.size() >= limit) + { + break; + } + + result.Matches.emplace_back(select.GetColumn(0), + PackageMatchFilter(select.GetColumn(1), select.GetColumn(2), select.GetColumn(3))); + } + + result.Truncated = (select.GetState() != SQLite::Statement::State::Completed); + + return result; + } + + std::vector SearchResultsTable::BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const + { + return BuildSearchStatement(builder, field, s_SearchResultsTable_SubSelect_ManifestAlias, s_SearchResultsTable_SubSelect_ValueAlias, MatchUsesLike(match)); + } + + std::vector SearchResultsTable::BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const + { + switch (field) + { + case PackageMatchField::Id: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::Name: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::Moniker: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::Tag: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::Command: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + default: + return {}; + } + } + + bool SearchResultsTable::MatchUsesLike(MatchType match) + { + return (match != MatchType::Exact); + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value) + { + std::string valueToUse; + + if (MatchUsesLike(match)) + { + valueToUse = SQLite::EscapeStringForLike(value); + } + else + { + valueToUse = value; + } + + switch (match) + { + case AppInstaller::Repository::MatchType::StartsWith: + valueToUse += '%'; + break; + case AppInstaller::Repository::MatchType::Substring: + valueToUse = "%"s + valueToUse + '%'; + break; + default: + // No changes required for others. + break; + } + + statement.Bind(bindIndex, valueToUse); + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) + { + // TODO: Implement these more complex match types + if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) + { + AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); + return; + } + + BindStatementForMatchType(statement, filter.Type, bindIndex[0], filter.Value); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp index dd52c5e8c8..88a9a2c80c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp @@ -1,131 +1,131 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestMetadataTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - using namespace SQLite; - - static constexpr std::string_view s_ManifestMetadataTable_Table_Name = "manifest_metadata"sv; - static constexpr std::string_view s_ManifestMetadataTable_PrimaryKeyIndex_Name = "manifest_metadata_pk"sv; - static constexpr std::string_view s_ManifestMetadataTable_Manifest_Column = "manifest"sv; - static constexpr std::string_view s_ManifestMetadataTable_Metadata_Column = "metadata"sv; - static constexpr std::string_view s_ManifestMetadataTable_Value_Column = "value"sv; - - bool ManifestMetadataTable::Exists(const SQLite::Connection& connection) - { - Builder::StatementBuilder builder; - builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). - Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_ManifestMetadataTable_Table_Name); - - Statement statement = builder.Prepare(connection); - THROW_HR_IF(E_UNEXPECTED, !statement.Step()); - return statement.GetColumn(0) != 0; - } - - void ManifestMetadataTable::Create(SQLite::Connection& connection) - { - using namespace Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createmanifestmetadata_v1_1"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_ManifestMetadataTable_Table_Name).Columns({ - ColumnBuilder(s_ManifestMetadataTable_Manifest_Column, Type::Int64).NotNull(), - ColumnBuilder(s_ManifestMetadataTable_Metadata_Column, Type::Int64).NotNull(), - ColumnBuilder(s_ManifestMetadataTable_Value_Column, Type::Text) - }); - - createTableBuilder.Execute(connection); - - StatementBuilder createPKIndexBuilder; - createPKIndexBuilder.CreateUniqueIndex(s_ManifestMetadataTable_PrimaryKeyIndex_Name).On(s_ManifestMetadataTable_Table_Name). - Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column }); - createPKIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void ManifestMetadataTable::Drop(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTableIfExists(s_ManifestMetadataTable_Table_Name); - - dropTableBuilder.Execute(connection); - } - - ISQLiteIndex::MetadataResult ManifestMetadataTable::GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - using namespace Builder; - - StatementBuilder builder; - builder.Select({ s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }).From(s_ManifestMetadataTable_Table_Name). - Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); - - Statement statement = builder.Prepare(connection); - - ISQLiteIndex::MetadataResult result; - while (statement.Step()) - { - result.emplace_back(std::make_pair(statement.GetColumn(0), statement.GetColumn(1))); - } - - return result; - } - - std::optional ManifestMetadataTable::GetMetadataByManifestIdAndMetadata(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata) - { - using namespace Builder; - - StatementBuilder builder; - builder.Select(s_ManifestMetadataTable_Value_Column).From(s_ManifestMetadataTable_Table_Name). - Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId). - And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); - - Statement statement = builder.Prepare(connection); - - if (statement.Step()) - { - return statement.GetColumn(0); - } - - return {}; - } - - void ManifestMetadataTable::SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) - { - using namespace Builder; - - // First, we attempt to update an existing row. If not changes occurred, we then insert the new value. - // UPSERT (aka ON CONFLICT) is not available to us, as it was only introduced in 3.24.0 (2018-06-04), - // and we need to support Windows 10 (17763) which was released in 2017. - StatementBuilder updateBuilder; - updateBuilder.Update(s_ManifestMetadataTable_Table_Name).Set().Column(s_ManifestMetadataTable_Value_Column).AssignValue(value). - Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId).And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); - - updateBuilder.Execute(connection); - - // No changes means we need to insert the row - if (connection.GetChanges() == 0) - { - StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_ManifestMetadataTable_Table_Name). - Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }) - .Values(manifestId, metadata, value); - - insertBuilder.Execute(connection); - } - } - - void ManifestMetadataTable::DeleteByManifestId(SQLite::Connection & connection, SQLite::rowid_t manifestId) - { - using namespace Builder; - - StatementBuilder builder; - builder.DeleteFrom(s_ManifestMetadataTable_Table_Name).Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); - builder.Execute(connection); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestMetadataTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + using namespace SQLite; + + static constexpr std::string_view s_ManifestMetadataTable_Table_Name = "manifest_metadata"sv; + static constexpr std::string_view s_ManifestMetadataTable_PrimaryKeyIndex_Name = "manifest_metadata_pk"sv; + static constexpr std::string_view s_ManifestMetadataTable_Manifest_Column = "manifest"sv; + static constexpr std::string_view s_ManifestMetadataTable_Metadata_Column = "metadata"sv; + static constexpr std::string_view s_ManifestMetadataTable_Value_Column = "value"sv; + + bool ManifestMetadataTable::Exists(const SQLite::Connection& connection) + { + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). + Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_ManifestMetadataTable_Table_Name); + + Statement statement = builder.Prepare(connection); + THROW_HR_IF(E_UNEXPECTED, !statement.Step()); + return statement.GetColumn(0) != 0; + } + + void ManifestMetadataTable::Create(SQLite::Connection& connection) + { + using namespace Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createmanifestmetadata_v1_1"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_ManifestMetadataTable_Table_Name).Columns({ + ColumnBuilder(s_ManifestMetadataTable_Manifest_Column, Type::Int64).NotNull(), + ColumnBuilder(s_ManifestMetadataTable_Metadata_Column, Type::Int64).NotNull(), + ColumnBuilder(s_ManifestMetadataTable_Value_Column, Type::Text) + }); + + createTableBuilder.Execute(connection); + + StatementBuilder createPKIndexBuilder; + createPKIndexBuilder.CreateUniqueIndex(s_ManifestMetadataTable_PrimaryKeyIndex_Name).On(s_ManifestMetadataTable_Table_Name). + Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column }); + createPKIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void ManifestMetadataTable::Drop(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTableIfExists(s_ManifestMetadataTable_Table_Name); + + dropTableBuilder.Execute(connection); + } + + ISQLiteIndex::MetadataResult ManifestMetadataTable::GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + using namespace Builder; + + StatementBuilder builder; + builder.Select({ s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }).From(s_ManifestMetadataTable_Table_Name). + Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); + + Statement statement = builder.Prepare(connection); + + ISQLiteIndex::MetadataResult result; + while (statement.Step()) + { + result.emplace_back(std::make_pair(statement.GetColumn(0), statement.GetColumn(1))); + } + + return result; + } + + std::optional ManifestMetadataTable::GetMetadataByManifestIdAndMetadata(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata) + { + using namespace Builder; + + StatementBuilder builder; + builder.Select(s_ManifestMetadataTable_Value_Column).From(s_ManifestMetadataTable_Table_Name). + Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId). + And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); + + Statement statement = builder.Prepare(connection); + + if (statement.Step()) + { + return statement.GetColumn(0); + } + + return {}; + } + + void ManifestMetadataTable::SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) + { + using namespace Builder; + + // First, we attempt to update an existing row. If not changes occurred, we then insert the new value. + // UPSERT (aka ON CONFLICT) is not available to us, as it was only introduced in 3.24.0 (2018-06-04), + // and we need to support Windows 10 (17763) which was released in 2017. + StatementBuilder updateBuilder; + updateBuilder.Update(s_ManifestMetadataTable_Table_Name).Set().Column(s_ManifestMetadataTable_Value_Column).AssignValue(value). + Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId).And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); + + updateBuilder.Execute(connection); + + // No changes means we need to insert the row + if (connection.GetChanges() == 0) + { + StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_ManifestMetadataTable_Table_Name). + Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }) + .Values(manifestId, metadata, value); + + insertBuilder.Execute(connection); + } + } + + void ManifestMetadataTable::DeleteByManifestId(SQLite::Connection & connection, SQLite::rowid_t manifestId) + { + using namespace Builder; + + StatementBuilder builder; + builder.DeleteFrom(s_ManifestMetadataTable_Table_Name).Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); + builder.Execute(connection); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp index 8f0678427e..cf22ae15ff 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp @@ -1,450 +1,450 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" -#include "Microsoft/Schema/2_0/PackagesTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using PrimaryTable = PackagesTable; - - using namespace std::string_view_literals; - static constexpr std::string_view s_OneToManyTableWithMap_MapTable_PrimaryName = "package"sv; - static constexpr std::string_view s_OneToManyTableWithMap_MapTable_Suffix = "_map"sv; - static constexpr std::string_view s_OneToManyTableWithMap_MapTable_IndexSuffix = "_index"sv; - static constexpr std::string_view s_OneToManyTableWithMap_PrimaryKeyIndexSuffix = "_pkindex"sv; - - namespace anon - { - // Create the mapping table insert statement for multiple use. - // Bind the rowid of the value to 2. - SQLite::Statement CreateMappingInsertStatementForPrimaryId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) - { - SQLite::Builder::StatementBuilder insertMappingBuilder; - insertMappingBuilder.InsertOrIgnore({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }). - Columns({ s_OneToManyTableWithMap_MapTable_PrimaryName, valueName }).Values(manifestId, SQLite::Builder::Unbound); - - return insertMappingBuilder.Prepare(connection); - } - - // Get a collection of the value ids associated with the given primary id. - std::vector GetValueIdsByPrimaryId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) - { - std::vector result; - - SQLite::Builder::StatementBuilder selectMappingBuilder; - selectMappingBuilder.Select(valueName).From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Where(s_OneToManyTableWithMap_MapTable_PrimaryName).Equals(manifestId); - - SQLite::Statement selectMappingStatement = selectMappingBuilder.Prepare(connection); - - while (selectMappingStatement.Step()) - { - result.push_back(selectMappingStatement.GetColumn(0)); - } - - return result; - } - - void CreateDataTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); - - StatementBuilder createTableBuilder; - - createTableBuilder.CreateTable(tableName).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(valueName, Type::Text).NotNull() - }); - - createTableBuilder.Execute(connection); - - StatementBuilder indexBuilder; - indexBuilder.CreateUniqueIndex({ tableName, s_OneToManyTableWithMap_PrimaryKeyIndexSuffix }).On(tableName).Columns(valueName); - indexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void DropDataTable(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(tableName); - - dropTableBuilder.Execute(connection); - } - - std::optional DataTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); - - if (useLike) - { - selectBuilder.LikeWithEscape(value); - } - else - { - selectBuilder.Equals(value); - } - - SQLite::Statement select = selectBuilder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - std::optional DataTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t rowid) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(rowid); - - SQLite::Statement select = selectBuilder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - SQLite::rowid_t DataTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch = false) - { - auto selectResult = DataTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); - if (selectResult) - { - if (overwriteLikeMatch) - { - // If the value in the table is not an exact match, overwrite it with the incoming value - auto tableValue = DataTableSelectValueById(connection, tableName, valueName, selectResult.value()); - if (tableValue.value() != value) - { - SQLite::Builder::StatementBuilder updateBuilder; - updateBuilder.Update(tableName).Set().Column(valueName).AssignValue(value).Where(SQLite::RowIDName).Equals(selectResult); - - updateBuilder.Execute(connection); - } - } - - return selectResult.value(); - } - - SQLite::Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); - - insertBuilder.Execute(connection); - - return connection.GetLastInsertRowID(); - } - - void DataTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropIndexBuilder; - dropIndexBuilder.DropIndex({ tableName, s_OneToManyTableWithMap_PrimaryKeyIndexSuffix }); - dropIndexBuilder.Execute(connection); - } - - bool DataTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) - { - // Build a select statement to find values that contain an embedded null character - // Such as: - // Select count(*) from table where instr(value,char(0))>0 - SQLite::Builder::StatementBuilder builder; - builder. - Select({ SQLite::RowIDName, valueName }). - From(tableName). - WhereValueContainsEmbeddedNullCharacter(valueName); - - SQLite::Statement select = builder.Prepare(connection); - bool result = true; - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); - } - - return result; - } - } - - std::string OneToManyTableWithMapGetMapTableName(std::string_view tableName) - { - std::string result(tableName); - result += s_OneToManyTableWithMap_MapTable_Suffix; - return result; - } - - std::string_view OneToManyTableWithMapGetManifestColumnName() - { - return s_OneToManyTableWithMap_MapTable_PrimaryName; - } - - void CreateOneToManyTableWithMap(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); - - // Create the data table as a 1:1 - anon::CreateDataTable(connection, tableName, valueName); - - switch (schemaVersion) - { - case OneToManyTableSchema::Version_2_0: - { - // Create the mapping table - StatementBuilder createMapTableBuilder; - createMapTableBuilder.CreateTable({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Columns({ - ColumnBuilder(valueName, Type::Int64).NotNull(), - ColumnBuilder(s_OneToManyTableWithMap_MapTable_PrimaryName, Type::Int64).NotNull(), - PrimaryKeyBuilder({ valueName, s_OneToManyTableWithMap_MapTable_PrimaryName }) - }).WithoutRowID(); - - createMapTableBuilder.Execute(connection); - } - break; - default: - THROW_HR(E_UNEXPECTED); - } - - StatementBuilder createMapTableIndexBuilder; - createMapTableIndexBuilder.CreateIndex({ tableName, s_OneToManyTableWithMap_MapTable_Suffix, s_OneToManyTableWithMap_MapTable_IndexSuffix }). - On({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Columns({ s_OneToManyTableWithMap_MapTable_PrimaryName, valueName }); - - createMapTableIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void DropOneToManyTableWithMap(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_drop_v2_0"); - - anon::DropDataTable(connection, tableName); - - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }); - - dropTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - std::vector OneToManyTableWithMapGetValuesByPrimaryId( - const SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - SQLite::rowid_t manifestId) - { - using QCol = SQLite::Builder::QualifiedColumn; - - std::vector result; - - SQLite::Builder::StatementBuilder builder; - builder.Select(QCol(tableName, valueName)). - From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As("map").Join(tableName). - On(QCol("map", valueName), QCol(tableName, SQLite::RowIDName)).Where(QCol("map", s_OneToManyTableWithMap_MapTable_PrimaryName)).Equals(manifestId); - - SQLite::Statement statement = builder.Prepare(connection); - - while (statement.Step()) - { - result.emplace_back(statement.GetColumn(0)); - } - - return result; - } - - void OneToManyTableWithMapEnsureExistsAndInsert(SQLite::Connection& connection, - std::string_view tableName, std::string_view valueName, - const std::vector& values, SQLite::rowid_t manifestId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_ensureandinsert_v2_0"); - - SQLite::Statement insertMapping = anon::CreateMappingInsertStatementForPrimaryId(connection, tableName, valueName, manifestId); - - for (const std::string& value : values) - { - // First, ensure that the data exists - SQLite::rowid_t dataId = anon::DataTableEnsureExists(connection, tableName, valueName, value); - - // Second, insert into the mapping table - insertMapping.Reset(); - insertMapping.Bind(2, dataId); - - insertMapping.Execute(); - } - - savepoint.Commit(); - } - - void OneToManyTableWithMapPrepareForPackaging(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropMapTableIndexBuilder; - dropMapTableIndexBuilder.DropIndex({ tableName, s_OneToManyTableWithMap_MapTable_Suffix, s_OneToManyTableWithMap_MapTable_IndexSuffix }); - - dropMapTableIndexBuilder.Execute(connection); - - anon::DataTablePrepareForPackaging(connection, tableName); - } - - bool OneToManyTableWithMapCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) - { - using QCol = SQLite::Builder::QualifiedColumn; - constexpr std::string_view s_map = "map"sv; - - bool result = true; - - { - // Build a select statement to find map rows containing references to primaries with nonexistent rowids - // Such as: - // Select map.rowid, map.primary from tags_map as map left outer join primary on map.primary = primary.rowid where primary.id is null - - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(s_map, valueName) }). - From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map). - LeftOuterJoin(details::PrimaryTable::TableName()).On(QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)). - Where(QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTableWithMap_MapTable_Suffix << " [" << select.GetColumn(0) << - ", " << select.GetColumn(1) << "] refers to invalid " << details::PrimaryTable::TableName()); - } - } - - if (!result && !log) - { - return result; - } - - { - // Build a select statement to find map rows containing references to 1:1 tables with nonexistent rowids - // Such as: - // Select map.rowid, map.tag from tags_map as map left outer join tags on map.tag = tags.rowid where tags.tag is null - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(s_map, valueName) }). - From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map). - LeftOuterJoin(tableName).On(QCol(s_map, valueName), QCol(tableName, SQLite::RowIDName)). - Where(QCol(tableName, valueName)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - bool secondaryResult = true; - - while (select.Step()) - { - secondaryResult = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTableWithMap_MapTable_Suffix << " [" << select.GetColumn(0) << - ", " << select.GetColumn(1) << "] refers to invalid " << tableName); - } - - result = result && secondaryResult; - } - - if (!result && !log) - { - return result; - } - - result = anon::DataTableCheckConsistency(connection, tableName, valueName, log) && result; - - return result; - } - - bool OneToManyTableWithMapIsEmpty(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder countBuilder; - countBuilder.Select(SQLite::Builder::RowCount).From(tableName); - - SQLite::Statement countStatement = countBuilder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - SQLite::Builder::StatementBuilder countMapBuilder; - countMapBuilder.Select(SQLite::Builder::RowCount).From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }); - - SQLite::Statement countMapStatement = countMapBuilder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countMapStatement.Step()); - - return ((countStatement.GetColumn(0) == 0) && (countMapStatement.GetColumn(0) == 0)); - } - - int OneToManyTableWithMapBuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::string_view tableName, - std::string_view valueName, - std::string_view primaryAlias, - std::string_view valueAlias, - bool useLike) - { - using QCol = SQLite::Builder::QualifiedColumn; - constexpr std::string_view s_map = "map"sv; - - // Build a statement like: - // SELECT map.package as p, table.value as v from table - // join map on table.rowid = map.value - // where table.value = - builder.Select(). - Column(QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName)).As(primaryAlias). - Column(QCol(tableName, valueName)).As(valueAlias). - From(tableName). - Join({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map).On(QCol(tableName, SQLite::RowIDName), QCol(s_map, valueName)). - Where(QCol(tableName, valueName)); - - int result = -1; - - if (useLike) - { - builder.Like(SQLite::Builder::Unbound); - result = builder.GetLastBindIndex(); - builder.Escape(SQLite::EscapeCharForLike); - } - else - { - builder.Equals(SQLite::Builder::Unbound); - result = builder.GetLastBindIndex(); - } - - return result; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" +#include "Microsoft/Schema/2_0/PackagesTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using PrimaryTable = PackagesTable; + + using namespace std::string_view_literals; + static constexpr std::string_view s_OneToManyTableWithMap_MapTable_PrimaryName = "package"sv; + static constexpr std::string_view s_OneToManyTableWithMap_MapTable_Suffix = "_map"sv; + static constexpr std::string_view s_OneToManyTableWithMap_MapTable_IndexSuffix = "_index"sv; + static constexpr std::string_view s_OneToManyTableWithMap_PrimaryKeyIndexSuffix = "_pkindex"sv; + + namespace anon + { + // Create the mapping table insert statement for multiple use. + // Bind the rowid of the value to 2. + SQLite::Statement CreateMappingInsertStatementForPrimaryId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) + { + SQLite::Builder::StatementBuilder insertMappingBuilder; + insertMappingBuilder.InsertOrIgnore({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }). + Columns({ s_OneToManyTableWithMap_MapTable_PrimaryName, valueName }).Values(manifestId, SQLite::Builder::Unbound); + + return insertMappingBuilder.Prepare(connection); + } + + // Get a collection of the value ids associated with the given primary id. + std::vector GetValueIdsByPrimaryId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) + { + std::vector result; + + SQLite::Builder::StatementBuilder selectMappingBuilder; + selectMappingBuilder.Select(valueName).From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Where(s_OneToManyTableWithMap_MapTable_PrimaryName).Equals(manifestId); + + SQLite::Statement selectMappingStatement = selectMappingBuilder.Prepare(connection); + + while (selectMappingStatement.Step()) + { + result.push_back(selectMappingStatement.GetColumn(0)); + } + + return result; + } + + void CreateDataTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); + + StatementBuilder createTableBuilder; + + createTableBuilder.CreateTable(tableName).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(valueName, Type::Text).NotNull() + }); + + createTableBuilder.Execute(connection); + + StatementBuilder indexBuilder; + indexBuilder.CreateUniqueIndex({ tableName, s_OneToManyTableWithMap_PrimaryKeyIndexSuffix }).On(tableName).Columns(valueName); + indexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void DropDataTable(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(tableName); + + dropTableBuilder.Execute(connection); + } + + std::optional DataTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); + + if (useLike) + { + selectBuilder.LikeWithEscape(value); + } + else + { + selectBuilder.Equals(value); + } + + SQLite::Statement select = selectBuilder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + std::optional DataTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t rowid) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(rowid); + + SQLite::Statement select = selectBuilder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + SQLite::rowid_t DataTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch = false) + { + auto selectResult = DataTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); + if (selectResult) + { + if (overwriteLikeMatch) + { + // If the value in the table is not an exact match, overwrite it with the incoming value + auto tableValue = DataTableSelectValueById(connection, tableName, valueName, selectResult.value()); + if (tableValue.value() != value) + { + SQLite::Builder::StatementBuilder updateBuilder; + updateBuilder.Update(tableName).Set().Column(valueName).AssignValue(value).Where(SQLite::RowIDName).Equals(selectResult); + + updateBuilder.Execute(connection); + } + } + + return selectResult.value(); + } + + SQLite::Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); + + insertBuilder.Execute(connection); + + return connection.GetLastInsertRowID(); + } + + void DataTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropIndexBuilder; + dropIndexBuilder.DropIndex({ tableName, s_OneToManyTableWithMap_PrimaryKeyIndexSuffix }); + dropIndexBuilder.Execute(connection); + } + + bool DataTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) + { + // Build a select statement to find values that contain an embedded null character + // Such as: + // Select count(*) from table where instr(value,char(0))>0 + SQLite::Builder::StatementBuilder builder; + builder. + Select({ SQLite::RowIDName, valueName }). + From(tableName). + WhereValueContainsEmbeddedNullCharacter(valueName); + + SQLite::Statement select = builder.Prepare(connection); + bool result = true; + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); + } + + return result; + } + } + + std::string OneToManyTableWithMapGetMapTableName(std::string_view tableName) + { + std::string result(tableName); + result += s_OneToManyTableWithMap_MapTable_Suffix; + return result; + } + + std::string_view OneToManyTableWithMapGetManifestColumnName() + { + return s_OneToManyTableWithMap_MapTable_PrimaryName; + } + + void CreateOneToManyTableWithMap(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); + + // Create the data table as a 1:1 + anon::CreateDataTable(connection, tableName, valueName); + + switch (schemaVersion) + { + case OneToManyTableSchema::Version_2_0: + { + // Create the mapping table + StatementBuilder createMapTableBuilder; + createMapTableBuilder.CreateTable({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Columns({ + ColumnBuilder(valueName, Type::Int64).NotNull(), + ColumnBuilder(s_OneToManyTableWithMap_MapTable_PrimaryName, Type::Int64).NotNull(), + PrimaryKeyBuilder({ valueName, s_OneToManyTableWithMap_MapTable_PrimaryName }) + }).WithoutRowID(); + + createMapTableBuilder.Execute(connection); + } + break; + default: + THROW_HR(E_UNEXPECTED); + } + + StatementBuilder createMapTableIndexBuilder; + createMapTableIndexBuilder.CreateIndex({ tableName, s_OneToManyTableWithMap_MapTable_Suffix, s_OneToManyTableWithMap_MapTable_IndexSuffix }). + On({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Columns({ s_OneToManyTableWithMap_MapTable_PrimaryName, valueName }); + + createMapTableIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void DropOneToManyTableWithMap(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_drop_v2_0"); + + anon::DropDataTable(connection, tableName); + + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }); + + dropTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::vector OneToManyTableWithMapGetValuesByPrimaryId( + const SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + SQLite::rowid_t manifestId) + { + using QCol = SQLite::Builder::QualifiedColumn; + + std::vector result; + + SQLite::Builder::StatementBuilder builder; + builder.Select(QCol(tableName, valueName)). + From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As("map").Join(tableName). + On(QCol("map", valueName), QCol(tableName, SQLite::RowIDName)).Where(QCol("map", s_OneToManyTableWithMap_MapTable_PrimaryName)).Equals(manifestId); + + SQLite::Statement statement = builder.Prepare(connection); + + while (statement.Step()) + { + result.emplace_back(statement.GetColumn(0)); + } + + return result; + } + + void OneToManyTableWithMapEnsureExistsAndInsert(SQLite::Connection& connection, + std::string_view tableName, std::string_view valueName, + const std::vector& values, SQLite::rowid_t manifestId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_ensureandinsert_v2_0"); + + SQLite::Statement insertMapping = anon::CreateMappingInsertStatementForPrimaryId(connection, tableName, valueName, manifestId); + + for (const std::string& value : values) + { + // First, ensure that the data exists + SQLite::rowid_t dataId = anon::DataTableEnsureExists(connection, tableName, valueName, value); + + // Second, insert into the mapping table + insertMapping.Reset(); + insertMapping.Bind(2, dataId); + + insertMapping.Execute(); + } + + savepoint.Commit(); + } + + void OneToManyTableWithMapPrepareForPackaging(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropMapTableIndexBuilder; + dropMapTableIndexBuilder.DropIndex({ tableName, s_OneToManyTableWithMap_MapTable_Suffix, s_OneToManyTableWithMap_MapTable_IndexSuffix }); + + dropMapTableIndexBuilder.Execute(connection); + + anon::DataTablePrepareForPackaging(connection, tableName); + } + + bool OneToManyTableWithMapCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) + { + using QCol = SQLite::Builder::QualifiedColumn; + constexpr std::string_view s_map = "map"sv; + + bool result = true; + + { + // Build a select statement to find map rows containing references to primaries with nonexistent rowids + // Such as: + // Select map.rowid, map.primary from tags_map as map left outer join primary on map.primary = primary.rowid where primary.id is null + + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(s_map, valueName) }). + From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map). + LeftOuterJoin(details::PrimaryTable::TableName()).On(QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)). + Where(QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTableWithMap_MapTable_Suffix << " [" << select.GetColumn(0) << + ", " << select.GetColumn(1) << "] refers to invalid " << details::PrimaryTable::TableName()); + } + } + + if (!result && !log) + { + return result; + } + + { + // Build a select statement to find map rows containing references to 1:1 tables with nonexistent rowids + // Such as: + // Select map.rowid, map.tag from tags_map as map left outer join tags on map.tag = tags.rowid where tags.tag is null + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(s_map, valueName) }). + From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map). + LeftOuterJoin(tableName).On(QCol(s_map, valueName), QCol(tableName, SQLite::RowIDName)). + Where(QCol(tableName, valueName)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + bool secondaryResult = true; + + while (select.Step()) + { + secondaryResult = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTableWithMap_MapTable_Suffix << " [" << select.GetColumn(0) << + ", " << select.GetColumn(1) << "] refers to invalid " << tableName); + } + + result = result && secondaryResult; + } + + if (!result && !log) + { + return result; + } + + result = anon::DataTableCheckConsistency(connection, tableName, valueName, log) && result; + + return result; + } + + bool OneToManyTableWithMapIsEmpty(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder countBuilder; + countBuilder.Select(SQLite::Builder::RowCount).From(tableName); + + SQLite::Statement countStatement = countBuilder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + SQLite::Builder::StatementBuilder countMapBuilder; + countMapBuilder.Select(SQLite::Builder::RowCount).From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }); + + SQLite::Statement countMapStatement = countMapBuilder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countMapStatement.Step()); + + return ((countStatement.GetColumn(0) == 0) && (countMapStatement.GetColumn(0) == 0)); + } + + int OneToManyTableWithMapBuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::string_view tableName, + std::string_view valueName, + std::string_view primaryAlias, + std::string_view valueAlias, + bool useLike) + { + using QCol = SQLite::Builder::QualifiedColumn; + constexpr std::string_view s_map = "map"sv; + + // Build a statement like: + // SELECT map.package as p, table.value as v from table + // join map on table.rowid = map.value + // where table.value = + builder.Select(). + Column(QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName)).As(primaryAlias). + Column(QCol(tableName, valueName)).As(valueAlias). + From(tableName). + Join({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map).On(QCol(tableName, SQLite::RowIDName), QCol(s_map, valueName)). + Where(QCol(tableName, valueName)); + + int result = -1; + + if (useLike) + { + builder.Like(SQLite::Builder::Unbound); + result = builder.GetLastBindIndex(); + builder.Escape(SQLite::EscapeCharForLike); + } + else + { + builder.Equals(SQLite::Builder::Unbound); + result = builder.GetLastBindIndex(); + } + + return result; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp index fceea986ad..294baf3905 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp @@ -1,249 +1,249 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PackageUpdateTrackingTable.h" -#include -#include - -using namespace AppInstaller::SQLite; - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - using namespace std::string_view_literals; - static constexpr std::string_view s_PUTT_Table_Name = "update_tracking"sv; - static constexpr std::string_view s_PUTT_WriteTimeIndex_Name = "update_tracking_write_idx"sv; - static constexpr std::string_view s_PUTT_Package = "package"sv; - static constexpr std::string_view s_PUTT_WriteTime = "write_time"sv; - static constexpr std::string_view s_PUTT_Manifest = "manifest"sv; - static constexpr std::string_view s_PUTT_Hash = "hash"sv; - - std::string_view PackageUpdateTrackingTable::TableName() - { - return s_PUTT_Table_Name; - } - - void PackageUpdateTrackingTable::Create(SQLite::Connection& connection) - { - using namespace Builder; - - StatementBuilder builder; - builder.CreateTable(s_PUTT_Table_Name).BeginColumns(); - - builder.Column(IntegerPrimaryKey()); - builder.Column(ColumnBuilder(s_PUTT_Package, Type::Text).NotNull()); - builder.Column(ColumnBuilder(s_PUTT_WriteTime, Type::Int64).NotNull()); - builder.Column(ColumnBuilder(s_PUTT_Manifest, Type::Blob).NotNull()); - builder.Column(ColumnBuilder(s_PUTT_Hash, Type::Blob).NotNull()); - - builder.EndColumns(); - - builder.Execute(connection); - - StatementBuilder indexBuilder; - indexBuilder.CreateIndex(s_PUTT_WriteTimeIndex_Name).On(s_PUTT_Table_Name).Columns(s_PUTT_WriteTime); - indexBuilder.Execute(connection); - } - - void PackageUpdateTrackingTable::EnsureExists(SQLite::Connection& connection) - { - if (!Exists(connection)) - { - Create(connection); - } - } - - void PackageUpdateTrackingTable::Drop(SQLite::Connection& connection) - { - Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(s_PUTT_Table_Name); - - dropTableBuilder.Execute(connection); - } - - bool PackageUpdateTrackingTable::Exists(const SQLite::Connection& connection) - { - Builder::StatementBuilder builder; - builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). - Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_PUTT_Table_Name); - - Statement statement = builder.Prepare(connection); - THROW_HR_IF(E_UNEXPECTED, !statement.Step()); - return statement.GetColumn(0) != 0; - } - - void PackageUpdateTrackingTable::Update(SQLite::Connection& connection, const ISQLiteIndex* internalIndex, const std::string& packageIdentifier, bool ensureTable) - { - if (ensureTable) - { - EnsureExists(connection); - } - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageIdentifier); - auto result = internalIndex->Search(connection, request); - - if (result.Matches.empty()) - { - // Remove any existing package update row - Builder::StatementBuilder deleteBuilder; - deleteBuilder.DeleteFrom(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); - - deleteBuilder.Execute(connection); - } - else - { - THROW_HR_IF(E_UNEXPECTED, result.Matches.size() != 1); - - // Insert or update the package row - std::vector versionKeys = internalIndex->GetVersionKeysById(connection, result.Matches[0].first); - - Manifest::PackageVersionDataManifest manifest; - - for (const auto& key : versionKeys) - { - Manifest::PackageVersionDataManifest::VersionData versionData{ - key.VersionAndChannel, - internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMinVersion), - internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMaxVersion), - internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::RelativePath), - internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ManifestSHA256Hash) - }; - - manifest.AddVersion(std::move(versionData)); - } - - std::string manifestString = manifest.Serialize(); - - auto compressor = Manifest::PackageVersionDataManifest::CreateCompressor(); - std::vector compressedManifest = compressor.Compress(manifestString); - - Utility::SHA256::HashBuffer manifestHash = Utility::SHA256::ComputeHash(compressedManifest); - int64_t currentTime = Utility::GetCurrentUnixEpoch(); - - // First attempt to update the row and then insert it if no modification occurred. - Builder::StatementBuilder updateBuilder; - updateBuilder.Update(s_PUTT_Table_Name).Set(). - Column(s_PUTT_WriteTime).AssignValue(currentTime). - Column(s_PUTT_Manifest).AssignValue(compressedManifest). - Column(s_PUTT_Hash).AssignValue(manifestHash). - Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); - - updateBuilder.Execute(connection); - - if (connection.GetChanges() == 0) - { - Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_PUTT_Table_Name). - Columns({ s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }). - Values(packageIdentifier, currentTime, compressedManifest, manifestHash); - - insertBuilder.Execute(connection); - } - } - } - - bool PackageUpdateTrackingTable::CheckConsistency(const SQLite::Connection& connection, ISQLiteIndex* internalIndex, bool log) - { - bool result = true; - - // Ensure that all data in the update table matches the internal index - for (const PackageData& packageData : GetUpdatesSince(connection, 0)) - { - auto manifestHash = Utility::SHA256::ComputeHash(packageData.Manifest); - if (!Utility::SHA256::AreEqual(packageData.Hash, manifestHash)) - { - if (!log) - { - return false; - } - - result = false; - AICLI_LOG(Repo, Info, << " [INVALID] value [" << s_PUTT_Hash << "] in table [" << s_PUTT_Table_Name << - "] at row [" << packageData.RowID << "]; the hash of the manifest value does not match the hash in the row"); - } - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageData.PackageIdentifier); - - if (internalIndex->Search(connection, request).Matches.empty()) - { - if (!log) - { - return false; - } - - result = false; - AICLI_LOG(Repo, Info, << " [INVALID] value [" << s_PUTT_Package << "] in table [" << s_PUTT_Table_Name << - "] at row [" << packageData.RowID << "]; the package [" << packageData.PackageIdentifier << "] was not found in the internal index"); - } - } - - // Ensure that all packages in the internal index are present in the update table - Builder::StatementBuilder builder; - builder.Select(Builder::RowCount).From(s_PUTT_Table_Name).Where(s_PUTT_Package).Like(Builder::Unbound).Escape(EscapeCharForLike); - - Statement select = builder.Prepare(connection); - - for (const auto& packageMatch : internalIndex->Search(connection, {}).Matches) - { - std::vector versionKeys = internalIndex->GetVersionKeysById(connection, packageMatch.first); - ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0]; - - std::string packageIdentifier = internalIndex->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value(); - - select.Reset(); - select.Bind(1, packageIdentifier); - select.Step(); - - if (select.GetColumn(0) != 1) - { - if (!log) - { - return false; - } - - result = false; - AICLI_LOG(Repo, Info, << " [INVALID] value [" << packageIdentifier << "] in the internal index was not found in [" << s_PUTT_Table_Name << "]"); - } - } - - return result; - } - - std::vector PackageUpdateTrackingTable::GetUpdatesSince(const SQLite::Connection& connection, int64_t updateBaseTime) - { - Builder::StatementBuilder builder; - builder.Select({ RowIDName, s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }). - From(s_PUTT_Table_Name).Where(s_PUTT_WriteTime).IsGreaterThanOrEqualTo(updateBaseTime); - - Statement select = builder.Prepare(connection); - - std::vector result; - - while (select.Step()) - { - PackageData item; - item.RowID = select.GetColumn(0); - item.PackageIdentifier = select.GetColumn(1); - item.WriteTime = select.GetColumn(2); - item.Manifest = select.GetColumn(3); - item.Hash = select.GetColumn(4); - - result.emplace_back(std::move(item)); - } - - return result; - } - - SQLite::blob_t PackageUpdateTrackingTable::GetDataHash(const SQLite::Connection& connection, const std::string& packageIdentifier) - { - Builder::StatementBuilder builder; - builder.Select(s_PUTT_Hash).From(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); - - Statement select = builder.Prepare(connection); - - THROW_HR_IF(E_NOT_SET, !select.Step()); - - return select.GetColumn(0); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageUpdateTrackingTable.h" +#include +#include + +using namespace AppInstaller::SQLite; + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + using namespace std::string_view_literals; + static constexpr std::string_view s_PUTT_Table_Name = "update_tracking"sv; + static constexpr std::string_view s_PUTT_WriteTimeIndex_Name = "update_tracking_write_idx"sv; + static constexpr std::string_view s_PUTT_Package = "package"sv; + static constexpr std::string_view s_PUTT_WriteTime = "write_time"sv; + static constexpr std::string_view s_PUTT_Manifest = "manifest"sv; + static constexpr std::string_view s_PUTT_Hash = "hash"sv; + + std::string_view PackageUpdateTrackingTable::TableName() + { + return s_PUTT_Table_Name; + } + + void PackageUpdateTrackingTable::Create(SQLite::Connection& connection) + { + using namespace Builder; + + StatementBuilder builder; + builder.CreateTable(s_PUTT_Table_Name).BeginColumns(); + + builder.Column(IntegerPrimaryKey()); + builder.Column(ColumnBuilder(s_PUTT_Package, Type::Text).NotNull()); + builder.Column(ColumnBuilder(s_PUTT_WriteTime, Type::Int64).NotNull()); + builder.Column(ColumnBuilder(s_PUTT_Manifest, Type::Blob).NotNull()); + builder.Column(ColumnBuilder(s_PUTT_Hash, Type::Blob).NotNull()); + + builder.EndColumns(); + + builder.Execute(connection); + + StatementBuilder indexBuilder; + indexBuilder.CreateIndex(s_PUTT_WriteTimeIndex_Name).On(s_PUTT_Table_Name).Columns(s_PUTT_WriteTime); + indexBuilder.Execute(connection); + } + + void PackageUpdateTrackingTable::EnsureExists(SQLite::Connection& connection) + { + if (!Exists(connection)) + { + Create(connection); + } + } + + void PackageUpdateTrackingTable::Drop(SQLite::Connection& connection) + { + Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(s_PUTT_Table_Name); + + dropTableBuilder.Execute(connection); + } + + bool PackageUpdateTrackingTable::Exists(const SQLite::Connection& connection) + { + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). + Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_PUTT_Table_Name); + + Statement statement = builder.Prepare(connection); + THROW_HR_IF(E_UNEXPECTED, !statement.Step()); + return statement.GetColumn(0) != 0; + } + + void PackageUpdateTrackingTable::Update(SQLite::Connection& connection, const ISQLiteIndex* internalIndex, const std::string& packageIdentifier, bool ensureTable) + { + if (ensureTable) + { + EnsureExists(connection); + } + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageIdentifier); + auto result = internalIndex->Search(connection, request); + + if (result.Matches.empty()) + { + // Remove any existing package update row + Builder::StatementBuilder deleteBuilder; + deleteBuilder.DeleteFrom(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); + + deleteBuilder.Execute(connection); + } + else + { + THROW_HR_IF(E_UNEXPECTED, result.Matches.size() != 1); + + // Insert or update the package row + std::vector versionKeys = internalIndex->GetVersionKeysById(connection, result.Matches[0].first); + + Manifest::PackageVersionDataManifest manifest; + + for (const auto& key : versionKeys) + { + Manifest::PackageVersionDataManifest::VersionData versionData{ + key.VersionAndChannel, + internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMinVersion), + internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMaxVersion), + internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::RelativePath), + internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ManifestSHA256Hash) + }; + + manifest.AddVersion(std::move(versionData)); + } + + std::string manifestString = manifest.Serialize(); + + auto compressor = Manifest::PackageVersionDataManifest::CreateCompressor(); + std::vector compressedManifest = compressor.Compress(manifestString); + + Utility::SHA256::HashBuffer manifestHash = Utility::SHA256::ComputeHash(compressedManifest); + int64_t currentTime = Utility::GetCurrentUnixEpoch(); + + // First attempt to update the row and then insert it if no modification occurred. + Builder::StatementBuilder updateBuilder; + updateBuilder.Update(s_PUTT_Table_Name).Set(). + Column(s_PUTT_WriteTime).AssignValue(currentTime). + Column(s_PUTT_Manifest).AssignValue(compressedManifest). + Column(s_PUTT_Hash).AssignValue(manifestHash). + Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); + + updateBuilder.Execute(connection); + + if (connection.GetChanges() == 0) + { + Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_PUTT_Table_Name). + Columns({ s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }). + Values(packageIdentifier, currentTime, compressedManifest, manifestHash); + + insertBuilder.Execute(connection); + } + } + } + + bool PackageUpdateTrackingTable::CheckConsistency(const SQLite::Connection& connection, ISQLiteIndex* internalIndex, bool log) + { + bool result = true; + + // Ensure that all data in the update table matches the internal index + for (const PackageData& packageData : GetUpdatesSince(connection, 0)) + { + auto manifestHash = Utility::SHA256::ComputeHash(packageData.Manifest); + if (!Utility::SHA256::AreEqual(packageData.Hash, manifestHash)) + { + if (!log) + { + return false; + } + + result = false; + AICLI_LOG(Repo, Info, << " [INVALID] value [" << s_PUTT_Hash << "] in table [" << s_PUTT_Table_Name << + "] at row [" << packageData.RowID << "]; the hash of the manifest value does not match the hash in the row"); + } + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageData.PackageIdentifier); + + if (internalIndex->Search(connection, request).Matches.empty()) + { + if (!log) + { + return false; + } + + result = false; + AICLI_LOG(Repo, Info, << " [INVALID] value [" << s_PUTT_Package << "] in table [" << s_PUTT_Table_Name << + "] at row [" << packageData.RowID << "]; the package [" << packageData.PackageIdentifier << "] was not found in the internal index"); + } + } + + // Ensure that all packages in the internal index are present in the update table + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(s_PUTT_Table_Name).Where(s_PUTT_Package).Like(Builder::Unbound).Escape(EscapeCharForLike); + + Statement select = builder.Prepare(connection); + + for (const auto& packageMatch : internalIndex->Search(connection, {}).Matches) + { + std::vector versionKeys = internalIndex->GetVersionKeysById(connection, packageMatch.first); + ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0]; + + std::string packageIdentifier = internalIndex->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value(); + + select.Reset(); + select.Bind(1, packageIdentifier); + select.Step(); + + if (select.GetColumn(0) != 1) + { + if (!log) + { + return false; + } + + result = false; + AICLI_LOG(Repo, Info, << " [INVALID] value [" << packageIdentifier << "] in the internal index was not found in [" << s_PUTT_Table_Name << "]"); + } + } + + return result; + } + + std::vector PackageUpdateTrackingTable::GetUpdatesSince(const SQLite::Connection& connection, int64_t updateBaseTime) + { + Builder::StatementBuilder builder; + builder.Select({ RowIDName, s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }). + From(s_PUTT_Table_Name).Where(s_PUTT_WriteTime).IsGreaterThanOrEqualTo(updateBaseTime); + + Statement select = builder.Prepare(connection); + + std::vector result; + + while (select.Step()) + { + PackageData item; + item.RowID = select.GetColumn(0); + item.PackageIdentifier = select.GetColumn(1); + item.WriteTime = select.GetColumn(2); + item.Manifest = select.GetColumn(3); + item.Hash = select.GetColumn(4); + + result.emplace_back(std::move(item)); + } + + return result; + } + + SQLite::blob_t PackageUpdateTrackingTable::GetDataHash(const SQLite::Connection& connection, const std::string& packageIdentifier) + { + Builder::StatementBuilder builder; + builder.Select(s_PUTT_Hash).From(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); + + Statement select = builder.Prepare(connection); + + THROW_HR_IF(E_NOT_SET, !select.Step()); + + return select.GetColumn(0); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp index 735a34f9e9..52d92ba8a5 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp @@ -1,320 +1,320 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResultsTable.h" -#include - -#include "Microsoft/Schema/2_0/PackagesTable.h" -#include "Microsoft/Schema/2_0/TagsTable.h" -#include "Microsoft/Schema/2_0/CommandsTable.h" -#include "Microsoft/Schema/2_0/PackageFamilyNameTable.h" -#include "Microsoft/Schema/2_0/ProductCodeTable.h" -#include "Microsoft/Schema/2_0/UpgradeCodeTable.h" -#include "Microsoft/Schema/2_0/NormalizedPackageNameTable.h" -#include "Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace - { - using namespace std::string_literals; - using namespace std::string_view_literals; - - constexpr std::string_view s_SearchResultsTable_Package = "package"sv; - constexpr std::string_view s_SearchResultsTable_MatchField = "field"sv; - constexpr std::string_view s_SearchResultsTable_MatchType = "match"sv; - constexpr std::string_view s_SearchResultsTable_MatchValue = "value"sv; - constexpr std::string_view s_SearchResultsTable_SortValue = "sort"sv; - constexpr std::string_view s_SearchResultsTable_Filter = "filter"sv; - - constexpr std::string_view s_SearchResultsTable_Index_Suffix = "_i_m"sv; - - constexpr std::string_view s_SearchResultsTable_SubSelect_TableAlias = "valueTable"sv; - constexpr std::string_view s_SearchResultsTable_SubSelect_PackageAlias = "p"sv; - constexpr std::string_view s_SearchResultsTable_SubSelect_ValueAlias = "v"sv; - } - - SearchResultsTable::SearchResultsTable(const SQLite::Connection& connection) : - m_connection(connection) - { - using namespace SQLite::Builder; - - { - StatementBuilder builder; - builder.CreateTable(GetQualifiedName()).BeginColumns(); - - builder.Column(ColumnBuilder(s_SearchResultsTable_Package, Type::RowId).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchField, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchType, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchValue, Type::Text).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_SortValue, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_Filter, Type::Bool).NotNull()); - - builder.EndColumns(); - - builder.Execute(m_connection); - } - - InitDropStatement(m_connection); - - { - SQLite::Builder::QualifiedTable index = GetQualifiedName(); - std::string indexName(index.Table); - indexName += s_SearchResultsTable_Index_Suffix; - index.Table = indexName; - - StatementBuilder builder; - builder.CreateIndex(indexName).On(GetQualifiedName().Table).Columns(s_SearchResultsTable_Package); - - builder.Execute(m_connection); - } - } - - void SearchResultsTable::SearchOnField(const PackageMatchFilter& filter) - { - using namespace SQLite::Builder; - - int sortOrdinal = m_sortOrdinalValue++; - - // Create an insert statement to select values into the table as requested. - // The goal is a statement like this: - // INSERT INTO - // SELECT valueTable.p, , , valueTable.v, , FROM - // (SELECT packages.rowid as p, packages.id as v from packages where packages.id = ) AS valueTable - // Where the subselect is built by the owning table. - StatementBuilder builder; - builder.InsertInto(GetQualifiedName()).Select(). - Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_PackageAlias)). - Value(filter.Field). - Value(filter.Type). - Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ValueAlias)). - Value(sortOrdinal). - Value(false). - From().BeginParenthetical(); - - // Add the field specific portion - std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); - - if (bindIndex.empty()) - { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); - return; - } - - builder.EndParenthetical().As(s_SearchResultsTable_SubSelect_TableAlias); - - SQLite::Statement statement = builder.Prepare(m_connection); - BindStatementForMatchType(statement, filter, bindIndex); - statement.Execute(); - AICLI_LOG(SQL, Verbose, << "Search found " << m_connection.GetChanges() << " rows"); - } - - void SearchResultsTable::RemoveDuplicatePackageRows() - { - using namespace SQLite::Builder; - - // Create a delete statement to leave only one row with a given package. - // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. - // The goal is a statement like this: - // DELETE from where rowid not in ( - // SELECT rowid from ( - // SELECT rowid, min(sort) from group by package - // ) - // ) - StatementBuilder builder; - builder.DeleteFrom(GetQualifiedName()).Where(SQLite::RowIDName).Not().In().BeginParenthetical(). - Select(SQLite::RowIDName).From().BeginParenthetical(). - Select().Column(SQLite::RowIDName).Column(Aggregate::Min, s_SearchResultsTable_SortValue).From(GetQualifiedName()).GroupBy(s_SearchResultsTable_Package). - EndParenthetical(). - EndParenthetical(); - - builder.Execute(m_connection); - AICLI_LOG(SQL, Verbose, << "Removed " << m_connection.GetChanges() << " duplicate rows"); - } - - void SearchResultsTable::PrepareToFilter() - { - // Reset all filter values to unselected - SQLite::Builder::StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(false); - - builder.Execute(m_connection); - } - - void SearchResultsTable::FilterOnField(const PackageMatchFilter& filter) - { - using namespace SQLite::Builder; - - // Create an update statement to mark rows that are found by the search. - // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. - // The goal is a statement like this: - // UPDATE set filter = 1 where package in ( - // SELECT p from ( - // SELECT packages.rowid as p, packages.id as v from packages where packages.id = - // ) - // ) - StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(true).Where(s_SearchResultsTable_Package).In().BeginParenthetical(). - Select(s_SearchResultsTable_SubSelect_PackageAlias).From().BeginParenthetical(); - - // Add the field specific portion - std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); - - if (bindIndex.empty()) - { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); - return; - } - - builder.EndParenthetical().EndParenthetical(); - - SQLite::Statement statement = builder.Prepare(m_connection); - BindStatementForMatchType(statement, filter, bindIndex); - statement.Execute(); - AICLI_LOG(SQL, Verbose, << "Filter kept " << m_connection.GetChanges() << " rows"); - } - - void SearchResultsTable::CompleteFilter() - { - // Delete all unselected values - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(GetQualifiedName()).Where(s_SearchResultsTable_Filter).Equals(false); - - builder.Execute(m_connection); - AICLI_LOG(SQL, Verbose, << "Filter deleted " << m_connection.GetChanges() << " rows"); - } - - ISQLiteIndex::SearchResult SearchResultsTable::GetSearchResults(size_t limit) - { - using namespace SQLite::Builder; - using QCol = QualifiedColumn; - - // Select all of the results from the table; it is expected that RemoveDuplicatePackageRows has been called. - StatementBuilder builder; - builder.Select({ - s_SearchResultsTable_Package, - s_SearchResultsTable_MatchField, - s_SearchResultsTable_MatchType, - s_SearchResultsTable_MatchValue, - }). - From(GetQualifiedName()).OrderBy(s_SearchResultsTable_SortValue); - - SQLite::Statement select = builder.Prepare(m_connection); - - ISQLiteIndex::SearchResult result; - while (select.Step()) - { - if (limit && result.Matches.size() >= limit) - { - break; - } - - result.Matches.emplace_back(select.GetColumn(0), - PackageMatchFilter(select.GetColumn(1), select.GetColumn(2), select.GetColumn(3))); - } - - result.Truncated = (select.GetState() != SQLite::Statement::State::Completed); - - return result; - } - - std::vector SearchResultsTable::BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const - { - return BuildSearchStatement(builder, field, s_SearchResultsTable_SubSelect_PackageAlias, s_SearchResultsTable_SubSelect_ValueAlias, MatchUsesLike(match)); - } - - std::vector SearchResultsTable::BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const - { - std::vector result; - - switch (field) - { - case PackageMatchField::Id: - result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::IdColumn::Name, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::Name: - result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::NameColumn::Name, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::Moniker: - result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::MonikerColumn::Name, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::Tag: - result.push_back(TagsTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::Command: - result.push_back(CommandsTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::PackageFamilyName: - result.push_back(PackageFamilyNameTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::ProductCode: - result.push_back(ProductCodeTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::UpgradeCode: - result.push_back(UpgradeCodeTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::NormalizedNameAndPublisher: - result = NormalizedPackageNameTable::BuildPairedSearchStatement(builder, manifestAlias, valueAlias, useLike); - break; - } - - return result; - } - - bool SearchResultsTable::MatchUsesLike(MatchType match) - { - return (match != MatchType::Exact); - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value) - { - std::string valueToUse; - - if (MatchUsesLike(match)) - { - valueToUse = SQLite::EscapeStringForLike(value); - } - else - { - valueToUse = value; - } - - switch (match) - { - case AppInstaller::Repository::MatchType::StartsWith: - valueToUse += '%'; - break; - case AppInstaller::Repository::MatchType::Substring: - valueToUse = "%"s + valueToUse + '%'; - break; - default: - // No changes required for others. - break; - } - - statement.Bind(bindIndex, valueToUse); - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) - { - // TODO: Implement these more complex match types - if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) - { - AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); - return; - } - - BindStatementForMatchType(statement, filter.Type, bindIndex[0], filter.Value); - - if (filter.Field == PackageMatchField::NormalizedNameAndPublisher) - { - BindStatementForMatchType(statement, filter.Type, bindIndex[1], filter.Additional.value()); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResultsTable.h" +#include + +#include "Microsoft/Schema/2_0/PackagesTable.h" +#include "Microsoft/Schema/2_0/TagsTable.h" +#include "Microsoft/Schema/2_0/CommandsTable.h" +#include "Microsoft/Schema/2_0/PackageFamilyNameTable.h" +#include "Microsoft/Schema/2_0/ProductCodeTable.h" +#include "Microsoft/Schema/2_0/UpgradeCodeTable.h" +#include "Microsoft/Schema/2_0/NormalizedPackageNameTable.h" +#include "Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace + { + using namespace std::string_literals; + using namespace std::string_view_literals; + + constexpr std::string_view s_SearchResultsTable_Package = "package"sv; + constexpr std::string_view s_SearchResultsTable_MatchField = "field"sv; + constexpr std::string_view s_SearchResultsTable_MatchType = "match"sv; + constexpr std::string_view s_SearchResultsTable_MatchValue = "value"sv; + constexpr std::string_view s_SearchResultsTable_SortValue = "sort"sv; + constexpr std::string_view s_SearchResultsTable_Filter = "filter"sv; + + constexpr std::string_view s_SearchResultsTable_Index_Suffix = "_i_m"sv; + + constexpr std::string_view s_SearchResultsTable_SubSelect_TableAlias = "valueTable"sv; + constexpr std::string_view s_SearchResultsTable_SubSelect_PackageAlias = "p"sv; + constexpr std::string_view s_SearchResultsTable_SubSelect_ValueAlias = "v"sv; + } + + SearchResultsTable::SearchResultsTable(const SQLite::Connection& connection) : + m_connection(connection) + { + using namespace SQLite::Builder; + + { + StatementBuilder builder; + builder.CreateTable(GetQualifiedName()).BeginColumns(); + + builder.Column(ColumnBuilder(s_SearchResultsTable_Package, Type::RowId).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchField, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchType, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchValue, Type::Text).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_SortValue, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_Filter, Type::Bool).NotNull()); + + builder.EndColumns(); + + builder.Execute(m_connection); + } + + InitDropStatement(m_connection); + + { + SQLite::Builder::QualifiedTable index = GetQualifiedName(); + std::string indexName(index.Table); + indexName += s_SearchResultsTable_Index_Suffix; + index.Table = indexName; + + StatementBuilder builder; + builder.CreateIndex(indexName).On(GetQualifiedName().Table).Columns(s_SearchResultsTable_Package); + + builder.Execute(m_connection); + } + } + + void SearchResultsTable::SearchOnField(const PackageMatchFilter& filter) + { + using namespace SQLite::Builder; + + int sortOrdinal = m_sortOrdinalValue++; + + // Create an insert statement to select values into the table as requested. + // The goal is a statement like this: + // INSERT INTO + // SELECT valueTable.p, , , valueTable.v, , FROM + // (SELECT packages.rowid as p, packages.id as v from packages where packages.id = ) AS valueTable + // Where the subselect is built by the owning table. + StatementBuilder builder; + builder.InsertInto(GetQualifiedName()).Select(). + Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_PackageAlias)). + Value(filter.Field). + Value(filter.Type). + Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ValueAlias)). + Value(sortOrdinal). + Value(false). + From().BeginParenthetical(); + + // Add the field specific portion + std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); + + if (bindIndex.empty()) + { + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); + return; + } + + builder.EndParenthetical().As(s_SearchResultsTable_SubSelect_TableAlias); + + SQLite::Statement statement = builder.Prepare(m_connection); + BindStatementForMatchType(statement, filter, bindIndex); + statement.Execute(); + AICLI_LOG(SQL, Verbose, << "Search found " << m_connection.GetChanges() << " rows"); + } + + void SearchResultsTable::RemoveDuplicatePackageRows() + { + using namespace SQLite::Builder; + + // Create a delete statement to leave only one row with a given package. + // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. + // The goal is a statement like this: + // DELETE from where rowid not in ( + // SELECT rowid from ( + // SELECT rowid, min(sort) from group by package + // ) + // ) + StatementBuilder builder; + builder.DeleteFrom(GetQualifiedName()).Where(SQLite::RowIDName).Not().In().BeginParenthetical(). + Select(SQLite::RowIDName).From().BeginParenthetical(). + Select().Column(SQLite::RowIDName).Column(Aggregate::Min, s_SearchResultsTable_SortValue).From(GetQualifiedName()).GroupBy(s_SearchResultsTable_Package). + EndParenthetical(). + EndParenthetical(); + + builder.Execute(m_connection); + AICLI_LOG(SQL, Verbose, << "Removed " << m_connection.GetChanges() << " duplicate rows"); + } + + void SearchResultsTable::PrepareToFilter() + { + // Reset all filter values to unselected + SQLite::Builder::StatementBuilder builder; + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(false); + + builder.Execute(m_connection); + } + + void SearchResultsTable::FilterOnField(const PackageMatchFilter& filter) + { + using namespace SQLite::Builder; + + // Create an update statement to mark rows that are found by the search. + // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. + // The goal is a statement like this: + // UPDATE set filter = 1 where package in ( + // SELECT p from ( + // SELECT packages.rowid as p, packages.id as v from packages where packages.id = + // ) + // ) + StatementBuilder builder; + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).AssignValue(true).Where(s_SearchResultsTable_Package).In().BeginParenthetical(). + Select(s_SearchResultsTable_SubSelect_PackageAlias).From().BeginParenthetical(); + + // Add the field specific portion + std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); + + if (bindIndex.empty()) + { + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); + return; + } + + builder.EndParenthetical().EndParenthetical(); + + SQLite::Statement statement = builder.Prepare(m_connection); + BindStatementForMatchType(statement, filter, bindIndex); + statement.Execute(); + AICLI_LOG(SQL, Verbose, << "Filter kept " << m_connection.GetChanges() << " rows"); + } + + void SearchResultsTable::CompleteFilter() + { + // Delete all unselected values + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(GetQualifiedName()).Where(s_SearchResultsTable_Filter).Equals(false); + + builder.Execute(m_connection); + AICLI_LOG(SQL, Verbose, << "Filter deleted " << m_connection.GetChanges() << " rows"); + } + + ISQLiteIndex::SearchResult SearchResultsTable::GetSearchResults(size_t limit) + { + using namespace SQLite::Builder; + using QCol = QualifiedColumn; + + // Select all of the results from the table; it is expected that RemoveDuplicatePackageRows has been called. + StatementBuilder builder; + builder.Select({ + s_SearchResultsTable_Package, + s_SearchResultsTable_MatchField, + s_SearchResultsTable_MatchType, + s_SearchResultsTable_MatchValue, + }). + From(GetQualifiedName()).OrderBy(s_SearchResultsTable_SortValue); + + SQLite::Statement select = builder.Prepare(m_connection); + + ISQLiteIndex::SearchResult result; + while (select.Step()) + { + if (limit && result.Matches.size() >= limit) + { + break; + } + + result.Matches.emplace_back(select.GetColumn(0), + PackageMatchFilter(select.GetColumn(1), select.GetColumn(2), select.GetColumn(3))); + } + + result.Truncated = (select.GetState() != SQLite::Statement::State::Completed); + + return result; + } + + std::vector SearchResultsTable::BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const + { + return BuildSearchStatement(builder, field, s_SearchResultsTable_SubSelect_PackageAlias, s_SearchResultsTable_SubSelect_ValueAlias, MatchUsesLike(match)); + } + + std::vector SearchResultsTable::BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const + { + std::vector result; + + switch (field) + { + case PackageMatchField::Id: + result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::IdColumn::Name, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::Name: + result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::NameColumn::Name, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::Moniker: + result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::MonikerColumn::Name, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::Tag: + result.push_back(TagsTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::Command: + result.push_back(CommandsTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::PackageFamilyName: + result.push_back(PackageFamilyNameTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::ProductCode: + result.push_back(ProductCodeTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::UpgradeCode: + result.push_back(UpgradeCodeTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::NormalizedNameAndPublisher: + result = NormalizedPackageNameTable::BuildPairedSearchStatement(builder, manifestAlias, valueAlias, useLike); + break; + } + + return result; + } + + bool SearchResultsTable::MatchUsesLike(MatchType match) + { + return (match != MatchType::Exact); + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value) + { + std::string valueToUse; + + if (MatchUsesLike(match)) + { + valueToUse = SQLite::EscapeStringForLike(value); + } + else + { + valueToUse = value; + } + + switch (match) + { + case AppInstaller::Repository::MatchType::StartsWith: + valueToUse += '%'; + break; + case AppInstaller::Repository::MatchType::Substring: + valueToUse = "%"s + valueToUse + '%'; + break; + default: + // No changes required for others. + break; + } + + statement.Bind(bindIndex, valueToUse); + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) + { + // TODO: Implement these more complex match types + if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) + { + AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); + return; + } + + BindStatementForMatchType(statement, filter.Type, bindIndex[0], filter.Value); + + if (filter.Field == PackageMatchField::NormalizedNameAndPublisher) + { + BindStatementForMatchType(statement, filter.Type, bindIndex[1], filter.Additional.value()); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h index 9fde5e84a1..64e3be2d53 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h @@ -1,45 +1,45 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "winget/Pin.h" - -namespace AppInstaller::Repository::Microsoft::Schema -{ - struct IPinningIndex - { - virtual ~IPinningIndex() = default; - - // Gets the schema version that this index interface is built for. - virtual SQLite::Version GetVersion() const = 0; - - // Creates all of the version dependent tables within the database. - virtual void CreateTables(SQLite::Connection& connection) = 0; - - // Migrates the schema from an older version. - // Returns true if migration succeeded, false if migration is not applicable. - virtual bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) = 0; - - // Version 1.0 - // Adds a pin to the index. - virtual SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; - - // Updates an existing pin in the index. - // The return value indicates whether the index was modified by the function. - virtual std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; - - // Removes a pin from the index. - virtual SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; - - // Returns the current pin for a given package if it exists. - virtual std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; - - // Returns a vector containing all the existing pins. - virtual std::vector GetAllPins(SQLite::Connection& connection) = 0; - - // Removes all the pins from a given source, or from all sources if none is specified. - // Returns a value indicating whether any pin was deleted. - virtual bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) = 0; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "winget/Pin.h" + +namespace AppInstaller::Repository::Microsoft::Schema +{ + struct IPinningIndex + { + virtual ~IPinningIndex() = default; + + // Gets the schema version that this index interface is built for. + virtual SQLite::Version GetVersion() const = 0; + + // Creates all of the version dependent tables within the database. + virtual void CreateTables(SQLite::Connection& connection) = 0; + + // Migrates the schema from an older version. + // Returns true if migration succeeded, false if migration is not applicable. + virtual bool MigrateFrom(SQLite::Connection& connection, const IPinningIndex* current) = 0; + + // Version 1.0 + // Adds a pin to the index. + virtual SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; + + // Updates an existing pin in the index. + // The return value indicates whether the index was modified by the function. + virtual std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; + + // Removes a pin from the index. + virtual SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; + + // Returns the current pin for a given package if it exists. + virtual std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; + + // Returns a vector containing all the existing pins. + virtual std::vector GetAllPins(SQLite::Connection& connection) = 0; + + // Removes all the pins from a given source, or from all sources if none is specified. + // Returns a value indicating whether any pin was deleted. + virtual bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) = 0; + }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index d998a21a07..27fd5f4df8 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -1,188 +1,188 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PinTable.h" -#include -#include "Microsoft/Schema/IPinningIndex.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 -{ - namespace - { - std::optional GetPinFromRow( - std::string_view packageId, - std::string_view sourceId, - Pinning::PinType type, - std::string_view version) - - { - switch (type) - { - case Pinning::PinType::Blocking: - return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); - case Pinning::PinType::Pinning: - return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); - case Pinning::PinType::Gating: - return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ version }); - default: - return {}; - } - } - } - - std::string_view PinTable::TableName() - { - return s_PinTable_Table_Name; - } - - void PinTable::Create(SQLite::Connection& connection) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); - - createTableBuilder.Column(ColumnBuilder(s_PinTable_PackageId_Column, Type::Text).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_SourceId_Column, Type::Text).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_Type_Column, Type::Int64).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_Version_Column, Type::Text).NotNull()); - - createTableBuilder.EndColumns(); - createTableBuilder.Execute(connection); - - // Create an index over the pairs package,source - StatementBuilder createIndexBuilder; - createIndexBuilder.CreateUniqueIndex(s_PinTable_Index).On(s_PinTable_Table_Name) - .Columns({ s_PinTable_PackageId_Column, s_PinTable_SourceId_Column }); - createIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - std::optional PinTable::GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) - .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) - .And(s_PinTable_SourceId_Column).Equals((std::string_view)pinKey.SourceId); - - SQLite::Statement select = builder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) - { - SQLite::Builder::StatementBuilder builder; - const auto& pinKey = pin.GetKey(); - builder.InsertInto(s_PinTable_Table_Name) - .Columns({ - s_PinTable_PackageId_Column, - s_PinTable_SourceId_Column, - s_PinTable_Type_Column, - s_PinTable_Version_Column }) - .Values( - (std::string_view)pinKey.PackageId, - pinKey.SourceId, - pin.GetType(), - pin.GetGatedVersion().ToString()); - - builder.Execute(connection); - return connection.GetLastInsertRowID(); - } - - bool PinTable::UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) - { - SQLite::Builder::StatementBuilder builder; - const auto& pinKey = pin.GetKey(); - builder.Update(s_PinTable_Table_Name).Set() - .Column(s_PinTable_PackageId_Column).AssignValue((std::string_view)pinKey.PackageId) - .Column(s_PinTable_SourceId_Column).AssignValue(pinKey.SourceId) - .Column(s_PinTable_Type_Column).AssignValue(pin.GetType()) - .Column(s_PinTable_Version_Column).AssignValue(pin.GetGatedVersion().ToString()) - .Where(SQLite::RowIDName).Equals(pinId); - - builder.Execute(connection); - return connection.GetChanges() != 0; - } - - void PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); - builder.Execute(connection); - } - - std::optional PinTable::GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ - s_PinTable_PackageId_Column, - s_PinTable_SourceId_Column, - s_PinTable_Type_Column, - s_PinTable_Version_Column }) - .From(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); - - SQLite::Statement select = builder.Prepare(connection); - - if (!select.Step()) - { - return {}; - } - - auto [packageId, sourceId, pinType, gatedVersion] = - select.GetRow(); - return GetPinFromRow(packageId, sourceId, pinType, gatedVersion); - } - - std::vector PinTable::GetAllPins(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ - s_PinTable_PackageId_Column, - s_PinTable_SourceId_Column, - s_PinTable_Type_Column, - s_PinTable_Version_Column }) - .From(s_PinTable_Table_Name); - - SQLite::Statement select = builder.Prepare(connection); - - std::vector pins; - while (select.Step()) - { - auto [packageId, sourceId, pinType, gatedVersion] = - select.GetRow(); - auto pin = GetPinFromRow(packageId, sourceId, pinType, gatedVersion); - if (pin) - { - pins.push_back(std::move(pin.value())); - } - } - - return pins; - } - - bool PinTable::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PinTable_Table_Name); - - if (!sourceId.empty()) - { - builder.Where(s_PinTable_SourceId_Column).Equals(sourceId); - } - - builder.Execute(connection); - - return connection.GetChanges() != 0; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinTable.h" +#include +#include "Microsoft/Schema/IPinningIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + namespace + { + std::optional GetPinFromRow( + std::string_view packageId, + std::string_view sourceId, + Pinning::PinType type, + std::string_view version) + + { + switch (type) + { + case Pinning::PinType::Blocking: + return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); + case Pinning::PinType::Pinning: + return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); + case Pinning::PinType::Gating: + return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ version }); + default: + return {}; + } + } + } + + std::string_view PinTable::TableName() + { + return s_PinTable_Table_Name; + } + + void PinTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); + + createTableBuilder.Column(ColumnBuilder(s_PinTable_PackageId_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_SourceId_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Type_Column, Type::Int64).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Version_Column, Type::Text).NotNull()); + + createTableBuilder.EndColumns(); + createTableBuilder.Execute(connection); + + // Create an index over the pairs package,source + StatementBuilder createIndexBuilder; + createIndexBuilder.CreateUniqueIndex(s_PinTable_Index).On(s_PinTable_Table_Name) + .Columns({ s_PinTable_PackageId_Column, s_PinTable_SourceId_Column }); + createIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::optional PinTable::GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) + .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) + .And(s_PinTable_SourceId_Column).Equals((std::string_view)pinKey.SourceId); + + SQLite::Statement select = builder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + SQLite::Builder::StatementBuilder builder; + const auto& pinKey = pin.GetKey(); + builder.InsertInto(s_PinTable_Table_Name) + .Columns({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column }) + .Values( + (std::string_view)pinKey.PackageId, + pinKey.SourceId, + pin.GetType(), + pin.GetGatedVersion().ToString()); + + builder.Execute(connection); + return connection.GetLastInsertRowID(); + } + + bool PinTable::UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) + { + SQLite::Builder::StatementBuilder builder; + const auto& pinKey = pin.GetKey(); + builder.Update(s_PinTable_Table_Name).Set() + .Column(s_PinTable_PackageId_Column).AssignValue((std::string_view)pinKey.PackageId) + .Column(s_PinTable_SourceId_Column).AssignValue(pinKey.SourceId) + .Column(s_PinTable_Type_Column).AssignValue(pin.GetType()) + .Column(s_PinTable_Version_Column).AssignValue(pin.GetGatedVersion().ToString()) + .Where(SQLite::RowIDName).Equals(pinId); + + builder.Execute(connection); + return connection.GetChanges() != 0; + } + + void PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); + builder.Execute(connection); + } + + std::optional PinTable::GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column }) + .From(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); + + SQLite::Statement select = builder.Prepare(connection); + + if (!select.Step()) + { + return {}; + } + + auto [packageId, sourceId, pinType, gatedVersion] = + select.GetRow(); + return GetPinFromRow(packageId, sourceId, pinType, gatedVersion); + } + + std::vector PinTable::GetAllPins(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column }) + .From(s_PinTable_Table_Name); + + SQLite::Statement select = builder.Prepare(connection); + + std::vector pins; + while (select.Step()) + { + auto [packageId, sourceId, pinType, gatedVersion] = + select.GetRow(); + auto pin = GetPinFromRow(packageId, sourceId, pinType, gatedVersion); + if (pin) + { + pins.push_back(std::move(pin.value())); + } + } + + return pins; + } + + bool PinTable::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PinTable_Table_Name); + + if (!sourceId.empty()) + { + builder.Where(s_PinTable_SourceId_Column).Equals(sourceId); + } + + builder.Execute(connection); + + return connection.GetChanges() != 0; + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index 56a6b0a8ad..d634e5e24a 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "Microsoft/Schema/IPinningIndex.h" -#include - -namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 -{ - struct PinTable - { - static inline constexpr std::string_view s_PinTable_Table_Name = "pin"; - static inline constexpr std::string_view s_PinTable_PackageId_Column = "package_id"; - static inline constexpr std::string_view s_PinTable_SourceId_Column = "source_id"; - static inline constexpr std::string_view s_PinTable_Type_Column = "type"; - static inline constexpr std::string_view s_PinTable_Version_Column = "version"; - static inline constexpr std::string_view s_PinTable_Index = "pin_index"; - - // Get the table name. - static std::string_view TableName(); - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection); - - // Gets the row ID for the pin, if it exists. - static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); - - // Adds a new pin. Returns the row ID of the added pin. - static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); - - // Updates an existing pin. - // Returns a value indicating whether there were any changes. - static bool UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); - - // Removes a pin given its row ID. - static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); - - // Gets a pin by its row ID if it exists. - // Used for testing - static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); - - // Gets all the currently existing pins. - static std::vector GetAllPins(SQLite::Connection& connection); - - // Resets all pins from a given source, or from all sources if none is specified. - // Returns a value indicating whether there were any changes. - static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "Microsoft/Schema/IPinningIndex.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + struct PinTable + { + static inline constexpr std::string_view s_PinTable_Table_Name = "pin"; + static inline constexpr std::string_view s_PinTable_PackageId_Column = "package_id"; + static inline constexpr std::string_view s_PinTable_SourceId_Column = "source_id"; + static inline constexpr std::string_view s_PinTable_Type_Column = "type"; + static inline constexpr std::string_view s_PinTable_Version_Column = "version"; + static inline constexpr std::string_view s_PinTable_Index = "pin_index"; + + // Get the table name. + static std::string_view TableName(); + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + + // Gets the row ID for the pin, if it exists. + static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); + + // Adds a new pin. Returns the row ID of the added pin. + static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); + + // Updates an existing pin. + // Returns a value indicating whether there were any changes. + static bool UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); + + // Removes a pin given its row ID. + static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); + + // Gets a pin by its row ID if it exists. + // Used for testing + static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); + + // Gets all the currently existing pins. + static std::vector GetAllPins(SQLite::Connection& connection); + + // Resets all pins from a given source, or from all sources if none is specified. + // Returns a value indicating whether there were any changes. + static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index 38a95fcbc3..f52b9f8954 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -1,132 +1,132 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h" -#include "Microsoft/Schema/Pinning_1_0/PinTable.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 -{ - namespace - { - std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - auto result = PinTable::GetIdByPinKey(connection, pinKey); - - if (!result) - { - AICLI_LOG(Repo, Verbose, << "Did not find pin " << pinKey.ToString()); - } - - return result; - } - - } - - // Version 1.0 - SQLite::Version PinningIndexInterface::GetVersion() const - { - return { 1, 0 }; - } - - void PinningIndexInterface::CreateTables(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); - Pinning_V1_0::PinTable::Create(connection); - savepoint.Commit(); - } - - bool PinningIndexInterface::MigrateFrom(SQLite::Connection&, const IPinningIndex*) - { - // Version 1.0 cannot migrate from any prior version. - return false; - } - - SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) - { - auto existingPin = GetExistingPinId(connection, pin.GetKey()); - - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_0"); - SQLite::rowid_t pinId = IAddPin(connection, pin); - - savepoint.Commit(); - return pinId; - } - - std::pair PinningIndexInterface::UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) - { - auto existingPinId = GetExistingPinId(connection, pin.GetKey()); - - // If the pin doesn't exist, fail the update - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); - - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_0"); - bool status = IUpdatePinById(connection, existingPinId.value(), pin); - - savepoint.Commit(); - return { status, existingPinId.value() }; - } - - SQLite::rowid_t PinningIndexInterface::RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - auto existingPinId = GetExistingPinId(connection, pinKey); - - // If the pin doesn't exist, fail the remove - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); - - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_0"); - PinTable::RemovePinById(connection, existingPinId.value()); - - savepoint.Commit(); - return existingPinId.value(); - } - - std::optional PinningIndexInterface::GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - auto existingPinId = GetExistingPinId(connection, pinKey); - - if (!existingPinId) - { - return {}; - } - - return IGetPinById(connection, existingPinId.value()); - } - - std::vector PinningIndexInterface::GetAllPins(SQLite::Connection& connection) - { - return IGetAllPins(connection); - } - - bool PinningIndexInterface::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetpins_v1_0"); - bool result = PinTable::ResetAllPins(connection, sourceId); - savepoint.Commit(); - - return result; - } - - SQLite::rowid_t PinningIndexInterface::IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin) - { - return PinTable::AddPin(connection, pin); - } - - bool PinningIndexInterface::IUpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) - { - return PinTable::UpdatePinById(connection, pinId, pin); - } - - std::optional PinningIndexInterface::IGetPinById(SQLite::Connection& connection, SQLite::rowid_t pinId) - { - return PinTable::GetPinById(connection, pinId); - } - - std::vector PinningIndexInterface::IGetAllPins(SQLite::Connection& connection) - { - return PinTable::GetAllPins(connection); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h" +#include "Microsoft/Schema/Pinning_1_0/PinTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + namespace + { + std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto result = PinTable::GetIdByPinKey(connection, pinKey); + + if (!result) + { + AICLI_LOG(Repo, Verbose, << "Did not find pin " << pinKey.ToString()); + } + + return result; + } + + } + + // Version 1.0 + SQLite::Version PinningIndexInterface::GetVersion() const + { + return { 1, 0 }; + } + + void PinningIndexInterface::CreateTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); + Pinning_V1_0::PinTable::Create(connection); + savepoint.Commit(); + } + + bool PinningIndexInterface::MigrateFrom(SQLite::Connection&, const IPinningIndex*) + { + // Version 1.0 cannot migrate from any prior version. + return false; + } + + SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + auto existingPin = GetExistingPinId(connection, pin.GetKey()); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_0"); + SQLite::rowid_t pinId = IAddPin(connection, pin); + + savepoint.Commit(); + return pinId; + } + + std::pair PinningIndexInterface::UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + auto existingPinId = GetExistingPinId(connection, pin.GetKey()); + + // If the pin doesn't exist, fail the update + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); + + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_0"); + bool status = IUpdatePinById(connection, existingPinId.value(), pin); + + savepoint.Commit(); + return { status, existingPinId.value() }; + } + + SQLite::rowid_t PinningIndexInterface::RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto existingPinId = GetExistingPinId(connection, pinKey); + + // If the pin doesn't exist, fail the remove + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); + + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_0"); + PinTable::RemovePinById(connection, existingPinId.value()); + + savepoint.Commit(); + return existingPinId.value(); + } + + std::optional PinningIndexInterface::GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto existingPinId = GetExistingPinId(connection, pinKey); + + if (!existingPinId) + { + return {}; + } + + return IGetPinById(connection, existingPinId.value()); + } + + std::vector PinningIndexInterface::GetAllPins(SQLite::Connection& connection) + { + return IGetAllPins(connection); + } + + bool PinningIndexInterface::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetpins_v1_0"); + bool result = PinTable::ResetAllPins(connection, sourceId); + savepoint.Commit(); + + return result; + } + + SQLite::rowid_t PinningIndexInterface::IAddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + return PinTable::AddPin(connection, pin); + } + + bool PinningIndexInterface::IUpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) + { + return PinTable::UpdatePinById(connection, pinId, pin); + } + + std::optional PinningIndexInterface::IGetPinById(SQLite::Connection& connection, SQLite::rowid_t pinId) + { + return PinTable::GetPinById(connection, pinId); + } + + std::vector PinningIndexInterface::IGetAllPins(SQLite::Connection& connection) + { + return PinTable::GetAllPins(connection); + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp index bcded14e6e..fd92fdeb12 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp @@ -1,178 +1,178 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PortableTable.h" -#include -#include "Microsoft/Schema/IPortableIndex.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 -{ - using namespace std::string_view_literals; - static constexpr std::string_view s_PortableTable_Table_Name = "portable"sv; - static constexpr std::string_view s_PortableTable_FilePath_Column = "filepath"sv; - static constexpr std::string_view s_PortableTable_FileType_Column = "filetype"sv; - static constexpr std::string_view s_PortableTable_SHA256_Column = "sha256"sv; - static constexpr std::string_view s_PortableTable_SymlinkTarget_Column = "symlinktarget"sv; - - std::string_view PortableTable::TableName() - { - return s_PortableTable_Table_Name; - } - - void PortableTable::Create(SQLite::Connection& connection) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPortableTable_v1_0"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_PortableTable_Table_Name).BeginColumns(); - - createTableBuilder.Column(ColumnBuilder(s_PortableTable_FilePath_Column, Type::Text).NotNull().Unique().CollateNoCase()); - createTableBuilder.Column(ColumnBuilder(s_PortableTable_FileType_Column, Type::Int64).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PortableTable_SHA256_Column, Type::Blob)); - createTableBuilder.Column(ColumnBuilder(s_PortableTable_SymlinkTarget_Column, Type::Text)); - - createTableBuilder.EndColumns(); - createTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - std::optional PortableTable::SelectByFilePath(const SQLite::Connection& connection, const std::filesystem::path& path) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::RowIDName).From(s_PortableTable_Table_Name).Where(s_PortableTable_FilePath_Column); - builder.Equals(path.u8string()); - - SQLite::Statement select = builder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - void PortableTable::RemovePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - builder.Execute(connection); - } - - SQLite::rowid_t PortableTable::AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) - { - SQLite::Builder::StatementBuilder builder; - builder.InsertInto(s_PortableTable_Table_Name) - .Columns({ s_PortableTable_FilePath_Column, - s_PortableTable_FileType_Column, - s_PortableTable_SHA256_Column, - s_PortableTable_SymlinkTarget_Column }) - .Values(file.GetFilePath().u8string(), file.FileType, file.SHA256, file.SymlinkTarget); - - builder.Execute(connection); - return connection.GetLastInsertRowID(); - } - - bool PortableTable::UpdatePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id, const Portable::PortableFileEntry& file) - { - SQLite::Builder::StatementBuilder builder; - builder.Update(s_PortableTable_Table_Name).Set() - .Column(s_PortableTable_FilePath_Column).AssignValue(file.GetFilePath().u8string()) - .Column(s_PortableTable_FileType_Column).AssignValue(file.FileType) - .Column(s_PortableTable_SHA256_Column).AssignValue(file.SHA256) - .Column(s_PortableTable_SymlinkTarget_Column).AssignValue(file.SymlinkTarget) - .Where(SQLite::RowIDName).Equals(id); - - builder.Execute(connection); - return connection.GetChanges() != 0; - } - - std::optional PortableTable::GetPortableFileById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ s_PortableTable_FilePath_Column, - s_PortableTable_FileType_Column, - s_PortableTable_SHA256_Column, - s_PortableTable_SymlinkTarget_Column}) - .From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement select = builder.Prepare(connection); - - Portable::PortableFileEntry portableFile; - if (select.Step()) - { - auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); - portableFile.FileType = fileType; - portableFile.SetFilePath(Utility::ConvertToUTF16(filePath)); - portableFile.SHA256 = std::move(sha256); - portableFile.SymlinkTarget = std::move(symlinkTarget); - return portableFile; - } - else - { - return {}; - } - } - - bool PortableTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) != 0); - } - - void PortableTable::DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - builder.Execute(connection); - } - - bool PortableTable::IsEmpty(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) == 0); - } - - std::vector PortableTable::GetAllPortableFiles(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ s_PortableTable_FilePath_Column, - s_PortableTable_FileType_Column, - s_PortableTable_SHA256_Column, - s_PortableTable_SymlinkTarget_Column }) - .From(s_PortableTable_Table_Name); - - SQLite::Statement select = builder.Prepare(connection); - std::vector result; - while (select.Step()) - { - Portable::PortableFileEntry portableFile; - auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); - portableFile.FileType = fileType; - portableFile.SetFilePath(Utility::ConvertToUTF16(filePath)); - portableFile.SHA256 = std::move(sha256); - portableFile.SymlinkTarget = std::move(symlinkTarget); - result.emplace_back(std::move(portableFile)); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PortableTable.h" +#include +#include "Microsoft/Schema/IPortableIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 +{ + using namespace std::string_view_literals; + static constexpr std::string_view s_PortableTable_Table_Name = "portable"sv; + static constexpr std::string_view s_PortableTable_FilePath_Column = "filepath"sv; + static constexpr std::string_view s_PortableTable_FileType_Column = "filetype"sv; + static constexpr std::string_view s_PortableTable_SHA256_Column = "sha256"sv; + static constexpr std::string_view s_PortableTable_SymlinkTarget_Column = "symlinktarget"sv; + + std::string_view PortableTable::TableName() + { + return s_PortableTable_Table_Name; + } + + void PortableTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPortableTable_v1_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PortableTable_Table_Name).BeginColumns(); + + createTableBuilder.Column(ColumnBuilder(s_PortableTable_FilePath_Column, Type::Text).NotNull().Unique().CollateNoCase()); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_FileType_Column, Type::Int64).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_SHA256_Column, Type::Blob)); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_SymlinkTarget_Column, Type::Text)); + + createTableBuilder.EndColumns(); + createTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::optional PortableTable::SelectByFilePath(const SQLite::Connection& connection, const std::filesystem::path& path) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_PortableTable_Table_Name).Where(s_PortableTable_FilePath_Column); + builder.Equals(path.u8string()); + + SQLite::Statement select = builder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + void PortableTable::RemovePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + builder.Execute(connection); + } + + SQLite::rowid_t PortableTable::AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) + { + SQLite::Builder::StatementBuilder builder; + builder.InsertInto(s_PortableTable_Table_Name) + .Columns({ s_PortableTable_FilePath_Column, + s_PortableTable_FileType_Column, + s_PortableTable_SHA256_Column, + s_PortableTable_SymlinkTarget_Column }) + .Values(file.GetFilePath().u8string(), file.FileType, file.SHA256, file.SymlinkTarget); + + builder.Execute(connection); + return connection.GetLastInsertRowID(); + } + + bool PortableTable::UpdatePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id, const Portable::PortableFileEntry& file) + { + SQLite::Builder::StatementBuilder builder; + builder.Update(s_PortableTable_Table_Name).Set() + .Column(s_PortableTable_FilePath_Column).AssignValue(file.GetFilePath().u8string()) + .Column(s_PortableTable_FileType_Column).AssignValue(file.FileType) + .Column(s_PortableTable_SHA256_Column).AssignValue(file.SHA256) + .Column(s_PortableTable_SymlinkTarget_Column).AssignValue(file.SymlinkTarget) + .Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + return connection.GetChanges() != 0; + } + + std::optional PortableTable::GetPortableFileById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ s_PortableTable_FilePath_Column, + s_PortableTable_FileType_Column, + s_PortableTable_SHA256_Column, + s_PortableTable_SymlinkTarget_Column}) + .From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement select = builder.Prepare(connection); + + Portable::PortableFileEntry portableFile; + if (select.Step()) + { + auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); + portableFile.FileType = fileType; + portableFile.SetFilePath(Utility::ConvertToUTF16(filePath)); + portableFile.SHA256 = std::move(sha256); + portableFile.SymlinkTarget = std::move(symlinkTarget); + return portableFile; + } + else + { + return {}; + } + } + + bool PortableTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) != 0); + } + + void PortableTable::DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + } + + bool PortableTable::IsEmpty(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) == 0); + } + + std::vector PortableTable::GetAllPortableFiles(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ s_PortableTable_FilePath_Column, + s_PortableTable_FileType_Column, + s_PortableTable_SHA256_Column, + s_PortableTable_SymlinkTarget_Column }) + .From(s_PortableTable_Table_Name); + + SQLite::Statement select = builder.Prepare(connection); + std::vector result; + while (select.Step()) + { + Portable::PortableFileEntry portableFile; + auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); + portableFile.FileType = fileType; + portableFile.SetFilePath(Utility::ConvertToUTF16(filePath)); + portableFile.SHA256 = std::move(sha256); + portableFile.SymlinkTarget = std::move(symlinkTarget); + result.emplace_back(std::move(portableFile)); + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/PinningData.cpp b/src/AppInstallerRepositoryCore/PinningData.cpp index 801f4f5fe1..884a2565e8 100644 --- a/src/AppInstallerRepositoryCore/PinningData.cpp +++ b/src/AppInstallerRepositoryCore/PinningData.cpp @@ -1,227 +1,227 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/PinningData.h" -#include "Microsoft/PinningIndex.h" -#include "Public/winget/RepositorySource.h" - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Microsoft; - -namespace AppInstaller::Pinning -{ - namespace - { - // Evaluates the pinning state of a version for a single pin. - PinType EvaluatePinnedStateForVersion( - const Utility::Version& version, - const std::optional& pin, - PinBehavior behavior) - { - if (pin) - { - if (pin->GetType() == PinType::Blocking - || (pin->GetType() == PinType::Pinning && behavior != PinBehavior::IncludePinned) - || (pin->GetType() == PinType::Gating && !pin->GetGatedVersion().IsValidVersion(version))) - { - return pin->GetType(); - } - } - - return PinType::Unknown; - } - - // Gets the pinned state for an available version that may have a pin, - // and optionally an additional pin that could come from the installed version. - // If both pins are present, we return the one that is the most strict. - Pinning::PinType GetPinnedStateForVersion( - const Utility::Version& version, - const std::optional& availablePin, - const std::optional& installedPin, - PinBehavior behavior) - { - if (behavior == PinBehavior::IgnorePins) - { - return Pinning::PinType::Unknown; - } - - return Stricter( - EvaluatePinnedStateForVersion(version, availablePin, behavior), - EvaluatePinnedStateForVersion(version, installedPin, behavior)); - } - } - - PinningData::PinningData() = default; - PinningData::PinningData(const PinningData&) = default; - PinningData& PinningData::operator=(const PinningData&) = default; - PinningData::PinningData(PinningData&&) noexcept = default; - PinningData& PinningData::operator=(PinningData&&) noexcept = default; - PinningData::~PinningData() = default; - - PinningData::PinningData(Disposition disposition) - { - if (disposition == Disposition::ReadOnly) - { - m_database = PinningIndex::OpenIfExists(SQLiteStorageBase::OpenDisposition::Read); - } - else - { - m_database = PinningIndex::OpenOrCreateDefault(SQLiteStorageBase::OpenDisposition::ReadWrite); - } - } - - PinningData::operator bool() const - { - return IsDatabaseConnected(); - } - - bool PinningData::IsDatabaseConnected() const - { - return static_cast(m_database); - } - - void PinningData::AddOrUpdatePin(const Pin& pin) - { - THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); - m_database->AddOrUpdatePin(pin); - } - - void PinningData::RemovePin(const PinKey& pinKey) - { - THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); - m_database->RemovePin(pinKey); - } - - bool PinningData::TryRemovePin(const PinKey& pinKey) - { - THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); - if (!m_database->GetPin(pinKey)) - { - return false; - } - m_database->RemovePin(pinKey); - return true; - } - - std::optional PinningData::GetPin(const PinKey& pinKey) - { - return IsDatabaseConnected() ? m_database->GetPin(pinKey) : std::nullopt; - } - - std::vector PinningData::GetAllPins() - { - return IsDatabaseConnected() ? m_database->GetAllPins() : std::vector{}; - } - - bool PinningData::ResetAllPins(std::string_view sourceId) - { - THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); - return m_database->ResetAllPins(sourceId); - } - - PinningData::PinStateEvaluator::PinStateEvaluator( - PinBehavior behavior, - std::shared_ptr database, - const std::shared_ptr& installedVersion) : - m_behavior(behavior), m_database(std::move(database)) - { - if (m_behavior == PinBehavior::IgnorePins || !installedVersion) - { - // Because the database isn't guaranteed to be present, align ignoring pins with there being no pins to ignore. - // Also do not consider pins when there is no installed version. This is to remain consistent with the previous - // implementation. If this is to be changed, more install paths will need to be do pinning checks to ensure - // that one could, for instance, block the install of a package. - m_database.reset(); - } - else if (m_database) - { - PinKey key = PinKey::GetPinKeyForInstalled(installedVersion->GetProperty(PackageVersionProperty::Id)); - m_installedPin = m_database->GetPin(key); - } - - if (installedVersion) - { - m_installedVersion = Utility::VersionAndChannel{ - Utility::Version{ installedVersion->GetProperty(PackageVersionProperty::Version) }, - Utility::Channel{ installedVersion->GetProperty(PackageVersionProperty::Channel) } - }; - } - } - - PinningData::PinStateEvaluator::PinStateEvaluator(const PinStateEvaluator&) = default; - PinningData::PinStateEvaluator& PinningData::PinStateEvaluator::operator=(const PinStateEvaluator&) = default; - PinningData::PinStateEvaluator::PinStateEvaluator(PinStateEvaluator&&) noexcept = default; - PinningData::PinStateEvaluator& PinningData::PinStateEvaluator::operator=(PinStateEvaluator&&) noexcept = default; - - PinningData::PinStateEvaluator::~PinStateEvaluator() = default; - - std::shared_ptr PinningData::PinStateEvaluator::GetLatestAvailableVersionForPins(const std::shared_ptr& package) - { - if (!m_database) - { - return package->GetLatestVersion(); - } - - auto availableVersionKeys = package->GetVersionKeys(); - - // Skip until we find a version that isn't pinned - for (const auto& availableVersion : availableVersionKeys) - { - std::shared_ptr packageVersion = package->GetVersion(availableVersion); - if (EvaluatePinType(packageVersion) == Pinning::PinType::Unknown) - { - return packageVersion; - } - } - - return {}; - } - - bool PinningData::PinStateEvaluator::IsUpdate(const std::shared_ptr& availableVersion) - { - if (m_installedVersion && availableVersion) - { - Utility::VersionAndChannel availableVersionAndChannel{ - Utility::Version{ availableVersion->GetProperty(PackageVersionProperty::Version) }, - Utility::Channel{ availableVersion->GetProperty(PackageVersionProperty::Channel) } - }; - - return m_installedVersion->IsUpdatedBy(availableVersionAndChannel); - } - - return false; - } - - PinType PinningData::PinStateEvaluator::EvaluatePinType(const std::shared_ptr& packageVersion) - { - if (!m_database || !packageVersion) - { - return PinType::Unknown; - } - - std::optional incomingPin; - - PinKey pinKey{ packageVersion->GetProperty(PackageVersionProperty::Id).get(), packageVersion->GetSource().GetIdentifier()}; - auto itr = m_availablePins.find(pinKey); - if (itr == m_availablePins.end()) - { - incomingPin = m_database->GetPin(pinKey); - m_availablePins[pinKey] = incomingPin; - } - else - { - incomingPin = itr->second; - } - - return GetPinnedStateForVersion(packageVersion->GetProperty(PackageVersionProperty::Version).get(), incomingPin, m_installedPin, m_behavior); - } - - // Creates an object for use in evaluating pinning data for a given package - PinningData::PinStateEvaluator PinningData::CreatePinStateEvaluator( - PinBehavior behavior, - const std::shared_ptr& installedVersion) - { - return { behavior, m_database, installedVersion }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/PinningData.h" +#include "Microsoft/PinningIndex.h" +#include "Public/winget/RepositorySource.h" + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; + +namespace AppInstaller::Pinning +{ + namespace + { + // Evaluates the pinning state of a version for a single pin. + PinType EvaluatePinnedStateForVersion( + const Utility::Version& version, + const std::optional& pin, + PinBehavior behavior) + { + if (pin) + { + if (pin->GetType() == PinType::Blocking + || (pin->GetType() == PinType::Pinning && behavior != PinBehavior::IncludePinned) + || (pin->GetType() == PinType::Gating && !pin->GetGatedVersion().IsValidVersion(version))) + { + return pin->GetType(); + } + } + + return PinType::Unknown; + } + + // Gets the pinned state for an available version that may have a pin, + // and optionally an additional pin that could come from the installed version. + // If both pins are present, we return the one that is the most strict. + Pinning::PinType GetPinnedStateForVersion( + const Utility::Version& version, + const std::optional& availablePin, + const std::optional& installedPin, + PinBehavior behavior) + { + if (behavior == PinBehavior::IgnorePins) + { + return Pinning::PinType::Unknown; + } + + return Stricter( + EvaluatePinnedStateForVersion(version, availablePin, behavior), + EvaluatePinnedStateForVersion(version, installedPin, behavior)); + } + } + + PinningData::PinningData() = default; + PinningData::PinningData(const PinningData&) = default; + PinningData& PinningData::operator=(const PinningData&) = default; + PinningData::PinningData(PinningData&&) noexcept = default; + PinningData& PinningData::operator=(PinningData&&) noexcept = default; + PinningData::~PinningData() = default; + + PinningData::PinningData(Disposition disposition) + { + if (disposition == Disposition::ReadOnly) + { + m_database = PinningIndex::OpenIfExists(SQLiteStorageBase::OpenDisposition::Read); + } + else + { + m_database = PinningIndex::OpenOrCreateDefault(SQLiteStorageBase::OpenDisposition::ReadWrite); + } + } + + PinningData::operator bool() const + { + return IsDatabaseConnected(); + } + + bool PinningData::IsDatabaseConnected() const + { + return static_cast(m_database); + } + + void PinningData::AddOrUpdatePin(const Pin& pin) + { + THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); + m_database->AddOrUpdatePin(pin); + } + + void PinningData::RemovePin(const PinKey& pinKey) + { + THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); + m_database->RemovePin(pinKey); + } + + bool PinningData::TryRemovePin(const PinKey& pinKey) + { + THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); + if (!m_database->GetPin(pinKey)) + { + return false; + } + m_database->RemovePin(pinKey); + return true; + } + + std::optional PinningData::GetPin(const PinKey& pinKey) + { + return IsDatabaseConnected() ? m_database->GetPin(pinKey) : std::nullopt; + } + + std::vector PinningData::GetAllPins() + { + return IsDatabaseConnected() ? m_database->GetAllPins() : std::vector{}; + } + + bool PinningData::ResetAllPins(std::string_view sourceId) + { + THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); + return m_database->ResetAllPins(sourceId); + } + + PinningData::PinStateEvaluator::PinStateEvaluator( + PinBehavior behavior, + std::shared_ptr database, + const std::shared_ptr& installedVersion) : + m_behavior(behavior), m_database(std::move(database)) + { + if (m_behavior == PinBehavior::IgnorePins || !installedVersion) + { + // Because the database isn't guaranteed to be present, align ignoring pins with there being no pins to ignore. + // Also do not consider pins when there is no installed version. This is to remain consistent with the previous + // implementation. If this is to be changed, more install paths will need to be do pinning checks to ensure + // that one could, for instance, block the install of a package. + m_database.reset(); + } + else if (m_database) + { + PinKey key = PinKey::GetPinKeyForInstalled(installedVersion->GetProperty(PackageVersionProperty::Id)); + m_installedPin = m_database->GetPin(key); + } + + if (installedVersion) + { + m_installedVersion = Utility::VersionAndChannel{ + Utility::Version{ installedVersion->GetProperty(PackageVersionProperty::Version) }, + Utility::Channel{ installedVersion->GetProperty(PackageVersionProperty::Channel) } + }; + } + } + + PinningData::PinStateEvaluator::PinStateEvaluator(const PinStateEvaluator&) = default; + PinningData::PinStateEvaluator& PinningData::PinStateEvaluator::operator=(const PinStateEvaluator&) = default; + PinningData::PinStateEvaluator::PinStateEvaluator(PinStateEvaluator&&) noexcept = default; + PinningData::PinStateEvaluator& PinningData::PinStateEvaluator::operator=(PinStateEvaluator&&) noexcept = default; + + PinningData::PinStateEvaluator::~PinStateEvaluator() = default; + + std::shared_ptr PinningData::PinStateEvaluator::GetLatestAvailableVersionForPins(const std::shared_ptr& package) + { + if (!m_database) + { + return package->GetLatestVersion(); + } + + auto availableVersionKeys = package->GetVersionKeys(); + + // Skip until we find a version that isn't pinned + for (const auto& availableVersion : availableVersionKeys) + { + std::shared_ptr packageVersion = package->GetVersion(availableVersion); + if (EvaluatePinType(packageVersion) == Pinning::PinType::Unknown) + { + return packageVersion; + } + } + + return {}; + } + + bool PinningData::PinStateEvaluator::IsUpdate(const std::shared_ptr& availableVersion) + { + if (m_installedVersion && availableVersion) + { + Utility::VersionAndChannel availableVersionAndChannel{ + Utility::Version{ availableVersion->GetProperty(PackageVersionProperty::Version) }, + Utility::Channel{ availableVersion->GetProperty(PackageVersionProperty::Channel) } + }; + + return m_installedVersion->IsUpdatedBy(availableVersionAndChannel); + } + + return false; + } + + PinType PinningData::PinStateEvaluator::EvaluatePinType(const std::shared_ptr& packageVersion) + { + if (!m_database || !packageVersion) + { + return PinType::Unknown; + } + + std::optional incomingPin; + + PinKey pinKey{ packageVersion->GetProperty(PackageVersionProperty::Id).get(), packageVersion->GetSource().GetIdentifier()}; + auto itr = m_availablePins.find(pinKey); + if (itr == m_availablePins.end()) + { + incomingPin = m_database->GetPin(pinKey); + m_availablePins[pinKey] = incomingPin; + } + else + { + incomingPin = itr->second; + } + + return GetPinnedStateForVersion(packageVersion->GetProperty(PackageVersionProperty::Version).get(), incomingPin, m_installedPin, m_behavior); + } + + // Creates an object for use in evaluating pinning data for a given package + PinningData::PinStateEvaluator PinningData::CreatePinStateEvaluator( + PinBehavior behavior, + const std::shared_ptr& installedVersion) + { + return { behavior, m_database, installedVersion }; + } +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/PinningData.h b/src/AppInstallerRepositoryCore/Public/winget/PinningData.h index d754611593..6bf60c25fd 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PinningData.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PinningData.h @@ -1,110 +1,110 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Repository::Microsoft -{ - struct PinningIndex; -} - -namespace AppInstaller::Pinning -{ - // Possible ways to consider pins when getting a package's available versions - enum class PinBehavior - { - // Ignore pins, returns all available versions. - IgnorePins, - // Include available versions for packages with a Pinning pin. - // Blocking pins and Gating pins still respected. - IncludePinned, - // Respect all the types of pins. - ConsiderPins, - }; - - // The public representation of the pinning database. - struct PinningData - { - // Creates an empty pinning data. - PinningData(); - - // Enum to make the pinning data disposition clear in the caller. - enum class Disposition - { - // The data can only be read. - ReadOnly, - // The data can be read and written. - ReadWrite, - }; - - // Creates a usable pinning data with the given read/write capability. - PinningData(Disposition disposition); - - PinningData(const PinningData&); - PinningData& operator=(const PinningData&); - PinningData(PinningData&&) noexcept; - PinningData& operator=(PinningData&&) noexcept; - ~PinningData(); - - // Determines if the pinning database is opened - operator bool() const; - bool IsDatabaseConnected() const; - - // Pass through functions to the index itself - void AddOrUpdatePin(const Pin& pin); - void RemovePin(const PinKey& pinKey); - // Removes the pin if it exists. Returns true if a pin was removed, false if none was found. - bool TryRemovePin(const PinKey& pinKey); - std::optional GetPin(const PinKey& pinKey); - std::vector GetAllPins(); - bool ResetAllPins(std::string_view sourceId = {}); - - // A type used for evaluating the pinning state for a given package. - struct PinStateEvaluator - { - PinStateEvaluator( - PinBehavior behavior, - std::shared_ptr database, - const std::shared_ptr& installedVersion); - - PinStateEvaluator(const PinStateEvaluator&); - PinStateEvaluator& operator=(const PinStateEvaluator&); - PinStateEvaluator(PinStateEvaluator&&) noexcept; - PinStateEvaluator& operator=(PinStateEvaluator&&) noexcept; - - ~PinStateEvaluator(); - - // Gets the latest available package version that fits within the pinning restrictions. - // This should be the package object that contains available versions associated with the installed version for which this evaluator was created. - std::shared_ptr GetLatestAvailableVersionForPins(const std::shared_ptr& package); - - // Determines if the given version is an update to the installed version that this object was created with. - // This should be a version associated with the installed version for which this evaluator was created. - bool IsUpdate(const std::shared_ptr& availableVersion); - - // Determines the pin type to apply to the given version. - PinType EvaluatePinType(const std::shared_ptr& packageVersion); - - private: - PinBehavior m_behavior; - std::shared_ptr m_database; - std::optional m_installedPin; - std::optional m_installedVersion; - // Cache pins for available version to reduce database lookups. - std::map> m_availablePins; - }; - - // Creates an object for use in evaluating pinning data for a given package - PinStateEvaluator CreatePinStateEvaluator( - PinBehavior behavior, - const std::shared_ptr& installedVersion); - - private: - std::shared_ptr m_database; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Repository::Microsoft +{ + struct PinningIndex; +} + +namespace AppInstaller::Pinning +{ + // Possible ways to consider pins when getting a package's available versions + enum class PinBehavior + { + // Ignore pins, returns all available versions. + IgnorePins, + // Include available versions for packages with a Pinning pin. + // Blocking pins and Gating pins still respected. + IncludePinned, + // Respect all the types of pins. + ConsiderPins, + }; + + // The public representation of the pinning database. + struct PinningData + { + // Creates an empty pinning data. + PinningData(); + + // Enum to make the pinning data disposition clear in the caller. + enum class Disposition + { + // The data can only be read. + ReadOnly, + // The data can be read and written. + ReadWrite, + }; + + // Creates a usable pinning data with the given read/write capability. + PinningData(Disposition disposition); + + PinningData(const PinningData&); + PinningData& operator=(const PinningData&); + PinningData(PinningData&&) noexcept; + PinningData& operator=(PinningData&&) noexcept; + ~PinningData(); + + // Determines if the pinning database is opened + operator bool() const; + bool IsDatabaseConnected() const; + + // Pass through functions to the index itself + void AddOrUpdatePin(const Pin& pin); + void RemovePin(const PinKey& pinKey); + // Removes the pin if it exists. Returns true if a pin was removed, false if none was found. + bool TryRemovePin(const PinKey& pinKey); + std::optional GetPin(const PinKey& pinKey); + std::vector GetAllPins(); + bool ResetAllPins(std::string_view sourceId = {}); + + // A type used for evaluating the pinning state for a given package. + struct PinStateEvaluator + { + PinStateEvaluator( + PinBehavior behavior, + std::shared_ptr database, + const std::shared_ptr& installedVersion); + + PinStateEvaluator(const PinStateEvaluator&); + PinStateEvaluator& operator=(const PinStateEvaluator&); + PinStateEvaluator(PinStateEvaluator&&) noexcept; + PinStateEvaluator& operator=(PinStateEvaluator&&) noexcept; + + ~PinStateEvaluator(); + + // Gets the latest available package version that fits within the pinning restrictions. + // This should be the package object that contains available versions associated with the installed version for which this evaluator was created. + std::shared_ptr GetLatestAvailableVersionForPins(const std::shared_ptr& package); + + // Determines if the given version is an update to the installed version that this object was created with. + // This should be a version associated with the installed version for which this evaluator was created. + bool IsUpdate(const std::shared_ptr& availableVersion); + + // Determines the pin type to apply to the given version. + PinType EvaluatePinType(const std::shared_ptr& packageVersion); + + private: + PinBehavior m_behavior; + std::shared_ptr m_database; + std::optional m_installedPin; + std::optional m_installedVersion; + // Cache pins for available version to reduce database lookups. + std::map> m_availablePins; + }; + + // Creates an object for use in evaluating pinning data for a given package + PinStateEvaluator CreatePinStateEvaluator( + PinBehavior behavior, + const std::shared_ptr& installedVersion); + + private: + std::shared_ptr m_database; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h index 0452f4f5d0..635835633f 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h @@ -1,598 +1,598 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace std::string_view_literals; - -namespace AppInstaller::SQLite::Builder -{ - namespace details - { - // Sentinel types to indicate special cases to the builder. - struct unbound_t {}; - struct rowcount_t {}; - - // Class for intake from external functions. - struct SubBuilder - { - SubBuilder(std::string&& s) : m_string(std::move(s)) {} - - SubBuilder(const SubBuilder&) = default; - SubBuilder& operator=(const SubBuilder&) = default; - - SubBuilder(SubBuilder&&) noexcept = default; - SubBuilder& operator=(SubBuilder&&) noexcept = default; - - const std::string& GetString() const { return m_string; } - - protected: - std::string m_string; - }; - - // Base class for all sub-builders. - struct SubBuilderBase - { - SubBuilderBase() = default; - - SubBuilderBase(const SubBuilderBase&) = default; - SubBuilderBase& operator=(const SubBuilderBase&) = default; - - SubBuilderBase(SubBuilderBase&&) noexcept = default; - SubBuilderBase& operator=(SubBuilderBase&&) noexcept = default; - - virtual operator SubBuilder() { return { m_stream.str() }; } - - protected: - std::ostringstream m_stream; - }; - } - - // Pass this value to indicate that the caller will bind the value later. - __declspec_selectany_ details::unbound_t Unbound; - - // Pass this value to indicate that the number of rows is to be selected. - __declspec_selectany_ details::rowcount_t RowCount; - - // A qualified table reference. - struct QualifiedTable - { - std::string_view Schema; - std::string_view Table; - - explicit constexpr QualifiedTable(std::string_view table) : Table(table) {} - explicit constexpr QualifiedTable(std::string_view schema, std::string_view table) : Schema(schema), Table(table) {} - }; - - namespace Schema - { - // The main database's schema table. - // More info can be found at: https://www.sqlite.org/schematab.html - constexpr QualifiedTable MainTable{ "main"sv, "sqlite_master"sv }; - - // The sqlite_schema column name for the type of the object. - constexpr std::string_view TypeColumn = "type"sv; - - // The sqlite_schema type value for a table. - constexpr std::string_view Type_Table = "table"sv; - - // The sqlite_schema type value for an index. - constexpr std::string_view Type_Index = "index"sv; - - // The sqlite_schema column name for the name of the object. - constexpr std::string_view NameColumn = "name"sv; - } - - // A qualified column reference. - struct QualifiedColumn - { - std::string_view Table; - std::string_view Column; - - explicit QualifiedColumn(std::string_view column) : Column(column) {} - explicit QualifiedColumn(std::string_view table, std::string_view column) : Table(table), Column(column) {} - }; - - // SQLite types as an enum. - enum class Type - { - Int, - Bool = Int, - Int64, - RowId = Int64, - Text, - Blob, - Integer, // Type for specifying a primary key column as a row id alias. - None, // Does not declare a type - }; - - template - struct TypeInfo - { - }; - - template <> - struct TypeInfo - { - using value_t = std::string; - }; - - template <> - struct TypeInfo - { - using value_t = std::optional; - }; - - template <> - struct TypeInfo - { - using value_t = SQLite::blob_t; - }; - - template <> - struct TypeInfo - { - using value_t = std::optional; - }; - - // Aggregate functions. - enum class Aggregate - { - Min, - Max, - }; - - // Helper to mark create an integer primary key for rowid, making it stable across vacuum. - struct IntegerPrimaryKey : public details::SubBuilderBase - { - IntegerPrimaryKey(); - - IntegerPrimaryKey(const IntegerPrimaryKey&) = default; - IntegerPrimaryKey& operator=(const IntegerPrimaryKey&) = default; - - IntegerPrimaryKey(IntegerPrimaryKey&&) noexcept = default; - IntegerPrimaryKey& operator=(IntegerPrimaryKey&&) noexcept = default; - - // Set the column to autoincrement. SQLite recommends against using this value unless - // you need to ensure that rowids are not ever reused. - IntegerPrimaryKey& AutoIncrement(bool isTrue = true); - }; - - // Helper used when creating a table. - struct ColumnBuilder : public details::SubBuilderBase - { - // Specify the column name and type when creating the builder. - ColumnBuilder(std::string_view column, Type type); - - ColumnBuilder(const ColumnBuilder&) = default; - ColumnBuilder& operator=(const ColumnBuilder&) = default; - - ColumnBuilder(ColumnBuilder&&) noexcept = default; - ColumnBuilder& operator=(ColumnBuilder&&) noexcept = default; - - // Indicate that the column is not able to be null. - // Allow for data driven construction with input value. - ColumnBuilder& NotNull(bool isTrue = true); - - // Indicate that the column is case-insensitive. - // Allow for data driven construction with input value. - ColumnBuilder& CollateNoCase(bool isTrue = true); - - // Indicate the default value for the column. - // Note that a default value is not considered constant if it is bound, - // so this function directly places the incoming value into the SQL statement. - ColumnBuilder& Default(int64_t value); - - // Indicate that the column is unique. - // Allow for data driven construction with input value. - ColumnBuilder& Unique(bool isTrue = true); - - // Indicate that the column is the primary key. - // Allow for data driven construction with input value. - ColumnBuilder& PrimaryKey(bool isTrue = true); - }; - - // Helper used to specify a primary key with multiple columns. - struct PrimaryKeyBuilder : public details::SubBuilderBase - { - PrimaryKeyBuilder(); - PrimaryKeyBuilder(std::initializer_list columns); - - PrimaryKeyBuilder(const PrimaryKeyBuilder&) = default; - PrimaryKeyBuilder& operator=(const PrimaryKeyBuilder&) = default; - - PrimaryKeyBuilder(PrimaryKeyBuilder&&) noexcept = default; - PrimaryKeyBuilder& operator=(PrimaryKeyBuilder&&) noexcept = default; - - virtual operator details::SubBuilder() override; - - // Add a column to the primary key. - PrimaryKeyBuilder& Column(std::string_view column); - - private: - bool m_isFirst = true; - bool m_needsClosing = true; - }; - - // A class that aids in building SQL statements in a more expressive manner than simple strings. - struct StatementBuilder - { - StatementBuilder() = default; - - StatementBuilder(const StatementBuilder&) = default; - StatementBuilder& operator=(const StatementBuilder&) = default; - - StatementBuilder(StatementBuilder&&) = default; - StatementBuilder& operator=(StatementBuilder&&) = default; - - // Begin a select statement for the given columns. - StatementBuilder& Select(); - StatementBuilder& Select(std::string_view column); - StatementBuilder& Select(std::initializer_list columns); - StatementBuilder& Select(const QualifiedColumn& column); - StatementBuilder& Select(std::initializer_list columns); - StatementBuilder& Select(details::rowcount_t); - - // Indicate the table that the statement will be operating on. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& From(); - StatementBuilder& From(std::string_view table); - StatementBuilder& From(QualifiedTable table); - StatementBuilder& From(std::initializer_list table); - - // Begin a filter clause on the given column. - StatementBuilder& Where(std::string_view column); - StatementBuilder& Where(const QualifiedColumn& column); - - // A full filter clause looking for an embedded null character. - // Is extremely specific to consistency checks, and so a more detailed construct is not required. - StatementBuilder& WhereValueContainsEmbeddedNullCharacter(std::string_view column); - StatementBuilder& WhereValueContainsEmbeddedNullCharacter(const QualifiedColumn& column); - - // Indicate the operation of the filter clause. - template - StatementBuilder& Equals(const ValueType& value) - { - AddBindFunctor(AppendOpAndBinder(Op::Equals), value); - return *this; - } - template - StatementBuilder& Equals(const std::optional& value) - { - if (value) - { - AddBindFunctor(AppendOpAndBinder(Op::Equals), value.value()); - return *this; - } - else - { - return IsNull(); - } - } - - // Assigns a value using "= ?" binding semantics. When the optional is empty, binds NULL - // via the "= ?" parameter rather than producing "IS NULL". Use this instead of - // Equals(optional) in UPDATE SET clauses, where "IS NULL" is invalid SQL. - template - StatementBuilder& AssignValue(const std::optional& value) - { - if (value) - { - AddBindFunctor(AppendOpAndBinder(Op::Equals), value.value()); - } - else - { - AddBindFunctor(AppendOpAndBinder(Op::Equals), nullptr); - } - return *this; - } - - // Assigns a non-nullable value using "= ?" binding semantics. Prefer this over Equals() - // in UPDATE SET clauses to clearly signal assignment intent and prevent future breakage - // if the type is later made optional. - template - StatementBuilder& AssignValue(const ValueType& value) - { - AddBindFunctor(AppendOpAndBinder(Op::Equals), value); - return *this; - } - - // The optional index value can be used to specify the parameter index. - StatementBuilder& Equals(details::unbound_t, std::optional index = {}); - StatementBuilder& Equals(std::nullptr_t); - StatementBuilder& Equals(); - StatementBuilder& Equals(const QualifiedColumn& column); - - template - StatementBuilder& IsGreaterThan(const ValueType& value) - { - AddBindFunctor(AppendOpAndBinder(Op::GreaterThan), value); - return *this; - } - StatementBuilder& IsGreaterThan(details::unbound_t, std::optional index = {}); - - template - StatementBuilder& IsGreaterThanOrEqualTo(const ValueType& value) - { - AddBindFunctor(AppendOpAndBinder(Op::GreaterThanOrEqualTo), value); - return *this; - } - StatementBuilder& IsGreaterThanOrEqualTo(details::unbound_t, std::optional index = {}); - - StatementBuilder& LikeWithEscape(std::string_view value); - StatementBuilder& Like(details::unbound_t); - - StatementBuilder& Escape(std::string_view escapeChar); - - StatementBuilder& Not(); - StatementBuilder& In(); - - // Appends a set of value binders for the In clause. - StatementBuilder& In(size_t count); - - // IsNull(true) means the value is null; IsNull(false) means the value is not null. - StatementBuilder& IsNull(bool isNull = true); - StatementBuilder& IsNotNull() { return IsNull(false); } - - // Operators for combining filter clauses. - StatementBuilder& And(std::string_view column); - StatementBuilder& And(const QualifiedColumn& column); - StatementBuilder& Or(const QualifiedColumn& column); - - // Begin a join clause. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& Join(std::string_view table); - StatementBuilder& Join(QualifiedTable table); - StatementBuilder& Join(std::initializer_list table); - - // Begin a left outer join clause. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& LeftOuterJoin(std::string_view table); - StatementBuilder& LeftOuterJoin(QualifiedTable table); - StatementBuilder& LeftOuterJoin(std::initializer_list table); - - // Set the join constraint. - StatementBuilder& On(const QualifiedColumn& column1, const QualifiedColumn& column2); - - // Specify the grouping to use. - StatementBuilder& GroupBy(std::string_view column); - StatementBuilder& GroupBy(const QualifiedColumn& column); - - // Specify the ordering to use. - StatementBuilder& OrderBy(std::string_view column); - StatementBuilder& OrderBy(const QualifiedColumn& column); - StatementBuilder& OrderBy(std::initializer_list columns); - - // Specify the ordering behavior. - StatementBuilder& Ascending(); - StatementBuilder& Descending(); - - // Limits the result set to the given number of rows. - StatementBuilder& Limit(size_t rowCount); - - // Begin an insert statement for the given table. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& InsertInto(std::string_view table); - StatementBuilder& InsertInto(QualifiedTable table); - StatementBuilder& InsertInto(std::initializer_list table); - - // Begin an insert or ignore statement for the given table. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& InsertOrIgnore(std::string_view table); - StatementBuilder& InsertOrIgnore(QualifiedTable table); - StatementBuilder& InsertOrIgnore(std::initializer_list table); - - // Set the columns for a statement (typically insert). - StatementBuilder& Columns(std::string_view column); - StatementBuilder& Columns(std::initializer_list columns); - StatementBuilder& Columns(const QualifiedColumn& column); - StatementBuilder& Columns(std::initializer_list columns); - - // Set the columns for a select or create table statement. - StatementBuilder& Columns(std::initializer_list columns); - StatementBuilder& BeginColumns(); - StatementBuilder& Column(std::string_view column); - StatementBuilder& Column(const QualifiedColumn& column); - StatementBuilder& Column(Aggregate aggOp, std::string_view column); - StatementBuilder& Column(Aggregate aggOp, const QualifiedColumn& column); - StatementBuilder& Column(const details::SubBuilder& column); - StatementBuilder& EndColumns(); - - // Set the columns null constraint. - StatementBuilder& NotNull(bool isTrue = true); - - // Set the column's default value. - template - StatementBuilder& Default(const ValueType& value) - { - m_stream << " DEFAULT (" << value << ")"; - return *this; - } - - // Add the values clause for an insert statement. - template - StatementBuilder& Values(const ValueTypes&... values) - { - int bindIndexBegin = AppendValuesAndBinders(sizeof...(ValueTypes)); - // Use folding to add a binder for every value, specifically in the order they were given. - // Do not change this expression without understanding the implications to the bind order. - // See: https://en.cppreference.com/w/cpp/language/fold for more details. - (FoldHelper{}, ..., InsertValuesValueBinder(bindIndexBegin++, values)); - return *this; - } - StatementBuilder& BeginValues(); - template - StatementBuilder& Value(const ValueType& value) - { - InsertValuesValueBinder(AppendValueAndBinder(), value); - return *this; - } - StatementBuilder& EndValues(); - - // Begin a table creation statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& CreateTable(std::string_view table); - StatementBuilder& CreateTable(QualifiedTable table); - StatementBuilder& CreateTable(std::initializer_list table); - - // Begin an alter table statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& AlterTable(std::string_view table); - StatementBuilder& AlterTable(QualifiedTable table); - StatementBuilder& AlterTable(std::initializer_list table); - - // Complete an alter table statement by adding a column. - StatementBuilder& Add(std::string_view column, Type type); - - // Begin a table deletion statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& DropTable(std::string_view table); - StatementBuilder& DropTable(QualifiedTable table); - StatementBuilder& DropTable(std::initializer_list table); - - // Begin a table deletion statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& DropTableIfExists(std::string_view table); - StatementBuilder& DropTableIfExists(QualifiedTable table); - StatementBuilder& DropTableIfExists(std::initializer_list table); - - // Begin an index creation statement. - // The initializer_list form enables the index name to be constructed from multiple parts. - StatementBuilder& CreateIndex(std::string_view table); - StatementBuilder& CreateIndex(QualifiedTable table); - StatementBuilder& CreateIndex(std::initializer_list table); - - // Begin an unique index creation statement. - // The initializer_list form enables the index name to be constructed from multiple parts. - StatementBuilder& CreateUniqueIndex(std::string_view table); - StatementBuilder& CreateUniqueIndex(QualifiedTable table); - StatementBuilder& CreateUniqueIndex(std::initializer_list table); - - // Begin an index deletion statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& DropIndex(std::string_view index); - StatementBuilder& DropIndex(QualifiedTable index); - StatementBuilder& DropIndex(std::initializer_list index); - - // Set index target table. - StatementBuilder& On(std::string_view table); - StatementBuilder& On(std::initializer_list table); - - // Begin a delete statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& DeleteFrom(std::string_view table); - StatementBuilder& DeleteFrom(QualifiedTable table); - StatementBuilder& DeleteFrom(std::initializer_list table); - - // Begin an update statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& Update(std::string_view table); - StatementBuilder& Update(QualifiedTable table); - StatementBuilder& Update(std::initializer_list table); - - // Begin an `update or replace` statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& UpdateOrReplace(std::string_view table); - StatementBuilder& UpdateOrReplace(QualifiedTable table); - StatementBuilder& UpdateOrReplace(std::initializer_list table); - - // Output the set portion of an update statement. - StatementBuilder& Set(); - - // Output the set portion of an update statement. - StatementBuilder& Vacuum(); - - // General purpose functions to begin and end a parenthetical expression. - StatementBuilder& BeginParenthetical(); - StatementBuilder& EndParenthetical(); - - // Adds the `without rowid` clause. - StatementBuilder& WithoutRowID(); - - // Assign an alias to the previous item. - StatementBuilder& As(std::string_view alias); - - // Gets the last bound index. - // A value of zero indicates that nothing has been bound. - int GetLastBindIndex() const { return m_bindIndex - 1; } - - // Prepares and returns the statement, applying any bindings that were requested. - Statement Prepare(const Connection& connection); - - // A convenience function that prepares, binds, and then executes a statement that does not return rows. - void Execute(const Connection& connection); - - private: - enum class Op - { - Equals, - Like, - Escape, - Literal, - GreaterThan, - GreaterThanOrEqualTo, - }; - - // Appends given the operation. - // The optional index value can be used to specify the parameter index. - int AppendOpAndBinder(Op op, std::optional index = {}); - - // Appends a set of binders for the values clause of an insert. - int AppendValuesAndBinders(size_t count); - - // Appends a binder for the values clause of an insert. - int AppendValueAndBinder(); - - // Adds a functor to our list that will bind the given value. - template - void AddBindFunctor(int binderIndex, const ValueType& value) - { - m_binders.emplace_back([binderIndex, value](Statement& s) { s.Bind(binderIndex, value); }); - } - - // Helper template for binding incoming values for an insert. - template - StatementBuilder& InsertValuesValueBinder(int bindIndex, const ValueType& value) - { - AddBindFunctor(bindIndex, value); - return *this; - } - template - StatementBuilder& InsertValuesValueBinder(int bindIndex, const std::optional& value) - { - if (value) - { - AddBindFunctor(bindIndex, value.value()); - } - else - { - AddBindFunctor(bindIndex, nullptr); - } - return *this; - } - StatementBuilder& InsertValuesValueBinder(int, details::unbound_t) - { - return *this; - } - StatementBuilder& InsertValuesValueBinder(int bindIndex, std::nullptr_t) - { - AddBindFunctor(bindIndex, nullptr); - return *this; - } - - std::ostringstream m_stream; - // Because binding values starts at 1 - int m_bindIndex = 1; - std::vector> m_binders; - bool m_needsComma = false; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; + +namespace AppInstaller::SQLite::Builder +{ + namespace details + { + // Sentinel types to indicate special cases to the builder. + struct unbound_t {}; + struct rowcount_t {}; + + // Class for intake from external functions. + struct SubBuilder + { + SubBuilder(std::string&& s) : m_string(std::move(s)) {} + + SubBuilder(const SubBuilder&) = default; + SubBuilder& operator=(const SubBuilder&) = default; + + SubBuilder(SubBuilder&&) noexcept = default; + SubBuilder& operator=(SubBuilder&&) noexcept = default; + + const std::string& GetString() const { return m_string; } + + protected: + std::string m_string; + }; + + // Base class for all sub-builders. + struct SubBuilderBase + { + SubBuilderBase() = default; + + SubBuilderBase(const SubBuilderBase&) = default; + SubBuilderBase& operator=(const SubBuilderBase&) = default; + + SubBuilderBase(SubBuilderBase&&) noexcept = default; + SubBuilderBase& operator=(SubBuilderBase&&) noexcept = default; + + virtual operator SubBuilder() { return { m_stream.str() }; } + + protected: + std::ostringstream m_stream; + }; + } + + // Pass this value to indicate that the caller will bind the value later. + __declspec_selectany_ details::unbound_t Unbound; + + // Pass this value to indicate that the number of rows is to be selected. + __declspec_selectany_ details::rowcount_t RowCount; + + // A qualified table reference. + struct QualifiedTable + { + std::string_view Schema; + std::string_view Table; + + explicit constexpr QualifiedTable(std::string_view table) : Table(table) {} + explicit constexpr QualifiedTable(std::string_view schema, std::string_view table) : Schema(schema), Table(table) {} + }; + + namespace Schema + { + // The main database's schema table. + // More info can be found at: https://www.sqlite.org/schematab.html + constexpr QualifiedTable MainTable{ "main"sv, "sqlite_master"sv }; + + // The sqlite_schema column name for the type of the object. + constexpr std::string_view TypeColumn = "type"sv; + + // The sqlite_schema type value for a table. + constexpr std::string_view Type_Table = "table"sv; + + // The sqlite_schema type value for an index. + constexpr std::string_view Type_Index = "index"sv; + + // The sqlite_schema column name for the name of the object. + constexpr std::string_view NameColumn = "name"sv; + } + + // A qualified column reference. + struct QualifiedColumn + { + std::string_view Table; + std::string_view Column; + + explicit QualifiedColumn(std::string_view column) : Column(column) {} + explicit QualifiedColumn(std::string_view table, std::string_view column) : Table(table), Column(column) {} + }; + + // SQLite types as an enum. + enum class Type + { + Int, + Bool = Int, + Int64, + RowId = Int64, + Text, + Blob, + Integer, // Type for specifying a primary key column as a row id alias. + None, // Does not declare a type + }; + + template + struct TypeInfo + { + }; + + template <> + struct TypeInfo + { + using value_t = std::string; + }; + + template <> + struct TypeInfo + { + using value_t = std::optional; + }; + + template <> + struct TypeInfo + { + using value_t = SQLite::blob_t; + }; + + template <> + struct TypeInfo + { + using value_t = std::optional; + }; + + // Aggregate functions. + enum class Aggregate + { + Min, + Max, + }; + + // Helper to mark create an integer primary key for rowid, making it stable across vacuum. + struct IntegerPrimaryKey : public details::SubBuilderBase + { + IntegerPrimaryKey(); + + IntegerPrimaryKey(const IntegerPrimaryKey&) = default; + IntegerPrimaryKey& operator=(const IntegerPrimaryKey&) = default; + + IntegerPrimaryKey(IntegerPrimaryKey&&) noexcept = default; + IntegerPrimaryKey& operator=(IntegerPrimaryKey&&) noexcept = default; + + // Set the column to autoincrement. SQLite recommends against using this value unless + // you need to ensure that rowids are not ever reused. + IntegerPrimaryKey& AutoIncrement(bool isTrue = true); + }; + + // Helper used when creating a table. + struct ColumnBuilder : public details::SubBuilderBase + { + // Specify the column name and type when creating the builder. + ColumnBuilder(std::string_view column, Type type); + + ColumnBuilder(const ColumnBuilder&) = default; + ColumnBuilder& operator=(const ColumnBuilder&) = default; + + ColumnBuilder(ColumnBuilder&&) noexcept = default; + ColumnBuilder& operator=(ColumnBuilder&&) noexcept = default; + + // Indicate that the column is not able to be null. + // Allow for data driven construction with input value. + ColumnBuilder& NotNull(bool isTrue = true); + + // Indicate that the column is case-insensitive. + // Allow for data driven construction with input value. + ColumnBuilder& CollateNoCase(bool isTrue = true); + + // Indicate the default value for the column. + // Note that a default value is not considered constant if it is bound, + // so this function directly places the incoming value into the SQL statement. + ColumnBuilder& Default(int64_t value); + + // Indicate that the column is unique. + // Allow for data driven construction with input value. + ColumnBuilder& Unique(bool isTrue = true); + + // Indicate that the column is the primary key. + // Allow for data driven construction with input value. + ColumnBuilder& PrimaryKey(bool isTrue = true); + }; + + // Helper used to specify a primary key with multiple columns. + struct PrimaryKeyBuilder : public details::SubBuilderBase + { + PrimaryKeyBuilder(); + PrimaryKeyBuilder(std::initializer_list columns); + + PrimaryKeyBuilder(const PrimaryKeyBuilder&) = default; + PrimaryKeyBuilder& operator=(const PrimaryKeyBuilder&) = default; + + PrimaryKeyBuilder(PrimaryKeyBuilder&&) noexcept = default; + PrimaryKeyBuilder& operator=(PrimaryKeyBuilder&&) noexcept = default; + + virtual operator details::SubBuilder() override; + + // Add a column to the primary key. + PrimaryKeyBuilder& Column(std::string_view column); + + private: + bool m_isFirst = true; + bool m_needsClosing = true; + }; + + // A class that aids in building SQL statements in a more expressive manner than simple strings. + struct StatementBuilder + { + StatementBuilder() = default; + + StatementBuilder(const StatementBuilder&) = default; + StatementBuilder& operator=(const StatementBuilder&) = default; + + StatementBuilder(StatementBuilder&&) = default; + StatementBuilder& operator=(StatementBuilder&&) = default; + + // Begin a select statement for the given columns. + StatementBuilder& Select(); + StatementBuilder& Select(std::string_view column); + StatementBuilder& Select(std::initializer_list columns); + StatementBuilder& Select(const QualifiedColumn& column); + StatementBuilder& Select(std::initializer_list columns); + StatementBuilder& Select(details::rowcount_t); + + // Indicate the table that the statement will be operating on. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& From(); + StatementBuilder& From(std::string_view table); + StatementBuilder& From(QualifiedTable table); + StatementBuilder& From(std::initializer_list table); + + // Begin a filter clause on the given column. + StatementBuilder& Where(std::string_view column); + StatementBuilder& Where(const QualifiedColumn& column); + + // A full filter clause looking for an embedded null character. + // Is extremely specific to consistency checks, and so a more detailed construct is not required. + StatementBuilder& WhereValueContainsEmbeddedNullCharacter(std::string_view column); + StatementBuilder& WhereValueContainsEmbeddedNullCharacter(const QualifiedColumn& column); + + // Indicate the operation of the filter clause. + template + StatementBuilder& Equals(const ValueType& value) + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), value); + return *this; + } + template + StatementBuilder& Equals(const std::optional& value) + { + if (value) + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), value.value()); + return *this; + } + else + { + return IsNull(); + } + } + + // Assigns a value using "= ?" binding semantics. When the optional is empty, binds NULL + // via the "= ?" parameter rather than producing "IS NULL". Use this instead of + // Equals(optional) in UPDATE SET clauses, where "IS NULL" is invalid SQL. + template + StatementBuilder& AssignValue(const std::optional& value) + { + if (value) + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), value.value()); + } + else + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), nullptr); + } + return *this; + } + + // Assigns a non-nullable value using "= ?" binding semantics. Prefer this over Equals() + // in UPDATE SET clauses to clearly signal assignment intent and prevent future breakage + // if the type is later made optional. + template + StatementBuilder& AssignValue(const ValueType& value) + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), value); + return *this; + } + + // The optional index value can be used to specify the parameter index. + StatementBuilder& Equals(details::unbound_t, std::optional index = {}); + StatementBuilder& Equals(std::nullptr_t); + StatementBuilder& Equals(); + StatementBuilder& Equals(const QualifiedColumn& column); + + template + StatementBuilder& IsGreaterThan(const ValueType& value) + { + AddBindFunctor(AppendOpAndBinder(Op::GreaterThan), value); + return *this; + } + StatementBuilder& IsGreaterThan(details::unbound_t, std::optional index = {}); + + template + StatementBuilder& IsGreaterThanOrEqualTo(const ValueType& value) + { + AddBindFunctor(AppendOpAndBinder(Op::GreaterThanOrEqualTo), value); + return *this; + } + StatementBuilder& IsGreaterThanOrEqualTo(details::unbound_t, std::optional index = {}); + + StatementBuilder& LikeWithEscape(std::string_view value); + StatementBuilder& Like(details::unbound_t); + + StatementBuilder& Escape(std::string_view escapeChar); + + StatementBuilder& Not(); + StatementBuilder& In(); + + // Appends a set of value binders for the In clause. + StatementBuilder& In(size_t count); + + // IsNull(true) means the value is null; IsNull(false) means the value is not null. + StatementBuilder& IsNull(bool isNull = true); + StatementBuilder& IsNotNull() { return IsNull(false); } + + // Operators for combining filter clauses. + StatementBuilder& And(std::string_view column); + StatementBuilder& And(const QualifiedColumn& column); + StatementBuilder& Or(const QualifiedColumn& column); + + // Begin a join clause. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& Join(std::string_view table); + StatementBuilder& Join(QualifiedTable table); + StatementBuilder& Join(std::initializer_list table); + + // Begin a left outer join clause. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& LeftOuterJoin(std::string_view table); + StatementBuilder& LeftOuterJoin(QualifiedTable table); + StatementBuilder& LeftOuterJoin(std::initializer_list table); + + // Set the join constraint. + StatementBuilder& On(const QualifiedColumn& column1, const QualifiedColumn& column2); + + // Specify the grouping to use. + StatementBuilder& GroupBy(std::string_view column); + StatementBuilder& GroupBy(const QualifiedColumn& column); + + // Specify the ordering to use. + StatementBuilder& OrderBy(std::string_view column); + StatementBuilder& OrderBy(const QualifiedColumn& column); + StatementBuilder& OrderBy(std::initializer_list columns); + + // Specify the ordering behavior. + StatementBuilder& Ascending(); + StatementBuilder& Descending(); + + // Limits the result set to the given number of rows. + StatementBuilder& Limit(size_t rowCount); + + // Begin an insert statement for the given table. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& InsertInto(std::string_view table); + StatementBuilder& InsertInto(QualifiedTable table); + StatementBuilder& InsertInto(std::initializer_list table); + + // Begin an insert or ignore statement for the given table. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& InsertOrIgnore(std::string_view table); + StatementBuilder& InsertOrIgnore(QualifiedTable table); + StatementBuilder& InsertOrIgnore(std::initializer_list table); + + // Set the columns for a statement (typically insert). + StatementBuilder& Columns(std::string_view column); + StatementBuilder& Columns(std::initializer_list columns); + StatementBuilder& Columns(const QualifiedColumn& column); + StatementBuilder& Columns(std::initializer_list columns); + + // Set the columns for a select or create table statement. + StatementBuilder& Columns(std::initializer_list columns); + StatementBuilder& BeginColumns(); + StatementBuilder& Column(std::string_view column); + StatementBuilder& Column(const QualifiedColumn& column); + StatementBuilder& Column(Aggregate aggOp, std::string_view column); + StatementBuilder& Column(Aggregate aggOp, const QualifiedColumn& column); + StatementBuilder& Column(const details::SubBuilder& column); + StatementBuilder& EndColumns(); + + // Set the columns null constraint. + StatementBuilder& NotNull(bool isTrue = true); + + // Set the column's default value. + template + StatementBuilder& Default(const ValueType& value) + { + m_stream << " DEFAULT (" << value << ")"; + return *this; + } + + // Add the values clause for an insert statement. + template + StatementBuilder& Values(const ValueTypes&... values) + { + int bindIndexBegin = AppendValuesAndBinders(sizeof...(ValueTypes)); + // Use folding to add a binder for every value, specifically in the order they were given. + // Do not change this expression without understanding the implications to the bind order. + // See: https://en.cppreference.com/w/cpp/language/fold for more details. + (FoldHelper{}, ..., InsertValuesValueBinder(bindIndexBegin++, values)); + return *this; + } + StatementBuilder& BeginValues(); + template + StatementBuilder& Value(const ValueType& value) + { + InsertValuesValueBinder(AppendValueAndBinder(), value); + return *this; + } + StatementBuilder& EndValues(); + + // Begin a table creation statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& CreateTable(std::string_view table); + StatementBuilder& CreateTable(QualifiedTable table); + StatementBuilder& CreateTable(std::initializer_list table); + + // Begin an alter table statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& AlterTable(std::string_view table); + StatementBuilder& AlterTable(QualifiedTable table); + StatementBuilder& AlterTable(std::initializer_list table); + + // Complete an alter table statement by adding a column. + StatementBuilder& Add(std::string_view column, Type type); + + // Begin a table deletion statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& DropTable(std::string_view table); + StatementBuilder& DropTable(QualifiedTable table); + StatementBuilder& DropTable(std::initializer_list table); + + // Begin a table deletion statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& DropTableIfExists(std::string_view table); + StatementBuilder& DropTableIfExists(QualifiedTable table); + StatementBuilder& DropTableIfExists(std::initializer_list table); + + // Begin an index creation statement. + // The initializer_list form enables the index name to be constructed from multiple parts. + StatementBuilder& CreateIndex(std::string_view table); + StatementBuilder& CreateIndex(QualifiedTable table); + StatementBuilder& CreateIndex(std::initializer_list table); + + // Begin an unique index creation statement. + // The initializer_list form enables the index name to be constructed from multiple parts. + StatementBuilder& CreateUniqueIndex(std::string_view table); + StatementBuilder& CreateUniqueIndex(QualifiedTable table); + StatementBuilder& CreateUniqueIndex(std::initializer_list table); + + // Begin an index deletion statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& DropIndex(std::string_view index); + StatementBuilder& DropIndex(QualifiedTable index); + StatementBuilder& DropIndex(std::initializer_list index); + + // Set index target table. + StatementBuilder& On(std::string_view table); + StatementBuilder& On(std::initializer_list table); + + // Begin a delete statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& DeleteFrom(std::string_view table); + StatementBuilder& DeleteFrom(QualifiedTable table); + StatementBuilder& DeleteFrom(std::initializer_list table); + + // Begin an update statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& Update(std::string_view table); + StatementBuilder& Update(QualifiedTable table); + StatementBuilder& Update(std::initializer_list table); + + // Begin an `update or replace` statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& UpdateOrReplace(std::string_view table); + StatementBuilder& UpdateOrReplace(QualifiedTable table); + StatementBuilder& UpdateOrReplace(std::initializer_list table); + + // Output the set portion of an update statement. + StatementBuilder& Set(); + + // Output the set portion of an update statement. + StatementBuilder& Vacuum(); + + // General purpose functions to begin and end a parenthetical expression. + StatementBuilder& BeginParenthetical(); + StatementBuilder& EndParenthetical(); + + // Adds the `without rowid` clause. + StatementBuilder& WithoutRowID(); + + // Assign an alias to the previous item. + StatementBuilder& As(std::string_view alias); + + // Gets the last bound index. + // A value of zero indicates that nothing has been bound. + int GetLastBindIndex() const { return m_bindIndex - 1; } + + // Prepares and returns the statement, applying any bindings that were requested. + Statement Prepare(const Connection& connection); + + // A convenience function that prepares, binds, and then executes a statement that does not return rows. + void Execute(const Connection& connection); + + private: + enum class Op + { + Equals, + Like, + Escape, + Literal, + GreaterThan, + GreaterThanOrEqualTo, + }; + + // Appends given the operation. + // The optional index value can be used to specify the parameter index. + int AppendOpAndBinder(Op op, std::optional index = {}); + + // Appends a set of binders for the values clause of an insert. + int AppendValuesAndBinders(size_t count); + + // Appends a binder for the values clause of an insert. + int AppendValueAndBinder(); + + // Adds a functor to our list that will bind the given value. + template + void AddBindFunctor(int binderIndex, const ValueType& value) + { + m_binders.emplace_back([binderIndex, value](Statement& s) { s.Bind(binderIndex, value); }); + } + + // Helper template for binding incoming values for an insert. + template + StatementBuilder& InsertValuesValueBinder(int bindIndex, const ValueType& value) + { + AddBindFunctor(bindIndex, value); + return *this; + } + template + StatementBuilder& InsertValuesValueBinder(int bindIndex, const std::optional& value) + { + if (value) + { + AddBindFunctor(bindIndex, value.value()); + } + else + { + AddBindFunctor(bindIndex, nullptr); + } + return *this; + } + StatementBuilder& InsertValuesValueBinder(int, details::unbound_t) + { + return *this; + } + StatementBuilder& InsertValuesValueBinder(int bindIndex, std::nullptr_t) + { + AddBindFunctor(bindIndex, nullptr); + return *this; + } + + std::ostringstream m_stream; + // Because binding values starts at 1 + int m_bindIndex = 1; + std::vector> m_binders; + bool m_needsComma = false; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp index 669826fa86..7f83a3d624 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp @@ -1,264 +1,264 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SetInfoTable.h" -#include "UnitInfoTable.h" -#include "ConfigurationSetSerializer.h" -#include "ConfigurationSetParser.h" -#include -#include -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::SQLite::Builder; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 -{ - namespace - { - constexpr std::string_view s_SetInfoTable_Table = "set_info"sv; - - constexpr std::string_view s_SetInfoTable_Column_InstanceIdentifier = "instance_identifier"sv; - constexpr std::string_view s_SetInfoTable_Column_Name = "name"sv; - constexpr std::string_view s_SetInfoTable_Column_Origin = "origin"sv; - constexpr std::string_view s_SetInfoTable_Column_Path = "path"sv; - constexpr std::string_view s_SetInfoTable_Column_FirstApply = "first_apply"sv; - constexpr std::string_view s_SetInfoTable_Column_SchemaVersion = "schema_version"sv; - constexpr std::string_view s_SetInfoTable_Column_Metadata = "metadata"sv; - constexpr std::string_view s_SetInfoTable_Column_Parameters = "parameters"sv; - constexpr std::string_view s_SetInfoTable_Column_Variables = "variables"sv; - - void BuildBaseSetSelectStatement(StatementBuilder& builder) - { - builder.Select({ - RowIDName, // 0 - s_SetInfoTable_Column_InstanceIdentifier, // 1 - s_SetInfoTable_Column_Name, // 2 - s_SetInfoTable_Column_Origin, // 3 - s_SetInfoTable_Column_Path, // 4 - s_SetInfoTable_Column_SchemaVersion, // 5 - s_SetInfoTable_Column_Metadata, // 6 - s_SetInfoTable_Column_Parameters, // 7 - s_SetInfoTable_Column_Variables, // 8 - }).From(s_SetInfoTable_Table); - } - - IConfigurationDatabase::ConfigurationSetPtr GetSetFromStatement(Statement& statement, UnitInfoTable& unitInfoTable) - { - auto configurationSet = make_self(statement.GetColumn(1)); - - configurationSet->Name(hstring{ ConvertToUTF16(statement.GetColumn(2)) }); - configurationSet->Origin(hstring{ ConvertToUTF16(statement.GetColumn(3)) }); - configurationSet->Path(hstring{ ConvertToUTF16(statement.GetColumn(4)) }); - - std::string schemaVersion = statement.GetColumn(5); - configurationSet->SchemaVersion(hstring{ ConvertToUTF16(schemaVersion) }); - - auto parser = ConfigurationSetParser::CreateForSchemaVersion(schemaVersion); - configurationSet->Metadata(parser->ParseValueSet(statement.GetColumn(6))); - parser->ExtractEnvironmentFromMetadata(configurationSet->Metadata(), configurationSet->EnvironmentInternal()); - - THROW_HR_IF(E_NOTIMPL, !statement.GetColumn(7).empty()); - configurationSet->Variables(parser->ParseValueSet(statement.GetColumn(8))); - - std::vector winrtUnits; - for (const auto& unit : unitInfoTable.GetAllUnitsForSet(statement.GetColumn(0), schemaVersion)) - { - winrtUnits.emplace_back(*unit); - } - configurationSet->Units(std::move(winrtUnits)); - - return configurationSet; - } - } - - SetInfoTable::SetInfoTable(Connection& connection) : m_connection(connection) {} - - std::string_view SetInfoTable::TableName() - { - return s_SetInfoTable_Table; - } - - std::string_view SetInfoTable::InstanceIdentifierColumn() - { - return s_SetInfoTable_Column_InstanceIdentifier; - } - - void SetInfoTable::Create() - { - Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Create_0_1"); - - StatementBuilder tableBuilder; - tableBuilder.CreateTable(s_SetInfoTable_Table).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_SetInfoTable_Column_InstanceIdentifier, Type::Blob).Unique().NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Name, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Origin, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Path, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_FirstApply, Type::Int64).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_SchemaVersion, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Metadata, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Parameters, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Variables, Type::Text).NotNull(), - }); - - tableBuilder.Execute(m_connection); - - savepoint.Commit(); - } - - rowid_t SetInfoTable::Add(const Configuration::ConfigurationSet& configurationSet) - { - THROW_HR_IF(E_NOTIMPL, configurationSet.Parameters().Size() > 0); - - Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Add_0_1"); - - hstring schemaVersion = configurationSet.SchemaVersion(); - auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); - - StatementBuilder builder; - builder.InsertInto(s_SetInfoTable_Table).Columns({ - s_SetInfoTable_Column_InstanceIdentifier, - s_SetInfoTable_Column_Name, - s_SetInfoTable_Column_Origin, - s_SetInfoTable_Column_Path, - s_SetInfoTable_Column_FirstApply, - s_SetInfoTable_Column_SchemaVersion, - s_SetInfoTable_Column_Metadata, - s_SetInfoTable_Column_Parameters, - s_SetInfoTable_Column_Variables, - }).Values( - static_cast(configurationSet.InstanceIdentifier()), - ConvertToUTF8(configurationSet.Name()), - ConvertToUTF8(configurationSet.Origin()), - ConvertToUTF8(configurationSet.Path()), - GetCurrentUnixEpoch(), - ConvertToUTF8(schemaVersion), - serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment()), - std::string{}, // Parameters - serializer->SerializeValueSet(configurationSet.Variables()) - ); - - builder.Execute(m_connection); - rowid_t result = m_connection.GetLastInsertRowID(); - - UnitInfoTable unitInfoTable(m_connection); - - auto winrtUnits = configurationSet.Units(); - std::vector units{ winrtUnits.Size() }; - winrtUnits.GetMany(0, units); - - for (const auto& unit : units) - { - unitInfoTable.Add(unit, result, schemaVersion); - } - - savepoint.Commit(); - return result; - } - - void SetInfoTable::Update(rowid_t target, const Configuration::ConfigurationSet& configurationSet) - { - THROW_HR_IF(E_NOTIMPL, configurationSet.Parameters().Size() > 0); - - Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Update_0_1"); - - hstring schemaVersion = configurationSet.SchemaVersion(); - auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); - - StatementBuilder builder; - builder.Update(s_SetInfoTable_Table).Set(). - Column(s_SetInfoTable_Column_Name).AssignValue(ConvertToUTF8(configurationSet.Name())). - Column(s_SetInfoTable_Column_Origin).AssignValue(ConvertToUTF8(configurationSet.Origin())). - Column(s_SetInfoTable_Column_Path).AssignValue(ConvertToUTF8(configurationSet.Path())). - Column(s_SetInfoTable_Column_SchemaVersion).AssignValue(ConvertToUTF8(schemaVersion)). - Column(s_SetInfoTable_Column_Metadata).AssignValue(serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment())). - Column(s_SetInfoTable_Column_Variables).AssignValue(serializer->SerializeValueSet(configurationSet.Variables())). - Where(RowIDName).Equals(target); - - builder.Execute(m_connection); - - UnitInfoTable unitInfoTable(m_connection); - unitInfoTable.UpdateForSet(target, configurationSet.Units(), schemaVersion); - - savepoint.Commit(); - } - - void SetInfoTable::Remove(rowid_t target) - { - Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Remove_0_1"); - - StatementBuilder builder; - builder.DeleteFrom(s_SetInfoTable_Table).Where(RowIDName).Equals(target); - builder.Execute(m_connection); - - UnitInfoTable unitInfoTable(m_connection); - unitInfoTable.RemoveForSet(target); - - savepoint.Commit(); - } - - std::vector SetInfoTable::GetAllSets() - { - std::vector result; - - StatementBuilder builder; - BuildBaseSetSelectStatement(builder); - - Statement getAllSets = builder.Prepare(m_connection); - - UnitInfoTable unitInfoTable(m_connection); - - while (getAllSets.Step()) - { - result.emplace_back(GetSetFromStatement(getAllSets, unitInfoTable)); - } - - return result; - } - - std::optional SetInfoTable::GetSetRowId(const GUID& instanceIdentifier) - { - StatementBuilder builder; - builder.Select(RowIDName).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); - - Statement select = builder.Prepare(m_connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - - return std::nullopt; - } - - IConfigurationDatabase::ConfigurationSetPtr SetInfoTable::GetSet(const GUID& instanceIdentifier) - { - IConfigurationDatabase::ConfigurationSetPtr result; - - StatementBuilder builder; - BuildBaseSetSelectStatement(builder); - builder.Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); - - Statement getSet = builder.Prepare(m_connection); - - if (getSet.Step()) - { - UnitInfoTable unitInfoTable(m_connection); - result = GetSetFromStatement(getSet, unitInfoTable); - } - - return result; - } - - std::chrono::system_clock::time_point SetInfoTable::GetSetFirstApply(const GUID& instanceIdentifier) - { - StatementBuilder builder; - builder.Select(s_SetInfoTable_Column_FirstApply).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); - - Statement statement = builder.Prepare(m_connection); - - return (statement.Step() ? ConvertUnixEpochToSystemClock(statement.GetColumn(0)) : std::chrono::system_clock::time_point{}); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SetInfoTable.h" +#include "UnitInfoTable.h" +#include "ConfigurationSetSerializer.h" +#include "ConfigurationSetParser.h" +#include +#include +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 +{ + namespace + { + constexpr std::string_view s_SetInfoTable_Table = "set_info"sv; + + constexpr std::string_view s_SetInfoTable_Column_InstanceIdentifier = "instance_identifier"sv; + constexpr std::string_view s_SetInfoTable_Column_Name = "name"sv; + constexpr std::string_view s_SetInfoTable_Column_Origin = "origin"sv; + constexpr std::string_view s_SetInfoTable_Column_Path = "path"sv; + constexpr std::string_view s_SetInfoTable_Column_FirstApply = "first_apply"sv; + constexpr std::string_view s_SetInfoTable_Column_SchemaVersion = "schema_version"sv; + constexpr std::string_view s_SetInfoTable_Column_Metadata = "metadata"sv; + constexpr std::string_view s_SetInfoTable_Column_Parameters = "parameters"sv; + constexpr std::string_view s_SetInfoTable_Column_Variables = "variables"sv; + + void BuildBaseSetSelectStatement(StatementBuilder& builder) + { + builder.Select({ + RowIDName, // 0 + s_SetInfoTable_Column_InstanceIdentifier, // 1 + s_SetInfoTable_Column_Name, // 2 + s_SetInfoTable_Column_Origin, // 3 + s_SetInfoTable_Column_Path, // 4 + s_SetInfoTable_Column_SchemaVersion, // 5 + s_SetInfoTable_Column_Metadata, // 6 + s_SetInfoTable_Column_Parameters, // 7 + s_SetInfoTable_Column_Variables, // 8 + }).From(s_SetInfoTable_Table); + } + + IConfigurationDatabase::ConfigurationSetPtr GetSetFromStatement(Statement& statement, UnitInfoTable& unitInfoTable) + { + auto configurationSet = make_self(statement.GetColumn(1)); + + configurationSet->Name(hstring{ ConvertToUTF16(statement.GetColumn(2)) }); + configurationSet->Origin(hstring{ ConvertToUTF16(statement.GetColumn(3)) }); + configurationSet->Path(hstring{ ConvertToUTF16(statement.GetColumn(4)) }); + + std::string schemaVersion = statement.GetColumn(5); + configurationSet->SchemaVersion(hstring{ ConvertToUTF16(schemaVersion) }); + + auto parser = ConfigurationSetParser::CreateForSchemaVersion(schemaVersion); + configurationSet->Metadata(parser->ParseValueSet(statement.GetColumn(6))); + parser->ExtractEnvironmentFromMetadata(configurationSet->Metadata(), configurationSet->EnvironmentInternal()); + + THROW_HR_IF(E_NOTIMPL, !statement.GetColumn(7).empty()); + configurationSet->Variables(parser->ParseValueSet(statement.GetColumn(8))); + + std::vector winrtUnits; + for (const auto& unit : unitInfoTable.GetAllUnitsForSet(statement.GetColumn(0), schemaVersion)) + { + winrtUnits.emplace_back(*unit); + } + configurationSet->Units(std::move(winrtUnits)); + + return configurationSet; + } + } + + SetInfoTable::SetInfoTable(Connection& connection) : m_connection(connection) {} + + std::string_view SetInfoTable::TableName() + { + return s_SetInfoTable_Table; + } + + std::string_view SetInfoTable::InstanceIdentifierColumn() + { + return s_SetInfoTable_Column_InstanceIdentifier; + } + + void SetInfoTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Create_0_1"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_SetInfoTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_SetInfoTable_Column_InstanceIdentifier, Type::Blob).Unique().NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Name, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Origin, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Path, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_FirstApply, Type::Int64).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_SchemaVersion, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Metadata, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Parameters, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Variables, Type::Text).NotNull(), + }); + + tableBuilder.Execute(m_connection); + + savepoint.Commit(); + } + + rowid_t SetInfoTable::Add(const Configuration::ConfigurationSet& configurationSet) + { + THROW_HR_IF(E_NOTIMPL, configurationSet.Parameters().Size() > 0); + + Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Add_0_1"); + + hstring schemaVersion = configurationSet.SchemaVersion(); + auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); + + StatementBuilder builder; + builder.InsertInto(s_SetInfoTable_Table).Columns({ + s_SetInfoTable_Column_InstanceIdentifier, + s_SetInfoTable_Column_Name, + s_SetInfoTable_Column_Origin, + s_SetInfoTable_Column_Path, + s_SetInfoTable_Column_FirstApply, + s_SetInfoTable_Column_SchemaVersion, + s_SetInfoTable_Column_Metadata, + s_SetInfoTable_Column_Parameters, + s_SetInfoTable_Column_Variables, + }).Values( + static_cast(configurationSet.InstanceIdentifier()), + ConvertToUTF8(configurationSet.Name()), + ConvertToUTF8(configurationSet.Origin()), + ConvertToUTF8(configurationSet.Path()), + GetCurrentUnixEpoch(), + ConvertToUTF8(schemaVersion), + serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment()), + std::string{}, // Parameters + serializer->SerializeValueSet(configurationSet.Variables()) + ); + + builder.Execute(m_connection); + rowid_t result = m_connection.GetLastInsertRowID(); + + UnitInfoTable unitInfoTable(m_connection); + + auto winrtUnits = configurationSet.Units(); + std::vector units{ winrtUnits.Size() }; + winrtUnits.GetMany(0, units); + + for (const auto& unit : units) + { + unitInfoTable.Add(unit, result, schemaVersion); + } + + savepoint.Commit(); + return result; + } + + void SetInfoTable::Update(rowid_t target, const Configuration::ConfigurationSet& configurationSet) + { + THROW_HR_IF(E_NOTIMPL, configurationSet.Parameters().Size() > 0); + + Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Update_0_1"); + + hstring schemaVersion = configurationSet.SchemaVersion(); + auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); + + StatementBuilder builder; + builder.Update(s_SetInfoTable_Table).Set(). + Column(s_SetInfoTable_Column_Name).AssignValue(ConvertToUTF8(configurationSet.Name())). + Column(s_SetInfoTable_Column_Origin).AssignValue(ConvertToUTF8(configurationSet.Origin())). + Column(s_SetInfoTable_Column_Path).AssignValue(ConvertToUTF8(configurationSet.Path())). + Column(s_SetInfoTable_Column_SchemaVersion).AssignValue(ConvertToUTF8(schemaVersion)). + Column(s_SetInfoTable_Column_Metadata).AssignValue(serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment())). + Column(s_SetInfoTable_Column_Variables).AssignValue(serializer->SerializeValueSet(configurationSet.Variables())). + Where(RowIDName).Equals(target); + + builder.Execute(m_connection); + + UnitInfoTable unitInfoTable(m_connection); + unitInfoTable.UpdateForSet(target, configurationSet.Units(), schemaVersion); + + savepoint.Commit(); + } + + void SetInfoTable::Remove(rowid_t target) + { + Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Remove_0_1"); + + StatementBuilder builder; + builder.DeleteFrom(s_SetInfoTable_Table).Where(RowIDName).Equals(target); + builder.Execute(m_connection); + + UnitInfoTable unitInfoTable(m_connection); + unitInfoTable.RemoveForSet(target); + + savepoint.Commit(); + } + + std::vector SetInfoTable::GetAllSets() + { + std::vector result; + + StatementBuilder builder; + BuildBaseSetSelectStatement(builder); + + Statement getAllSets = builder.Prepare(m_connection); + + UnitInfoTable unitInfoTable(m_connection); + + while (getAllSets.Step()) + { + result.emplace_back(GetSetFromStatement(getAllSets, unitInfoTable)); + } + + return result; + } + + std::optional SetInfoTable::GetSetRowId(const GUID& instanceIdentifier) + { + StatementBuilder builder; + builder.Select(RowIDName).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); + + Statement select = builder.Prepare(m_connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + + return std::nullopt; + } + + IConfigurationDatabase::ConfigurationSetPtr SetInfoTable::GetSet(const GUID& instanceIdentifier) + { + IConfigurationDatabase::ConfigurationSetPtr result; + + StatementBuilder builder; + BuildBaseSetSelectStatement(builder); + builder.Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); + + Statement getSet = builder.Prepare(m_connection); + + if (getSet.Step()) + { + UnitInfoTable unitInfoTable(m_connection); + result = GetSetFromStatement(getSet, unitInfoTable); + } + + return result; + } + + std::chrono::system_clock::time_point SetInfoTable::GetSetFirstApply(const GUID& instanceIdentifier) + { + StatementBuilder builder; + builder.Select(s_SetInfoTable_Column_FirstApply).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); + + Statement statement = builder.Prepare(m_connection); + + return (statement.Step() ? ConvertUnixEpochToSystemClock(statement.GetColumn(0)) : std::chrono::system_clock::time_point{}); + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp index 7920aa8fc5..4e61ddac9b 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp @@ -1,155 +1,155 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "QueueTable.h" -#include -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::SQLite::Builder; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 -{ - namespace - { - constexpr std::string_view s_QueueTable_Table = "queue"sv; - - constexpr std::string_view s_QueueTable_Column_SetInstanceIdentifier = "set_instance_identifier"sv; - constexpr std::string_view s_QueueTable_Column_ObjectName = "object_name"sv; - constexpr std::string_view s_QueueTable_Column_QueuedAt = "queued_at"sv; - constexpr std::string_view s_QueueTable_Column_Active = "active"sv; - constexpr std::string_view s_QueueTable_Column_Process = "process"sv; - } - - QueueTable::QueueTable(Connection& connection) : m_connection(connection) {} - - void QueueTable::Create() - { - Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_Create_0_2"); - - StatementBuilder tableBuilder; - tableBuilder.CreateTable(s_QueueTable_Table).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_QueueTable_Column_SetInstanceIdentifier, Type::Blob).NotNull(), - ColumnBuilder(s_QueueTable_Column_ObjectName, Type::Text).Unique().NotNull(), - ColumnBuilder(s_QueueTable_Column_QueuedAt, Type::Int64).NotNull(), - ColumnBuilder(s_QueueTable_Column_Active, Type::Bool).NotNull(), - }); - - tableBuilder.Execute(m_connection); - - savepoint.Commit(); - } - - void QueueTable::AddProcessColumn() - { - Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_AddProcessColumn_0_3"); - - StatementBuilder builder; - builder.AlterTable(s_QueueTable_Table).Add(s_QueueTable_Column_Process, Type::Int64).NotNull().Default(0); - - builder.Execute(m_connection); - - savepoint.Commit(); - } - - void QueueTable::AddQueueItemWithoutProcess(const GUID& instanceIdentifier, const std::string& objectName) - { - StatementBuilder builder; - builder.InsertInto(s_QueueTable_Table).Columns({ - s_QueueTable_Column_SetInstanceIdentifier, - s_QueueTable_Column_ObjectName, - s_QueueTable_Column_QueuedAt, - s_QueueTable_Column_Active - }).Values( - instanceIdentifier, - objectName, - GetCurrentUnixEpoch(), - false - ); - - builder.Execute(m_connection); - } - - void QueueTable::AddQueueItemWithProcess(const GUID& instanceIdentifier, const std::string& objectName) - { - StatementBuilder builder; - builder.InsertInto(s_QueueTable_Table).Columns({ - s_QueueTable_Column_SetInstanceIdentifier, - s_QueueTable_Column_ObjectName, - s_QueueTable_Column_QueuedAt, - s_QueueTable_Column_Active, - s_QueueTable_Column_Process - }).Values( - instanceIdentifier, - objectName, - GetCurrentUnixEpoch(), - false, - static_cast(GetCurrentProcessId()) - ); - - builder.Execute(m_connection); - } - - void QueueTable::SetActiveQueueItem(const std::string& objectName) - { - StatementBuilder builder; - builder.Update(s_QueueTable_Table).Set().Column(s_QueueTable_Column_Active).AssignValue(true).Where(s_QueueTable_Column_ObjectName).Equals(objectName); - - builder.Execute(m_connection); - } - - std::vector> QueueTable::GetQueueItemsWithoutProcess() - { - StatementBuilder builder; - builder.Select({ - s_QueueTable_Column_SetInstanceIdentifier, - s_QueueTable_Column_ObjectName, - s_QueueTable_Column_QueuedAt, - s_QueueTable_Column_Active - }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); - - Statement statement = builder.Prepare(m_connection); - - std::vector> result; - - while (statement.Step()) - { - result.emplace_back(std::make_tuple(statement.GetColumn(0), statement.GetColumn(1), ConvertUnixEpochToSystemClock(statement.GetColumn(2)), 0, statement.GetColumn(3))); - } - - return result; - } - - std::vector> QueueTable::GetQueueItemsWithProcess() - { - StatementBuilder builder; - builder.Select({ - s_QueueTable_Column_SetInstanceIdentifier, - s_QueueTable_Column_ObjectName, - s_QueueTable_Column_QueuedAt, - s_QueueTable_Column_Active, - s_QueueTable_Column_Process - }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); - - Statement statement = builder.Prepare(m_connection); - - std::vector> result; - - while (statement.Step()) - { - result.emplace_back(std::make_tuple(statement.GetColumn(0), statement.GetColumn(1), ConvertUnixEpochToSystemClock(statement.GetColumn(2)), static_cast(statement.GetColumn(4)), statement.GetColumn(3))); - } - - return result; - } - - void QueueTable::RemoveQueueItem(const std::string& objectName) - { - StatementBuilder builder; - builder.DeleteFrom(s_QueueTable_Table).Where(s_QueueTable_Column_ObjectName).Equals(objectName); - - builder.Execute(m_connection); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "QueueTable.h" +#include +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 +{ + namespace + { + constexpr std::string_view s_QueueTable_Table = "queue"sv; + + constexpr std::string_view s_QueueTable_Column_SetInstanceIdentifier = "set_instance_identifier"sv; + constexpr std::string_view s_QueueTable_Column_ObjectName = "object_name"sv; + constexpr std::string_view s_QueueTable_Column_QueuedAt = "queued_at"sv; + constexpr std::string_view s_QueueTable_Column_Active = "active"sv; + constexpr std::string_view s_QueueTable_Column_Process = "process"sv; + } + + QueueTable::QueueTable(Connection& connection) : m_connection(connection) {} + + void QueueTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_Create_0_2"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_QueueTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_QueueTable_Column_SetInstanceIdentifier, Type::Blob).NotNull(), + ColumnBuilder(s_QueueTable_Column_ObjectName, Type::Text).Unique().NotNull(), + ColumnBuilder(s_QueueTable_Column_QueuedAt, Type::Int64).NotNull(), + ColumnBuilder(s_QueueTable_Column_Active, Type::Bool).NotNull(), + }); + + tableBuilder.Execute(m_connection); + + savepoint.Commit(); + } + + void QueueTable::AddProcessColumn() + { + Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_AddProcessColumn_0_3"); + + StatementBuilder builder; + builder.AlterTable(s_QueueTable_Table).Add(s_QueueTable_Column_Process, Type::Int64).NotNull().Default(0); + + builder.Execute(m_connection); + + savepoint.Commit(); + } + + void QueueTable::AddQueueItemWithoutProcess(const GUID& instanceIdentifier, const std::string& objectName) + { + StatementBuilder builder; + builder.InsertInto(s_QueueTable_Table).Columns({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active + }).Values( + instanceIdentifier, + objectName, + GetCurrentUnixEpoch(), + false + ); + + builder.Execute(m_connection); + } + + void QueueTable::AddQueueItemWithProcess(const GUID& instanceIdentifier, const std::string& objectName) + { + StatementBuilder builder; + builder.InsertInto(s_QueueTable_Table).Columns({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active, + s_QueueTable_Column_Process + }).Values( + instanceIdentifier, + objectName, + GetCurrentUnixEpoch(), + false, + static_cast(GetCurrentProcessId()) + ); + + builder.Execute(m_connection); + } + + void QueueTable::SetActiveQueueItem(const std::string& objectName) + { + StatementBuilder builder; + builder.Update(s_QueueTable_Table).Set().Column(s_QueueTable_Column_Active).AssignValue(true).Where(s_QueueTable_Column_ObjectName).Equals(objectName); + + builder.Execute(m_connection); + } + + std::vector> QueueTable::GetQueueItemsWithoutProcess() + { + StatementBuilder builder; + builder.Select({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active + }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); + + Statement statement = builder.Prepare(m_connection); + + std::vector> result; + + while (statement.Step()) + { + result.emplace_back(std::make_tuple(statement.GetColumn(0), statement.GetColumn(1), ConvertUnixEpochToSystemClock(statement.GetColumn(2)), 0, statement.GetColumn(3))); + } + + return result; + } + + std::vector> QueueTable::GetQueueItemsWithProcess() + { + StatementBuilder builder; + builder.Select({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active, + s_QueueTable_Column_Process + }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); + + Statement statement = builder.Prepare(m_connection); + + std::vector> result; + + while (statement.Step()) + { + result.emplace_back(std::make_tuple(statement.GetColumn(0), statement.GetColumn(1), ConvertUnixEpochToSystemClock(statement.GetColumn(2)), static_cast(statement.GetColumn(4)), statement.GetColumn(3))); + } + + return result; + } + + void QueueTable::RemoveQueueItem(const std::string& objectName) + { + StatementBuilder builder; + builder.DeleteFrom(s_QueueTable_Table).Where(s_QueueTable_Column_ObjectName).Equals(objectName); + + builder.Execute(m_connection); + } +} diff --git a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs index c4f148a182..b4c90c2506 100644 --- a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs +++ b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs @@ -68,8 +68,8 @@ internal static class ClassesDefinition [ClsidContext.OutOfProc] = new Guid("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"), [ClsidContext.OutOfProcDev] = new Guid("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"), } - }, - + }, + [typeof(DownloadOptions)] = new() { ProjectedClassType = typeof(DownloadOptions), @@ -92,8 +92,8 @@ internal static class ClassesDefinition [ClsidContext.OutOfProc] = new Guid("D02C9DAF-99DC-429C-B503-4E504E4AB000"), [ClsidContext.OutOfProcDev] = new Guid("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"), } - }, - + }, + [typeof(AuthenticationArguments)] = new() { ProjectedClassType = typeof(AuthenticationArguments), @@ -104,8 +104,8 @@ internal static class ClassesDefinition [ClsidContext.OutOfProc] = new Guid("BA580786-BDE3-4F6C-B8F3-44698AC8711A"), [ClsidContext.OutOfProcDev] = new Guid("6484A61D-50FA-41F0-B71E-F4370C6EB37C"), } - }, - + }, + [typeof(PackageManagerSettings)] = new() { ProjectedClassType = typeof(PackageManagerSettings), @@ -114,54 +114,54 @@ internal static class ClassesDefinition { [ClsidContext.InProc] = new Guid("80CF9D63-5505-4342-B9B4-BB87895CA8BB"), } - }, - - [typeof(RepairOptions)] = new () - { - ProjectedClassType = typeof(RepairOptions), - InterfaceType = typeof(IRepairOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("30C024C4-852C-4DD4-9810-1348C51EF9BB"), - [ClsidContext.OutOfProc] = new Guid("0498F441-3097-455F-9CAF-148F28293865"), - [ClsidContext.OutOfProcDev] = new Guid("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"), - } - }, - - [typeof(AddPackageCatalogOptions)] = new() - { - ProjectedClassType = typeof(AddPackageCatalogOptions), - InterfaceType = typeof(IAddPackageCatalogOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("24E6F1FA-E4C3-4ACD-965D-DF213FD58F15"), - [ClsidContext.OutOfProc] = new Guid("DB9D012D-00D7-47EE-8FB1-606E10AC4F51"), - [ClsidContext.OutOfProcDev] = new Guid("D58C7E4C-70E6-476C-A5D4-80341ED80252"), - } - }, - - [typeof(RemovePackageCatalogOptions)] = new() - { - ProjectedClassType = typeof(RemovePackageCatalogOptions), - InterfaceType = typeof(IRemovePackageCatalogOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("1125D3A6-E2CE-479A-91D5-71A3F6F8B00B"), - [ClsidContext.OutOfProc] = new Guid("032B1C58-B975-469B-A013-E632B6ECE8D8"), - [ClsidContext.OutOfProcDev] = new Guid("87A96609-1A39-4955-BE72-7174E147B7DC"), - } - }, - - [typeof(EditPackageCatalogOptions)] = new() - { - ProjectedClassType = typeof(EditPackageCatalogOptions), - InterfaceType = typeof(IEditPackageCatalogOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("E8E12FE1-AB77-40C4-A562-E91FB51B4E82"), - [ClsidContext.OutOfProc] = new Guid("A9F5E736-68CE-463C-BA6D-DE968F0CCE04"), - [ClsidContext.OutOfProcDev] = new Guid("29B19238-81AD-4A8E-A2FC-ADF17C38CAEB"), - } + }, + + [typeof(RepairOptions)] = new () + { + ProjectedClassType = typeof(RepairOptions), + InterfaceType = typeof(IRepairOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("30C024C4-852C-4DD4-9810-1348C51EF9BB"), + [ClsidContext.OutOfProc] = new Guid("0498F441-3097-455F-9CAF-148F28293865"), + [ClsidContext.OutOfProcDev] = new Guid("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"), + } + }, + + [typeof(AddPackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(AddPackageCatalogOptions), + InterfaceType = typeof(IAddPackageCatalogOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("24E6F1FA-E4C3-4ACD-965D-DF213FD58F15"), + [ClsidContext.OutOfProc] = new Guid("DB9D012D-00D7-47EE-8FB1-606E10AC4F51"), + [ClsidContext.OutOfProcDev] = new Guid("D58C7E4C-70E6-476C-A5D4-80341ED80252"), + } + }, + + [typeof(RemovePackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(RemovePackageCatalogOptions), + InterfaceType = typeof(IRemovePackageCatalogOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("1125D3A6-E2CE-479A-91D5-71A3F6F8B00B"), + [ClsidContext.OutOfProc] = new Guid("032B1C58-B975-469B-A013-E632B6ECE8D8"), + [ClsidContext.OutOfProcDev] = new Guid("87A96609-1A39-4955-BE72-7174E147B7DC"), + } + }, + + [typeof(EditPackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(EditPackageCatalogOptions), + InterfaceType = typeof(IEditPackageCatalogOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("E8E12FE1-AB77-40C4-A562-E91FB51B4E82"), + [ClsidContext.OutOfProc] = new Guid("A9F5E736-68CE-463C-BA6D-DE968F0CCE04"), + [ClsidContext.OutOfProcDev] = new Guid("29B19238-81AD-4A8E-A2FC-ADF17C38CAEB"), + } }, [typeof(PinPackageOptions)] = new() diff --git a/src/Microsoft.Management.Deployment/ComClsids.cpp b/src/Microsoft.Management.Deployment/ComClsids.cpp index f1d6df5f7e..2296b794d2 100644 --- a/src/Microsoft.Management.Deployment/ComClsids.cpp +++ b/src/Microsoft.Management.Deployment/ComClsids.cpp @@ -13,12 +13,12 @@ #include "PackageMatchFilter.h" #include "PackageManagerSettings.h" #include "DownloadOptions.h" -#include "AuthenticationArguments.h" -#include "RepairOptions.h" -#include "AddPackageCatalogOptions.h" +#include "AuthenticationArguments.h" +#include "RepairOptions.h" +#include "AddPackageCatalogOptions.h" #include "RemovePackageCatalogOptions.h" #include "EditPackageCatalogOptions.h" -#include "PinPackageOptions.h" +#include "PinPackageOptions.h" #pragma warning( pop ) namespace winrt::Microsoft::Management::Deployment @@ -60,30 +60,30 @@ namespace winrt::Microsoft::Management::Deployment else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageManagerSettings)) { return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageManagerSettings); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RepairOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RepairOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::AddPackageCatalogOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RemovePackageCatalogOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::EditPackageCatalogOptions); } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PinPackageOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PinPackageOptions); - } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RepairOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RepairOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::AddPackageCatalogOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RemovePackageCatalogOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::EditPackageCatalogOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PinPackageOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PinPackageOptions); + } else { return CLSID_NULL; } } -} +} diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp index da6ae41e91..07d0c72810 100644 --- a/src/Microsoft.Management.Deployment/Converters.cpp +++ b/src/Microsoft.Management.Deployment/Converters.cpp @@ -1,599 +1,599 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include "Microsoft/PredefinedInstalledSourceFactory.h" -#include "Workflows/WorkflowBase.h" -#include "Converters.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - Microsoft::Management::Deployment::PackageMatchField GetDeploymentMatchField(::AppInstaller::Repository::PackageMatchField field) - { - Microsoft::Management::Deployment::PackageMatchField matchField = Microsoft::Management::Deployment::PackageMatchField::Id; - switch (field) - { - case ::AppInstaller::Repository::PackageMatchField::Command: - matchField = Microsoft::Management::Deployment::PackageMatchField::Command; - break; - case ::AppInstaller::Repository::PackageMatchField::Id: - matchField = Microsoft::Management::Deployment::PackageMatchField::Id; - break; - case ::AppInstaller::Repository::PackageMatchField::Moniker: - matchField = Microsoft::Management::Deployment::PackageMatchField::Moniker; - break; - case ::AppInstaller::Repository::PackageMatchField::Name: - matchField = Microsoft::Management::Deployment::PackageMatchField::Name; - break; - case ::AppInstaller::Repository::PackageMatchField::Tag: - matchField = Microsoft::Management::Deployment::PackageMatchField::Tag; - break; - case ::AppInstaller::Repository::PackageMatchField::ProductCode: - matchField = Microsoft::Management::Deployment::PackageMatchField::ProductCode; - break; - case ::AppInstaller::Repository::PackageMatchField::PackageFamilyName: - matchField = Microsoft::Management::Deployment::PackageMatchField::PackageFamilyName; - break; - default: - matchField = Microsoft::Management::Deployment::PackageMatchField::Id; - break; - } - return matchField; - } - - ::AppInstaller::Repository::PackageMatchField GetRepositoryMatchField(Microsoft::Management::Deployment::PackageMatchField field) - { - ::AppInstaller::Repository::PackageMatchField matchField = ::AppInstaller::Repository::PackageMatchField::Id; - switch (field) - { - case Microsoft::Management::Deployment::PackageMatchField::Command: - matchField = ::AppInstaller::Repository::PackageMatchField::Command; - break; - case Microsoft::Management::Deployment::PackageMatchField::Id: - matchField = ::AppInstaller::Repository::PackageMatchField::Id; - break; - case Microsoft::Management::Deployment::PackageMatchField::Moniker: - matchField = ::AppInstaller::Repository::PackageMatchField::Moniker; - break; - case Microsoft::Management::Deployment::PackageMatchField::Name: - matchField = ::AppInstaller::Repository::PackageMatchField::Name; - break; - case Microsoft::Management::Deployment::PackageMatchField::Tag: - matchField = ::AppInstaller::Repository::PackageMatchField::Tag; - break; - case Microsoft::Management::Deployment::PackageMatchField::ProductCode: - matchField = ::AppInstaller::Repository::PackageMatchField::ProductCode; - break; - case Microsoft::Management::Deployment::PackageMatchField::PackageFamilyName: - matchField = ::AppInstaller::Repository::PackageMatchField::PackageFamilyName; - break; - default: - matchField = ::AppInstaller::Repository::PackageMatchField::Id; - break; - } - return matchField; - } - - Microsoft::Management::Deployment::PackageFieldMatchOption GetDeploymentMatchOption(::AppInstaller::Repository::MatchType type) - { - Microsoft::Management::Deployment::PackageFieldMatchOption matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; - switch (type) - { - case ::AppInstaller::Repository::MatchType::CaseInsensitive: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::EqualsCaseInsensitive; - break; - case ::AppInstaller::Repository::MatchType::Exact: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; - break; - case ::AppInstaller::Repository::MatchType::StartsWith: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::StartsWithCaseInsensitive; - break; - case ::AppInstaller::Repository::MatchType::Substring: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::ContainsCaseInsensitive; - break; - default: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; - break; - } - return matchOption; - } - - ::AppInstaller::Repository::MatchType GetRepositoryMatchType(Microsoft::Management::Deployment::PackageFieldMatchOption option) - { - ::AppInstaller::Repository::MatchType packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; - switch (option) - { - case Microsoft::Management::Deployment::PackageFieldMatchOption::EqualsCaseInsensitive: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::CaseInsensitive; - break; - case Microsoft::Management::Deployment::PackageFieldMatchOption::Equals: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; - break; - case Microsoft::Management::Deployment::PackageFieldMatchOption::StartsWithCaseInsensitive: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::StartsWith; - break; - case Microsoft::Management::Deployment::PackageFieldMatchOption::ContainsCaseInsensitive: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Substring; - break; - default: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; - break; - } - return packageFieldMatchOption; - } - - ::AppInstaller::Repository::CompositeSearchBehavior GetRepositoryCompositeSearchBehavior(Microsoft::Management::Deployment::CompositeSearchBehavior searchBehavior) - { - ::AppInstaller::Repository::CompositeSearchBehavior repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AllPackages; - switch (searchBehavior) - { - case Microsoft::Management::Deployment::CompositeSearchBehavior::LocalCatalogs: - repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::Installed; - break; - case Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs: - repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AvailablePackages; - break; - case Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromAllCatalogs: - repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AvailablePackages; - break; - case Microsoft::Management::Deployment::CompositeSearchBehavior::AllCatalogs: - default: - repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AllPackages; - break; - } - return repositorySearchBehavior; - } - - ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField) - { - ::AppInstaller::Repository::PackageVersionMetadata metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledLocation; - switch (packageVersionMetadataField) - { - case Microsoft::Management::Deployment::PackageVersionMetadataField::InstalledLocation: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledLocation; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::InstalledScope: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledScope; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::InstallerType: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledType; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::PublisherDisplayName: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::Publisher; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::SilentUninstallCommand: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::SilentUninstallCommand; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::StandardUninstallCommand: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::StandardUninstallCommand; - break; - } - return metadataKey; - } - - winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult) - { - winrt::Microsoft::Management::Deployment::FindPackagesResultStatus resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::Ok; - switch (hresult) - { - case(S_OK): - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::Ok; - break; - case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::BlockedByPolicy; - break; - case APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE: - case APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA: - case APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND: - case APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR: - case APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE: - case APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION: - case APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::CatalogError; - break; - case E_INVALIDARG: - case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::InvalidOptions; - break; - case APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::AuthenticationError; - break; - case HTTP_E_STATUS_DENIED: - case HTTP_E_STATUS_FORBIDDEN: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::AccessDenied; - break; - case APPINSTALLER_CLI_ERROR_COMMAND_FAILED: - case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: - case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: - default: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::InternalError; - break; - } - return resultStatus; - } - - std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture) - { - return ::AppInstaller::Utility::ConvertToArchitectureEnum(architecture); - } - - std::optional GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture) - { - switch (architecture) - { - case ::AppInstaller::Utility::Architecture::X86: - return winrt::Windows::System::ProcessorArchitecture::X86; - case ::AppInstaller::Utility::Architecture::Arm: - return winrt::Windows::System::ProcessorArchitecture::Arm; - case ::AppInstaller::Utility::Architecture::X64: - return winrt::Windows::System::ProcessorArchitecture::X64; - case ::AppInstaller::Utility::Architecture::Neutral: - return winrt::Windows::System::ProcessorArchitecture::Neutral; - case ::AppInstaller::Utility::Architecture::Arm64: - return winrt::Windows::System::ProcessorArchitecture::Arm64; - } - - return {}; - } - - std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope) - { - switch (scope) - { - case winrt::Microsoft::Management::Deployment::PackageInstallScope::Any: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false); - case winrt::Microsoft::Management::Deployment::PackageInstallScope::User: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, false); - case winrt::Microsoft::Management::Deployment::PackageInstallScope::System: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, false); - case winrt::Microsoft::Management::Deployment::PackageInstallScope::UserOrUnknown: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, true); - case winrt::Microsoft::Management::Deployment::PackageInstallScope::SystemOrUnknown: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, true); - } - - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false); - } - - winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType) - { - switch (installerType) - { - case ::AppInstaller::Manifest::InstallerTypeEnum::Burn: - return Microsoft::Management::Deployment::PackageInstallerType::Burn; - case ::AppInstaller::Manifest::InstallerTypeEnum::Exe: - return Microsoft::Management::Deployment::PackageInstallerType::Exe; - case ::AppInstaller::Manifest::InstallerTypeEnum::Inno: - return Microsoft::Management::Deployment::PackageInstallerType::Inno; - case ::AppInstaller::Manifest::InstallerTypeEnum::Msi: - return Microsoft::Management::Deployment::PackageInstallerType::Msi; - case ::AppInstaller::Manifest::InstallerTypeEnum::Msix: - return Microsoft::Management::Deployment::PackageInstallerType::Msix; - case ::AppInstaller::Manifest::InstallerTypeEnum::MSStore: - return Microsoft::Management::Deployment::PackageInstallerType::MSStore; - case ::AppInstaller::Manifest::InstallerTypeEnum::Nullsoft: - return Microsoft::Management::Deployment::PackageInstallerType::Nullsoft; - case ::AppInstaller::Manifest::InstallerTypeEnum::Portable: - return Microsoft::Management::Deployment::PackageInstallerType::Portable; - case ::AppInstaller::Manifest::InstallerTypeEnum::Wix: - return Microsoft::Management::Deployment::PackageInstallerType::Wix; - case ::AppInstaller::Manifest::InstallerTypeEnum::Zip: - return Microsoft::Management::Deployment::PackageInstallerType::Zip; - case ::AppInstaller::Manifest::InstallerTypeEnum::Unknown: - return Microsoft::Management::Deployment::PackageInstallerType::Unknown; - } - - return Microsoft::Management::Deployment::PackageInstallerType::Unknown; - } - - ::AppInstaller::Manifest::InstallerTypeEnum GetManifestInstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType installerType) - { - switch (installerType) - { - case Microsoft::Management::Deployment::PackageInstallerType::Burn: - return ::AppInstaller::Manifest::InstallerTypeEnum::Burn; - case Microsoft::Management::Deployment::PackageInstallerType::Exe: - return ::AppInstaller::Manifest::InstallerTypeEnum::Exe; - case Microsoft::Management::Deployment::PackageInstallerType::Inno: - return ::AppInstaller::Manifest::InstallerTypeEnum::Inno; - case Microsoft::Management::Deployment::PackageInstallerType::Msi: - return ::AppInstaller::Manifest::InstallerTypeEnum::Msi; - case Microsoft::Management::Deployment::PackageInstallerType::Msix: - return ::AppInstaller::Manifest::InstallerTypeEnum::Msix; - case Microsoft::Management::Deployment::PackageInstallerType::MSStore: - return ::AppInstaller::Manifest::InstallerTypeEnum::MSStore; - case Microsoft::Management::Deployment::PackageInstallerType::Nullsoft: - return ::AppInstaller::Manifest::InstallerTypeEnum::Nullsoft; - case Microsoft::Management::Deployment::PackageInstallerType::Portable: - return ::AppInstaller::Manifest::InstallerTypeEnum::Portable; - case Microsoft::Management::Deployment::PackageInstallerType::Wix: - return ::AppInstaller::Manifest::InstallerTypeEnum::Wix; - case Microsoft::Management::Deployment::PackageInstallerType::Zip: - return ::AppInstaller::Manifest::InstallerTypeEnum::Zip; - case Microsoft::Management::Deployment::PackageInstallerType::Unknown: - return ::AppInstaller::Manifest::InstallerTypeEnum::Unknown; - } - - return ::AppInstaller::Manifest::InstallerTypeEnum::Unknown; - } - - winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope) - { - switch (installerScope) - { - case ::AppInstaller::Manifest::ScopeEnum::User: - return Microsoft::Management::Deployment::PackageInstallerScope::User; - case ::AppInstaller::Manifest::ScopeEnum::Machine: - return Microsoft::Management::Deployment::PackageInstallerScope::System; - case ::AppInstaller::Manifest::ScopeEnum::Unknown: - return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; - } - - return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; - } - - ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope) - { - switch (scope) - { - case winrt::Microsoft::Management::Deployment::PackageUninstallScope::Any: - return ::AppInstaller::Manifest::ScopeEnum::Unknown; - case winrt::Microsoft::Management::Deployment::PackageUninstallScope::User: - return ::AppInstaller::Manifest::ScopeEnum::User; - case winrt::Microsoft::Management::Deployment::PackageUninstallScope::System: - return ::AppInstaller::Manifest::ScopeEnum::Machine; - } - - return ::AppInstaller::Manifest::ScopeEnum::Unknown; - } - - ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope) - { - switch (scope) - { - case winrt::Microsoft::Management::Deployment::PackageRepairScope::Any: - return ::AppInstaller::Manifest::ScopeEnum::Unknown; - case winrt::Microsoft::Management::Deployment::PackageRepairScope::User: - return ::AppInstaller::Manifest::ScopeEnum::User; - case winrt::Microsoft::Management::Deployment::PackageRepairScope::System: - return ::AppInstaller::Manifest::ScopeEnum::Machine; - } - - return ::AppInstaller::Manifest::ScopeEnum::Unknown; - } - - winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement) - { - switch (elevationRequirement) - { - case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevationRequired: - return Microsoft::Management::Deployment::ElevationRequirement::ElevationRequired; - case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevationProhibited: - return Microsoft::Management::Deployment::ElevationRequirement::ElevationProhibited; - case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevatesSelf: - return Microsoft::Management::Deployment::ElevationRequirement::ElevatesSelf; - case ::AppInstaller::Manifest::ElevationRequirementEnum::Unknown: - return Microsoft::Management::Deployment::ElevationRequirement::Unknown; - } - - return Microsoft::Management::Deployment::ElevationRequirement::Unknown; - } - - winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType) - { - switch (iconFileType) - { - case ::AppInstaller::Manifest::IconFileTypeEnum::Ico: - return Microsoft::Management::Deployment::IconFileType::Ico; - case ::AppInstaller::Manifest::IconFileTypeEnum::Jpeg: - return Microsoft::Management::Deployment::IconFileType::Jpeg; - case ::AppInstaller::Manifest::IconFileTypeEnum::Png: - return Microsoft::Management::Deployment::IconFileType::Png; - } - - return Microsoft::Management::Deployment::IconFileType::Unknown; - } - - winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution) - { - switch (iconResolution) - { - case ::AppInstaller::Manifest::IconResolutionEnum::Custom: - return Microsoft::Management::Deployment::IconResolution::Custom; - case ::AppInstaller::Manifest::IconResolutionEnum::Square16: - return Microsoft::Management::Deployment::IconResolution::Square16; - case ::AppInstaller::Manifest::IconResolutionEnum::Square20: - return Microsoft::Management::Deployment::IconResolution::Square20; - case ::AppInstaller::Manifest::IconResolutionEnum::Square24: - return Microsoft::Management::Deployment::IconResolution::Square24; - case ::AppInstaller::Manifest::IconResolutionEnum::Square30: - return Microsoft::Management::Deployment::IconResolution::Square30; - case ::AppInstaller::Manifest::IconResolutionEnum::Square32: - return Microsoft::Management::Deployment::IconResolution::Square32; - case ::AppInstaller::Manifest::IconResolutionEnum::Square36: - return Microsoft::Management::Deployment::IconResolution::Square36; - case ::AppInstaller::Manifest::IconResolutionEnum::Square40: - return Microsoft::Management::Deployment::IconResolution::Square40; - case ::AppInstaller::Manifest::IconResolutionEnum::Square48: - return Microsoft::Management::Deployment::IconResolution::Square48; - case ::AppInstaller::Manifest::IconResolutionEnum::Square60: - return Microsoft::Management::Deployment::IconResolution::Square60; - case ::AppInstaller::Manifest::IconResolutionEnum::Square64: - return Microsoft::Management::Deployment::IconResolution::Square64; - case ::AppInstaller::Manifest::IconResolutionEnum::Square72: - return Microsoft::Management::Deployment::IconResolution::Square72; - case ::AppInstaller::Manifest::IconResolutionEnum::Square80: - return Microsoft::Management::Deployment::IconResolution::Square80; - case ::AppInstaller::Manifest::IconResolutionEnum::Square96: - return Microsoft::Management::Deployment::IconResolution::Square96; - case ::AppInstaller::Manifest::IconResolutionEnum::Square256: - return Microsoft::Management::Deployment::IconResolution::Square256; - } - - return Microsoft::Management::Deployment::IconResolution::Custom; - } - - winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme) - { - switch (iconTheme) - { - case ::AppInstaller::Manifest::IconThemeEnum::Default: - return Microsoft::Management::Deployment::IconTheme::Default; - case ::AppInstaller::Manifest::IconThemeEnum::Light: - return Microsoft::Management::Deployment::IconTheme::Light; - case ::AppInstaller::Manifest::IconThemeEnum::Dark: - return Microsoft::Management::Deployment::IconTheme::Dark; - case ::AppInstaller::Manifest::IconThemeEnum::HighContrast: - return Microsoft::Management::Deployment::IconTheme::HighContrast; - } - - return Microsoft::Management::Deployment::IconTheme::Unknown; - } - - winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType) - { - switch (authType) - { - case ::AppInstaller::Authentication::AuthenticationType::None: - return Microsoft::Management::Deployment::AuthenticationType::None; - case ::AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId: - return Microsoft::Management::Deployment::AuthenticationType::MicrosoftEntraId; - case ::AppInstaller::Authentication::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage: - return Microsoft::Management::Deployment::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage; - } - - return Microsoft::Management::Deployment::AuthenticationType::Unknown; - } - - ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode) - { - switch (authMode) - { - case winrt::Microsoft::Management::Deployment::AuthenticationMode::Interactive: - return ::AppInstaller::Authentication::AuthenticationMode::Interactive; - case winrt::Microsoft::Management::Deployment::AuthenticationMode::SilentPreferred: - return ::AppInstaller::Authentication::AuthenticationMode::SilentPreferred; - case winrt::Microsoft::Management::Deployment::AuthenticationMode::Silent: - return ::AppInstaller::Authentication::AuthenticationMode::Silent; - } - - return ::AppInstaller::Authentication::AuthenticationMode::Unknown; - } - - ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs) - { - ::AppInstaller::Authentication::AuthenticationArguments result; - result.Mode = ::AppInstaller::Authentication::AuthenticationMode::Silent; // Default to silent for com invocations. - - if (authArgs) - { - result.Mode = GetAuthenticationMode(authArgs.AuthenticationMode()); - result.AuthenticationAccount = ::AppInstaller::Utility::ConvertToUTF8(authArgs.AuthenticationAccount()); - } - - return result; - } - - AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult) - { - switch (hresult) - { - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED: - return AddPackageCatalogStatus::AuthenticationError; - case APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE: - case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: - case APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE: - case APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS: - case APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS: - return AddPackageCatalogStatus::InvalidOptions; - default: - return HandleCommonCatalogOperationStatus(hresult); - } - } - - RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult) - { - switch (hresult) - { - case APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST: - return RemovePackageCatalogStatus::InvalidOptions; - case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: - return RemovePackageCatalogStatus::CatalogError; - default: - return HandleCommonCatalogOperationStatus(hresult); - } - } - - EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult) - { - switch (hresult) - { - case APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST: - return EditPackageCatalogStatus::InvalidOptions; - case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: - return EditPackageCatalogStatus::CatalogError; - default: - return HandleCommonCatalogOperationStatus(hresult); - } - } - - PinResultStatus GetPinOperationStatus(winrt::hresult hresult) - { - switch (hresult) - { - case S_OK: - return PinResultStatus::Ok; - case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: - case APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY: - case APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY: - case APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED: - return PinResultStatus::BlockedByPolicy; - case APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS: - return PinResultStatus::PackagePinAlreadyExists; - case APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST: - return PinResultStatus::PackagePinNotFound; - case E_INVALIDARG: - case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: - return PinResultStatus::InvalidOptions; - default: - return PinResultStatus::InternalError; - } - } - - ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(WindowsPlatform value) - { - switch (value) - { - case WindowsPlatform::Unknown: return AppInstaller::Manifest::PlatformEnum::Unknown; - case WindowsPlatform::Universal: return AppInstaller::Manifest::PlatformEnum::Universal; - case WindowsPlatform::Desktop: return AppInstaller::Manifest::PlatformEnum::Desktop; - case WindowsPlatform::IoT: return AppInstaller::Manifest::PlatformEnum::IoT; - case WindowsPlatform::Team: return AppInstaller::Manifest::PlatformEnum::Team; - case WindowsPlatform::Holographic: return AppInstaller::Manifest::PlatformEnum::Holographic; - default: return AppInstaller::Manifest::PlatformEnum::Unknown; - } - } - - winrt::Microsoft::Management::Deployment::PackagePinType ConvertPinType(::AppInstaller::Pinning::PinType type) - { - switch (type) - { - case ::AppInstaller::Pinning::PinType::PinnedByManifest: - return winrt::Microsoft::Management::Deployment::PackagePinType::PinnedByManifest; - case ::AppInstaller::Pinning::PinType::Pinning: - return winrt::Microsoft::Management::Deployment::PackagePinType::Pinning; - case ::AppInstaller::Pinning::PinType::Gating: - return winrt::Microsoft::Management::Deployment::PackagePinType::Gating; - case ::AppInstaller::Pinning::PinType::Blocking: - return winrt::Microsoft::Management::Deployment::PackagePinType::Blocking; - default: - return winrt::Microsoft::Management::Deployment::PackagePinType::Unknown; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include "Microsoft/PredefinedInstalledSourceFactory.h" +#include "Workflows/WorkflowBase.h" +#include "Converters.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + Microsoft::Management::Deployment::PackageMatchField GetDeploymentMatchField(::AppInstaller::Repository::PackageMatchField field) + { + Microsoft::Management::Deployment::PackageMatchField matchField = Microsoft::Management::Deployment::PackageMatchField::Id; + switch (field) + { + case ::AppInstaller::Repository::PackageMatchField::Command: + matchField = Microsoft::Management::Deployment::PackageMatchField::Command; + break; + case ::AppInstaller::Repository::PackageMatchField::Id: + matchField = Microsoft::Management::Deployment::PackageMatchField::Id; + break; + case ::AppInstaller::Repository::PackageMatchField::Moniker: + matchField = Microsoft::Management::Deployment::PackageMatchField::Moniker; + break; + case ::AppInstaller::Repository::PackageMatchField::Name: + matchField = Microsoft::Management::Deployment::PackageMatchField::Name; + break; + case ::AppInstaller::Repository::PackageMatchField::Tag: + matchField = Microsoft::Management::Deployment::PackageMatchField::Tag; + break; + case ::AppInstaller::Repository::PackageMatchField::ProductCode: + matchField = Microsoft::Management::Deployment::PackageMatchField::ProductCode; + break; + case ::AppInstaller::Repository::PackageMatchField::PackageFamilyName: + matchField = Microsoft::Management::Deployment::PackageMatchField::PackageFamilyName; + break; + default: + matchField = Microsoft::Management::Deployment::PackageMatchField::Id; + break; + } + return matchField; + } + + ::AppInstaller::Repository::PackageMatchField GetRepositoryMatchField(Microsoft::Management::Deployment::PackageMatchField field) + { + ::AppInstaller::Repository::PackageMatchField matchField = ::AppInstaller::Repository::PackageMatchField::Id; + switch (field) + { + case Microsoft::Management::Deployment::PackageMatchField::Command: + matchField = ::AppInstaller::Repository::PackageMatchField::Command; + break; + case Microsoft::Management::Deployment::PackageMatchField::Id: + matchField = ::AppInstaller::Repository::PackageMatchField::Id; + break; + case Microsoft::Management::Deployment::PackageMatchField::Moniker: + matchField = ::AppInstaller::Repository::PackageMatchField::Moniker; + break; + case Microsoft::Management::Deployment::PackageMatchField::Name: + matchField = ::AppInstaller::Repository::PackageMatchField::Name; + break; + case Microsoft::Management::Deployment::PackageMatchField::Tag: + matchField = ::AppInstaller::Repository::PackageMatchField::Tag; + break; + case Microsoft::Management::Deployment::PackageMatchField::ProductCode: + matchField = ::AppInstaller::Repository::PackageMatchField::ProductCode; + break; + case Microsoft::Management::Deployment::PackageMatchField::PackageFamilyName: + matchField = ::AppInstaller::Repository::PackageMatchField::PackageFamilyName; + break; + default: + matchField = ::AppInstaller::Repository::PackageMatchField::Id; + break; + } + return matchField; + } + + Microsoft::Management::Deployment::PackageFieldMatchOption GetDeploymentMatchOption(::AppInstaller::Repository::MatchType type) + { + Microsoft::Management::Deployment::PackageFieldMatchOption matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; + switch (type) + { + case ::AppInstaller::Repository::MatchType::CaseInsensitive: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::EqualsCaseInsensitive; + break; + case ::AppInstaller::Repository::MatchType::Exact: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; + break; + case ::AppInstaller::Repository::MatchType::StartsWith: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::StartsWithCaseInsensitive; + break; + case ::AppInstaller::Repository::MatchType::Substring: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::ContainsCaseInsensitive; + break; + default: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; + break; + } + return matchOption; + } + + ::AppInstaller::Repository::MatchType GetRepositoryMatchType(Microsoft::Management::Deployment::PackageFieldMatchOption option) + { + ::AppInstaller::Repository::MatchType packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; + switch (option) + { + case Microsoft::Management::Deployment::PackageFieldMatchOption::EqualsCaseInsensitive: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::CaseInsensitive; + break; + case Microsoft::Management::Deployment::PackageFieldMatchOption::Equals: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; + break; + case Microsoft::Management::Deployment::PackageFieldMatchOption::StartsWithCaseInsensitive: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::StartsWith; + break; + case Microsoft::Management::Deployment::PackageFieldMatchOption::ContainsCaseInsensitive: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Substring; + break; + default: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; + break; + } + return packageFieldMatchOption; + } + + ::AppInstaller::Repository::CompositeSearchBehavior GetRepositoryCompositeSearchBehavior(Microsoft::Management::Deployment::CompositeSearchBehavior searchBehavior) + { + ::AppInstaller::Repository::CompositeSearchBehavior repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AllPackages; + switch (searchBehavior) + { + case Microsoft::Management::Deployment::CompositeSearchBehavior::LocalCatalogs: + repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::Installed; + break; + case Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs: + repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AvailablePackages; + break; + case Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromAllCatalogs: + repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AvailablePackages; + break; + case Microsoft::Management::Deployment::CompositeSearchBehavior::AllCatalogs: + default: + repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AllPackages; + break; + } + return repositorySearchBehavior; + } + + ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField) + { + ::AppInstaller::Repository::PackageVersionMetadata metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledLocation; + switch (packageVersionMetadataField) + { + case Microsoft::Management::Deployment::PackageVersionMetadataField::InstalledLocation: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledLocation; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::InstalledScope: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledScope; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::InstallerType: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledType; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::PublisherDisplayName: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::Publisher; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::SilentUninstallCommand: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::SilentUninstallCommand; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::StandardUninstallCommand: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::StandardUninstallCommand; + break; + } + return metadataKey; + } + + winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult) + { + winrt::Microsoft::Management::Deployment::FindPackagesResultStatus resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::Ok; + switch (hresult) + { + case(S_OK): + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::Ok; + break; + case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::BlockedByPolicy; + break; + case APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE: + case APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA: + case APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND: + case APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR: + case APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE: + case APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION: + case APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::CatalogError; + break; + case E_INVALIDARG: + case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::InvalidOptions; + break; + case APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::AuthenticationError; + break; + case HTTP_E_STATUS_DENIED: + case HTTP_E_STATUS_FORBIDDEN: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::AccessDenied; + break; + case APPINSTALLER_CLI_ERROR_COMMAND_FAILED: + case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: + case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: + default: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::InternalError; + break; + } + return resultStatus; + } + + std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture) + { + return ::AppInstaller::Utility::ConvertToArchitectureEnum(architecture); + } + + std::optional GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture) + { + switch (architecture) + { + case ::AppInstaller::Utility::Architecture::X86: + return winrt::Windows::System::ProcessorArchitecture::X86; + case ::AppInstaller::Utility::Architecture::Arm: + return winrt::Windows::System::ProcessorArchitecture::Arm; + case ::AppInstaller::Utility::Architecture::X64: + return winrt::Windows::System::ProcessorArchitecture::X64; + case ::AppInstaller::Utility::Architecture::Neutral: + return winrt::Windows::System::ProcessorArchitecture::Neutral; + case ::AppInstaller::Utility::Architecture::Arm64: + return winrt::Windows::System::ProcessorArchitecture::Arm64; + } + + return {}; + } + + std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope) + { + switch (scope) + { + case winrt::Microsoft::Management::Deployment::PackageInstallScope::Any: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false); + case winrt::Microsoft::Management::Deployment::PackageInstallScope::User: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, false); + case winrt::Microsoft::Management::Deployment::PackageInstallScope::System: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, false); + case winrt::Microsoft::Management::Deployment::PackageInstallScope::UserOrUnknown: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, true); + case winrt::Microsoft::Management::Deployment::PackageInstallScope::SystemOrUnknown: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, true); + } + + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false); + } + + winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType) + { + switch (installerType) + { + case ::AppInstaller::Manifest::InstallerTypeEnum::Burn: + return Microsoft::Management::Deployment::PackageInstallerType::Burn; + case ::AppInstaller::Manifest::InstallerTypeEnum::Exe: + return Microsoft::Management::Deployment::PackageInstallerType::Exe; + case ::AppInstaller::Manifest::InstallerTypeEnum::Inno: + return Microsoft::Management::Deployment::PackageInstallerType::Inno; + case ::AppInstaller::Manifest::InstallerTypeEnum::Msi: + return Microsoft::Management::Deployment::PackageInstallerType::Msi; + case ::AppInstaller::Manifest::InstallerTypeEnum::Msix: + return Microsoft::Management::Deployment::PackageInstallerType::Msix; + case ::AppInstaller::Manifest::InstallerTypeEnum::MSStore: + return Microsoft::Management::Deployment::PackageInstallerType::MSStore; + case ::AppInstaller::Manifest::InstallerTypeEnum::Nullsoft: + return Microsoft::Management::Deployment::PackageInstallerType::Nullsoft; + case ::AppInstaller::Manifest::InstallerTypeEnum::Portable: + return Microsoft::Management::Deployment::PackageInstallerType::Portable; + case ::AppInstaller::Manifest::InstallerTypeEnum::Wix: + return Microsoft::Management::Deployment::PackageInstallerType::Wix; + case ::AppInstaller::Manifest::InstallerTypeEnum::Zip: + return Microsoft::Management::Deployment::PackageInstallerType::Zip; + case ::AppInstaller::Manifest::InstallerTypeEnum::Unknown: + return Microsoft::Management::Deployment::PackageInstallerType::Unknown; + } + + return Microsoft::Management::Deployment::PackageInstallerType::Unknown; + } + + ::AppInstaller::Manifest::InstallerTypeEnum GetManifestInstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType installerType) + { + switch (installerType) + { + case Microsoft::Management::Deployment::PackageInstallerType::Burn: + return ::AppInstaller::Manifest::InstallerTypeEnum::Burn; + case Microsoft::Management::Deployment::PackageInstallerType::Exe: + return ::AppInstaller::Manifest::InstallerTypeEnum::Exe; + case Microsoft::Management::Deployment::PackageInstallerType::Inno: + return ::AppInstaller::Manifest::InstallerTypeEnum::Inno; + case Microsoft::Management::Deployment::PackageInstallerType::Msi: + return ::AppInstaller::Manifest::InstallerTypeEnum::Msi; + case Microsoft::Management::Deployment::PackageInstallerType::Msix: + return ::AppInstaller::Manifest::InstallerTypeEnum::Msix; + case Microsoft::Management::Deployment::PackageInstallerType::MSStore: + return ::AppInstaller::Manifest::InstallerTypeEnum::MSStore; + case Microsoft::Management::Deployment::PackageInstallerType::Nullsoft: + return ::AppInstaller::Manifest::InstallerTypeEnum::Nullsoft; + case Microsoft::Management::Deployment::PackageInstallerType::Portable: + return ::AppInstaller::Manifest::InstallerTypeEnum::Portable; + case Microsoft::Management::Deployment::PackageInstallerType::Wix: + return ::AppInstaller::Manifest::InstallerTypeEnum::Wix; + case Microsoft::Management::Deployment::PackageInstallerType::Zip: + return ::AppInstaller::Manifest::InstallerTypeEnum::Zip; + case Microsoft::Management::Deployment::PackageInstallerType::Unknown: + return ::AppInstaller::Manifest::InstallerTypeEnum::Unknown; + } + + return ::AppInstaller::Manifest::InstallerTypeEnum::Unknown; + } + + winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope) + { + switch (installerScope) + { + case ::AppInstaller::Manifest::ScopeEnum::User: + return Microsoft::Management::Deployment::PackageInstallerScope::User; + case ::AppInstaller::Manifest::ScopeEnum::Machine: + return Microsoft::Management::Deployment::PackageInstallerScope::System; + case ::AppInstaller::Manifest::ScopeEnum::Unknown: + return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; + } + + return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; + } + + ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope) + { + switch (scope) + { + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::Any: + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::User: + return ::AppInstaller::Manifest::ScopeEnum::User; + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::System: + return ::AppInstaller::Manifest::ScopeEnum::Machine; + } + + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + } + + ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope) + { + switch (scope) + { + case winrt::Microsoft::Management::Deployment::PackageRepairScope::Any: + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + case winrt::Microsoft::Management::Deployment::PackageRepairScope::User: + return ::AppInstaller::Manifest::ScopeEnum::User; + case winrt::Microsoft::Management::Deployment::PackageRepairScope::System: + return ::AppInstaller::Manifest::ScopeEnum::Machine; + } + + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + } + + winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement) + { + switch (elevationRequirement) + { + case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevationRequired: + return Microsoft::Management::Deployment::ElevationRequirement::ElevationRequired; + case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevationProhibited: + return Microsoft::Management::Deployment::ElevationRequirement::ElevationProhibited; + case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevatesSelf: + return Microsoft::Management::Deployment::ElevationRequirement::ElevatesSelf; + case ::AppInstaller::Manifest::ElevationRequirementEnum::Unknown: + return Microsoft::Management::Deployment::ElevationRequirement::Unknown; + } + + return Microsoft::Management::Deployment::ElevationRequirement::Unknown; + } + + winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType) + { + switch (iconFileType) + { + case ::AppInstaller::Manifest::IconFileTypeEnum::Ico: + return Microsoft::Management::Deployment::IconFileType::Ico; + case ::AppInstaller::Manifest::IconFileTypeEnum::Jpeg: + return Microsoft::Management::Deployment::IconFileType::Jpeg; + case ::AppInstaller::Manifest::IconFileTypeEnum::Png: + return Microsoft::Management::Deployment::IconFileType::Png; + } + + return Microsoft::Management::Deployment::IconFileType::Unknown; + } + + winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution) + { + switch (iconResolution) + { + case ::AppInstaller::Manifest::IconResolutionEnum::Custom: + return Microsoft::Management::Deployment::IconResolution::Custom; + case ::AppInstaller::Manifest::IconResolutionEnum::Square16: + return Microsoft::Management::Deployment::IconResolution::Square16; + case ::AppInstaller::Manifest::IconResolutionEnum::Square20: + return Microsoft::Management::Deployment::IconResolution::Square20; + case ::AppInstaller::Manifest::IconResolutionEnum::Square24: + return Microsoft::Management::Deployment::IconResolution::Square24; + case ::AppInstaller::Manifest::IconResolutionEnum::Square30: + return Microsoft::Management::Deployment::IconResolution::Square30; + case ::AppInstaller::Manifest::IconResolutionEnum::Square32: + return Microsoft::Management::Deployment::IconResolution::Square32; + case ::AppInstaller::Manifest::IconResolutionEnum::Square36: + return Microsoft::Management::Deployment::IconResolution::Square36; + case ::AppInstaller::Manifest::IconResolutionEnum::Square40: + return Microsoft::Management::Deployment::IconResolution::Square40; + case ::AppInstaller::Manifest::IconResolutionEnum::Square48: + return Microsoft::Management::Deployment::IconResolution::Square48; + case ::AppInstaller::Manifest::IconResolutionEnum::Square60: + return Microsoft::Management::Deployment::IconResolution::Square60; + case ::AppInstaller::Manifest::IconResolutionEnum::Square64: + return Microsoft::Management::Deployment::IconResolution::Square64; + case ::AppInstaller::Manifest::IconResolutionEnum::Square72: + return Microsoft::Management::Deployment::IconResolution::Square72; + case ::AppInstaller::Manifest::IconResolutionEnum::Square80: + return Microsoft::Management::Deployment::IconResolution::Square80; + case ::AppInstaller::Manifest::IconResolutionEnum::Square96: + return Microsoft::Management::Deployment::IconResolution::Square96; + case ::AppInstaller::Manifest::IconResolutionEnum::Square256: + return Microsoft::Management::Deployment::IconResolution::Square256; + } + + return Microsoft::Management::Deployment::IconResolution::Custom; + } + + winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme) + { + switch (iconTheme) + { + case ::AppInstaller::Manifest::IconThemeEnum::Default: + return Microsoft::Management::Deployment::IconTheme::Default; + case ::AppInstaller::Manifest::IconThemeEnum::Light: + return Microsoft::Management::Deployment::IconTheme::Light; + case ::AppInstaller::Manifest::IconThemeEnum::Dark: + return Microsoft::Management::Deployment::IconTheme::Dark; + case ::AppInstaller::Manifest::IconThemeEnum::HighContrast: + return Microsoft::Management::Deployment::IconTheme::HighContrast; + } + + return Microsoft::Management::Deployment::IconTheme::Unknown; + } + + winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType) + { + switch (authType) + { + case ::AppInstaller::Authentication::AuthenticationType::None: + return Microsoft::Management::Deployment::AuthenticationType::None; + case ::AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId: + return Microsoft::Management::Deployment::AuthenticationType::MicrosoftEntraId; + case ::AppInstaller::Authentication::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage: + return Microsoft::Management::Deployment::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage; + } + + return Microsoft::Management::Deployment::AuthenticationType::Unknown; + } + + ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode) + { + switch (authMode) + { + case winrt::Microsoft::Management::Deployment::AuthenticationMode::Interactive: + return ::AppInstaller::Authentication::AuthenticationMode::Interactive; + case winrt::Microsoft::Management::Deployment::AuthenticationMode::SilentPreferred: + return ::AppInstaller::Authentication::AuthenticationMode::SilentPreferred; + case winrt::Microsoft::Management::Deployment::AuthenticationMode::Silent: + return ::AppInstaller::Authentication::AuthenticationMode::Silent; + } + + return ::AppInstaller::Authentication::AuthenticationMode::Unknown; + } + + ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs) + { + ::AppInstaller::Authentication::AuthenticationArguments result; + result.Mode = ::AppInstaller::Authentication::AuthenticationMode::Silent; // Default to silent for com invocations. + + if (authArgs) + { + result.Mode = GetAuthenticationMode(authArgs.AuthenticationMode()); + result.AuthenticationAccount = ::AppInstaller::Utility::ConvertToUTF8(authArgs.AuthenticationAccount()); + } + + return result; + } + + AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult) + { + switch (hresult) + { + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED: + return AddPackageCatalogStatus::AuthenticationError; + case APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE: + case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: + case APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE: + case APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS: + case APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS: + return AddPackageCatalogStatus::InvalidOptions; + default: + return HandleCommonCatalogOperationStatus(hresult); + } + } + + RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult) + { + switch (hresult) + { + case APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST: + return RemovePackageCatalogStatus::InvalidOptions; + case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: + return RemovePackageCatalogStatus::CatalogError; + default: + return HandleCommonCatalogOperationStatus(hresult); + } + } + + EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult) + { + switch (hresult) + { + case APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST: + return EditPackageCatalogStatus::InvalidOptions; + case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: + return EditPackageCatalogStatus::CatalogError; + default: + return HandleCommonCatalogOperationStatus(hresult); + } + } + + PinResultStatus GetPinOperationStatus(winrt::hresult hresult) + { + switch (hresult) + { + case S_OK: + return PinResultStatus::Ok; + case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED: + return PinResultStatus::BlockedByPolicy; + case APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS: + return PinResultStatus::PackagePinAlreadyExists; + case APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST: + return PinResultStatus::PackagePinNotFound; + case E_INVALIDARG: + case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: + return PinResultStatus::InvalidOptions; + default: + return PinResultStatus::InternalError; + } + } + + ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(WindowsPlatform value) + { + switch (value) + { + case WindowsPlatform::Unknown: return AppInstaller::Manifest::PlatformEnum::Unknown; + case WindowsPlatform::Universal: return AppInstaller::Manifest::PlatformEnum::Universal; + case WindowsPlatform::Desktop: return AppInstaller::Manifest::PlatformEnum::Desktop; + case WindowsPlatform::IoT: return AppInstaller::Manifest::PlatformEnum::IoT; + case WindowsPlatform::Team: return AppInstaller::Manifest::PlatformEnum::Team; + case WindowsPlatform::Holographic: return AppInstaller::Manifest::PlatformEnum::Holographic; + default: return AppInstaller::Manifest::PlatformEnum::Unknown; + } + } + + winrt::Microsoft::Management::Deployment::PackagePinType ConvertPinType(::AppInstaller::Pinning::PinType type) + { + switch (type) + { + case ::AppInstaller::Pinning::PinType::PinnedByManifest: + return winrt::Microsoft::Management::Deployment::PackagePinType::PinnedByManifest; + case ::AppInstaller::Pinning::PinType::Pinning: + return winrt::Microsoft::Management::Deployment::PackagePinType::Pinning; + case ::AppInstaller::Pinning::PinType::Gating: + return winrt::Microsoft::Management::Deployment::PackagePinType::Gating; + case ::AppInstaller::Pinning::PinType::Blocking: + return winrt::Microsoft::Management::Deployment::PackagePinType::Blocking; + default: + return winrt::Microsoft::Management::Deployment::PackagePinType::Unknown; + } + } +} diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index 903a77e61e..a4294b4ac5 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -1,211 +1,211 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "PackageMatchFilter.g.h" -#include -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - winrt::Microsoft::Management::Deployment::PackageMatchField GetDeploymentMatchField(::AppInstaller::Repository::PackageMatchField field); - ::AppInstaller::Repository::PackageMatchField GetRepositoryMatchField(winrt::Microsoft::Management::Deployment::PackageMatchField field); - winrt::Microsoft::Management::Deployment::PackageFieldMatchOption GetDeploymentMatchOption(::AppInstaller::Repository::MatchType type); - ::AppInstaller::Repository::MatchType GetRepositoryMatchType(winrt::Microsoft::Management::Deployment::PackageFieldMatchOption option); - ::AppInstaller::Repository::CompositeSearchBehavior GetRepositoryCompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior searchBehavior); - ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(winrt::Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField); - winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult); - std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture); - std::optional GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture); - std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope); - ::AppInstaller::Manifest::InstallerTypeEnum GetManifestInstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType installerType); - winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType); - winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope); - ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope); - winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement); - winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType); - winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution); - winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme); - winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType); - ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode); - ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs); - ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope); - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult); - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult); - winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult); - winrt::Microsoft::Management::Deployment::PinResultStatus GetPinOperationStatus(winrt::hresult hresult); - winrt::Microsoft::Management::Deployment::PackagePinType ConvertPinType(::AppInstaller::Pinning::PinType type); - ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(winrt::Microsoft::Management::Deployment::WindowsPlatform value); - -#define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_, _downloadResultStatus_, _repairResultStatus_) \ - if constexpr (std::is_same_v) \ - { \ - resultStatus = TStatus::_installResultStatus_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - resultStatus = TStatus::_uninstallResultStatus_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - resultStatus = TStatus::_downloadResultStatus_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - resultStatus = TStatus::_repairResultStatus_; \ - } \ - - template - TStatus GetOperationResultStatus(::AppInstaller::CLI::Workflow::ExecutionStage executionStage, winrt::hresult hresult) - { - TStatus resultStatus = TStatus::Ok; - - // Map some known hresults to specific statuses, otherwise use the execution stage to determine the status. - switch (hresult) - { - case S_OK: - resultStatus = TStatus::Ok; - break; - case APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY: - case APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY: - case APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED: - case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: - resultStatus = TStatus::BlockedByPolicy; - break; - case APPINSTALLER_CLI_ERROR_INVALID_MANIFEST: - resultStatus = TStatus::ManifestError; - break; - case E_INVALIDARG: - case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: - resultStatus = TStatus::InvalidOptions; - break; - case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER: - WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers, NoApplicableRepairer); - break; - case APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE: - case APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH: - case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN: - case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER: - WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableUpgrade, InternalError, InternalError, InternalError); - break; - case APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND: - case APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED: - WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, InternalError); - break; - case APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND: - case APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE: - case APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED: - case APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED: - case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED: - WINGET_GET_OPERATION_RESULT_STATUS(InternalError, InternalError, InternalError, RepairError); - break; - case APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED: - WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted, PackageAgreementsNotAccepted); - break; - case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: - case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: - case APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED: - case APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY: - case APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY: - case APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION: - case APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED: - case APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE: - case APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA: - case APPINSTALLER_CLI_ERROR_LIBYAML_ERROR: - case APPINSTALLER_CLI_ERROR_INTERNAL_ERROR: - resultStatus = TStatus::InternalError; - break; - default: - switch (executionStage) - { - case ::AppInstaller::CLI::Workflow::ExecutionStage::Initial: - resultStatus = TStatus::InternalError; - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::ParseArgs: - resultStatus = TStatus::InvalidOptions; - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::Discovery: - resultStatus = TStatus::CatalogError; - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::Download: - WINGET_GET_OPERATION_RESULT_STATUS(DownloadError, InternalError, DownloadError, DownloadError); - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::PreExecution: - resultStatus = TStatus::InternalError; - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::Execution: - WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, RepairError); - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::PostExecution: - resultStatus = TStatus::InternalError; - break; - default: - resultStatus = TStatus::InternalError; - break; - } - } - - return resultStatus; - } - - template - TStatus HandleCommonCatalogOperationStatus(winrt::hresult hresult) - { - // Common status handling for AddPackageCatalogStatus and RemovePackageCatalogStatus. - if constexpr (std::is_same_v || std::is_same_v) - { - switch (hresult) - { - case APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN: - case E_ACCESSDENIED: - return TStatus::AccessDenied; - case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: - case E_INVALIDARG: - return TStatus::InvalidOptions; - default: - break; - } - } - - // Common status handling for AddPackageCatalogStatus, RemovePackageCatalogStatus, and RefreshPackageCatalogStatus. - switch (hresult) - { - case S_OK: - return TStatus::Ok; - case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: - return TStatus::GroupPolicyError; - case APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE: - return TStatus::CatalogError; - case APPINSTALLER_CLI_ERROR_INTERNAL_ERROR: - default: - return TStatus::InternalError; - } - } - - template - TStatus GetPackageCatalogOperationStatus(winrt::hresult hresult) - { - if constexpr (std::is_same_v) - { - return GetAddPackageCatalogOperationStatus(hresult); - } - else if constexpr (std::is_same_v) - { - return GetRemovePackageCatalogOperationStatus(hresult); - } - else if constexpr (std::is_same_v) - { - return HandleCommonCatalogOperationStatus(hresult); - } - else if constexpr (std::is_same_v) - { - return GetEditPackageCatalogOperationStatus(hresult); - } - else - { - throw winrt::hresult_error(E_UNEXPECTED); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackageMatchFilter.g.h" +#include +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + winrt::Microsoft::Management::Deployment::PackageMatchField GetDeploymentMatchField(::AppInstaller::Repository::PackageMatchField field); + ::AppInstaller::Repository::PackageMatchField GetRepositoryMatchField(winrt::Microsoft::Management::Deployment::PackageMatchField field); + winrt::Microsoft::Management::Deployment::PackageFieldMatchOption GetDeploymentMatchOption(::AppInstaller::Repository::MatchType type); + ::AppInstaller::Repository::MatchType GetRepositoryMatchType(winrt::Microsoft::Management::Deployment::PackageFieldMatchOption option); + ::AppInstaller::Repository::CompositeSearchBehavior GetRepositoryCompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior searchBehavior); + ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(winrt::Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField); + winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult); + std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture); + std::optional GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture); + std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope); + ::AppInstaller::Manifest::InstallerTypeEnum GetManifestInstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType installerType); + winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType); + winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope); + ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope); + winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement); + winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType); + winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution); + winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme); + winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType); + ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode); + ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs); + ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope); + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult); + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult); + winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult); + winrt::Microsoft::Management::Deployment::PinResultStatus GetPinOperationStatus(winrt::hresult hresult); + winrt::Microsoft::Management::Deployment::PackagePinType ConvertPinType(::AppInstaller::Pinning::PinType type); + ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(winrt::Microsoft::Management::Deployment::WindowsPlatform value); + +#define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_, _downloadResultStatus_, _repairResultStatus_) \ + if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_installResultStatus_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_uninstallResultStatus_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_downloadResultStatus_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_repairResultStatus_; \ + } \ + + template + TStatus GetOperationResultStatus(::AppInstaller::CLI::Workflow::ExecutionStage executionStage, winrt::hresult hresult) + { + TStatus resultStatus = TStatus::Ok; + + // Map some known hresults to specific statuses, otherwise use the execution stage to determine the status. + switch (hresult) + { + case S_OK: + resultStatus = TStatus::Ok; + break; + case APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED: + case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: + resultStatus = TStatus::BlockedByPolicy; + break; + case APPINSTALLER_CLI_ERROR_INVALID_MANIFEST: + resultStatus = TStatus::ManifestError; + break; + case E_INVALIDARG: + case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: + resultStatus = TStatus::InvalidOptions; + break; + case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER: + WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers, NoApplicableRepairer); + break; + case APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE: + case APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH: + case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN: + case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER: + WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableUpgrade, InternalError, InternalError, InternalError); + break; + case APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND: + case APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED: + WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, InternalError); + break; + case APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND: + case APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE: + case APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED: + case APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED: + case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED: + WINGET_GET_OPERATION_RESULT_STATUS(InternalError, InternalError, InternalError, RepairError); + break; + case APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED: + WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted, PackageAgreementsNotAccepted); + break; + case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: + case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: + case APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED: + case APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY: + case APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY: + case APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION: + case APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED: + case APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE: + case APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA: + case APPINSTALLER_CLI_ERROR_LIBYAML_ERROR: + case APPINSTALLER_CLI_ERROR_INTERNAL_ERROR: + resultStatus = TStatus::InternalError; + break; + default: + switch (executionStage) + { + case ::AppInstaller::CLI::Workflow::ExecutionStage::Initial: + resultStatus = TStatus::InternalError; + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::ParseArgs: + resultStatus = TStatus::InvalidOptions; + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::Discovery: + resultStatus = TStatus::CatalogError; + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::Download: + WINGET_GET_OPERATION_RESULT_STATUS(DownloadError, InternalError, DownloadError, DownloadError); + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::PreExecution: + resultStatus = TStatus::InternalError; + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::Execution: + WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, RepairError); + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::PostExecution: + resultStatus = TStatus::InternalError; + break; + default: + resultStatus = TStatus::InternalError; + break; + } + } + + return resultStatus; + } + + template + TStatus HandleCommonCatalogOperationStatus(winrt::hresult hresult) + { + // Common status handling for AddPackageCatalogStatus and RemovePackageCatalogStatus. + if constexpr (std::is_same_v || std::is_same_v) + { + switch (hresult) + { + case APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN: + case E_ACCESSDENIED: + return TStatus::AccessDenied; + case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: + case E_INVALIDARG: + return TStatus::InvalidOptions; + default: + break; + } + } + + // Common status handling for AddPackageCatalogStatus, RemovePackageCatalogStatus, and RefreshPackageCatalogStatus. + switch (hresult) + { + case S_OK: + return TStatus::Ok; + case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: + return TStatus::GroupPolicyError; + case APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE: + return TStatus::CatalogError; + case APPINSTALLER_CLI_ERROR_INTERNAL_ERROR: + default: + return TStatus::InternalError; + } + } + + template + TStatus GetPackageCatalogOperationStatus(winrt::hresult hresult) + { + if constexpr (std::is_same_v) + { + return GetAddPackageCatalogOperationStatus(hresult); + } + else if constexpr (std::is_same_v) + { + return GetRemovePackageCatalogOperationStatus(hresult); + } + else if constexpr (std::is_same_v) + { + return HandleCommonCatalogOperationStatus(hresult); + } + else if constexpr (std::is_same_v) + { + return GetEditPackageCatalogOperationStatus(hresult); + } + else + { + throw winrt::hresult_error(E_UNEXPECTED); + } + } +} diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj index eb1ad26685..f952b51730 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj @@ -1,292 +1,292 @@ - - - - - true - true - true - true - {1cc41a9a-ae66-459d-9210-1e572dd7be69} - Microsoft.Management.Deployment - Microsoft.Management.Deployment - en-US - 14.0 - 10.0 - 10.0.26100.0 - 10.0.17763.0 - true - -library Microsoft_Management_Deployment - - - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - StaticLibrary - false - - - true - true - - - false - true - false - Spectre - - - false - true - false - Spectre - - - - - - - - - - - - - - - - $(VC_IncludePath);$(WindowsSDK_IncludePath); - $(SolutionDir)$(PlatformTarget)\$(Configuration)\$(ProjectName)\ - $(PlatformTarget)\$(Configuration)\ - $(RootNamespace).Server - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - Level4 - %(AdditionalOptions) /bigobj - true - true - _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) - $(ProjectDir)..\AppInstallerCLICore;$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - - - Console - false - Microsoft_Management_Deployment.def - $(OutDir)$(ProjectName).winmd - AppInstallerCLICore.lib;AppInstallerCommonCore.lib;AppInstallerRepositoryCore.lib;JsonCppLib.lib;YamlCppLib.lib;wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;pure.lib%(AdditionalDependencies) - - - - - _DEBUG;%(PreprocessorDefinitions) - $(OutDir)$(TargetName)Debug.pdb - false - false - false - - - - - NDEBUG;%(PreprocessorDefinitions) - false - false - false - - - true - true - - - - - NDEBUG;%(PreprocessorDefinitions) - MultiThreaded - MultiThreaded - MultiThreaded - false - false - false - - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + true + true + true + true + {1cc41a9a-ae66-459d-9210-1e572dd7be69} + Microsoft.Management.Deployment + Microsoft.Management.Deployment + en-US + 14.0 + 10.0 + 10.0.26100.0 + 10.0.17763.0 + true + -library Microsoft_Management_Deployment + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + StaticLibrary + false + + + true + true + + + false + true + false + Spectre + + + false + true + false + Spectre + + + + + + + + + + + + + + + + $(VC_IncludePath);$(WindowsSDK_IncludePath); + $(SolutionDir)$(PlatformTarget)\$(Configuration)\$(ProjectName)\ + $(PlatformTarget)\$(Configuration)\ + $(RootNamespace).Server + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + true + true + _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + $(ProjectDir)..\AppInstallerCLICore;$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + + + Console + false + Microsoft_Management_Deployment.def + $(OutDir)$(ProjectName).winmd + AppInstallerCLICore.lib;AppInstallerCommonCore.lib;AppInstallerRepositoryCore.lib;JsonCppLib.lib;YamlCppLib.lib;wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;pure.lib%(AdditionalDependencies) + + + + + _DEBUG;%(PreprocessorDefinitions) + $(OutDir)$(TargetName)Debug.pdb + false + false + false + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + false + + + true + true + + + + + NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + MultiThreaded + MultiThreaded + false + false + false + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 50d83853a3..2f3a42d23c 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1,1751 +1,1751 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerCLICore.h" -#include "Microsoft/PredefinedInstalledSourceFactory.h" -#include "Commands/RootCommand.h" -#include "ComContext.h" -#include "ExecutionContext.h" -#include "Workflows/WorkflowBase.h" -#include -#include -#include -#include "Commands/COMCommand.h" -#include -#include -#include -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "PackageManager.h" -#include "PackagePin.h" -#include "PinPackageOptions.h" -#include "PinPackageResult.h" -#pragma warning( pop ) -#include "PackageManager.g.cpp" -#include "CatalogPackage.h" -#include "DownloadResult.h" -#include "InstallResult.h" -#include "UninstallResult.h" -#include "RepairResult.h" -#include "PackageCatalogInfo.h" -#include "PackageCatalogReference.h" -#include "PackageVersionInfo.h" -#include "PackageVersionId.h" -#include "AddPackageCatalogResult.h" -#include "RemovePackageCatalogResult.h" -#include "EditPackageCatalogResult.h" -#include "Converters.h" -#include "Helpers.h" -#include "ContextOrchestrator.h" -#include "AppInstallerRuntime.h" -#include -#include -#include -#include -#include -#include - -using namespace std::literals::chrono_literals; -using namespace ::AppInstaller::CLI; -using namespace ::AppInstaller::CLI::Execution; - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - namespace - { - void LogStartupIfApplicable() - { - static std::once_flag logStartupOnceFlag; - std::call_once(logStartupOnceFlag, - [&]() - { - ::AppInstaller::Logging::Telemetry().SetCaller(GetCallerName()); - ::AppInstaller::Logging::Telemetry().LogStartup(true); - }); - } - - winrt::Microsoft::Management::Deployment::AddPackageCatalogResult GetAddPackageCatalogResult(winrt::hresult terminationStatus) - { - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); - auto addPackageCatalogResult = winrt::make_self>(); - addPackageCatalogResult->Initialize(status, terminationStatus); - return *addPackageCatalogResult; - } - - void CheckForDuplicateSource(const std::string& name, const std::string& type, const std::string& sourceUri) - { - auto sourceList = ::AppInstaller::Repository::Source::GetCurrentSources(); - - std::string sourceType = type; - - // [NOTE:] If the source type is not specified, the default source type will be used for validation.In cases where the source type is empty, - // it remains unassigned until the add operation, at which point it is assigned.Without this default assignment, an empty string could be - // compared to the default type, potentially allowing different source names with the same URI to be seen as unique. - // To avoid this, assign the default source type prior to comparison. - if (sourceType.empty()) - { - // This method of obtaining the default source type is slightly expensive as it requires creating a SourceFactory object - // and fetching the type name.Nonetheless, it future-proofs the code against any changes in the SourceFactory's default type. - sourceType = ::AppInstaller::Repository::Source::GetDefaultSourceType(); - } - - for (const auto& source : sourceList) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, ::AppInstaller::Utility::ICUCaseInsensitiveEquals(source.Name, name)); - - bool sourceUriAlreadyExists = !source.Arg.empty() && source.Arg == sourceUri && source.Type == sourceType; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS, sourceUriAlreadyExists); - } - } - - ::AppInstaller::Repository::Source CreateSourceFromOptions(const winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions& options) - { - std::string name = winrt::to_string(options.Name()); - std::string type = winrt::to_string(options.Type()); - std::string sourceUri = winrt::to_string(options.SourceUri()); - - AppInstaller::Repository::SourceTrustLevel trustLevel = AppInstaller::Repository::SourceTrustLevel::None; - if (options.TrustLevel() == winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel::Trusted) - { - trustLevel = AppInstaller::Repository::SourceTrustLevel::Trusted; - } - - CheckForDuplicateSource(name, type, sourceUri); - - ::AppInstaller::Repository::SourceEdit additionalProperties; - additionalProperties.Explicit = options.Explicit(); - additionalProperties.Priority = options.Priority(); - - ::AppInstaller::Repository::Source source = ::AppInstaller::Repository::Source{ name, sourceUri, type, trustLevel, additionalProperties }; - - std::string customHeader = winrt::to_string(options.CustomHeader()); - if (!customHeader.empty()) - { - source.SetCustomHeader(customHeader); - } - - auto sourceInfo = source.GetInformation(); - - if (sourceInfo.Authentication.Type == ::AppInstaller::Authentication::AuthenticationType::Unknown) - { - THROW_HR(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); - } - - return source; - } - - winrt::Microsoft::Management::Deployment::RemovePackageCatalogResult GetRemovePackageCatalogResult(winrt::hresult terminationStatus) - { - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); - auto removeResult = winrt::make_self>(); - removeResult->Initialize(status, terminationStatus); - return *removeResult; - } - - std::optional<::AppInstaller::Repository::SourceDetails> GetMatchingSource(const std::string& name) - { - auto sourceList = ::AppInstaller::Repository::Source::GetCurrentSources(); - - for (const auto& source : sourceList) - { - if (::AppInstaller::Utility::ICUCaseInsensitiveEquals(source.Name, name)) - { - return source; // Return the first matching source - } - } - - return std::nullopt; // Return std::nullopt if no matching source is found - } - } - - PackageManager::PackageManager() - { - Execution::ContextOrchestrator::RegisterForShutdownSynchronization(); - } - - winrt::Windows::Foundation::Collections::IVectorView PackageManager::GetPackageCatalogs() - { - LogStartupIfApplicable(); - Windows::Foundation::Collections::IVector catalogs{ winrt::single_threaded_vector() }; - std::vector<::AppInstaller::Repository::SourceDetails> sources = ::AppInstaller::Repository::Source::GetCurrentSources(); - for (uint32_t i = 0; i < sources.size(); i++) - { - auto packageCatalogInfo = winrt::make_self>(); - ::AppInstaller::Repository::Source sourceReference{ sources.at(i).Name }; - packageCatalogInfo->Initialize(sourceReference.GetDetails()); - auto packageCatalogRef = winrt::make_self>(); - packageCatalogRef->Initialize(*packageCatalogInfo, sourceReference); - catalogs.Append(*packageCatalogRef); - } - return catalogs.GetView(); - } - - winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog) - { - LogStartupIfApplicable(); - ::AppInstaller::Repository::Source source; - switch (predefinedPackageCatalog) - { - case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalog: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGet }; - break; - case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::MicrosoftStore: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::MicrosoftStore }; - break; - case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::DesktopFrameworks: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::DesktopFrameworks }; - break; - case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalogFont: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGetFont }; - break; - default: - throw hresult_invalid_argument(); - } - auto packageCatalogInfo = winrt::make_self>(); - packageCatalogInfo->Initialize(source.GetDetails()); - auto packageCatalogRef = winrt::make_self>(); - packageCatalogRef->Initialize(*packageCatalogInfo, source); - return *packageCatalogRef; - } - - winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog) - { - LogStartupIfApplicable(); - ::AppInstaller::Repository::Source source; - switch (localPackageCatalog) - { - case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstalledPackages: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; - break; - case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstallingPackages: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installing }; - break; - default: - throw hresult_invalid_argument(); - } - auto packageCatalogInfo = winrt::make_self>(); - packageCatalogInfo->Initialize(source.GetDetails()); - auto packageCatalogRef = winrt::make_self>(); - packageCatalogRef->Initialize(*packageCatalogInfo, source); - return *packageCatalogRef; - } - - winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPackageCatalogByName(hstring const& catalogName) - { - LogStartupIfApplicable(); - std::string name = winrt::to_string(catalogName); - if (name.empty()) - { - return nullptr; - } - - ::AppInstaller::Repository::Source source{ name }; - // Create the catalog object if the source is found, otherwise return null. Don't throw. - if (source) - { - auto packageCatalogInfo = winrt::make_self>(); - packageCatalogInfo->Initialize(source.GetDetails()); - auto packageCatalogRef = winrt::make_self>(); - packageCatalogRef->Initialize(*packageCatalogInfo, source); - return *packageCatalogRef; - } - else - { - return nullptr; - } - } - - void AddPackageManifestToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo, ::AppInstaller::CLI::Execution::Context* context) - { - winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* packageVersionInfoImpl = get_self(packageVersionInfo); - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalPackageVersion = packageVersionInfoImpl->GetRepositoryPackageVersion(); - ::AppInstaller::Manifest::Manifest manifest = internalPackageVersion->GetManifest(); - - std::string targetLocale; - if (context->Args.Contains(::AppInstaller::CLI::Execution::Args::Type::Locale)) - { - targetLocale = context->Args.GetArg(::AppInstaller::CLI::Execution::Args::Type::Locale); - } - manifest.ApplyLocale(targetLocale); - - context->GetThreadGlobals().GetTelemetryLogger().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get<::AppInstaller::Manifest::Localization::PackageName>(), manifest.Version); - - context->Add<::AppInstaller::CLI::Execution::Data::Manifest>(std::move(manifest)); - context->Add<::AppInstaller::CLI::Execution::Data::PackageVersion>(std::move(internalPackageVersion)); - } - - void AddInstalledVersionToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo, ::AppInstaller::CLI::Execution::Context* context) - { - winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* installedVersionInfoImpl = get_self(installedVersionInfo); - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalInstalledVersion = installedVersionInfoImpl->GetRepositoryPackageVersion(); - context->Add(internalInstalledVersion); - } - - winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options) - { - LogStartupIfApplicable(); - if (!options) - { - // Can't make a composite source if the options aren't specified. - throw hresult_invalid_argument(); - } - - for (uint32_t i = 0; i < options.Catalogs().Size(); ++i) - { - auto catalog = options.Catalogs().GetAt(i); - if (catalog.IsComposite()) - { - // Can't make a composite source out of a source that's already a composite. - throw hresult_invalid_argument(); - } - } - auto packageCatalogImpl = winrt::make_self>(); - packageCatalogImpl->Initialize(options); - return *packageCatalogImpl; - } - - winrt::Microsoft::Management::Deployment::InstallResult GetInstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t installerError, winrt::hstring correlationData, bool rebootRequired) - { - winrt::Microsoft::Management::Deployment::InstallResultStatus installResultStatus = GetOperationResultStatus(executionStage, terminationHR); - auto installResult = winrt::make_self>(); - installResult->Initialize(installResultStatus, terminationHR, installerError, correlationData, rebootRequired); - return *installResult; - } - - winrt::Microsoft::Management::Deployment::UninstallResult GetUninstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t uninstallerError, winrt::hstring correlationData, bool rebootRequired) - { - winrt::Microsoft::Management::Deployment::UninstallResultStatus uninstallResultStatus = GetOperationResultStatus(executionStage, terminationHR); - auto uninstallResult = winrt::make_self>(); - uninstallResult->Initialize(uninstallResultStatus, terminationHR, uninstallerError, correlationData, rebootRequired); - return *uninstallResult; - } - - winrt::Microsoft::Management::Deployment::DownloadResult GetDownloadResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, winrt::hstring correlationData) - { - winrt::Microsoft::Management::Deployment::DownloadResultStatus downloadResultStatus = GetOperationResultStatus(executionStage, terminationHR); - auto downloadResult = winrt::make_self>(); - downloadResult->Initialize(downloadResultStatus, terminationHR, correlationData); - return *downloadResult; - } - - winrt::Microsoft::Management::Deployment::RepairResult GetRepairResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t repairError, winrt::hstring correlationData, bool rebootRequired) - { - winrt::Microsoft::Management::Deployment::RepairResultStatus repairResultStatus = GetOperationResultStatus(executionStage, terminationHR); - auto repairResult = winrt::make_self>(); - repairResult->Initialize(repairResultStatus, terminationHR, repairError, correlationData, rebootRequired); - return *repairResult; - } - - template - TResult GetOperationResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t operationError, winrt::hstring correlationData, bool rebootRequired) - { - if constexpr (std::is_same_v) - { - return GetInstallResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); - } - else if constexpr (std::is_same_v) - { - return GetUninstallResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); - } - else if constexpr (std::is_same_v) - { - return GetDownloadResult(executionStage, terminationHR, correlationData); - } - else if constexpr (std::is_same_v) - { - return GetRepairResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); - } - } - -#define WINGET_GET_PROGRESS_STATE(_installState_, _uninstallState_, _repairState_) \ - if constexpr (std::is_same_v) \ - { \ - progressState = TState::_installState_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - progressState = TState::_uninstallState_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - progressState = TState::_repairState_; \ - } \ - - template - std::optional GetProgress( - ReportType reportType, - uint64_t current, - uint64_t maximum, - ::AppInstaller::ProgressType progressType, - ::Workflow::ExecutionStage executionPhase) - { - bool reportProgress = false; - TState progressState = TState::Queued; - double downloadProgress = 0; - double operationProgress = 0; - uint64_t downloadBytesDownloaded = 0; - uint64_t downloadBytesRequired = 0; - switch (executionPhase) - { - case ::Workflow::ExecutionStage::Initial: - case ::Workflow::ExecutionStage::ParseArgs: - case ::Workflow::ExecutionStage::Discovery: - // We already reported queued progress up front. - break; - case ::Workflow::ExecutionStage::Download: - if constexpr (std::is_same_v || - std::is_same_v) - { - progressState = TState::Downloading; - if (reportType == ReportType::BeginProgress) - { - reportProgress = true; - } - else if (progressType == ::AppInstaller::ProgressType::Bytes) - { - downloadBytesDownloaded = current; - downloadBytesRequired = maximum; - if (maximum > 0 && maximum >= current) - { - reportProgress = true; - downloadProgress = static_cast(current) / static_cast(maximum); - } - } - } - break; - case ::Workflow::ExecutionStage::PreExecution: - // Wait until installer starts to report operation. - break; - case ::Workflow::ExecutionStage::Execution: - WINGET_GET_PROGRESS_STATE(Installing, Uninstalling, Repairing); - downloadProgress = 1; - if (reportType == ReportType::ExecutionPhaseUpdate) - { - // Operation is starting. Send progress so callers know the AsyncOperation can't be cancelled. - reportProgress = true; - } - else if (reportType == ReportType::EndProgress) - { - // Operation is "finished". May not have succeeded. - reportProgress = true; - operationProgress = 1; - } - else if (progressType == ::AppInstaller::ProgressType::Percent) - { - if (maximum > 0 && maximum >= current) - { - // Operation is progressing - reportProgress = true; - operationProgress = static_cast(current) / static_cast(maximum); - } - } - break; - case ::Workflow::ExecutionStage::PostExecution: - if (reportType == ReportType::ExecutionPhaseUpdate) - { - // Send PostInstall progress when it switches to PostExecution phase. - reportProgress = true; - WINGET_GET_PROGRESS_STATE(PostInstall, PostUninstall, PostRepair); - downloadProgress = 1; - operationProgress = 1; - } - break; - } - if (reportProgress) - { - if constexpr (std::is_same_v) - { - TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress, operationProgress }; - return progress; - } - else if constexpr (std::is_same_v) - { - TProgress progress{ progressState, operationProgress }; - return progress; - } - else if constexpr (std::is_same_v) - { - TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress }; - return progress; - } - else if constexpr (std::is_same_v) - { - TProgress progress{ progressState, operationProgress }; - return progress; - } - } - else - { - return {}; - } - } - - template - Microsoft::Management::Deployment::PackageVersionInfo GetPackageVersionInfo(winrt::Microsoft::Management::Deployment::CatalogPackage package, TOptions options) - { - Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; - - winrt::Microsoft::Management::Deployment::PackageVersionId versionId = (options) ? options.PackageVersionId() : nullptr; - // If the version of the package is specified use that, otherwise use the default. - if (versionId) - { - packageVersionInfo = package.GetPackageVersionInfo(versionId); - } - else - { - if constexpr (std::is_same_v) - { - packageVersionInfo = package.DefaultInstallVersion(); - } - else if constexpr (std::is_same_v) - { - // For download, applicability check is not needed. Just use latest. - if (package.AvailableVersions().Size() > 0) - { - packageVersionInfo = package.GetPackageVersionInfo(package.AvailableVersions().GetAt(0)); - } - } - } - // If the specified version wasn't found then return a failure. This is unusual, since all packages that came from a non-local catalog have a default version, - // and the versionId is strongly typed and comes from the CatalogPackage.GetAvailableVersions. - // If version is not specified, DefaultInstallVersion may be empty due to applicability check. - THROW_HR_IF(versionId ? APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND : APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER, !packageVersionInfo); - return packageVersionInfo; - } - - void PopulateContextFromInstallOptions( - ::AppInstaller::CLI::Execution::Context* context, - winrt::Microsoft::Management::Deployment::InstallOptions options) - { - if (options) - { - if (!options.LogOutputPath().empty()) - { - context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); - context->Args.AddArg(Execution::Args::Type::VerboseLogs); - } - if (options.AllowHashMismatch()) - { - context->Args.AddArg(Execution::Args::Type::HashOverride); - } - - if (options.BypassIsStoreClientBlockedPolicyCheck()) - { - context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); - } - - if (options.Force()) - { - context->Args.AddArg(Execution::Args::Type::Force); - } - - // If the PackageInstallScope is anything other than ::Any then set it as a requirement. - auto manifestScope = GetManifestScope(options.PackageInstallScope()); - if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first)); - context->Add(manifestScope.second); - } - - if (options.PackageInstallMode() == PackageInstallMode::Interactive) - { - context->Args.AddArg(Execution::Args::Type::Interactive); - } - else if (options.PackageInstallMode() == PackageInstallMode::Silent) - { - context->Args.AddArg(Execution::Args::Type::Silent); - } - - auto installerType = GetManifestInstallerType(options.InstallerType()); - if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallerType, AppInstaller::Manifest::InstallerTypeToString(installerType)); - } - - if (!options.PreferredInstallLocation().empty()) - { - context->Args.AddArg(Execution::Args::Type::InstallLocation, ::AppInstaller::Utility::ConvertToUTF8(options.PreferredInstallLocation())); - } - - if (!options.ReplacementInstallerArguments().empty()) - { - context->Args.AddArg(Execution::Args::Type::Override, ::AppInstaller::Utility::ConvertToUTF8(options.ReplacementInstallerArguments())); - } - - if (!options.AdditionalInstallerArguments().empty()) - { - context->Args.AddArg(Execution::Args::Type::CustomSwitches, ::AppInstaller::Utility::ConvertToUTF8(options.AdditionalInstallerArguments())); - } - - if (options.AllowedArchitectures().Size() != 0) - { - std::vector allowedArchitectures; - for (auto architecture : options.AllowedArchitectures()) - { - auto convertedArchitecture = GetUtilityArchitecture(architecture); - if (convertedArchitecture) - { - allowedArchitectures.push_back(convertedArchitecture.value()); - } - } - context->Add(std::move(allowedArchitectures)); - } - - // Note: AdditionalPackageCatalogArguments is not needed during install since the manifest is already known so no additional calls to the source are needed. The property is deprecated. - - if (options.AcceptPackageAgreements()) - { - context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - - if (options.SkipDependencies()) - { - context->Args.AddArg(Execution::Args::Type::SkipDependencies); - } - - if (options.AuthenticationArguments()) - { - context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); - context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); - } - } - else - { - // Note: If no install options are specified, we assume the caller is accepting the package agreements by default. - context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - } - - void PopulateContextFromUninstallOptions( - ::AppInstaller::CLI::Execution::Context* context, - winrt::Microsoft::Management::Deployment::UninstallOptions options) - { - if (options) - { - if (!options.LogOutputPath().empty()) - { - context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); - context->Args.AddArg(Execution::Args::Type::VerboseLogs); - } - if (options.Force()) - { - context->Args.AddArg(Execution::Args::Type::Force); - } - - if (options.PackageUninstallMode() == PackageUninstallMode::Interactive) - { - context->Args.AddArg(Execution::Args::Type::Interactive); - } - else if (options.PackageUninstallMode() == PackageUninstallMode::Silent) - { - context->Args.AddArg(Execution::Args::Type::Silent); - } - - auto uninstallScope = GetManifestUninstallScope(options.PackageUninstallScope()); - if (uninstallScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(uninstallScope)); - } - } - } - - void PopulateContextFromDownloadOptions( - ::AppInstaller::CLI::Execution::Context* context, - winrt::Microsoft::Management::Deployment::DownloadOptions options) - { - if (options) - { - if (!options.DownloadDirectory().empty()) - { - context->Args.AddArg(Execution::Args::Type::DownloadDirectory, ::AppInstaller::Utility::ConvertToUTF8(options.DownloadDirectory())); - } - if (!options.Locale().empty()) - { - context->Args.AddArg(Execution::Args::Type::Locale, ::AppInstaller::Utility::ConvertToUTF8(options.Locale())); - } - if (options.AllowHashMismatch()) - { - context->Args.AddArg(Execution::Args::Type::HashOverride); - } - if (options.SkipDependencies()) - { - context->Args.AddArg(Execution::Args::Type::SkipDependencies); - } - if (options.AcceptPackageAgreements()) - { - context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - auto manifestScope = GetManifestScope(options.Scope()); - if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first)); - } - - auto architecture = options.Architecture(); - if (architecture != Windows::System::ProcessorArchitecture::Unknown) - { - auto convertedArchitecture = GetUtilityArchitecture(architecture); - if (convertedArchitecture) - { - context->Args.AddArg(Execution::Args::Type::InstallerArchitecture, ToString(convertedArchitecture.value())); - } - } - - auto installerType = GetManifestInstallerType(options.InstallerType()); - if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallerType, AppInstaller::Manifest::InstallerTypeToString(installerType)); - } - - if (options.AuthenticationArguments()) - { - context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); - context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); - } - - if (options.SkipMicrosoftStoreLicense()) - { - context->Args.AddArg(Execution::Args::Type::SkipMicrosoftStorePackageLicense); - } - - WindowsPlatform platform = options.Platform(); - if (platform != WindowsPlatform::Unknown) - { - context->Args.AddArg(Execution::Args::Type::Platform, AppInstaller::Manifest::PlatformToString(GetPlatformEnum(platform))); - } - - hstring targetOSVersion = options.TargetOSVersion(); - if (!targetOSVersion.empty()) - { - context->Args.AddArg(Execution::Args::Type::OSVersion, ::AppInstaller::Utility::ConvertToUTF8(targetOSVersion)); - } - } - } - - void PopulateContextFromRepairOptions( - ::AppInstaller::CLI::Execution::Context* context, - winrt::Microsoft::Management::Deployment::RepairOptions options) - { - if (options) - { - if (!options.LogOutputPath().empty()) - { - context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); - context->Args.AddArg(Execution::Args::Type::VerboseLogs); - } - - if (options.PackageRepairMode() == PackageRepairMode::Interactive) - { - context->Args.AddArg(Execution::Args::Type::Interactive); - } - else if (options.PackageRepairMode() == PackageRepairMode::Silent) - { - context->Args.AddArg(Execution::Args::Type::Silent); - } - - if (options.AcceptPackageAgreements()) - { - context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - - if (options.AllowHashMismatch()) - { - context->Args.AddArg(Execution::Args::Type::HashOverride); - } - - if (options.BypassIsStoreClientBlockedPolicyCheck()) - { - context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); - } - - if (options.Force()) - { - context->Args.AddArg(Execution::Args::Type::Force); - } - - auto repairScope = GetManifestRepairScope(options.PackageRepairScope()); - if (repairScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(repairScope)); - } - - if (options.AuthenticationArguments()) - { - context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); - context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); - } - } - } - - template - std::unique_ptr CreateContextFromOperationOptions( - TOptions options, - std::wstring callerProcessInfoString) - { - std::unique_ptr context = std::make_unique(); - hstring correlationData = (options) ? options.CorrelationData() : L""; - - context->SetContextLoggers(correlationData, GetComCallerName(AppInstaller::Utility::ConvertToUTF8(callerProcessInfoString))); - - // Convert the options to arguments for the installer. - if constexpr (std::is_same_v) - { - PopulateContextFromInstallOptions(context.get(), options); - } - else if constexpr (std::is_same_v) - { - PopulateContextFromUninstallOptions(context.get(), options); - } - else if constexpr (std::is_same_v) - { - PopulateContextFromDownloadOptions(context.get(), options); - } - else if constexpr (std::is_same_v) - { - PopulateContextFromRepairOptions(context.get(), options); - } - - return context; - } - - std::shared_ptr GetExistingQueueItemForPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - std::shared_ptr queueItem = nullptr; - std::unique_ptr context = std::make_unique(); - if (catalogInfo) - { - // If the caller has passed in the catalog they expect the package to have come from, then only look for an install from that catalog. - // Fail if they've used a catalog that doesn't have an Id. This can currently happen for Info objects that come from PackageCatalogReference objects for REST catalogs. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, catalogInfo.Id().empty()); - auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ catalogInfo.Id() }, std::move(context)); - queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); - return queueItem; - } - - // If the caller has not specified the catalog, then check InstalledVersion. When the package comes from the Installing catalog the PackageCatalog - // of the InstalledVersion will be set to the original catalog that the install was from, so checking the InstalledVersion first is most likely to - // find a result. - Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo = package.InstalledVersion(); - if (installedVersionInfo) - { - auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ installedVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); - queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); - if (queueItem) - { - return queueItem; - } - } - - // If InstalledVersion was not found, check DefaultInstallVersion - Microsoft::Management::Deployment::PackageVersionInfo defaultInstallVersionInfo = package.DefaultInstallVersion(); - if (defaultInstallVersionInfo) - { - auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ defaultInstallVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); - queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); - if (queueItem) - { - return queueItem; - } - } - - // Finally check all catalogs in AvailableVersions. - for (Microsoft::Management::Deployment::PackageVersionId versionId : package.AvailableVersions()) - { - auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ package.GetPackageVersionInfo(versionId).PackageCatalog().Info().Id() }, std::move(context)); - queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); - if (queueItem) - { - return queueItem; - } - } - return nullptr; - } - - std::unique_ptr CreateQueueItemForInstall( - std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, - winrt::Microsoft::Management::Deployment::CatalogPackage package, - winrt::Microsoft::Management::Deployment::InstallOptions options, - bool isUpgrade) - { - // Add manifest and PackageVersion to context for install/upgrade. - // If the version of the package is specified use that, otherwise use the default. - Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); - AddPackageManifestToContext(packageVersionInfo, comContext.get()); - - if (isUpgrade) - { - AppInstaller::Utility::VersionAndChannel installedVersion{ winrt::to_string(package.InstalledVersion().Version()), winrt::to_string(package.InstalledVersion().Channel()) }; - AppInstaller::Utility::VersionAndChannel upgradeVersion{ winrt::to_string(packageVersionInfo.Version()), winrt::to_string(packageVersionInfo.Channel()) }; - - // Perform upgrade version check - if (upgradeVersion.GetVersion().IsUnknown()) - { - if (!(options.AllowUpgradeToUnknownVersion() && - AppInstaller::Utility::ICUCaseInsensitiveEquals(installedVersion.GetChannel().ToString(), upgradeVersion.GetChannel().ToString()))) - { - THROW_HR(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN); - } - } - else if (!installedVersion.IsUpdatedBy(upgradeVersion)) - { - THROW_HR(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER); - } - - // Set upgrade flag - comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseUpdate); - // Add installed version - AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); - } - - return Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext), isUpgrade); - } - - std::unique_ptr CreateQueueItemForUninstall( - std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, - winrt::Microsoft::Management::Deployment::CatalogPackage package) - { - // Add installed version - AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); - - // Add Package which is used by RecordUninstall later for removing from tracking catalog of correlated available sources as best effort - winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); - comContext->Add(internalPackage); - - return Execution::OrchestratorQueueItemFactory::CreateItemForUninstall(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); - } - - std::unique_ptr CreateQueueItemForDownload( - std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, - winrt::Microsoft::Management::Deployment::CatalogPackage package, - winrt::Microsoft::Management::Deployment::DownloadOptions options) - { - // Add manifest and PackageVersion to context for download. - // If the version of the package is specified use that, otherwise use the default. - Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); - AddPackageManifestToContext(packageVersionInfo, comContext.get()); - - comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly); - - return Execution::OrchestratorQueueItemFactory::CreateItemForDownload(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext)); - } - - std::unique_ptr CreateQueueItemForRepair( - std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, - winrt::Microsoft::Management::Deployment::CatalogPackage package) - { - // Add installed version - AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); - - // Add Package which is used to co-relate installed package with available package for repair - winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); - comContext->Add(internalPackage); - - comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseRepair); - - return Execution::OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); - } - - template - winrt::Windows::Foundation::IAsyncOperationWithProgress GetPackageOperation( - bool canCancelQueueItem, - std::shared_ptr queueItemParam, - winrt::Microsoft::Management::Deployment::CatalogPackage package = nullptr, - TOptions options = nullptr, - std::wstring callerProcessInfoString = {}, - bool isUpgrade = false) - { - winrt::hresult terminationHR = S_OK; - uint32_t operationError = 0; - hstring correlationData = (options) ? options.CorrelationData() : L""; - ::Workflow::ExecutionStage executionStage = ::Workflow::ExecutionStage::Initial; - - try - { - // re-scope the parameter to inside the try block to avoid lifetime management issues. - std::shared_ptr queueItem = std::move(queueItemParam); - - auto report_progress{ co_await winrt::get_progress_token() }; - auto cancellationToken{ co_await winrt::get_cancellation_token() }; - // co_await does not guarantee that it's on a background thread, so do so explicitly. - co_await winrt::resume_background(); - - if (queueItem == nullptr) - { - std::unique_ptr comContext = CreateContextFromOperationOptions(options, callerProcessInfoString); - - if constexpr (std::is_same_v) - { - queueItem = CreateQueueItemForInstall(std::move(comContext), package, options, isUpgrade); - } - else if constexpr (std::is_same_v) - { - queueItem = CreateQueueItemForUninstall(std::move(comContext), package); - } - else if constexpr (std::is_same_v) - { - queueItem = CreateQueueItemForDownload(std::move(comContext), package, options); - } - else if constexpr (std::is_same_v) - { - queueItem = CreateQueueItemForRepair(std::move(comContext), package); - } - - Execution::ContextOrchestrator::Instance().EnqueueAndRunItem(queueItem); - - if constexpr (std::is_same_v) - { - TProgress queuedProgress{ TProgressState::Queued, 0, 0, 0 }; - report_progress(queuedProgress); - } - else if constexpr (std::is_same_v) - { - TProgress queuedProgress{ TProgressState::Queued, 0 }; - report_progress(queuedProgress); - } - else if constexpr (std::is_same_v) - { - TProgress queuedProgress{ TProgressState::Queued, 0 }; - report_progress(queuedProgress); - } - else if constexpr (std::is_same_v) - { - TProgress queuedProgress{ TProgressState::Queued, 0 }; - report_progress(queuedProgress); - } - } - { - // correlation data is not passed in when retrieving an existing queue item, so get it from the existing context. - correlationData = hstring(queueItem->GetContext().GetCorrelationJson()); - } - - wil::unique_event progressEvent{ wil::EventOptions::None }; - - std::atomic operationProgress; - queueItem->GetContext().AddProgressCallbackFunction([&operationProgress, &progressEvent]( - ReportType reportType, - uint64_t current, - uint64_t maximum, - ::AppInstaller::ProgressType progressType, - ::Workflow::ExecutionStage executionPhase) - { - std::optional operationProgressOptional = GetProgress(reportType, current, maximum, progressType, executionPhase); - if (operationProgressOptional.has_value()) - { - operationProgress = operationProgressOptional.value(); - progressEvent.SetEvent(); - } - return; - } - ); - - std::weak_ptr weakQueueItem(queueItem); - cancellationToken.callback([weakQueueItem, &canCancelQueueItem] - { - if (canCancelQueueItem) - { - auto strongQueueItem = weakQueueItem.lock(); - if (strongQueueItem) { - // The cancellation of the AsyncOperation on the client triggers Cancel which causes the Execute to end. - Execution::ContextOrchestrator::Instance().CancelQueueItem(*strongQueueItem); - } - } - }); - - // Wait for completion or progress events. - // Waiting for both on the same thread ensures that progress is never reported after the async operation itself has completed. - bool completionEventFired = false; - HANDLE operationEvents[2]; - operationEvents[0] = progressEvent.get(); - operationEvents[1] = queueItem->GetCompletedEvent().get(); - while (!completionEventFired) - { - DWORD dwEvent = WaitForMultipleObjects( - _countof(operationEvents) /* number of events */, - operationEvents /* event array */, - FALSE /* bWaitAll, FALSE to wake on any event */, - INFINITE /* wait until operation completion */); - - switch (dwEvent) - { - // operationEvents[0] was signaled, progress - case WAIT_OBJECT_0 + 0: - // The report_progress call will hang when making callbacks to suspended processes so it's important that this is now on a background thread. - // Progress events are not queued - some will be missed if multiple progress events are fired from the ComContext to the callback - // while the report_progress call is hung\in progress. - // Duplicate progress events can be fired if another progress event comes from the ComContext to the callback after the listener - // has been awaked, but before it has gotten the installProgress. - report_progress(operationProgress); - break; - - // operationEvents[1] was signaled, operation completed - case WAIT_OBJECT_0 + 1: - completionEventFired = true; - break; - - // Return value is invalid. - default: - THROW_LAST_ERROR(); - } - } - - if (completionEventFired) - { - // The install command has finished, check for success/failure and how far it got. - terminationHR = queueItem->GetContext().GetTerminationHR(); - executionStage = queueItem->GetContext().GetExecutionStage(); - if (queueItem->GetContext().Contains(Data::OperationReturnCode)) - { - operationError = static_cast(queueItem->GetContext().Get()); - } - } - } - WINGET_CATCH_STORE(terminationHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - - // TODO - RebootRequired not yet populated, msi arguments not returned from Execute. - co_return GetOperationResult(executionStage, terminationHR, operationError, correlationData, false); - } - - template - winrt::Windows::Foundation::IAsyncOperationWithProgress GetEmptyAsynchronousResultForOperation( - HRESULT hr, - hstring correlationData) - { - // If a function uses co_await or co_return (i.e. if it is a co_routine), it cannot use return directly. - // This helper helps a function that is not a coroutine itself to return errors asynchronously. - co_return GetOperationResult(::Workflow::ExecutionStage::Initial, hr, 0, correlationData, false); - } - -#define WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} -#define WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, FAILED(hr)) } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::UpgradePackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - // the package should have an installed version to be upgraded. - WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString), true /* isUpgrade */); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - hstring correlationData; - WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::shared_ptr queueItem = nullptr; - bool canCancelQueueItem = false; - try - { - // Check for permissions - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - if (!canCancelQueueItem) - { - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); - } - - // Get the queueItem synchronously. - queueItem = GetExistingQueueItemForPackage(package, catalogInfo); - if (queueItem == nullptr || - (queueItem->GetPackageOperationType() != PackageOperationType::Install && queueItem->GetPackageOperationType() != PackageOperationType::Upgrade)) - { - return nullptr; - } - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - canCancelQueueItem, std::move(queueItem)); - } - -#define WINGET_RETURN_UNINSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} -#define WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_UNINSTALL_RESULT_HR_IF(hr, FAILED(hr)) } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::UninstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::UninstallOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - // the package should have an installed version to be uninstalled. - WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetUninstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - hstring correlationData; - WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::shared_ptr queueItem = nullptr; - bool canCancelQueueItem = false; - try - { - // Check for permissions - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - if (!canCancelQueueItem) - { - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); - } - - // Get the queueItem synchronously. - queueItem = GetExistingQueueItemForPackage(package, catalogInfo); - if (queueItem == nullptr || - queueItem->GetPackageOperationType() != PackageOperationType::Uninstall) - { - return nullptr; - } - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - canCancelQueueItem, std::move(queueItem)); - } - -#define WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} -#define WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(hr, FAILED(hr)) } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - hstring correlationData; - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::shared_ptr queueItem = nullptr; - try - { - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); - - // Get the queueItem synchronously. - queueItem = GetExistingQueueItemForPackage(package, catalogInfo); - if (queueItem == nullptr || - queueItem->GetPackageOperationType() != PackageOperationType::Download) - { - return nullptr; - } - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation(true, std::move(queueItem)); - } - -#define WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} -#define WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, FAILED(hr)) } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - // the package should have an installed version to be repaired. - WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::AddPackageCatalogAsync(winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions options) - { - LogStartupIfApplicable(); - - // options must be set. - THROW_HR_IF_NULL(E_POINTER, options); - THROW_HR_IF(E_INVALIDARG, options.Name().empty()); - THROW_HR_IF(E_INVALIDARG, options.SourceUri().empty()); - - HRESULT terminationHR = S_OK; - try { - - // Check if running as admin/system. - // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, - // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); - - ::AppInstaller::Repository::Source sourceToAdd = CreateSourceFromOptions(options); - - auto strong_this = get_strong(); - auto report_progress{ co_await winrt::get_progress_token() }; - co_await winrt::resume_background(); - - std::string type = winrt::to_string(options.Type()); - auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(type, report_progress ); - - packageCatalogProgressSink->BeginProgress(); - ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); - sourceToAdd.Add(progress); - packageCatalogProgressSink->EndProgress(false); - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - co_return GetAddPackageCatalogResult(terminationHR); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RemovePackageCatalogAsync(winrt::Microsoft::Management::Deployment::RemovePackageCatalogOptions options) - { - LogStartupIfApplicable(); - - // options must be set. - THROW_HR_IF_NULL(E_POINTER, options); - THROW_HR_IF(E_INVALIDARG, options.Name().empty()); - - HRESULT terminationHR = S_OK; - try { - - // Check if running as admin/system. - // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, - // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); - - auto matchingSource = GetMatchingSource(winrt::to_string(options.Name())); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !matchingSource.has_value()); - - auto strong_this = get_strong(); - auto report_progress{ co_await winrt::get_progress_token() }; - co_await winrt::resume_background(); - - auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(matchingSource.value().Type, report_progress, true); - - packageCatalogProgressSink->BeginProgress(); - ::AppInstaller::Repository::Source sourceToRemove = ::AppInstaller::Repository::Source{ matchingSource.value().Name }; - ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); - - // If the PreserveData option is set, this is equivalent to the WinGet CLI Reset command on a single source; otherwise, it removes the source. - if (options.PreserveData()) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !sourceToRemove.DropSource(matchingSource.value().Name)); - } - else - { - sourceToRemove.Remove(progress); - } - packageCatalogProgressSink->EndProgress(false); - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - co_return GetRemovePackageCatalogResult(terminationHR); - } - - winrt::hstring PackageManager::Version() const - { - return winrt::hstring{ AppInstaller::Utility::ConvertToUTF16(AppInstaller::Runtime::GetClientVersion()) }; - } - - winrt::Microsoft::Management::Deployment::EditPackageCatalogResult GetEditPackageCatalogResult(winrt::hresult terminationStatus) - { - winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); - auto editResult = winrt::make_self>(); - editResult->Initialize(status, terminationStatus); - return *editResult; - } - - winrt::Microsoft::Management::Deployment::EditPackageCatalogResult PackageManager::EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options) - { - LogStartupIfApplicable(); - - // options must be set. - THROW_HR_IF_NULL(E_POINTER, options); - THROW_HR_IF(E_INVALIDARG, options.Name().empty()); - - HRESULT terminationHR = S_OK; - try { - - // Check if running as admin/system. - // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, - // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); - - auto matchingSource = GetMatchingSource(winrt::to_string(options.Name())); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !matchingSource.has_value()); - ::AppInstaller::Repository::Source sourceToEdit = ::AppInstaller::Repository::Source{ matchingSource.value().Name }; - - ::AppInstaller::Repository::SourceEdit edits; - edits.Explicit = options.Explicit(); - edits.Priority = options.Priority(); - - if (sourceToEdit.RequiresChanges(edits)) - { - sourceToEdit.Edit(edits); - } - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - return GetEditPackageCatalogResult(terminationHR); - } - - namespace - { - // Builds PackagePin WinRT objects from a vector of internal Pin objects. - winrt::Windows::Foundation::Collections::IVectorView - MakePackagePinVectorView(const std::vector<::AppInstaller::Pinning::Pin>& pins) - { - auto result = winrt::single_threaded_vector(); - for (const auto& pin : pins) - { - auto packagePin = winrt::make_self>(); - packagePin->Initialize(pin); - result.Append(*packagePin); - } - return result.GetView(); - } - - // Collects PinKeys for all available sources of a package, and optionally for its installed identity. - std::vector<::AppInstaller::Pinning::PinKey> GetPinKeysForCatalogPackage( - winrt::Microsoft::Management::Deployment::CatalogPackage const& package, - bool includeInstalled) - { - std::vector<::AppInstaller::Pinning::PinKey> pinKeys; - - // Keys for each available source version - for (auto const& versionId : package.AvailableVersions()) - { - auto versionInfo = package.GetPackageVersionInfo(versionId); - if (versionInfo) - { - std::string packageId = winrt::to_string(versionInfo.Id()); - std::string sourceId = winrt::to_string(versionInfo.PackageCatalog().Info().Id()); - if (!packageId.empty() && !sourceId.empty()) - { - ::AppInstaller::Pinning::PinKey key{ packageId, sourceId }; - // Avoid duplicates (same packageId+sourceId may appear for multiple versions) - if (std::find(pinKeys.begin(), pinKeys.end(), key) == pinKeys.end()) - { - pinKeys.push_back(std::move(key)); - } - } - } - } - - // Key for the installed package identity (ProductCode / PackageFamilyName) - if (includeInstalled) - { - auto installedVersion = package.InstalledVersion(); - if (installedVersion) - { - // Prefer PackageFamilyName (MSIX), fall back to ProductCode (MSI/EXE) - auto pfns = installedVersion.PackageFamilyNames(); - if (pfns && pfns.Size() > 0) - { - for (auto const& pfn : pfns) - { - pinKeys.push_back(::AppInstaller::Pinning::PinKey::GetPinKeyForInstalled(winrt::to_string(pfn))); - } - } - else - { - auto productCodes = installedVersion.ProductCodes(); - if (productCodes) - { - for (auto const& productCode : productCodes) - { - pinKeys.push_back(::AppInstaller::Pinning::PinKey::GetPinKeyForInstalled(winrt::to_string(productCode))); - } - } - } - } - } - - return pinKeys; - } - - winrt::Microsoft::Management::Deployment::PinPackageResult MakePinPackageResult(HRESULT hr) - { - auto result = winrt::make_self>(); - result->Initialize(GetPinOperationStatus(hr), hr); - return *result; - } - - // Converts PinPackageOptions.PinType to the internal pin representation. - ::AppInstaller::Pinning::Pin CreatePinFromOptions( - const ::AppInstaller::Pinning::PinKey& pinKey, - winrt::Microsoft::Management::Deployment::PinPackageOptions const& options) - { - switch (options.PinType()) - { - case winrt::Microsoft::Management::Deployment::PackagePinType::Pinning: - return ::AppInstaller::Pinning::Pin::CreatePinningPin(pinKey); - case winrt::Microsoft::Management::Deployment::PackagePinType::Blocking: - return ::AppInstaller::Pinning::Pin::CreateBlockingPin(pinKey); - case winrt::Microsoft::Management::Deployment::PackagePinType::Gating: - return ::AppInstaller::Pinning::Pin::CreateGatingPin( - pinKey, - ::AppInstaller::Utility::GatedVersion{ winrt::to_string(options.GatedVersion()) }); - default: - // Unknown, PinnedByManifest, and any future unrecognized values are not valid - // user-settable pin types. - THROW_HR(E_INVALIDARG); - } - } - } - - winrt::Windows::Foundation::Collections::IVectorView - PackageManager::GetAllPins() - { - LogStartupIfApplicable(); - - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); - - auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadOnly }; - return MakePackagePinVectorView(pinningData.GetAllPins()); - } - - winrt::Windows::Foundation::Collections::IVectorView - PackageManager::GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package) - { - LogStartupIfApplicable(); - - THROW_HR_IF_NULL(E_POINTER, package); - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); - - auto pinKeys = GetPinKeysForCatalogPackage(package, /* includeInstalled */ true); - auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadOnly }; - - std::vector<::AppInstaller::Pinning::Pin> pins; - for (const auto& pinKey : pinKeys) - { - auto pin = pinningData.GetPin(pinKey); - if (pin) - { - pins.push_back(std::move(*pin)); - } - } - - return MakePackagePinVectorView(pins); - } - - winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::PinPackage( - winrt::Microsoft::Management::Deployment::CatalogPackage package, - winrt::Microsoft::Management::Deployment::PinPackageOptions options) - { - LogStartupIfApplicable(); - - THROW_HR_IF_NULL(E_POINTER, package); - THROW_HR_IF_NULL(E_POINTER, options); - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); - - // Gating pins require a non-empty version range. - THROW_HR_IF(E_INVALIDARG, - options.PinType() == winrt::Microsoft::Management::Deployment::PackagePinType::Gating && - options.GatedVersion().empty()); - - HRESULT terminationHR = S_OK; - try - { - auto pinKeys = GetPinKeysForCatalogPackage(package, options.PinInstalledPackage()); - THROW_HR_IF(E_INVALIDARG, pinKeys.empty()); - - auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; - - auto pinTime = std::chrono::system_clock::now(); - - std::optional note; - if (!options.Note().empty()) - { - note = winrt::to_string(options.Note()); - } - - for (const auto& pinKey : pinKeys) - { - auto newPin = CreatePinFromOptions(pinKey, options); - - auto existingPin = pinningData.GetPin(pinKey); - if (existingPin && !(*existingPin == newPin)) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS, !options.Force()); - } - - newPin.SetDateAdded(pinTime); - newPin.SetNote(note); - pinningData.AddOrUpdatePin(newPin); - } - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - return MakePinPackageResult(terminationHR); - } - - winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::UnpinPackage( - winrt::Microsoft::Management::Deployment::CatalogPackage package) - { - LogStartupIfApplicable(); - - THROW_HR_IF_NULL(E_POINTER, package); - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); - - HRESULT terminationHR = S_OK; - try - { - auto pinKeys = GetPinKeysForCatalogPackage(package, /* includeInstalled */ true); - auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; - - bool anyRemoved = false; - for (const auto& pinKey : pinKeys) - { - anyRemoved |= pinningData.TryRemovePin(pinKey); - } - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST, !anyRemoved); - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - return MakePinPackageResult(terminationHR); - } - - winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::ResetAllPins(winrt::Microsoft::Management::Deployment::PackageCatalogReference packageCatalogReference) - { - LogStartupIfApplicable(); - - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); - - HRESULT terminationHR = S_OK; - try - { - std::string sourceId; - if (packageCatalogReference) - { - sourceId = winrt::to_string(packageCatalogReference.Info().Id()); - } - - auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; - pinningData.ResetAllPins(sourceId); - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - return MakePinPackageResult(terminationHR); - } - - CoCreatableMicrosoftManagementDeploymentClass(PackageManager); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerCLICore.h" +#include "Microsoft/PredefinedInstalledSourceFactory.h" +#include "Commands/RootCommand.h" +#include "ComContext.h" +#include "ExecutionContext.h" +#include "Workflows/WorkflowBase.h" +#include +#include +#include +#include "Commands/COMCommand.h" +#include +#include +#include +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "PackageManager.h" +#include "PackagePin.h" +#include "PinPackageOptions.h" +#include "PinPackageResult.h" +#pragma warning( pop ) +#include "PackageManager.g.cpp" +#include "CatalogPackage.h" +#include "DownloadResult.h" +#include "InstallResult.h" +#include "UninstallResult.h" +#include "RepairResult.h" +#include "PackageCatalogInfo.h" +#include "PackageCatalogReference.h" +#include "PackageVersionInfo.h" +#include "PackageVersionId.h" +#include "AddPackageCatalogResult.h" +#include "RemovePackageCatalogResult.h" +#include "EditPackageCatalogResult.h" +#include "Converters.h" +#include "Helpers.h" +#include "ContextOrchestrator.h" +#include "AppInstallerRuntime.h" +#include +#include +#include +#include +#include +#include + +using namespace std::literals::chrono_literals; +using namespace ::AppInstaller::CLI; +using namespace ::AppInstaller::CLI::Execution; + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + namespace + { + void LogStartupIfApplicable() + { + static std::once_flag logStartupOnceFlag; + std::call_once(logStartupOnceFlag, + [&]() + { + ::AppInstaller::Logging::Telemetry().SetCaller(GetCallerName()); + ::AppInstaller::Logging::Telemetry().LogStartup(true); + }); + } + + winrt::Microsoft::Management::Deployment::AddPackageCatalogResult GetAddPackageCatalogResult(winrt::hresult terminationStatus) + { + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); + auto addPackageCatalogResult = winrt::make_self>(); + addPackageCatalogResult->Initialize(status, terminationStatus); + return *addPackageCatalogResult; + } + + void CheckForDuplicateSource(const std::string& name, const std::string& type, const std::string& sourceUri) + { + auto sourceList = ::AppInstaller::Repository::Source::GetCurrentSources(); + + std::string sourceType = type; + + // [NOTE:] If the source type is not specified, the default source type will be used for validation.In cases where the source type is empty, + // it remains unassigned until the add operation, at which point it is assigned.Without this default assignment, an empty string could be + // compared to the default type, potentially allowing different source names with the same URI to be seen as unique. + // To avoid this, assign the default source type prior to comparison. + if (sourceType.empty()) + { + // This method of obtaining the default source type is slightly expensive as it requires creating a SourceFactory object + // and fetching the type name.Nonetheless, it future-proofs the code against any changes in the SourceFactory's default type. + sourceType = ::AppInstaller::Repository::Source::GetDefaultSourceType(); + } + + for (const auto& source : sourceList) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, ::AppInstaller::Utility::ICUCaseInsensitiveEquals(source.Name, name)); + + bool sourceUriAlreadyExists = !source.Arg.empty() && source.Arg == sourceUri && source.Type == sourceType; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS, sourceUriAlreadyExists); + } + } + + ::AppInstaller::Repository::Source CreateSourceFromOptions(const winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions& options) + { + std::string name = winrt::to_string(options.Name()); + std::string type = winrt::to_string(options.Type()); + std::string sourceUri = winrt::to_string(options.SourceUri()); + + AppInstaller::Repository::SourceTrustLevel trustLevel = AppInstaller::Repository::SourceTrustLevel::None; + if (options.TrustLevel() == winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel::Trusted) + { + trustLevel = AppInstaller::Repository::SourceTrustLevel::Trusted; + } + + CheckForDuplicateSource(name, type, sourceUri); + + ::AppInstaller::Repository::SourceEdit additionalProperties; + additionalProperties.Explicit = options.Explicit(); + additionalProperties.Priority = options.Priority(); + + ::AppInstaller::Repository::Source source = ::AppInstaller::Repository::Source{ name, sourceUri, type, trustLevel, additionalProperties }; + + std::string customHeader = winrt::to_string(options.CustomHeader()); + if (!customHeader.empty()) + { + source.SetCustomHeader(customHeader); + } + + auto sourceInfo = source.GetInformation(); + + if (sourceInfo.Authentication.Type == ::AppInstaller::Authentication::AuthenticationType::Unknown) + { + THROW_HR(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + } + + return source; + } + + winrt::Microsoft::Management::Deployment::RemovePackageCatalogResult GetRemovePackageCatalogResult(winrt::hresult terminationStatus) + { + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); + auto removeResult = winrt::make_self>(); + removeResult->Initialize(status, terminationStatus); + return *removeResult; + } + + std::optional<::AppInstaller::Repository::SourceDetails> GetMatchingSource(const std::string& name) + { + auto sourceList = ::AppInstaller::Repository::Source::GetCurrentSources(); + + for (const auto& source : sourceList) + { + if (::AppInstaller::Utility::ICUCaseInsensitiveEquals(source.Name, name)) + { + return source; // Return the first matching source + } + } + + return std::nullopt; // Return std::nullopt if no matching source is found + } + } + + PackageManager::PackageManager() + { + Execution::ContextOrchestrator::RegisterForShutdownSynchronization(); + } + + winrt::Windows::Foundation::Collections::IVectorView PackageManager::GetPackageCatalogs() + { + LogStartupIfApplicable(); + Windows::Foundation::Collections::IVector catalogs{ winrt::single_threaded_vector() }; + std::vector<::AppInstaller::Repository::SourceDetails> sources = ::AppInstaller::Repository::Source::GetCurrentSources(); + for (uint32_t i = 0; i < sources.size(); i++) + { + auto packageCatalogInfo = winrt::make_self>(); + ::AppInstaller::Repository::Source sourceReference{ sources.at(i).Name }; + packageCatalogInfo->Initialize(sourceReference.GetDetails()); + auto packageCatalogRef = winrt::make_self>(); + packageCatalogRef->Initialize(*packageCatalogInfo, sourceReference); + catalogs.Append(*packageCatalogRef); + } + return catalogs.GetView(); + } + + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog) + { + LogStartupIfApplicable(); + ::AppInstaller::Repository::Source source; + switch (predefinedPackageCatalog) + { + case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalog: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGet }; + break; + case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::MicrosoftStore: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::MicrosoftStore }; + break; + case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::DesktopFrameworks: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::DesktopFrameworks }; + break; + case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalogFont: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGetFont }; + break; + default: + throw hresult_invalid_argument(); + } + auto packageCatalogInfo = winrt::make_self>(); + packageCatalogInfo->Initialize(source.GetDetails()); + auto packageCatalogRef = winrt::make_self>(); + packageCatalogRef->Initialize(*packageCatalogInfo, source); + return *packageCatalogRef; + } + + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog) + { + LogStartupIfApplicable(); + ::AppInstaller::Repository::Source source; + switch (localPackageCatalog) + { + case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstalledPackages: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; + break; + case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstallingPackages: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installing }; + break; + default: + throw hresult_invalid_argument(); + } + auto packageCatalogInfo = winrt::make_self>(); + packageCatalogInfo->Initialize(source.GetDetails()); + auto packageCatalogRef = winrt::make_self>(); + packageCatalogRef->Initialize(*packageCatalogInfo, source); + return *packageCatalogRef; + } + + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPackageCatalogByName(hstring const& catalogName) + { + LogStartupIfApplicable(); + std::string name = winrt::to_string(catalogName); + if (name.empty()) + { + return nullptr; + } + + ::AppInstaller::Repository::Source source{ name }; + // Create the catalog object if the source is found, otherwise return null. Don't throw. + if (source) + { + auto packageCatalogInfo = winrt::make_self>(); + packageCatalogInfo->Initialize(source.GetDetails()); + auto packageCatalogRef = winrt::make_self>(); + packageCatalogRef->Initialize(*packageCatalogInfo, source); + return *packageCatalogRef; + } + else + { + return nullptr; + } + } + + void AddPackageManifestToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo, ::AppInstaller::CLI::Execution::Context* context) + { + winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* packageVersionInfoImpl = get_self(packageVersionInfo); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalPackageVersion = packageVersionInfoImpl->GetRepositoryPackageVersion(); + ::AppInstaller::Manifest::Manifest manifest = internalPackageVersion->GetManifest(); + + std::string targetLocale; + if (context->Args.Contains(::AppInstaller::CLI::Execution::Args::Type::Locale)) + { + targetLocale = context->Args.GetArg(::AppInstaller::CLI::Execution::Args::Type::Locale); + } + manifest.ApplyLocale(targetLocale); + + context->GetThreadGlobals().GetTelemetryLogger().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get<::AppInstaller::Manifest::Localization::PackageName>(), manifest.Version); + + context->Add<::AppInstaller::CLI::Execution::Data::Manifest>(std::move(manifest)); + context->Add<::AppInstaller::CLI::Execution::Data::PackageVersion>(std::move(internalPackageVersion)); + } + + void AddInstalledVersionToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo, ::AppInstaller::CLI::Execution::Context* context) + { + winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* installedVersionInfoImpl = get_self(installedVersionInfo); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalInstalledVersion = installedVersionInfoImpl->GetRepositoryPackageVersion(); + context->Add(internalInstalledVersion); + } + + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options) + { + LogStartupIfApplicable(); + if (!options) + { + // Can't make a composite source if the options aren't specified. + throw hresult_invalid_argument(); + } + + for (uint32_t i = 0; i < options.Catalogs().Size(); ++i) + { + auto catalog = options.Catalogs().GetAt(i); + if (catalog.IsComposite()) + { + // Can't make a composite source out of a source that's already a composite. + throw hresult_invalid_argument(); + } + } + auto packageCatalogImpl = winrt::make_self>(); + packageCatalogImpl->Initialize(options); + return *packageCatalogImpl; + } + + winrt::Microsoft::Management::Deployment::InstallResult GetInstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t installerError, winrt::hstring correlationData, bool rebootRequired) + { + winrt::Microsoft::Management::Deployment::InstallResultStatus installResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto installResult = winrt::make_self>(); + installResult->Initialize(installResultStatus, terminationHR, installerError, correlationData, rebootRequired); + return *installResult; + } + + winrt::Microsoft::Management::Deployment::UninstallResult GetUninstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t uninstallerError, winrt::hstring correlationData, bool rebootRequired) + { + winrt::Microsoft::Management::Deployment::UninstallResultStatus uninstallResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto uninstallResult = winrt::make_self>(); + uninstallResult->Initialize(uninstallResultStatus, terminationHR, uninstallerError, correlationData, rebootRequired); + return *uninstallResult; + } + + winrt::Microsoft::Management::Deployment::DownloadResult GetDownloadResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, winrt::hstring correlationData) + { + winrt::Microsoft::Management::Deployment::DownloadResultStatus downloadResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto downloadResult = winrt::make_self>(); + downloadResult->Initialize(downloadResultStatus, terminationHR, correlationData); + return *downloadResult; + } + + winrt::Microsoft::Management::Deployment::RepairResult GetRepairResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t repairError, winrt::hstring correlationData, bool rebootRequired) + { + winrt::Microsoft::Management::Deployment::RepairResultStatus repairResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto repairResult = winrt::make_self>(); + repairResult->Initialize(repairResultStatus, terminationHR, repairError, correlationData, rebootRequired); + return *repairResult; + } + + template + TResult GetOperationResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t operationError, winrt::hstring correlationData, bool rebootRequired) + { + if constexpr (std::is_same_v) + { + return GetInstallResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); + } + else if constexpr (std::is_same_v) + { + return GetUninstallResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); + } + else if constexpr (std::is_same_v) + { + return GetDownloadResult(executionStage, terminationHR, correlationData); + } + else if constexpr (std::is_same_v) + { + return GetRepairResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); + } + } + +#define WINGET_GET_PROGRESS_STATE(_installState_, _uninstallState_, _repairState_) \ + if constexpr (std::is_same_v) \ + { \ + progressState = TState::_installState_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + progressState = TState::_uninstallState_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + progressState = TState::_repairState_; \ + } \ + + template + std::optional GetProgress( + ReportType reportType, + uint64_t current, + uint64_t maximum, + ::AppInstaller::ProgressType progressType, + ::Workflow::ExecutionStage executionPhase) + { + bool reportProgress = false; + TState progressState = TState::Queued; + double downloadProgress = 0; + double operationProgress = 0; + uint64_t downloadBytesDownloaded = 0; + uint64_t downloadBytesRequired = 0; + switch (executionPhase) + { + case ::Workflow::ExecutionStage::Initial: + case ::Workflow::ExecutionStage::ParseArgs: + case ::Workflow::ExecutionStage::Discovery: + // We already reported queued progress up front. + break; + case ::Workflow::ExecutionStage::Download: + if constexpr (std::is_same_v || + std::is_same_v) + { + progressState = TState::Downloading; + if (reportType == ReportType::BeginProgress) + { + reportProgress = true; + } + else if (progressType == ::AppInstaller::ProgressType::Bytes) + { + downloadBytesDownloaded = current; + downloadBytesRequired = maximum; + if (maximum > 0 && maximum >= current) + { + reportProgress = true; + downloadProgress = static_cast(current) / static_cast(maximum); + } + } + } + break; + case ::Workflow::ExecutionStage::PreExecution: + // Wait until installer starts to report operation. + break; + case ::Workflow::ExecutionStage::Execution: + WINGET_GET_PROGRESS_STATE(Installing, Uninstalling, Repairing); + downloadProgress = 1; + if (reportType == ReportType::ExecutionPhaseUpdate) + { + // Operation is starting. Send progress so callers know the AsyncOperation can't be cancelled. + reportProgress = true; + } + else if (reportType == ReportType::EndProgress) + { + // Operation is "finished". May not have succeeded. + reportProgress = true; + operationProgress = 1; + } + else if (progressType == ::AppInstaller::ProgressType::Percent) + { + if (maximum > 0 && maximum >= current) + { + // Operation is progressing + reportProgress = true; + operationProgress = static_cast(current) / static_cast(maximum); + } + } + break; + case ::Workflow::ExecutionStage::PostExecution: + if (reportType == ReportType::ExecutionPhaseUpdate) + { + // Send PostInstall progress when it switches to PostExecution phase. + reportProgress = true; + WINGET_GET_PROGRESS_STATE(PostInstall, PostUninstall, PostRepair); + downloadProgress = 1; + operationProgress = 1; + } + break; + } + if (reportProgress) + { + if constexpr (std::is_same_v) + { + TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress, operationProgress }; + return progress; + } + else if constexpr (std::is_same_v) + { + TProgress progress{ progressState, operationProgress }; + return progress; + } + else if constexpr (std::is_same_v) + { + TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress }; + return progress; + } + else if constexpr (std::is_same_v) + { + TProgress progress{ progressState, operationProgress }; + return progress; + } + } + else + { + return {}; + } + } + + template + Microsoft::Management::Deployment::PackageVersionInfo GetPackageVersionInfo(winrt::Microsoft::Management::Deployment::CatalogPackage package, TOptions options) + { + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; + + winrt::Microsoft::Management::Deployment::PackageVersionId versionId = (options) ? options.PackageVersionId() : nullptr; + // If the version of the package is specified use that, otherwise use the default. + if (versionId) + { + packageVersionInfo = package.GetPackageVersionInfo(versionId); + } + else + { + if constexpr (std::is_same_v) + { + packageVersionInfo = package.DefaultInstallVersion(); + } + else if constexpr (std::is_same_v) + { + // For download, applicability check is not needed. Just use latest. + if (package.AvailableVersions().Size() > 0) + { + packageVersionInfo = package.GetPackageVersionInfo(package.AvailableVersions().GetAt(0)); + } + } + } + // If the specified version wasn't found then return a failure. This is unusual, since all packages that came from a non-local catalog have a default version, + // and the versionId is strongly typed and comes from the CatalogPackage.GetAvailableVersions. + // If version is not specified, DefaultInstallVersion may be empty due to applicability check. + THROW_HR_IF(versionId ? APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND : APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER, !packageVersionInfo); + return packageVersionInfo; + } + + void PopulateContextFromInstallOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::InstallOptions options) + { + if (options) + { + if (!options.LogOutputPath().empty()) + { + context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); + context->Args.AddArg(Execution::Args::Type::VerboseLogs); + } + if (options.AllowHashMismatch()) + { + context->Args.AddArg(Execution::Args::Type::HashOverride); + } + + if (options.BypassIsStoreClientBlockedPolicyCheck()) + { + context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + } + + if (options.Force()) + { + context->Args.AddArg(Execution::Args::Type::Force); + } + + // If the PackageInstallScope is anything other than ::Any then set it as a requirement. + auto manifestScope = GetManifestScope(options.PackageInstallScope()); + if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first)); + context->Add(manifestScope.second); + } + + if (options.PackageInstallMode() == PackageInstallMode::Interactive) + { + context->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (options.PackageInstallMode() == PackageInstallMode::Silent) + { + context->Args.AddArg(Execution::Args::Type::Silent); + } + + auto installerType = GetManifestInstallerType(options.InstallerType()); + if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallerType, AppInstaller::Manifest::InstallerTypeToString(installerType)); + } + + if (!options.PreferredInstallLocation().empty()) + { + context->Args.AddArg(Execution::Args::Type::InstallLocation, ::AppInstaller::Utility::ConvertToUTF8(options.PreferredInstallLocation())); + } + + if (!options.ReplacementInstallerArguments().empty()) + { + context->Args.AddArg(Execution::Args::Type::Override, ::AppInstaller::Utility::ConvertToUTF8(options.ReplacementInstallerArguments())); + } + + if (!options.AdditionalInstallerArguments().empty()) + { + context->Args.AddArg(Execution::Args::Type::CustomSwitches, ::AppInstaller::Utility::ConvertToUTF8(options.AdditionalInstallerArguments())); + } + + if (options.AllowedArchitectures().Size() != 0) + { + std::vector allowedArchitectures; + for (auto architecture : options.AllowedArchitectures()) + { + auto convertedArchitecture = GetUtilityArchitecture(architecture); + if (convertedArchitecture) + { + allowedArchitectures.push_back(convertedArchitecture.value()); + } + } + context->Add(std::move(allowedArchitectures)); + } + + // Note: AdditionalPackageCatalogArguments is not needed during install since the manifest is already known so no additional calls to the source are needed. The property is deprecated. + + if (options.AcceptPackageAgreements()) + { + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + + if (options.SkipDependencies()) + { + context->Args.AddArg(Execution::Args::Type::SkipDependencies); + } + + if (options.AuthenticationArguments()) + { + context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); + context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); + } + } + else + { + // Note: If no install options are specified, we assume the caller is accepting the package agreements by default. + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + } + + void PopulateContextFromUninstallOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::UninstallOptions options) + { + if (options) + { + if (!options.LogOutputPath().empty()) + { + context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); + context->Args.AddArg(Execution::Args::Type::VerboseLogs); + } + if (options.Force()) + { + context->Args.AddArg(Execution::Args::Type::Force); + } + + if (options.PackageUninstallMode() == PackageUninstallMode::Interactive) + { + context->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (options.PackageUninstallMode() == PackageUninstallMode::Silent) + { + context->Args.AddArg(Execution::Args::Type::Silent); + } + + auto uninstallScope = GetManifestUninstallScope(options.PackageUninstallScope()); + if (uninstallScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(uninstallScope)); + } + } + } + + void PopulateContextFromDownloadOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::DownloadOptions options) + { + if (options) + { + if (!options.DownloadDirectory().empty()) + { + context->Args.AddArg(Execution::Args::Type::DownloadDirectory, ::AppInstaller::Utility::ConvertToUTF8(options.DownloadDirectory())); + } + if (!options.Locale().empty()) + { + context->Args.AddArg(Execution::Args::Type::Locale, ::AppInstaller::Utility::ConvertToUTF8(options.Locale())); + } + if (options.AllowHashMismatch()) + { + context->Args.AddArg(Execution::Args::Type::HashOverride); + } + if (options.SkipDependencies()) + { + context->Args.AddArg(Execution::Args::Type::SkipDependencies); + } + if (options.AcceptPackageAgreements()) + { + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + auto manifestScope = GetManifestScope(options.Scope()); + if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first)); + } + + auto architecture = options.Architecture(); + if (architecture != Windows::System::ProcessorArchitecture::Unknown) + { + auto convertedArchitecture = GetUtilityArchitecture(architecture); + if (convertedArchitecture) + { + context->Args.AddArg(Execution::Args::Type::InstallerArchitecture, ToString(convertedArchitecture.value())); + } + } + + auto installerType = GetManifestInstallerType(options.InstallerType()); + if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallerType, AppInstaller::Manifest::InstallerTypeToString(installerType)); + } + + if (options.AuthenticationArguments()) + { + context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); + context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); + } + + if (options.SkipMicrosoftStoreLicense()) + { + context->Args.AddArg(Execution::Args::Type::SkipMicrosoftStorePackageLicense); + } + + WindowsPlatform platform = options.Platform(); + if (platform != WindowsPlatform::Unknown) + { + context->Args.AddArg(Execution::Args::Type::Platform, AppInstaller::Manifest::PlatformToString(GetPlatformEnum(platform))); + } + + hstring targetOSVersion = options.TargetOSVersion(); + if (!targetOSVersion.empty()) + { + context->Args.AddArg(Execution::Args::Type::OSVersion, ::AppInstaller::Utility::ConvertToUTF8(targetOSVersion)); + } + } + } + + void PopulateContextFromRepairOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::RepairOptions options) + { + if (options) + { + if (!options.LogOutputPath().empty()) + { + context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); + context->Args.AddArg(Execution::Args::Type::VerboseLogs); + } + + if (options.PackageRepairMode() == PackageRepairMode::Interactive) + { + context->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (options.PackageRepairMode() == PackageRepairMode::Silent) + { + context->Args.AddArg(Execution::Args::Type::Silent); + } + + if (options.AcceptPackageAgreements()) + { + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + + if (options.AllowHashMismatch()) + { + context->Args.AddArg(Execution::Args::Type::HashOverride); + } + + if (options.BypassIsStoreClientBlockedPolicyCheck()) + { + context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + } + + if (options.Force()) + { + context->Args.AddArg(Execution::Args::Type::Force); + } + + auto repairScope = GetManifestRepairScope(options.PackageRepairScope()); + if (repairScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(repairScope)); + } + + if (options.AuthenticationArguments()) + { + context->Args.AddArg(Execution::Args::Type::AuthenticationMode, ::AppInstaller::Authentication::AuthenticationModeToString(GetAuthenticationMode(options.AuthenticationArguments().AuthenticationMode()))); + context->Args.AddArg(Execution::Args::Type::AuthenticationAccount, ::AppInstaller::Utility::ConvertToUTF8(options.AuthenticationArguments().AuthenticationAccount())); + } + } + } + + template + std::unique_ptr CreateContextFromOperationOptions( + TOptions options, + std::wstring callerProcessInfoString) + { + std::unique_ptr context = std::make_unique(); + hstring correlationData = (options) ? options.CorrelationData() : L""; + + context->SetContextLoggers(correlationData, GetComCallerName(AppInstaller::Utility::ConvertToUTF8(callerProcessInfoString))); + + // Convert the options to arguments for the installer. + if constexpr (std::is_same_v) + { + PopulateContextFromInstallOptions(context.get(), options); + } + else if constexpr (std::is_same_v) + { + PopulateContextFromUninstallOptions(context.get(), options); + } + else if constexpr (std::is_same_v) + { + PopulateContextFromDownloadOptions(context.get(), options); + } + else if constexpr (std::is_same_v) + { + PopulateContextFromRepairOptions(context.get(), options); + } + + return context; + } + + std::shared_ptr GetExistingQueueItemForPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + std::shared_ptr queueItem = nullptr; + std::unique_ptr context = std::make_unique(); + if (catalogInfo) + { + // If the caller has passed in the catalog they expect the package to have come from, then only look for an install from that catalog. + // Fail if they've used a catalog that doesn't have an Id. This can currently happen for Info objects that come from PackageCatalogReference objects for REST catalogs. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, catalogInfo.Id().empty()); + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ catalogInfo.Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + return queueItem; + } + + // If the caller has not specified the catalog, then check InstalledVersion. When the package comes from the Installing catalog the PackageCatalog + // of the InstalledVersion will be set to the original catalog that the install was from, so checking the InstalledVersion first is most likely to + // find a result. + Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo = package.InstalledVersion(); + if (installedVersionInfo) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ installedVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) + { + return queueItem; + } + } + + // If InstalledVersion was not found, check DefaultInstallVersion + Microsoft::Management::Deployment::PackageVersionInfo defaultInstallVersionInfo = package.DefaultInstallVersion(); + if (defaultInstallVersionInfo) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ defaultInstallVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) + { + return queueItem; + } + } + + // Finally check all catalogs in AvailableVersions. + for (Microsoft::Management::Deployment::PackageVersionId versionId : package.AvailableVersions()) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ package.GetPackageVersionInfo(versionId).PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) + { + return queueItem; + } + } + return nullptr; + } + + std::unique_ptr CreateQueueItemForInstall( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::InstallOptions options, + bool isUpgrade) + { + // Add manifest and PackageVersion to context for install/upgrade. + // If the version of the package is specified use that, otherwise use the default. + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); + AddPackageManifestToContext(packageVersionInfo, comContext.get()); + + if (isUpgrade) + { + AppInstaller::Utility::VersionAndChannel installedVersion{ winrt::to_string(package.InstalledVersion().Version()), winrt::to_string(package.InstalledVersion().Channel()) }; + AppInstaller::Utility::VersionAndChannel upgradeVersion{ winrt::to_string(packageVersionInfo.Version()), winrt::to_string(packageVersionInfo.Channel()) }; + + // Perform upgrade version check + if (upgradeVersion.GetVersion().IsUnknown()) + { + if (!(options.AllowUpgradeToUnknownVersion() && + AppInstaller::Utility::ICUCaseInsensitiveEquals(installedVersion.GetChannel().ToString(), upgradeVersion.GetChannel().ToString()))) + { + THROW_HR(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN); + } + } + else if (!installedVersion.IsUpdatedBy(upgradeVersion)) + { + THROW_HR(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER); + } + + // Set upgrade flag + comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseUpdate); + // Add installed version + AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); + } + + return Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext), isUpgrade); + } + + std::unique_ptr CreateQueueItemForUninstall( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package) + { + // Add installed version + AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); + + // Add Package which is used by RecordUninstall later for removing from tracking catalog of correlated available sources as best effort + winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); + comContext->Add(internalPackage); + + return Execution::OrchestratorQueueItemFactory::CreateItemForUninstall(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); + } + + std::unique_ptr CreateQueueItemForDownload( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::DownloadOptions options) + { + // Add manifest and PackageVersion to context for download. + // If the version of the package is specified use that, otherwise use the default. + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); + AddPackageManifestToContext(packageVersionInfo, comContext.get()); + + comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly); + + return Execution::OrchestratorQueueItemFactory::CreateItemForDownload(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext)); + } + + std::unique_ptr CreateQueueItemForRepair( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package) + { + // Add installed version + AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); + + // Add Package which is used to co-relate installed package with available package for repair + winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); + comContext->Add(internalPackage); + + comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseRepair); + + return Execution::OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); + } + + template + winrt::Windows::Foundation::IAsyncOperationWithProgress GetPackageOperation( + bool canCancelQueueItem, + std::shared_ptr queueItemParam, + winrt::Microsoft::Management::Deployment::CatalogPackage package = nullptr, + TOptions options = nullptr, + std::wstring callerProcessInfoString = {}, + bool isUpgrade = false) + { + winrt::hresult terminationHR = S_OK; + uint32_t operationError = 0; + hstring correlationData = (options) ? options.CorrelationData() : L""; + ::Workflow::ExecutionStage executionStage = ::Workflow::ExecutionStage::Initial; + + try + { + // re-scope the parameter to inside the try block to avoid lifetime management issues. + std::shared_ptr queueItem = std::move(queueItemParam); + + auto report_progress{ co_await winrt::get_progress_token() }; + auto cancellationToken{ co_await winrt::get_cancellation_token() }; + // co_await does not guarantee that it's on a background thread, so do so explicitly. + co_await winrt::resume_background(); + + if (queueItem == nullptr) + { + std::unique_ptr comContext = CreateContextFromOperationOptions(options, callerProcessInfoString); + + if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForInstall(std::move(comContext), package, options, isUpgrade); + } + else if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForUninstall(std::move(comContext), package); + } + else if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForDownload(std::move(comContext), package, options); + } + else if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForRepair(std::move(comContext), package); + } + + Execution::ContextOrchestrator::Instance().EnqueueAndRunItem(queueItem); + + if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0, 0, 0 }; + report_progress(queuedProgress); + } + else if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0 }; + report_progress(queuedProgress); + } + else if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0 }; + report_progress(queuedProgress); + } + else if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0 }; + report_progress(queuedProgress); + } + } + { + // correlation data is not passed in when retrieving an existing queue item, so get it from the existing context. + correlationData = hstring(queueItem->GetContext().GetCorrelationJson()); + } + + wil::unique_event progressEvent{ wil::EventOptions::None }; + + std::atomic operationProgress; + queueItem->GetContext().AddProgressCallbackFunction([&operationProgress, &progressEvent]( + ReportType reportType, + uint64_t current, + uint64_t maximum, + ::AppInstaller::ProgressType progressType, + ::Workflow::ExecutionStage executionPhase) + { + std::optional operationProgressOptional = GetProgress(reportType, current, maximum, progressType, executionPhase); + if (operationProgressOptional.has_value()) + { + operationProgress = operationProgressOptional.value(); + progressEvent.SetEvent(); + } + return; + } + ); + + std::weak_ptr weakQueueItem(queueItem); + cancellationToken.callback([weakQueueItem, &canCancelQueueItem] + { + if (canCancelQueueItem) + { + auto strongQueueItem = weakQueueItem.lock(); + if (strongQueueItem) { + // The cancellation of the AsyncOperation on the client triggers Cancel which causes the Execute to end. + Execution::ContextOrchestrator::Instance().CancelQueueItem(*strongQueueItem); + } + } + }); + + // Wait for completion or progress events. + // Waiting for both on the same thread ensures that progress is never reported after the async operation itself has completed. + bool completionEventFired = false; + HANDLE operationEvents[2]; + operationEvents[0] = progressEvent.get(); + operationEvents[1] = queueItem->GetCompletedEvent().get(); + while (!completionEventFired) + { + DWORD dwEvent = WaitForMultipleObjects( + _countof(operationEvents) /* number of events */, + operationEvents /* event array */, + FALSE /* bWaitAll, FALSE to wake on any event */, + INFINITE /* wait until operation completion */); + + switch (dwEvent) + { + // operationEvents[0] was signaled, progress + case WAIT_OBJECT_0 + 0: + // The report_progress call will hang when making callbacks to suspended processes so it's important that this is now on a background thread. + // Progress events are not queued - some will be missed if multiple progress events are fired from the ComContext to the callback + // while the report_progress call is hung\in progress. + // Duplicate progress events can be fired if another progress event comes from the ComContext to the callback after the listener + // has been awaked, but before it has gotten the installProgress. + report_progress(operationProgress); + break; + + // operationEvents[1] was signaled, operation completed + case WAIT_OBJECT_0 + 1: + completionEventFired = true; + break; + + // Return value is invalid. + default: + THROW_LAST_ERROR(); + } + } + + if (completionEventFired) + { + // The install command has finished, check for success/failure and how far it got. + terminationHR = queueItem->GetContext().GetTerminationHR(); + executionStage = queueItem->GetContext().GetExecutionStage(); + if (queueItem->GetContext().Contains(Data::OperationReturnCode)) + { + operationError = static_cast(queueItem->GetContext().Get()); + } + } + } + WINGET_CATCH_STORE(terminationHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + + // TODO - RebootRequired not yet populated, msi arguments not returned from Execute. + co_return GetOperationResult(executionStage, terminationHR, operationError, correlationData, false); + } + + template + winrt::Windows::Foundation::IAsyncOperationWithProgress GetEmptyAsynchronousResultForOperation( + HRESULT hr, + hstring correlationData) + { + // If a function uses co_await or co_return (i.e. if it is a co_routine), it cannot use return directly. + // This helper helps a function that is not a coroutine itself to return errors asynchronously. + co_return GetOperationResult(::Workflow::ExecutionStage::Initial, hr, 0, correlationData, false); + } + +#define WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, FAILED(hr)) } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::UpgradePackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + // the package should have an installed version to be upgraded. + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString), true /* isUpgrade */); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + hstring correlationData; + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::shared_ptr queueItem = nullptr; + bool canCancelQueueItem = false; + try + { + // Check for permissions + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + if (!canCancelQueueItem) + { + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); + } + + // Get the queueItem synchronously. + queueItem = GetExistingQueueItemForPackage(package, catalogInfo); + if (queueItem == nullptr || + (queueItem->GetPackageOperationType() != PackageOperationType::Install && queueItem->GetPackageOperationType() != PackageOperationType::Upgrade)) + { + return nullptr; + } + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + canCancelQueueItem, std::move(queueItem)); + } + +#define WINGET_RETURN_UNINSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_UNINSTALL_RESULT_HR_IF(hr, FAILED(hr)) } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::UninstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::UninstallOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + // the package should have an installed version to be uninstalled. + WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetUninstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + hstring correlationData; + WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::shared_ptr queueItem = nullptr; + bool canCancelQueueItem = false; + try + { + // Check for permissions + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + if (!canCancelQueueItem) + { + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); + } + + // Get the queueItem synchronously. + queueItem = GetExistingQueueItemForPackage(package, catalogInfo); + if (queueItem == nullptr || + queueItem->GetPackageOperationType() != PackageOperationType::Uninstall) + { + return nullptr; + } + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + canCancelQueueItem, std::move(queueItem)); + } + +#define WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(hr, FAILED(hr)) } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + hstring correlationData; + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::shared_ptr queueItem = nullptr; + try + { + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + + // Get the queueItem synchronously. + queueItem = GetExistingQueueItemForPackage(package, catalogInfo); + if (queueItem == nullptr || + queueItem->GetPackageOperationType() != PackageOperationType::Download) + { + return nullptr; + } + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation(true, std::move(queueItem)); + } + +#define WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, FAILED(hr)) } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + // the package should have an installed version to be repaired. + WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::AddPackageCatalogAsync(winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions options) + { + LogStartupIfApplicable(); + + // options must be set. + THROW_HR_IF_NULL(E_POINTER, options); + THROW_HR_IF(E_INVALIDARG, options.Name().empty()); + THROW_HR_IF(E_INVALIDARG, options.SourceUri().empty()); + + HRESULT terminationHR = S_OK; + try { + + // Check if running as admin/system. + // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, + // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); + + ::AppInstaller::Repository::Source sourceToAdd = CreateSourceFromOptions(options); + + auto strong_this = get_strong(); + auto report_progress{ co_await winrt::get_progress_token() }; + co_await winrt::resume_background(); + + std::string type = winrt::to_string(options.Type()); + auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(type, report_progress ); + + packageCatalogProgressSink->BeginProgress(); + ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); + sourceToAdd.Add(progress); + packageCatalogProgressSink->EndProgress(false); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + co_return GetAddPackageCatalogResult(terminationHR); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RemovePackageCatalogAsync(winrt::Microsoft::Management::Deployment::RemovePackageCatalogOptions options) + { + LogStartupIfApplicable(); + + // options must be set. + THROW_HR_IF_NULL(E_POINTER, options); + THROW_HR_IF(E_INVALIDARG, options.Name().empty()); + + HRESULT terminationHR = S_OK; + try { + + // Check if running as admin/system. + // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, + // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); + + auto matchingSource = GetMatchingSource(winrt::to_string(options.Name())); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !matchingSource.has_value()); + + auto strong_this = get_strong(); + auto report_progress{ co_await winrt::get_progress_token() }; + co_await winrt::resume_background(); + + auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(matchingSource.value().Type, report_progress, true); + + packageCatalogProgressSink->BeginProgress(); + ::AppInstaller::Repository::Source sourceToRemove = ::AppInstaller::Repository::Source{ matchingSource.value().Name }; + ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); + + // If the PreserveData option is set, this is equivalent to the WinGet CLI Reset command on a single source; otherwise, it removes the source. + if (options.PreserveData()) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !sourceToRemove.DropSource(matchingSource.value().Name)); + } + else + { + sourceToRemove.Remove(progress); + } + packageCatalogProgressSink->EndProgress(false); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + co_return GetRemovePackageCatalogResult(terminationHR); + } + + winrt::hstring PackageManager::Version() const + { + return winrt::hstring{ AppInstaller::Utility::ConvertToUTF16(AppInstaller::Runtime::GetClientVersion()) }; + } + + winrt::Microsoft::Management::Deployment::EditPackageCatalogResult GetEditPackageCatalogResult(winrt::hresult terminationStatus) + { + winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); + auto editResult = winrt::make_self>(); + editResult->Initialize(status, terminationStatus); + return *editResult; + } + + winrt::Microsoft::Management::Deployment::EditPackageCatalogResult PackageManager::EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options) + { + LogStartupIfApplicable(); + + // options must be set. + THROW_HR_IF_NULL(E_POINTER, options); + THROW_HR_IF(E_INVALIDARG, options.Name().empty()); + + HRESULT terminationHR = S_OK; + try { + + // Check if running as admin/system. + // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, + // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); + + auto matchingSource = GetMatchingSource(winrt::to_string(options.Name())); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !matchingSource.has_value()); + ::AppInstaller::Repository::Source sourceToEdit = ::AppInstaller::Repository::Source{ matchingSource.value().Name }; + + ::AppInstaller::Repository::SourceEdit edits; + edits.Explicit = options.Explicit(); + edits.Priority = options.Priority(); + + if (sourceToEdit.RequiresChanges(edits)) + { + sourceToEdit.Edit(edits); + } + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + return GetEditPackageCatalogResult(terminationHR); + } + + namespace + { + // Builds PackagePin WinRT objects from a vector of internal Pin objects. + winrt::Windows::Foundation::Collections::IVectorView + MakePackagePinVectorView(const std::vector<::AppInstaller::Pinning::Pin>& pins) + { + auto result = winrt::single_threaded_vector(); + for (const auto& pin : pins) + { + auto packagePin = winrt::make_self>(); + packagePin->Initialize(pin); + result.Append(*packagePin); + } + return result.GetView(); + } + + // Collects PinKeys for all available sources of a package, and optionally for its installed identity. + std::vector<::AppInstaller::Pinning::PinKey> GetPinKeysForCatalogPackage( + winrt::Microsoft::Management::Deployment::CatalogPackage const& package, + bool includeInstalled) + { + std::vector<::AppInstaller::Pinning::PinKey> pinKeys; + + // Keys for each available source version + for (auto const& versionId : package.AvailableVersions()) + { + auto versionInfo = package.GetPackageVersionInfo(versionId); + if (versionInfo) + { + std::string packageId = winrt::to_string(versionInfo.Id()); + std::string sourceId = winrt::to_string(versionInfo.PackageCatalog().Info().Id()); + if (!packageId.empty() && !sourceId.empty()) + { + ::AppInstaller::Pinning::PinKey key{ packageId, sourceId }; + // Avoid duplicates (same packageId+sourceId may appear for multiple versions) + if (std::find(pinKeys.begin(), pinKeys.end(), key) == pinKeys.end()) + { + pinKeys.push_back(std::move(key)); + } + } + } + } + + // Key for the installed package identity (ProductCode / PackageFamilyName) + if (includeInstalled) + { + auto installedVersion = package.InstalledVersion(); + if (installedVersion) + { + // Prefer PackageFamilyName (MSIX), fall back to ProductCode (MSI/EXE) + auto pfns = installedVersion.PackageFamilyNames(); + if (pfns && pfns.Size() > 0) + { + for (auto const& pfn : pfns) + { + pinKeys.push_back(::AppInstaller::Pinning::PinKey::GetPinKeyForInstalled(winrt::to_string(pfn))); + } + } + else + { + auto productCodes = installedVersion.ProductCodes(); + if (productCodes) + { + for (auto const& productCode : productCodes) + { + pinKeys.push_back(::AppInstaller::Pinning::PinKey::GetPinKeyForInstalled(winrt::to_string(productCode))); + } + } + } + } + } + + return pinKeys; + } + + winrt::Microsoft::Management::Deployment::PinPackageResult MakePinPackageResult(HRESULT hr) + { + auto result = winrt::make_self>(); + result->Initialize(GetPinOperationStatus(hr), hr); + return *result; + } + + // Converts PinPackageOptions.PinType to the internal pin representation. + ::AppInstaller::Pinning::Pin CreatePinFromOptions( + const ::AppInstaller::Pinning::PinKey& pinKey, + winrt::Microsoft::Management::Deployment::PinPackageOptions const& options) + { + switch (options.PinType()) + { + case winrt::Microsoft::Management::Deployment::PackagePinType::Pinning: + return ::AppInstaller::Pinning::Pin::CreatePinningPin(pinKey); + case winrt::Microsoft::Management::Deployment::PackagePinType::Blocking: + return ::AppInstaller::Pinning::Pin::CreateBlockingPin(pinKey); + case winrt::Microsoft::Management::Deployment::PackagePinType::Gating: + return ::AppInstaller::Pinning::Pin::CreateGatingPin( + pinKey, + ::AppInstaller::Utility::GatedVersion{ winrt::to_string(options.GatedVersion()) }); + default: + // Unknown, PinnedByManifest, and any future unrecognized values are not valid + // user-settable pin types. + THROW_HR(E_INVALIDARG); + } + } + } + + winrt::Windows::Foundation::Collections::IVectorView + PackageManager::GetAllPins() + { + LogStartupIfApplicable(); + + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadOnly }; + return MakePackagePinVectorView(pinningData.GetAllPins()); + } + + winrt::Windows::Foundation::Collections::IVectorView + PackageManager::GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package) + { + LogStartupIfApplicable(); + + THROW_HR_IF_NULL(E_POINTER, package); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + + auto pinKeys = GetPinKeysForCatalogPackage(package, /* includeInstalled */ true); + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadOnly }; + + std::vector<::AppInstaller::Pinning::Pin> pins; + for (const auto& pinKey : pinKeys) + { + auto pin = pinningData.GetPin(pinKey); + if (pin) + { + pins.push_back(std::move(*pin)); + } + } + + return MakePackagePinVectorView(pins); + } + + winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::PinPackage( + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::PinPackageOptions options) + { + LogStartupIfApplicable(); + + THROW_HR_IF_NULL(E_POINTER, package); + THROW_HR_IF_NULL(E_POINTER, options); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + + // Gating pins require a non-empty version range. + THROW_HR_IF(E_INVALIDARG, + options.PinType() == winrt::Microsoft::Management::Deployment::PackagePinType::Gating && + options.GatedVersion().empty()); + + HRESULT terminationHR = S_OK; + try + { + auto pinKeys = GetPinKeysForCatalogPackage(package, options.PinInstalledPackage()); + THROW_HR_IF(E_INVALIDARG, pinKeys.empty()); + + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; + + auto pinTime = std::chrono::system_clock::now(); + + std::optional note; + if (!options.Note().empty()) + { + note = winrt::to_string(options.Note()); + } + + for (const auto& pinKey : pinKeys) + { + auto newPin = CreatePinFromOptions(pinKey, options); + + auto existingPin = pinningData.GetPin(pinKey); + if (existingPin && !(*existingPin == newPin)) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS, !options.Force()); + } + + newPin.SetDateAdded(pinTime); + newPin.SetNote(note); + pinningData.AddOrUpdatePin(newPin); + } + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + return MakePinPackageResult(terminationHR); + } + + winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::UnpinPackage( + winrt::Microsoft::Management::Deployment::CatalogPackage package) + { + LogStartupIfApplicable(); + + THROW_HR_IF_NULL(E_POINTER, package); + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + + HRESULT terminationHR = S_OK; + try + { + auto pinKeys = GetPinKeysForCatalogPackage(package, /* includeInstalled */ true); + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; + + bool anyRemoved = false; + for (const auto& pinKey : pinKeys) + { + anyRemoved |= pinningData.TryRemovePin(pinKey); + } + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST, !anyRemoved); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + return MakePinPackageResult(terminationHR); + } + + winrt::Microsoft::Management::Deployment::PinPackageResult PackageManager::ResetAllPins(winrt::Microsoft::Management::Deployment::PackageCatalogReference packageCatalogReference) + { + LogStartupIfApplicable(); + + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageManagement)); + + HRESULT terminationHR = S_OK; + try + { + std::string sourceId; + if (packageCatalogReference) + { + sourceId = winrt::to_string(packageCatalogReference.Info().Id()); + } + + auto pinningData = ::AppInstaller::Pinning::PinningData{ ::AppInstaller::Pinning::PinningData::Disposition::ReadWrite }; + pinningData.ResetAllPins(sourceId); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + return MakePinPackageResult(terminationHR); + } + + CoCreatableMicrosoftManagementDeploymentClass(PackageManager); +} diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h index 014c49e0b2..d41324e297 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.h +++ b/src/Microsoft.Management.Deployment/PackageManager.h @@ -1,78 +1,78 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "PackageManager.g.h" -#include "Public/ComClsids.h" -#include - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -// Forward declaration -namespace AppInstaller::CLI::Execution -{ - struct Context; -} -#endif - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_PackageManager)] - struct PackageManager : PackageManagerT - { - PackageManager(); - - winrt::Windows::Foundation::Collections::IVectorView GetPackageCatalogs(); - winrt::Microsoft::Management::Deployment::PackageCatalogReference GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog); - winrt::Microsoft::Management::Deployment::PackageCatalogReference GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog); - winrt::Microsoft::Management::Deployment::PackageCatalogReference GetPackageCatalogByName(hstring const& catalogName); - winrt::Microsoft::Management::Deployment::PackageCatalogReference CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); - // Contract 2.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); - // Contract 4.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - UpgradePackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - UninstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::UninstallOptions options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - GetUninstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); - // Contract 7.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); - // Contract 11.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options); - // Contract 12.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - AddPackageCatalogAsync(winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - RemovePackageCatalogAsync(winrt::Microsoft::Management::Deployment::RemovePackageCatalogOptions options); - // Contract 13.0 - winrt::hstring Version() const; - // Contract 28.0 - winrt::Microsoft::Management::Deployment::EditPackageCatalogResult EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options); - // Contract 30.0 - winrt::Windows::Foundation::Collections::IVectorView GetAllPins(); - winrt::Windows::Foundation::Collections::IVectorView GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package); - winrt::Microsoft::Management::Deployment::PinPackageResult PinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PinPackageOptions options); - winrt::Microsoft::Management::Deployment::PinPackageResult UnpinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package); - winrt::Microsoft::Management::Deployment::PinPackageResult ResetAllPins(winrt::Microsoft::Management::Deployment::PackageCatalogReference packageCatalogReference); - }; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void SetComCallerName(std::string name); - void PopulateContextFromInstallOptions(AppInstaller::CLI::Execution::Context* context, winrt::Microsoft::Management::Deployment::InstallOptions options); -#endif -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct PackageManager : PackageManagerT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackageManager.g.h" +#include "Public/ComClsids.h" +#include + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +// Forward declaration +namespace AppInstaller::CLI::Execution +{ + struct Context; +} +#endif + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_PackageManager)] + struct PackageManager : PackageManagerT + { + PackageManager(); + + winrt::Windows::Foundation::Collections::IVectorView GetPackageCatalogs(); + winrt::Microsoft::Management::Deployment::PackageCatalogReference GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog); + winrt::Microsoft::Management::Deployment::PackageCatalogReference GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog); + winrt::Microsoft::Management::Deployment::PackageCatalogReference GetPackageCatalogByName(hstring const& catalogName); + winrt::Microsoft::Management::Deployment::PackageCatalogReference CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); + // Contract 2.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); + // Contract 4.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + UpgradePackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + UninstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::UninstallOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + GetUninstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); + // Contract 7.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); + // Contract 11.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options); + // Contract 12.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + AddPackageCatalogAsync(winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + RemovePackageCatalogAsync(winrt::Microsoft::Management::Deployment::RemovePackageCatalogOptions options); + // Contract 13.0 + winrt::hstring Version() const; + // Contract 28.0 + winrt::Microsoft::Management::Deployment::EditPackageCatalogResult EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options); + // Contract 30.0 + winrt::Windows::Foundation::Collections::IVectorView GetAllPins(); + winrt::Windows::Foundation::Collections::IVectorView GetPins(winrt::Microsoft::Management::Deployment::CatalogPackage package); + winrt::Microsoft::Management::Deployment::PinPackageResult PinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PinPackageOptions options); + winrt::Microsoft::Management::Deployment::PinPackageResult UnpinPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package); + winrt::Microsoft::Management::Deployment::PinPackageResult ResetAllPins(winrt::Microsoft::Management::Deployment::PackageCatalogReference packageCatalogReference); + }; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void SetComCallerName(std::string name); + void PopulateContextFromInstallOptions(AppInstaller::CLI::Execution::Context* context, winrt::Microsoft::Management::Deployment::InstallOptions options); +#endif +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct PackageManager : PackageManagerT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 2292b6d755..6737af1910 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1,1954 +1,1954 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -namespace Microsoft.Management.Deployment -{ - [contractversion(30)] // For version 1.30 - apicontract WindowsPackageManagerContract{}; - - /// State of the install - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallProgressState - { - /// The install is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from downloading or installing. - Queued, - /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will - /// end the download and prevent the package from installing. - Downloading, - /// The install is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not - /// stop the installation or the post install cleanup. - Installing, - /// The installer has completed and cleanup actions are in progress. Cancellation of the - /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the install. - PostInstall, - /// The operation has completed. - Finished, - }; - - /// Progress object for the install - /// DESIGN NOTE: percentage for the install as a whole is purposefully not included as there is no way to - /// estimate progress when the installer is running. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - struct InstallProgress - { - /// State of the install - PackageInstallProgressState State; - /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes downloaded if known - UInt64 BytesDownloaded; - /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes required if known - UInt64 BytesRequired; - /// Download percentage completed - Double DownloadProgress; - /// Install percentage if known. - Double InstallationProgress; - }; - - /// Status of the Install call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum InstallResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - DownloadError, - InstallError, - ManifestError, - NoApplicableInstallers, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - NoApplicableUpgrade, - }, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - PackageAgreementsNotAccepted, - } - }; - - /// Result of the install - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass InstallResult - { - /// Used by a caller to correlate the install with a caller's data. - String CorrelationData { get; }; - /// Whether a restart is required to complete the install. - Boolean RebootRequired { get; }; - - /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED - InstallResultStatus Status { get; }; - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - /// The error code from the install attempt. Only valid if the Status is InstallError. - /// This value's meaning will require knowledge of the specific installer or install technology. - UInt32 InstallerErrorCode { get; }; - } - } - - /// State of the uninstall - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - enum PackageUninstallProgressState - { - /// The uninstall is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from uninstalling. - Queued, - /// The uninstall is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not - /// stop the installation or the post uninstall steps. - Uninstalling, - /// The uninstaller has completed and cleanup actions are in progress. Cancellation of the - /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the uninstall. - PostUninstall, - /// The operation has completed. - Finished, - }; - - /// Progress object for the uninstall - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - struct UninstallProgress - { - /// State of the uninstall - PackageUninstallProgressState State; - /// Uninstall percentage if known. - Double UninstallationProgress; - }; - - /// Status of the uninstall call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - enum UninstallResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - UninstallError, - ManifestError, - }; - - /// Result of the uninstall - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - runtimeclass UninstallResult - { - /// Used by a caller to correlate the install with a caller's data. - String CorrelationData { get; }; - /// Whether a restart is required to complete the install. - Boolean RebootRequired { get; }; - - /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED - UninstallResultStatus Status { get; }; - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - - /// The error code from the uninstall attempt. Only valid if the Status is UninstallError. - /// This value's meaning will require knowledge of the specific uninstaller or install technology. - UInt32 UninstallerErrorCode { get; }; - } - - /// State of the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - enum PackageRepairProgressState - { - /// The repair is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from repairing. - Queued, - /// The repair is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not - /// stop the repair or the post repair steps. - Repairing, - /// The repair has completed and cleanup actions are in progress. Cancellation of the - /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the repair. - PostRepair, - /// The operation has completed. - Finished, - }; - - /// Progress object for the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - struct RepairProgress - { - /// State of the repair - PackageRepairProgressState State; - - /// Repair percentage if known. - Double RepairCompletionProgress; - }; - - /// Status of the repair call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - /// DESIGN NOTE: RepairResultStatus from AppInstallerErrors.h is not implemented in V1. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - enum RepairResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - DownloadError, - InternalError, - InvalidOptions, - RepairError, - ManifestError, - NoApplicableRepairer, - PackageAgreementsNotAccepted, - }; - - /// Result of the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - runtimeclass RepairResult - { - /// Used by a caller to correlate the repair with a caller's data. - String CorrelationData { get; }; - - /// Whether a restart is required to complete the repair. - Boolean RebootRequired { get; }; - - /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED - RepairResultStatus Status { get; }; - - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - - /// The error code from the repair attempt. Only valid if the Status is RepairError. - /// This value's meaning will require knowledge of the specific repairer or repair technology. - UInt32 RepairerErrorCode { get; }; - } - - /// State of the download - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - enum PackageDownloadProgressState - { - /// The download is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from downloading. - Queued, - /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will - /// end the download. - Downloading, - /// The operation has completed. - Finished, - }; - - /// Status of the download call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - enum DownloadResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - DownloadError, - ManifestError, - NoApplicableInstallers, - PackageAgreementsNotAccepted, - }; - - /// Result of the download - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - runtimeclass DownloadResult - { - /// Used by a caller to correlate the download with a caller's data. - String CorrelationData { get; }; - - /// Batched error code. - DownloadResultStatus Status { get; }; - - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - }; - - /// Progress object for the uninstall - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - struct PackageDownloadProgress - { - /// State of the download - PackageDownloadProgressState State; - - /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes downloaded if known - UInt64 BytesDownloaded; - - /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes required if known - UInt64 BytesRequired; - - /// Download percentage completed - Double DownloadProgress; - }; - - /// IMPLEMENTATION NOTE: SourceOrigin from winget/RepositorySource.h - /// Defines the origin of the package catalog details. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageCatalogOrigin - { - /// Predefined means it came as part of the Windows Package Manager package and cannot be removed. - Predefined, - /// User means it was added by the user and could be removed. - User, - }; - - /// IMPLEMENTATION NOTE: SourceTrustLevel from winget/RepositorySource.h - /// Defines the trust level of the package catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageCatalogTrustLevel - { - None, - Trusted, - }; - - /// IMPLEMENTATION NOTE: SourceDetails from winget/RepositorySource.h - /// Interface for retrieving information about an package catalog without acting on it. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalogInfo - { - /// The package catalog's unique identifier. - /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.Winget.Source_8wekyb3d8bbwe" - /// For contoso sample on msdn "contoso" - String Id { get; }; - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name { get; }; - /// The type of the package catalog. - /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" - /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". - /// For contoso sample on msdn "Microsoft.PreIndexed.Package" - String Type { get; }; - /// The argument used when adding the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" - /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" - String Argument { get; }; - /// The last time that this package catalog was updated. - Windows.Foundation.DateTime LastUpdateTime { get; }; - /// The origin of the package catalog. - PackageCatalogOrigin Origin { get; }; - /// The trust level of the package catalog - PackageCatalogTrustLevel TrustLevel { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - { - /// Excludes a source from discovery unless specified. - Boolean Explicit{ get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// The priority of this catalog. Higher values are sorted first. - Int32 Priority{ get; }; - } - } - - /// A metadata item of a package version. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageVersionMetadataField - { - /// The InstallerType of an installed package - InstallerType, - /// The Scope of an installed package - InstalledScope, - /// The system path where the package is installed - InstalledLocation, - /// The standard uninstall command; which may be interactive - StandardUninstallCommand, - /// An uninstall command that should be non-interactive - SilentUninstallCommand, - /// The publisher of the package - PublisherDisplayName, - }; - - /// The result of a comparison. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - enum CompareResult - { - /// The comparison did not result in a succesful ordering. - Unknown, - /// The object value is lesser than the given value. - Lesser, - /// The object value is equal to the given value. - Equal, - /// The object value is greater than the given value. - Greater, - }; - - /// IMPLEMENTATION NOTE: IPackageVersion from winget/RepositorySearch.h - /// A single package version. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageVersionInfo - { - /// IMPLEMENTATION NOTE: PackageVersionMetadata fields from winget/RepositorySearch.h - /// Gets any metadata associated with this package version. - /// Primarily stores data on installed packages. - /// Metadata fields may have no value (e.g. packages that aren't installed will not have an InstalledLocation). - String GetMetadata(PackageVersionMetadataField metadataField); - /// IMPLEMENTATION NOTE: PackageVersionProperty fields from winget/RepositorySearch.h - String Id { get; }; - String DisplayName { get; }; - String Version { get; }; - String Channel { get; }; - /// DESIGN NOTE: RelativePath from winget/RepositorySearch.h is excluded as not needed. - /// String RelativePath; - - /// IMPLEMENTATION NOTE: PackageVersionMultiProperty fields from winget/RepositorySearch.h - /// PackageFamilyName and ProductCode can have multiple values. - Windows.Foundation.Collections.IVectorView PackageFamilyNames { get; }; - Windows.Foundation.Collections.IVectorView ProductCodes { get; }; - - /// Gets the package catalog where this package version is from. - PackageCatalog PackageCatalog { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - { - /// Compares the given value against the package version of this object, with the result being - /// the enum value that represents where PackageVersionInfo::Version is ordered relative to the - /// versionString. "if (this.CompareToVersion(that) == Greater)" can be thought of as reading - /// the sentence "If this is compared to version that and is found to be greater". - /// IE if PackageVersionInfo::Version returns "2", then CompareToVersion("1") will return Greater. - /// Passing in an empty string will result in Unknown. - CompareResult CompareToVersion(String versionString); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - /// Checks if this package version has at least one applicable installer. - Boolean HasApplicableInstaller(InstallOptions options); - - /// Gets the publisher string for this package version, if one is available. - String Publisher { get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - /// Gets the package catalog metadata of this package version with the default localization based on user settings. - CatalogPackageMetadata GetCatalogPackageMetadata(); - - /// Gets the package catalog metadata of this package version with the preferred locale. - CatalogPackageMetadata GetCatalogPackageMetadata(String preferredLocale); - - /// Gets the applicable installer for this package version. - PackageInstallerInfo GetApplicableInstaller(InstallOptions options); - } - } - - /// IMPLEMENTATION NOTE: PackageVersionKey from winget/RepositorySearch.h - /// A key to identify a package version within a package. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageVersionId - { - /// The package catalog id that this version came from. - String PackageCatalogId { get; }; - /// The version. - String Version { get; }; - /// The channel. - String Channel { get; }; - }; - - /// The package installer type. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - enum PackageInstallerType - { - /// Unknown type. - Unknown, - /// Inno type. - Inno, - /// Wix type. - Wix, - /// Msi type. - Msi, - /// Nullsoft type. - Nullsoft, - /// Zip type. - Zip, - /// Msix or Appx type. - Msix, - /// Exe type. - Exe, - /// Burn type. - Burn, - /// MSStore type. - MSStore, - /// Portable type. - Portable, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - { - /// Font type. - Font, - }, - }; - - /// The package installer scope. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - enum PackageInstallerScope - { - /// Scope not declared. - Unknown, - /// User scope. - User, - /// System scope. - System, - }; - - /// The package installer elevation requirement. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - enum ElevationRequirement - { - /// Elevation requirement not declared. - Unknown, - /// Package installer requires elevation. - ElevationRequired, - /// Package installer prohibits elevation. - ElevationProhibited, - /// Package installer elevates self. - ElevatesSelf, - }; - - /// Interface for retrieving information about a package installer. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - runtimeclass PackageInstallerInfo - { - /// The package installer type. - PackageInstallerType InstallerType { get; }; - /// The nested package installer type for archives. - PackageInstallerType NestedInstallerType { get; }; - /// The package installer architecture. - Windows.System.ProcessorArchitecture Architecture { get; }; - /// The package installer scope. - PackageInstallerScope Scope { get; }; - /// The package installer locale. - String Locale { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - /// The package installer elevation requirement. - ElevationRequirement ElevationRequirement { get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - /// Authentication info from the package installer. - AuthenticationInfo AuthenticationInfo { get; }; - } - }; - - /// The installed status type. The values need to match InstalledStatusType from winget/RepositorySearch.h. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - [flags] - enum InstalledStatusType - { - /// None is checked. - None = 0x0, - /// Check Apps and Features entry. - AppsAndFeaturesEntry = 0x0001, - /// Check Apps and Features entry install location if applicable. - AppsAndFeaturesEntryInstallLocation = 0x0002, - /// Check Apps and Features entry install location with installed files if applicable. - AppsAndFeaturesEntryInstallLocationFile = 0x0004, - /// Check default install location if applicable. - DefaultInstallLocation = 0x0008, - /// Check default install location with installed files if applicable. - DefaultInstallLocationFile = 0x0010, - - /// Below are helper values for calling CheckInstalledStatus as input. - /// AppsAndFeaturesEntry related checks - AllAppsAndFeaturesEntryChecks = AppsAndFeaturesEntry | AppsAndFeaturesEntryInstallLocation | AppsAndFeaturesEntryInstallLocationFile, - /// DefaultInstallLocation related checks - AllDefaultInstallLocationChecks = DefaultInstallLocation | DefaultInstallLocationFile, - /// All checks - AllChecks = AllAppsAndFeaturesEntryChecks | AllDefaultInstallLocationChecks, - }; - - /// Interface representing an individual installed status. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - runtimeclass InstalledStatus - { - /// The installed status type. - InstalledStatusType Type { get; }; - /// The installed status path. - String Path { get; }; - /// The installed status result. - HRESULT Status { get; }; - }; - - /// Interface for retrieving information about a package installer installed status. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - runtimeclass PackageInstallerInstalledStatus - { - /// The package installer info. - PackageInstallerInfo InstallerInfo { get; }; - /// A list of various types of installed status of the package installer. - Windows.Foundation.Collections.IVectorView InstallerInstalledStatus { get; }; - }; - - /// Status of the check installed status call. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - enum CheckInstalledStatusResultStatus - { - Ok, - InternalError, - }; - - /// Interface for retrieving information about a package installer installed status. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - runtimeclass CheckInstalledStatusResult - { - /// Status of the check installed status call. - CheckInstalledStatusResultStatus Status { get; }; - - /// A list of package installer installed status. - Windows.Foundation.Collections.IVectorView PackageInstalledStatus { get; }; - }; - - /// IMPLEMENTATION NOTE: IPackage from winget/RepositorySearch.h - /// A package, potentially containing information about it's local state and the available versions. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass CatalogPackage - { - /// IMPLEMENTATION NOTE: PackageProperty fields from winget/RepositorySearch.h - /// Gets a property of this package. - String Id { get; }; - String Name { get; }; - - /// Gets the installed package information if the package is installed. - PackageVersionInfo InstalledVersion { get; }; - - /// Gets all available versions of this package. Ordering is not guaranteed. - Windows.Foundation.Collections.IVectorView AvailableVersions { get; }; - - /// Gets the version of this package that will be installed if version is not set in InstallOptions. - PackageVersionInfo DefaultInstallVersion { get; }; - - /// Gets a specific version of this package. - PackageVersionInfo GetPackageVersionInfo(PackageVersionId versionKey); - - /// Gets a value indicating whether an available version is newer than the installed version. - Boolean IsUpdateAvailable { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Check the installed status of the package. For more accurate and complete installed status, it's required to - /// call this method from a composite package from a newly created package catalog with installed info. - /// This may require downloading information from a server. - Windows.Foundation.IAsyncOperation CheckInstalledStatusAsync(InstalledStatusType checkTypes); - CheckInstalledStatusResult CheckInstalledStatus(InstalledStatusType checkTypes); - Windows.Foundation.IAsyncOperation CheckInstalledStatusAsync(); - CheckInstalledStatusResult CheckInstalledStatus(); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// Determines the priority of the catalog for this package object. - /// This should match the priority of the DefaultInstallVersion, but it is much more efficient than using that route. - /// May be null if the package refers only to an installed item. - Windows.Foundation.IReference CatalogPriority { get; }; - } - } - - /// IMPLEMENTATION NOTE: CompositeSearchBehavior from winget/RepositorySource.h - /// Search behavior for composite catalogs. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum CompositeSearchBehavior - { - /// Search local catalogs only - LocalCatalogs, - /// Search remote catalogs only, don't check local catalogs for InstalledVersion - RemotePackagesFromRemoteCatalogs, - /// Search remote catalogs, and check local catalogs for InstalledVersion - RemotePackagesFromAllCatalogs, - /// Search both local and remote catalogs. - AllCatalogs, - }; - - /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageFieldMatchOption - { - Equals, - EqualsCaseInsensitive, - StartsWithCaseInsensitive, - ContainsCaseInsensitive, - }; - - /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h - /// The field to match on. - /// The values must be declared in order of preference in search results. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageMatchField - { - CatalogDefault, - Id, - Name, - Moniker, - Command, - Tag, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 3)] - { - PackageFamilyName, - ProductCode, - } - /// DESIGN NOTE: The following PackageFieldMatchOption from winget/RepositorySearch.h are not implemented in V1. - /// NormalizedNameAndPublisher, - }; - - /// IMPLEMENTATION NOTE: PackageMatchFilter from winget/RepositorySearch.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageMatchFilter - { - PackageMatchFilter(); - /// The type of string comparison for matching - PackageFieldMatchOption Option; - /// The field to search - PackageMatchField Field; - /// The value to match - String Value; - /// DESIGN NOTE: "Additional" from RequestMatch winget/RepositorySearch.h is not implemented here. - } - - /// IMPLEMENTATION NOTE: MatchResult from winget/RepositorySearch.h - /// A single result from the search. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass MatchResult - { - /// The package found by the search request. - CatalogPackage CatalogPackage { get; }; - - /// The highest order field on which the package matched the search. - PackageMatchFilter MatchCriteria { get; }; - } - - /// Status of the FindPackages call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum FindPackagesResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - { - AuthenticationError, - AccessDenied, - } - }; - - /// IMPLEMENTATION NOTE: SearchResult from winget/RepositorySearch.h - /// Search result data returned from FindPackages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass FindPackagesResult - { - /// Error codes - FindPackagesResultStatus Status{ get; }; - - /// The full set of results from the search. - Windows.Foundation.Collections.IVectorView Matches { get; }; - - /// If true, the results were truncated by the given ResultLimit - /// USAGE NOTE: Windows Package Manager does not support result pagination, there is no way to continue - /// getting more results. - Boolean WasLimitExceeded { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - { - /// The error code of the operation. - HRESULT ExtendedErrorCode{ get; }; - } - } - - /// Options for FindPackages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass FindPackagesOptions - { - FindPackagesOptions(); - - /// DESIGN NOTE: - /// This class maps to SearchRequest from winget/RepositorySearch.h - /// That class is a container for data used to filter the available manifests in an package catalog. - /// Its properties can be thought of as: - /// (Query || Inclusions...) && Filters... - /// If Query and Inclusions are both empty, the starting data set will be the entire database. - /// Everything && Filters... - /// That has been translated in this api so that - /// Inclusions are Selectors below - /// Filters are Filters below - /// Query is PackageFieldMatchOption::PackageCatalogDefined and in the Selector list. - /// USAGE NOTE: Only one selector with PackageFieldMatchOption::PackageCatalogDefined is allowed. - - /// Selectors = you have to match at least one selector (if there are no selectors, then nothing is selected) - Windows.Foundation.Collections.IVector Selectors { get; }; - /// Filters = you have to match all filters(if there are no filters, then there is no filtering of selected items) - Windows.Foundation.Collections.IVector Filters{ get; }; - - /// Restricts the length of the returned results to the specified count. - UInt32 ResultLimit; - } - - /// IMPLEMENTATION NOTE: Source from winget/RepositorySource.h - /// A catalog for searching for packages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalog - { - /// Gets a value indicating whether this package catalog is a composite of other package catalogs, - /// and thus the packages may come from disparate package catalogs as well. - Boolean IsComposite { get; }; - /// The details of the package catalog if it is not a composite. - PackageCatalogInfo Info { get; }; - - /// Searches for Packages in the catalog. - Windows.Foundation.IAsyncOperation FindPackagesAsync(FindPackagesOptions options); - FindPackagesResult FindPackages(FindPackagesOptions options); - } - - /// Authentication mode - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - enum AuthenticationMode - { - /// Always use interactive authentication flow on first authentication request, following requests may use cached result. - Interactive, - /// Try silent authentication flow first. If failed, use interactive authentication flow. - SilentPreferred, - /// Only use silent authentication flow. If failed, fail the authentication. - Silent, - }; - - /// Authentication related arguments - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - runtimeclass AuthenticationArguments - { - AuthenticationArguments(); - - /// Choice of authentication flow behavior. - AuthenticationMode AuthenticationMode; - /// Optional. The authentication account to be used for authentication. - String AuthenticationAccount; - } - - /// Authentication method - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - enum AuthenticationType - { - Unknown, - None, - MicrosoftEntraId, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - MicrosoftEntraIdForAzureBlobStorage, - } - }; - - /// Microsoft Entra Id related authentication info. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - runtimeclass MicrosoftEntraIdAuthenticationInfo - { - /// The resource identifier or resource uri. - String Resource { get; }; - /// Requested scope. May be empty. - String Scope { get; }; - } - - /// Authentication info. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - runtimeclass AuthenticationInfo - { - /// The authentication type. - AuthenticationType AuthenticationType { get; }; - /// Microsoft Entra Id related authentication info. - MicrosoftEntraIdAuthenticationInfo MicrosoftEntraIdAuthenticationInfo { get; }; - } - - /// Result of a connection validation callback. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - enum PackageCatalogConnectionValidationResult - { - /// The connection was accepted. - Ok, - /// The connection was rejected because the certificate was not accepted. - CertificateRejected, - }; - - /// Arguments provided to a connection validation callback. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - runtimeclass PackageCatalogConnectionValidationEventArgs - { - /// The server certificate presented during the connection. - Windows.Security.Cryptography.Certificates.Certificate ServerCertificate { get; }; - } - - /// Callback invoked to validate a catalog connection. - /// Return Ok to accept the connection or another value to reject it for that reason. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - delegate PackageCatalogConnectionValidationResult PackageCatalogConnectionValidationHandler(PackageCatalogConnectionValidationEventArgs args); - - /// Status of the Connect call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum ConnectResultStatus - { - Ok, - CatalogError, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - SourceAgreementsNotAccepted, - } - }; - - /// Result of the Connect call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass ConnectResult - { - /// Error codes - ConnectResultStatus Status { get; }; - - PackageCatalog PackageCatalog { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - { - /// The error code of the operation. - HRESULT ExtendedErrorCode{ get; }; - } - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - enum RefreshPackageCatalogStatus - { - Ok, - GroupPolicyError, - CatalogError, - InternalError, - }; - - /// IMPLEMENTATION NOTE: RefreshPackageCatalogResult - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass RefreshPackageCatalogResult - { - RefreshPackageCatalogStatus Status { get; }; - - /// Error codes - HRESULT ExtendedErrorCode { get; }; - }; - - /// A reference to a catalog that callers can try to Connect. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalogReference - { - /// Gets a value indicating whether this package catalog is a composite of other package catalogs, - /// and thus the packages may come from disparate package catalogs as well. - Boolean IsComposite { get; }; - - /// The details of the package catalog if it is not a composite. - PackageCatalogInfo Info { get; }; - - /// Opens a catalog. Required before searching. For remote catalogs (i.e. not Installed and Installing) this - /// may require downloading information from a server. - Windows.Foundation.IAsyncOperation ConnectAsync(); - ConnectResult Connect(); - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - { - /// A string that will be passed to the source server if using a REST source - String AdditionalPackageCatalogArguments; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - /// Gets the required agreements for connecting to the package catalog (source). - Windows.Foundation.Collections.IVectorView SourceAgreements { get; }; - - Boolean AcceptSourceAgreements; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 8)] - { - /// Time interval for package catalog to check for an update. Setting to zero will disable the check for update. - Windows.Foundation.TimeSpan PackageCatalogBackgroundUpdateInterval; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 9)] - { - /// When set to true, the opened catalog will only provide the information regarding packages installed from this catalog. - /// In this mode, no external resources should be required. - Boolean InstalledPackageInformationOnly; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - { - /// Authentication arguments used in authentication flow during package catalog operations if applicable. - /// This is user or caller input. - AuthenticationArguments AuthenticationArguments; - - /// Authentication info from the package catalog. - /// This is defined by individual package catalog. - AuthenticationInfo AuthenticationInfo { get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - /// Updates the package catalog. - /// The progress value, represented as a double, indicates the percentage of update package catalog operation completion. - /// The progress range is from 0 to 100. - Windows.Foundation.IAsyncOperationWithProgress RefreshPackageCatalogAsync(); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// A callback invoked to validate the server certificate during Connect or ConnectAsync. - /// Only available to in-process callers; out-of-process callers will receive E_ACCESSDENIED on set. - /// If the BypassCertificatePinningForMicrosoftStore group policy is disabled, this cannot be set - /// for the MicrosoftStore catalog; attempting to do so produces APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY. - PackageCatalogConnectionValidationHandler ConnectionValidationHandler; - - /// Indicates whether the ConnectionValidationHandler can be set for this catalog reference. - /// Returns false if setting the handler would be blocked by policy (e.g., the - /// BypassCertificatePinningForMicrosoftStore group policy is disabled for the MicrosoftStore catalog). - Boolean IsConnectionValidationHandlerEnabled { get; }; - } - } - - /// Catalogs with PackageCatalogOrigin Predefined - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PredefinedPackageCatalog - { - OpenWindowsCatalog, - MicrosoftStore, - DesktopFrameworks, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - { - OpenWindowsCatalogFont, - }, - }; - - /// Local Catalogs with PackageCatalogOrigin Predefined - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum LocalPackageCatalog - { - InstalledPackages, - InstallingPackages - }; - - /// Options for creating a composite catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass CreateCompositePackageCatalogOptions - { - CreateCompositePackageCatalogOptions(); - - /// Create a composite catalog to allow searching a user defined or pre defined source - /// and a local source (Installed packages) together - IVector Catalogs { get; }; - /// Sets the default search behavior if the catalog is a composite catalog. - CompositeSearchBehavior CompositeSearchBehavior; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Create installed package catalog with required installed scope. - PackageInstallScope InstalledScope; - } - } - - /// Required install scope for the package. If the package does not have an installer that - /// supports the specified scope the Install call will fail with InstallResultStatus.NoApplicableInstallers - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallScope - { - /// An installer with any install scope is valid. - Any, - /// Only User install scope installers are valid - User, - /// Only System installers will be valid - System, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Both User and Unknown install scope installers are valid - UserOrUnknown, - /// Both System and Unknown install scope installers are valid - SystemOrUnknown, - } - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallMode - { - /// The default experience for the installer. Installer may show some UI. - Default, - /// Runs the installer in silent mode. This suppresses the installer's UI to the extent - /// possible (installer may still show some required UI). - Silent, - /// Runs the installer in interactive mode. - Interactive, - }; - - /// Options when installing a package. - /// Intended to allow full compatibility with the "winget install" command line interface. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass InstallOptions - { - InstallOptions(); - - /// Optionally specifies the version from the package to install. If unspecified, the CatalogPackage.DefaultInstallVersion - /// version is used. DefaultInstallVersion is the latest applicable version of the package. DefaultInstallVersion may be - /// empty if there's no applicable version. In that case, install attempts without setting this PackageVersionId - /// will return No Applicable Installer error code. - PackageVersionId PackageVersionId; - - /// Specifies alternate location to install package (if supported). - String PreferredInstallLocation; - /// User or Machine. - PackageInstallScope PackageInstallScope; - /// Silent, Interactive, or Default - PackageInstallMode PackageInstallMode; - /// Directs the logging to a log file. If provided, the installer must have write access to the file - String LogOutputPath; - /// Continues the install even if the hash in the catalog does not match the linked installer. - Boolean AllowHashMismatch; - /// A string that will be passed to the installer. - /// IMPLEMENTATION NOTE: maps to "--override" in the winget cmd line - String ReplacementInstallerArguments; - - /// Used by a caller to correlate the install with a caller's data. - /// The string must be JSON encoded. - String CorrelationData; - /// A string that will be passed to the source server if using a REST source - String AdditionalPackageCatalogArguments; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - { - /// The set of allowed Architectures, in preference order, that will be considered for - /// the install operation. Initially the vector contains the default allowed architectures - /// in the default preference order for the current system. It is allowed to have repeated - /// values in the list, to make prepending a preference override easier. Instances of an - /// architecture after the first will simply be ignored. - Windows.Foundation.Collections.IVector AllowedArchitectures { get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - /// Allow the upgrade to continue for upgrade packages with manifest versions Unknown. - Boolean AllowUpgradeToUnknownVersion; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Force the operation to continue upon non security related failures. - Boolean Force; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - /// A string that will be passed to the installer - /// IMPLEMENTATION NOTE: maps to "--custom" in the winget cmd line - String AdditionalInstallerArguments; - - /// Accept the package agreements required for installation. - Boolean AcceptPackageAgreements; - - /// Bypasses the Disabled Store Policy - Boolean BypassIsStoreClientBlockedPolicyCheck; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - { - /// Skip installing the dependencies for the package. - Boolean SkipDependencies; - - /// The package installer type. - PackageInstallerType InstallerType; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - /// Authentication arguments used when downloading the package installer if authentication is required. - AuthenticationArguments AuthenticationArguments; - } - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - enum PackageUninstallMode - { - /// The default experience for the installer. Installer may show some UI. - Default, - /// Runs the installer in silent mode. This suppresses the installer's UI to the extent - /// possible (installer may still show some required UI). - Silent, - /// Runs the installer in interactive mode. - Interactive, - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - enum PackageUninstallScope - { - /// Use default uninstall behavior. - Any, - /// Uninstall for current user. Currently only applicable to msix. - User, - /// Uninstall for all users. Currently only applicable to msix. - System, - }; - - /// Options when uninstalling a package. - /// Intended to allow full compatibility with the "winget uninstall" command line interface. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - runtimeclass UninstallOptions - { - UninstallOptions(); - - /// This property is not currently used. The version of CatalogPackage.InstalledVersion is used for uninstall. - PackageVersionId PackageVersionId; - - /// Silent, Interactive, or Default - PackageUninstallMode PackageUninstallMode; - - /// Directs the logging to a log file. If provided, the installer must have write access to the file - String LogOutputPath; - - /// Used by a caller to correlate the install with a caller's data. - /// The string must be JSON encoded. - String CorrelationData; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Force the operation to continue upon non security related failures. - Boolean Force; - // The scope the uninstall will perform. Currently only applicable to msix. - PackageUninstallScope PackageUninstallScope; - } - } - - /// The Windows platform type. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - enum WindowsPlatform - { - /// An unknown platform - Unknown, - /// Windows.Universal - Universal, - /// Windows.Desktop - Desktop, - /// Windows.IoT - IoT, - /// Windows.Team - Team, - /// Windows.Holographic - Holographic, - }; - - /// Options when downloading a package. - /// Intended to allow full compatibility with the "winget download" command line interface. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - runtimeclass DownloadOptions - { - DownloadOptions(); - - /// Optionally specifies the version from the package to download. If unspecified the version matching - /// CatalogPackage.GetLatestVersion() is used. - PackageVersionId PackageVersionId; - - /// The package installer type. - PackageInstallerType InstallerType; - - /// The package installer scope. - PackageInstallScope Scope; - - /// The package installer architecture. - Windows.System.ProcessorArchitecture Architecture; - - /// The package installer locale. - String Locale; - - /// The directory where the installers are downloaded to. - String DownloadDirectory; - - /// Continues the download even if the hash in the catalog does not match the linked installer. - Boolean AllowHashMismatch; - - /// Skip downloading the dependencies for the package. - Boolean SkipDependencies; - - /// Accept the package agreements required for download. - Boolean AcceptPackageAgreements; - - /// Used by a caller to correlate the download with a caller's data. - /// The string must be JSON encoded. - String CorrelationData; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - /// Authentication arguments used when downloading the package installer if authentication is required. - AuthenticationArguments AuthenticationArguments; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - { - /// If the package is licensed from the Microsoft Store, setting this value to true will not attempt to download the license file. - Boolean SkipMicrosoftStoreLicense; - - /// The platform to download the package for. - WindowsPlatform Platform; - - /// When applicable, uses the provided value as the target OS version for the download. - String TargetOSVersion; - } - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - enum PackageRepairMode - { - /// The default experience for the installer. Installer may show some UI. - Default, - /// Runs the installer in silent mode. This suppresses the installer's UI to the extent - /// possible (installer may still show some required UI). - Silent, - /// Runs the installer in interactive mode. - Interactive, - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - enum PackageRepairScope - { - /// Use default repair behavior. - Any, - /// Repair for current user. Currently only applicable to msix. - User, - /// Repair for all users. - System, - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - runtimeclass RepairOptions - { - RepairOptions(); - - /// This property is not currently used. The version of CatalogPackage.InstalledVersion is used for repair. - PackageVersionId PackageVersionId; - - /// The package Repair scope. - PackageRepairScope PackageRepairScope; - - /// The package repair mode. - PackageRepairMode PackageRepairMode; - - /// Optional parameter specifying Accept the package agreements required for download. - Boolean AcceptPackageAgreements; - - /// Used by a caller to correlate the repair with a caller's data. - /// The string must be JSON encoded. - String CorrelationData; - - /// Continues the download even if the hash in the catalog does not match the linked installer used for repair. - Boolean AllowHashMismatch; - - /// Directs the logging to a log file. If provided, the installer must have write access to the file - String LogOutputPath; - - /// Force the operation to continue upon non security related failures. - Boolean Force; - - /// Bypasses the Disabled Store Policy - Boolean BypassIsStoreClientBlockedPolicyCheck; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - /// Authentication arguments used when downloading the package installer if authentication is required. - AuthenticationArguments AuthenticationArguments; - } - } - - /// IMPLEMENTATION NOTE: Documentation from AppInstaller::Manifest::Documentation - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass Documentation - { - String DocumentLabel { get; }; - - String DocumentUrl { get; }; - } - - /// Icon resolution - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - enum IconResolution - { - Custom, - Square16, - Square20, - Square24, - Square30, - Square32, - Square36, - Square40, - Square48, - Square60, - Square64, - Square72, - Square80, - Square96, - Square256, - }; - - /// Icon file type - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - enum IconFileType - { - Unknown, - Jpeg, - Png, - Ico, - }; - - /// Icon theme - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - enum IconTheme - { - Unknown, - Default, - Light, - Dark, - HighContrast, - }; - - /// IMPLEMENTATION NOTE: Icon from AppInstaller::Manifest::Icon - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass Icon - { - String Url { get; }; - - IconFileType FileType{ get; }; - - IconResolution Resolution{ get; }; - - IconTheme Theme{ get; }; - - UInt8[] Sha256 { get; }; - } - - /// IMPLEMENTATION NOTE: SourceAgreement from AppInstaller::Manifest::SourceAgreement - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass SourceAgreement - { - String Label { get; }; - - String Text { get; }; - - String Url { get; }; - } - - /// IMPLEMENTATION NOTE: PackageAgreement from AppInstaller::Manifest::Agreement - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass PackageAgreement - { - String Label { get; }; - - String Text { get; }; - - String Url { get; }; - } - - /// IMPLEMENTATION NOTE: CatalogPackageMetadata from AppInstaller::Manifest::Localization - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass CatalogPackageMetadata - { - String Locale { get; }; - - String Publisher { get; }; - - String PublisherUrl { get; }; - - String PublisherSupportUrl { get; }; - - String PrivacyUrl { get; }; - - String Author { get; }; - - String PackageName { get; }; - - String PackageUrl { get; }; - - String License { get; }; - - String LicenseUrl { get; }; - - String Copyright { get; }; - - String CopyrightUrl { get; }; - - String ShortDescription { get; }; - - String Description { get; }; - - Windows.Foundation.Collections.IVectorView Tags { get; }; - - Windows.Foundation.Collections.IVectorView Agreements { get; }; - - Windows.Foundation.Collections.IVectorView Documentations { get; }; - - Windows.Foundation.Collections.IVectorView Icons { get; }; - - String ReleaseNotes { get; }; - - String ReleaseNotesUrl { get; }; - - String PurchaseUrl { get; }; - - String InstallationNotes { get; }; - } - - /// IMPLEMENTATION NOTE: AddPackageCatalogOptions - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass AddPackageCatalogOptions - { - AddPackageCatalogOptions(); - - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name; - - /// The SourceUri used when adding the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" - /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" - String SourceUri; - - /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" - /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". - /// For contoso sample on msdn "Microsoft.PreIndexed.Package" - String Type; - - /// The trust level of the catalog to add. - PackageCatalogTrustLevel TrustLevel; - - /// Custom header to pass to the catalog. - String CustomHeader; - - /// Excludes a source from discovery unless specified. - Boolean Explicit; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// The priority of this catalog. Higher values are sorted first. - Int32 Priority; - } - }; - - /// IMPLEMENTATION NOTE: AddPackageCatalogStatus - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - enum AddPackageCatalogStatus - { - Ok, - GroupPolicyError, - CatalogError, - InternalError, - InvalidOptions, - AccessDenied, - AuthenticationError, - }; - - /// IMPLEMENTATION NOTE: AddPackageCatalogResult - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass AddPackageCatalogResult - { - AddPackageCatalogStatus Status { get; }; - - /// Error codes - HRESULT ExtendedErrorCode { get; }; - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogOptions - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass RemovePackageCatalogOptions - { - RemovePackageCatalogOptions(); - - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name; - - /// By default, the value is 'false', resulting in the removal of the package catalog registration - /// from the winget Package catalogs list and the deletion of all associated system artifacts. This - /// mirrors the WinGet Source remove operation on a specific Package Catalog. - /// If set to 'true', it removes the package catalog registration from the Windows Package Catalogs - /// list without any cleanup, similar to the WinGet source reset operation on a specific Package - /// Catalog. - Boolean PreserveData; - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogStatus - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - enum RemovePackageCatalogStatus - { - Ok, - GroupPolicyError, - CatalogError, - InternalError, - AccessDenied, - InvalidOptions, - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogResult - /// Result of removing a package catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass RemovePackageCatalogResult - { - RemovePackageCatalogStatus Status { get; }; - - /// Error codes - HRESULT ExtendedErrorCode { get; }; - }; - - /// IMPLEMENTATION NOTE: EditPackageCatalogOptions - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - runtimeclass EditPackageCatalogOptions - { - EditPackageCatalogOptions(); - - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name; - - /// Editing the Explicit property has three states: true, false, and not specified (null). - Windows.Foundation.IReference Explicit; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// The priority of this catalog. Higher values are sorted first. - Windows.Foundation.IReference Priority; - } - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogStatus - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - enum EditPackageCatalogStatus - { - Ok, - GroupPolicyError, - CatalogError, - InternalError, - AccessDenied, - InvalidOptions, - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogResult - /// Result of editing a package catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - runtimeclass EditPackageCatalogResult - { - EditPackageCatalogStatus Status { get; }; - - /// Error codes - HRESULT ExtendedErrorCode { get; }; - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageManager - { - PackageManager(); - - /// Get the available catalogs. Each source will have a separate catalog. - /// This does not open the catalog. These catalogs can be used individually or merged with CreateCompositePackageCatalogAsync. - /// IMPLEMENTATION NOTE: This is a list of sources returned by Windows Package Manager source list - Windows.Foundation.Collections.IVectorView GetPackageCatalogs(); - /// Get a built in catalog - PackageCatalogReference GetPredefinedPackageCatalog(PredefinedPackageCatalog predefinedPackageCatalog); - /// Get a built in catalog - PackageCatalogReference GetLocalPackageCatalog(LocalPackageCatalog localPackageCatalog); - /// Get a catalog by a known name - PackageCatalogReference GetPackageCatalogByName(String catalogName); - /// Get a composite catalog to allow searching a user defined or pre defined source and a local source - /// (Installing, Installed) together at the same time. - PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options); - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - /// Add a catalog to the Windows Package Catalogs. - /// The progress value, represented as a double, indicates the percentage of add package catalog operation completion. - /// The progress range is from 0 to 100. - Windows.Foundation.IAsyncOperationWithProgress AddPackageCatalogAsync(AddPackageCatalogOptions options); - - /// Unregisters a Package Catalog from the Windows Package Catalogs and eliminates the system artifacts based on the provided options. - /// The progress value, represented as a double, indicates the percentage of remove package catalog operation completion. - /// The progress range is from 0 to 100. - Windows.Foundation.IAsyncOperationWithProgress RemovePackageCatalogAsync(RemovePackageCatalogOptions options); - } - - /// Install the specified package - Windows.Foundation.IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options); - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - { - /// Get install progress - Windows.Foundation.IAsyncOperationWithProgress GetInstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - /// Upgrade the specified package - Windows.Foundation.IAsyncOperationWithProgress UpgradePackageAsync(CatalogPackage package, InstallOptions options); - - /// Uninstall the specified package - Windows.Foundation.IAsyncOperationWithProgress UninstallPackageAsync(CatalogPackage package, UninstallOptions options); - - /// Get uninstall progress - Windows.Foundation.IAsyncOperationWithProgress GetUninstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - { - // Download the specified package - Windows.Foundation.IAsyncOperationWithProgress DownloadPackageAsync(CatalogPackage package, DownloadOptions options); - - // Get download progress - Windows.Foundation.IAsyncOperationWithProgress GetDownloadProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - { - // Repair the specified package - Windows.Foundation.IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - { - // The version of the Windows Package Manager that is running. - String Version{ get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - { - /// Edit an existing Windows Package Catalog. - EditPackageCatalogResult EditPackageCatalog(EditPackageCatalogOptions options); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] - { - /// Get all pins across all sources. - Windows.Foundation.Collections.IVectorView GetAllPins(); - - /// Get the pins associated with the specified package. - /// Returns pins for all sources the package is available from, and for the installed - /// package identity if InstalledVersion is present. - Windows.Foundation.Collections.IVectorView GetPins(CatalogPackage package); - - /// Add or update a pin for the specified package. - /// Requires Force = true to overwrite an existing pin of a different type. - PinPackageResult PinPackage(CatalogPackage package, PinPackageOptions options); - - /// Remove all pins associated with the specified package. - /// Returns PackagePinNotFound if no pin exists for the package. - PinPackageResult UnpinPackage(CatalogPackage package); - - /// Reset (remove) all pins. If packageCatalogReference is non-null, only pins from that - /// catalog are removed; otherwise all pins are removed. - PinPackageResult ResetAllPins(PackageCatalogReference packageCatalogReference); - } - } - - /// IMPLEMENTATION NOTE: Pinning::PinType from AppInstaller::Pinning - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] - enum PackagePinType - { - /// Unknown pin type or not pinned. - Unknown, - /// Pinned by the manifest using the RequiresExplicitUpgrade field. - /// Behaves the same as Pinning. - PinnedByManifest, - /// The package is excluded from upgrade --all, unless --include-pinned is added. - /// upgrade is not blocked. - Pinning, - /// The package is pinned to a specific version range. - Gating, - /// The package is blocked from upgrade --all and upgrade . - /// User must unpin to allow update. - Blocking, - }; - - /// IMPLEMENTATION NOTE: Pinning::Pin from AppInstaller::Pinning - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] - runtimeclass PackagePin - { - /// The package ID that the pin applies to (for available-package pins) or the - /// ProductCode/PackageFamilyName (for installed-package pins). - String PackageId { get; }; - - /// The source identifier the pin applies to. Empty for installed-package pins. - String SourceId { get; }; - - /// The type of the pin. - PackagePinType Type { get; }; - - /// The gated version range. Only meaningful when Type is Gating; empty otherwise. - String GatedVersion { get; }; - - /// The UTC date/time when the pin was added. Null if not set. - Windows.Foundation.IReference DateAdded { get; }; - - /// Optional note associated with the pin. May be empty. - String Note { get; }; - - /// True if this pin is keyed on the installed package identity - /// (ProductCode or PackageFamilyName) rather than available package ID + source. - Boolean IsForInstalledPackage { get; }; - }; - - /// Options for adding or updating a package pin. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] - runtimeclass PinPackageOptions - { - PinPackageOptions(); - - /// The type of pin to create. Required. - PackagePinType PinType; - - /// The gated version range. Required when PinType is Gating; ignored otherwise. - /// SAMPLE VALUE: "1.0.*" or "<2.0" - String GatedVersion; - - /// When true, the pin is keyed on the installed package identity (ProductCode or - /// PackageFamilyName) instead of the available package ID and source. - /// Mirrors the winget pin add --installed flag. Default is false. - Boolean PinInstalledPackage; - - /// When true, an existing pin with a different type will be overwritten. - /// When false and a pin already exists with a different type, the operation returns - /// PinResultStatus::PackagePinAlreadyExists. Default is false. - Boolean Force; - - /// Optional note to store with the pin. - String Note; - }; - - /// Status of a pin operation. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] - enum PinResultStatus - { - Ok, - BlockedByPolicy, - InternalError, - InvalidOptions, - /// Returned by UnpinPackage when no pin exists for the package. - PackagePinNotFound, - /// Returned by PinPackage when a pin with a different type already exists - /// and Force is false. - PackagePinAlreadyExists, - }; - - /// Result of a pin operation. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] - runtimeclass PinPackageResult - { - PinResultStatus Status { get; }; - - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - }; - - /// Global settings for PackageManager operations. - /// This settings should be invoked prior to invocation of PackageManager class. - /// This settings is only exposed in in-proc Com invocation. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - runtimeclass PackageManagerSettings - { - PackageManagerSettings(); - - /// Sets caller name to be used in telemetry logging. Default value is the calling process name. - /// Call this before any PackageManager operations. - /// Returns true if successful, false if caller name is already set. - /// This is a one time setup, multiple calls will not override existing caller name. - Boolean SetCallerIdentifier(String callerIdentifier); - - /// Sets state name for state separation. If not set, state will be written in a default location and states may be affected by other callers. - /// Call this before any PackageManager operations. - /// Returns true if successful, false if state name is already set. - /// This is a one time setup, multiple calls will not override existing state name. - Boolean SetStateIdentifier(String stateIdentifier); - - /// Sets custom UserSettings. - /// Returns true if successful, false if settingsContent cannot be parsed or UserSettings is already created. - /// This is a one time setup, multiple calls will not override existing UserSettings. - Boolean SetUserSettings(String settingsContent); - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - { - /// Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not. - /// This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if - /// there are active objects at all times. If set to true it will allow the unload when there are no - /// active objects. - /// Defaults to true. - Boolean CanUnloadPreference{ get; set; }; - - /// Gets or sets a value indicating whether the module should listen for termination signals (CTRL+C, window messages, package updates) - /// and begin the process of cancelling active operations and preventing new ones. - /// If set to false, the caller is responsible for handling these termination signals and cancelling active operations as necessary. - /// Set this to the desired state before any PackageManager operations. Changing it after the first operation for the process may have undefined behavior. - /// Defaults to true. - Boolean TerminationSignalMonitoring{ get; set; }; - } - } - - /// Force midl3 to generate vector marshalling info. - declare - { - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Microsoft.Management.Deployment +{ + [contractversion(30)] // For version 1.30 + apicontract WindowsPackageManagerContract{}; + + /// State of the install + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallProgressState + { + /// The install is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from downloading or installing. + Queued, + /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will + /// end the download and prevent the package from installing. + Downloading, + /// The install is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not + /// stop the installation or the post install cleanup. + Installing, + /// The installer has completed and cleanup actions are in progress. Cancellation of the + /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the install. + PostInstall, + /// The operation has completed. + Finished, + }; + + /// Progress object for the install + /// DESIGN NOTE: percentage for the install as a whole is purposefully not included as there is no way to + /// estimate progress when the installer is running. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + struct InstallProgress + { + /// State of the install + PackageInstallProgressState State; + /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes downloaded if known + UInt64 BytesDownloaded; + /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes required if known + UInt64 BytesRequired; + /// Download percentage completed + Double DownloadProgress; + /// Install percentage if known. + Double InstallationProgress; + }; + + /// Status of the Install call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum InstallResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + DownloadError, + InstallError, + ManifestError, + NoApplicableInstallers, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + NoApplicableUpgrade, + }, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + PackageAgreementsNotAccepted, + } + }; + + /// Result of the install + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass InstallResult + { + /// Used by a caller to correlate the install with a caller's data. + String CorrelationData { get; }; + /// Whether a restart is required to complete the install. + Boolean RebootRequired { get; }; + + /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED + InstallResultStatus Status { get; }; + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + /// The error code from the install attempt. Only valid if the Status is InstallError. + /// This value's meaning will require knowledge of the specific installer or install technology. + UInt32 InstallerErrorCode { get; }; + } + } + + /// State of the uninstall + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + enum PackageUninstallProgressState + { + /// The uninstall is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from uninstalling. + Queued, + /// The uninstall is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not + /// stop the installation or the post uninstall steps. + Uninstalling, + /// The uninstaller has completed and cleanup actions are in progress. Cancellation of the + /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the uninstall. + PostUninstall, + /// The operation has completed. + Finished, + }; + + /// Progress object for the uninstall + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + struct UninstallProgress + { + /// State of the uninstall + PackageUninstallProgressState State; + /// Uninstall percentage if known. + Double UninstallationProgress; + }; + + /// Status of the uninstall call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + enum UninstallResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + UninstallError, + ManifestError, + }; + + /// Result of the uninstall + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + runtimeclass UninstallResult + { + /// Used by a caller to correlate the install with a caller's data. + String CorrelationData { get; }; + /// Whether a restart is required to complete the install. + Boolean RebootRequired { get; }; + + /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED + UninstallResultStatus Status { get; }; + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + + /// The error code from the uninstall attempt. Only valid if the Status is UninstallError. + /// This value's meaning will require knowledge of the specific uninstaller or install technology. + UInt32 UninstallerErrorCode { get; }; + } + + /// State of the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairProgressState + { + /// The repair is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from repairing. + Queued, + /// The repair is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not + /// stop the repair or the post repair steps. + Repairing, + /// The repair has completed and cleanup actions are in progress. Cancellation of the + /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the repair. + PostRepair, + /// The operation has completed. + Finished, + }; + + /// Progress object for the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + struct RepairProgress + { + /// State of the repair + PackageRepairProgressState State; + + /// Repair percentage if known. + Double RepairCompletionProgress; + }; + + /// Status of the repair call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + /// DESIGN NOTE: RepairResultStatus from AppInstallerErrors.h is not implemented in V1. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum RepairResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + DownloadError, + InternalError, + InvalidOptions, + RepairError, + ManifestError, + NoApplicableRepairer, + PackageAgreementsNotAccepted, + }; + + /// Result of the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + runtimeclass RepairResult + { + /// Used by a caller to correlate the repair with a caller's data. + String CorrelationData { get; }; + + /// Whether a restart is required to complete the repair. + Boolean RebootRequired { get; }; + + /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED + RepairResultStatus Status { get; }; + + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + + /// The error code from the repair attempt. Only valid if the Status is RepairError. + /// This value's meaning will require knowledge of the specific repairer or repair technology. + UInt32 RepairerErrorCode { get; }; + } + + /// State of the download + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + enum PackageDownloadProgressState + { + /// The download is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from downloading. + Queued, + /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will + /// end the download. + Downloading, + /// The operation has completed. + Finished, + }; + + /// Status of the download call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + enum DownloadResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + DownloadError, + ManifestError, + NoApplicableInstallers, + PackageAgreementsNotAccepted, + }; + + /// Result of the download + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + runtimeclass DownloadResult + { + /// Used by a caller to correlate the download with a caller's data. + String CorrelationData { get; }; + + /// Batched error code. + DownloadResultStatus Status { get; }; + + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + }; + + /// Progress object for the uninstall + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + struct PackageDownloadProgress + { + /// State of the download + PackageDownloadProgressState State; + + /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes downloaded if known + UInt64 BytesDownloaded; + + /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes required if known + UInt64 BytesRequired; + + /// Download percentage completed + Double DownloadProgress; + }; + + /// IMPLEMENTATION NOTE: SourceOrigin from winget/RepositorySource.h + /// Defines the origin of the package catalog details. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageCatalogOrigin + { + /// Predefined means it came as part of the Windows Package Manager package and cannot be removed. + Predefined, + /// User means it was added by the user and could be removed. + User, + }; + + /// IMPLEMENTATION NOTE: SourceTrustLevel from winget/RepositorySource.h + /// Defines the trust level of the package catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageCatalogTrustLevel + { + None, + Trusted, + }; + + /// IMPLEMENTATION NOTE: SourceDetails from winget/RepositorySource.h + /// Interface for retrieving information about an package catalog without acting on it. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalogInfo + { + /// The package catalog's unique identifier. + /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.Winget.Source_8wekyb3d8bbwe" + /// For contoso sample on msdn "contoso" + String Id { get; }; + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name { get; }; + /// The type of the package catalog. + /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" + /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". + /// For contoso sample on msdn "Microsoft.PreIndexed.Package" + String Type { get; }; + /// The argument used when adding the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" + /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" + String Argument { get; }; + /// The last time that this package catalog was updated. + Windows.Foundation.DateTime LastUpdateTime { get; }; + /// The origin of the package catalog. + PackageCatalogOrigin Origin { get; }; + /// The trust level of the package catalog + PackageCatalogTrustLevel TrustLevel { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + /// Excludes a source from discovery unless specified. + Boolean Explicit{ get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// The priority of this catalog. Higher values are sorted first. + Int32 Priority{ get; }; + } + } + + /// A metadata item of a package version. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageVersionMetadataField + { + /// The InstallerType of an installed package + InstallerType, + /// The Scope of an installed package + InstalledScope, + /// The system path where the package is installed + InstalledLocation, + /// The standard uninstall command; which may be interactive + StandardUninstallCommand, + /// An uninstall command that should be non-interactive + SilentUninstallCommand, + /// The publisher of the package + PublisherDisplayName, + }; + + /// The result of a comparison. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + enum CompareResult + { + /// The comparison did not result in a succesful ordering. + Unknown, + /// The object value is lesser than the given value. + Lesser, + /// The object value is equal to the given value. + Equal, + /// The object value is greater than the given value. + Greater, + }; + + /// IMPLEMENTATION NOTE: IPackageVersion from winget/RepositorySearch.h + /// A single package version. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageVersionInfo + { + /// IMPLEMENTATION NOTE: PackageVersionMetadata fields from winget/RepositorySearch.h + /// Gets any metadata associated with this package version. + /// Primarily stores data on installed packages. + /// Metadata fields may have no value (e.g. packages that aren't installed will not have an InstalledLocation). + String GetMetadata(PackageVersionMetadataField metadataField); + /// IMPLEMENTATION NOTE: PackageVersionProperty fields from winget/RepositorySearch.h + String Id { get; }; + String DisplayName { get; }; + String Version { get; }; + String Channel { get; }; + /// DESIGN NOTE: RelativePath from winget/RepositorySearch.h is excluded as not needed. + /// String RelativePath; + + /// IMPLEMENTATION NOTE: PackageVersionMultiProperty fields from winget/RepositorySearch.h + /// PackageFamilyName and ProductCode can have multiple values. + Windows.Foundation.Collections.IVectorView PackageFamilyNames { get; }; + Windows.Foundation.Collections.IVectorView ProductCodes { get; }; + + /// Gets the package catalog where this package version is from. + PackageCatalog PackageCatalog { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + { + /// Compares the given value against the package version of this object, with the result being + /// the enum value that represents where PackageVersionInfo::Version is ordered relative to the + /// versionString. "if (this.CompareToVersion(that) == Greater)" can be thought of as reading + /// the sentence "If this is compared to version that and is found to be greater". + /// IE if PackageVersionInfo::Version returns "2", then CompareToVersion("1") will return Greater. + /// Passing in an empty string will result in Unknown. + CompareResult CompareToVersion(String versionString); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + /// Checks if this package version has at least one applicable installer. + Boolean HasApplicableInstaller(InstallOptions options); + + /// Gets the publisher string for this package version, if one is available. + String Publisher { get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + /// Gets the package catalog metadata of this package version with the default localization based on user settings. + CatalogPackageMetadata GetCatalogPackageMetadata(); + + /// Gets the package catalog metadata of this package version with the preferred locale. + CatalogPackageMetadata GetCatalogPackageMetadata(String preferredLocale); + + /// Gets the applicable installer for this package version. + PackageInstallerInfo GetApplicableInstaller(InstallOptions options); + } + } + + /// IMPLEMENTATION NOTE: PackageVersionKey from winget/RepositorySearch.h + /// A key to identify a package version within a package. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageVersionId + { + /// The package catalog id that this version came from. + String PackageCatalogId { get; }; + /// The version. + String Version { get; }; + /// The channel. + String Channel { get; }; + }; + + /// The package installer type. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum PackageInstallerType + { + /// Unknown type. + Unknown, + /// Inno type. + Inno, + /// Wix type. + Wix, + /// Msi type. + Msi, + /// Nullsoft type. + Nullsoft, + /// Zip type. + Zip, + /// Msix or Appx type. + Msix, + /// Exe type. + Exe, + /// Burn type. + Burn, + /// MSStore type. + MSStore, + /// Portable type. + Portable, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + { + /// Font type. + Font, + }, + }; + + /// The package installer scope. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum PackageInstallerScope + { + /// Scope not declared. + Unknown, + /// User scope. + User, + /// System scope. + System, + }; + + /// The package installer elevation requirement. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum ElevationRequirement + { + /// Elevation requirement not declared. + Unknown, + /// Package installer requires elevation. + ElevationRequired, + /// Package installer prohibits elevation. + ElevationProhibited, + /// Package installer elevates self. + ElevatesSelf, + }; + + /// Interface for retrieving information about a package installer. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + runtimeclass PackageInstallerInfo + { + /// The package installer type. + PackageInstallerType InstallerType { get; }; + /// The nested package installer type for archives. + PackageInstallerType NestedInstallerType { get; }; + /// The package installer architecture. + Windows.System.ProcessorArchitecture Architecture { get; }; + /// The package installer scope. + PackageInstallerScope Scope { get; }; + /// The package installer locale. + String Locale { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + /// The package installer elevation requirement. + ElevationRequirement ElevationRequirement { get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Authentication info from the package installer. + AuthenticationInfo AuthenticationInfo { get; }; + } + }; + + /// The installed status type. The values need to match InstalledStatusType from winget/RepositorySearch.h. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + [flags] + enum InstalledStatusType + { + /// None is checked. + None = 0x0, + /// Check Apps and Features entry. + AppsAndFeaturesEntry = 0x0001, + /// Check Apps and Features entry install location if applicable. + AppsAndFeaturesEntryInstallLocation = 0x0002, + /// Check Apps and Features entry install location with installed files if applicable. + AppsAndFeaturesEntryInstallLocationFile = 0x0004, + /// Check default install location if applicable. + DefaultInstallLocation = 0x0008, + /// Check default install location with installed files if applicable. + DefaultInstallLocationFile = 0x0010, + + /// Below are helper values for calling CheckInstalledStatus as input. + /// AppsAndFeaturesEntry related checks + AllAppsAndFeaturesEntryChecks = AppsAndFeaturesEntry | AppsAndFeaturesEntryInstallLocation | AppsAndFeaturesEntryInstallLocationFile, + /// DefaultInstallLocation related checks + AllDefaultInstallLocationChecks = DefaultInstallLocation | DefaultInstallLocationFile, + /// All checks + AllChecks = AllAppsAndFeaturesEntryChecks | AllDefaultInstallLocationChecks, + }; + + /// Interface representing an individual installed status. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + runtimeclass InstalledStatus + { + /// The installed status type. + InstalledStatusType Type { get; }; + /// The installed status path. + String Path { get; }; + /// The installed status result. + HRESULT Status { get; }; + }; + + /// Interface for retrieving information about a package installer installed status. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + runtimeclass PackageInstallerInstalledStatus + { + /// The package installer info. + PackageInstallerInfo InstallerInfo { get; }; + /// A list of various types of installed status of the package installer. + Windows.Foundation.Collections.IVectorView InstallerInstalledStatus { get; }; + }; + + /// Status of the check installed status call. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum CheckInstalledStatusResultStatus + { + Ok, + InternalError, + }; + + /// Interface for retrieving information about a package installer installed status. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + runtimeclass CheckInstalledStatusResult + { + /// Status of the check installed status call. + CheckInstalledStatusResultStatus Status { get; }; + + /// A list of package installer installed status. + Windows.Foundation.Collections.IVectorView PackageInstalledStatus { get; }; + }; + + /// IMPLEMENTATION NOTE: IPackage from winget/RepositorySearch.h + /// A package, potentially containing information about it's local state and the available versions. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass CatalogPackage + { + /// IMPLEMENTATION NOTE: PackageProperty fields from winget/RepositorySearch.h + /// Gets a property of this package. + String Id { get; }; + String Name { get; }; + + /// Gets the installed package information if the package is installed. + PackageVersionInfo InstalledVersion { get; }; + + /// Gets all available versions of this package. Ordering is not guaranteed. + Windows.Foundation.Collections.IVectorView AvailableVersions { get; }; + + /// Gets the version of this package that will be installed if version is not set in InstallOptions. + PackageVersionInfo DefaultInstallVersion { get; }; + + /// Gets a specific version of this package. + PackageVersionInfo GetPackageVersionInfo(PackageVersionId versionKey); + + /// Gets a value indicating whether an available version is newer than the installed version. + Boolean IsUpdateAvailable { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Check the installed status of the package. For more accurate and complete installed status, it's required to + /// call this method from a composite package from a newly created package catalog with installed info. + /// This may require downloading information from a server. + Windows.Foundation.IAsyncOperation CheckInstalledStatusAsync(InstalledStatusType checkTypes); + CheckInstalledStatusResult CheckInstalledStatus(InstalledStatusType checkTypes); + Windows.Foundation.IAsyncOperation CheckInstalledStatusAsync(); + CheckInstalledStatusResult CheckInstalledStatus(); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// Determines the priority of the catalog for this package object. + /// This should match the priority of the DefaultInstallVersion, but it is much more efficient than using that route. + /// May be null if the package refers only to an installed item. + Windows.Foundation.IReference CatalogPriority { get; }; + } + } + + /// IMPLEMENTATION NOTE: CompositeSearchBehavior from winget/RepositorySource.h + /// Search behavior for composite catalogs. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum CompositeSearchBehavior + { + /// Search local catalogs only + LocalCatalogs, + /// Search remote catalogs only, don't check local catalogs for InstalledVersion + RemotePackagesFromRemoteCatalogs, + /// Search remote catalogs, and check local catalogs for InstalledVersion + RemotePackagesFromAllCatalogs, + /// Search both local and remote catalogs. + AllCatalogs, + }; + + /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageFieldMatchOption + { + Equals, + EqualsCaseInsensitive, + StartsWithCaseInsensitive, + ContainsCaseInsensitive, + }; + + /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h + /// The field to match on. + /// The values must be declared in order of preference in search results. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageMatchField + { + CatalogDefault, + Id, + Name, + Moniker, + Command, + Tag, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 3)] + { + PackageFamilyName, + ProductCode, + } + /// DESIGN NOTE: The following PackageFieldMatchOption from winget/RepositorySearch.h are not implemented in V1. + /// NormalizedNameAndPublisher, + }; + + /// IMPLEMENTATION NOTE: PackageMatchFilter from winget/RepositorySearch.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageMatchFilter + { + PackageMatchFilter(); + /// The type of string comparison for matching + PackageFieldMatchOption Option; + /// The field to search + PackageMatchField Field; + /// The value to match + String Value; + /// DESIGN NOTE: "Additional" from RequestMatch winget/RepositorySearch.h is not implemented here. + } + + /// IMPLEMENTATION NOTE: MatchResult from winget/RepositorySearch.h + /// A single result from the search. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass MatchResult + { + /// The package found by the search request. + CatalogPackage CatalogPackage { get; }; + + /// The highest order field on which the package matched the search. + PackageMatchFilter MatchCriteria { get; }; + } + + /// Status of the FindPackages call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum FindPackagesResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + { + AuthenticationError, + AccessDenied, + } + }; + + /// IMPLEMENTATION NOTE: SearchResult from winget/RepositorySearch.h + /// Search result data returned from FindPackages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass FindPackagesResult + { + /// Error codes + FindPackagesResultStatus Status{ get; }; + + /// The full set of results from the search. + Windows.Foundation.Collections.IVectorView Matches { get; }; + + /// If true, the results were truncated by the given ResultLimit + /// USAGE NOTE: Windows Package Manager does not support result pagination, there is no way to continue + /// getting more results. + Boolean WasLimitExceeded { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + /// The error code of the operation. + HRESULT ExtendedErrorCode{ get; }; + } + } + + /// Options for FindPackages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass FindPackagesOptions + { + FindPackagesOptions(); + + /// DESIGN NOTE: + /// This class maps to SearchRequest from winget/RepositorySearch.h + /// That class is a container for data used to filter the available manifests in an package catalog. + /// Its properties can be thought of as: + /// (Query || Inclusions...) && Filters... + /// If Query and Inclusions are both empty, the starting data set will be the entire database. + /// Everything && Filters... + /// That has been translated in this api so that + /// Inclusions are Selectors below + /// Filters are Filters below + /// Query is PackageFieldMatchOption::PackageCatalogDefined and in the Selector list. + /// USAGE NOTE: Only one selector with PackageFieldMatchOption::PackageCatalogDefined is allowed. + + /// Selectors = you have to match at least one selector (if there are no selectors, then nothing is selected) + Windows.Foundation.Collections.IVector Selectors { get; }; + /// Filters = you have to match all filters(if there are no filters, then there is no filtering of selected items) + Windows.Foundation.Collections.IVector Filters{ get; }; + + /// Restricts the length of the returned results to the specified count. + UInt32 ResultLimit; + } + + /// IMPLEMENTATION NOTE: Source from winget/RepositorySource.h + /// A catalog for searching for packages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalog + { + /// Gets a value indicating whether this package catalog is a composite of other package catalogs, + /// and thus the packages may come from disparate package catalogs as well. + Boolean IsComposite { get; }; + /// The details of the package catalog if it is not a composite. + PackageCatalogInfo Info { get; }; + + /// Searches for Packages in the catalog. + Windows.Foundation.IAsyncOperation FindPackagesAsync(FindPackagesOptions options); + FindPackagesResult FindPackages(FindPackagesOptions options); + } + + /// Authentication mode + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + enum AuthenticationMode + { + /// Always use interactive authentication flow on first authentication request, following requests may use cached result. + Interactive, + /// Try silent authentication flow first. If failed, use interactive authentication flow. + SilentPreferred, + /// Only use silent authentication flow. If failed, fail the authentication. + Silent, + }; + + /// Authentication related arguments + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + runtimeclass AuthenticationArguments + { + AuthenticationArguments(); + + /// Choice of authentication flow behavior. + AuthenticationMode AuthenticationMode; + /// Optional. The authentication account to be used for authentication. + String AuthenticationAccount; + } + + /// Authentication method + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + enum AuthenticationType + { + Unknown, + None, + MicrosoftEntraId, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + MicrosoftEntraIdForAzureBlobStorage, + } + }; + + /// Microsoft Entra Id related authentication info. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + runtimeclass MicrosoftEntraIdAuthenticationInfo + { + /// The resource identifier or resource uri. + String Resource { get; }; + /// Requested scope. May be empty. + String Scope { get; }; + } + + /// Authentication info. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + runtimeclass AuthenticationInfo + { + /// The authentication type. + AuthenticationType AuthenticationType { get; }; + /// Microsoft Entra Id related authentication info. + MicrosoftEntraIdAuthenticationInfo MicrosoftEntraIdAuthenticationInfo { get; }; + } + + /// Result of a connection validation callback. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + enum PackageCatalogConnectionValidationResult + { + /// The connection was accepted. + Ok, + /// The connection was rejected because the certificate was not accepted. + CertificateRejected, + }; + + /// Arguments provided to a connection validation callback. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + runtimeclass PackageCatalogConnectionValidationEventArgs + { + /// The server certificate presented during the connection. + Windows.Security.Cryptography.Certificates.Certificate ServerCertificate { get; }; + } + + /// Callback invoked to validate a catalog connection. + /// Return Ok to accept the connection or another value to reject it for that reason. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + delegate PackageCatalogConnectionValidationResult PackageCatalogConnectionValidationHandler(PackageCatalogConnectionValidationEventArgs args); + + /// Status of the Connect call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum ConnectResultStatus + { + Ok, + CatalogError, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + SourceAgreementsNotAccepted, + } + }; + + /// Result of the Connect call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass ConnectResult + { + /// Error codes + ConnectResultStatus Status { get; }; + + PackageCatalog PackageCatalog { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + /// The error code of the operation. + HRESULT ExtendedErrorCode{ get; }; + } + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + enum RefreshPackageCatalogStatus + { + Ok, + GroupPolicyError, + CatalogError, + InternalError, + }; + + /// IMPLEMENTATION NOTE: RefreshPackageCatalogResult + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass RefreshPackageCatalogResult + { + RefreshPackageCatalogStatus Status { get; }; + + /// Error codes + HRESULT ExtendedErrorCode { get; }; + }; + + /// A reference to a catalog that callers can try to Connect. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalogReference + { + /// Gets a value indicating whether this package catalog is a composite of other package catalogs, + /// and thus the packages may come from disparate package catalogs as well. + Boolean IsComposite { get; }; + + /// The details of the package catalog if it is not a composite. + PackageCatalogInfo Info { get; }; + + /// Opens a catalog. Required before searching. For remote catalogs (i.e. not Installed and Installing) this + /// may require downloading information from a server. + Windows.Foundation.IAsyncOperation ConnectAsync(); + ConnectResult Connect(); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + { + /// A string that will be passed to the source server if using a REST source + String AdditionalPackageCatalogArguments; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + /// Gets the required agreements for connecting to the package catalog (source). + Windows.Foundation.Collections.IVectorView SourceAgreements { get; }; + + Boolean AcceptSourceAgreements; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 8)] + { + /// Time interval for package catalog to check for an update. Setting to zero will disable the check for update. + Windows.Foundation.TimeSpan PackageCatalogBackgroundUpdateInterval; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 9)] + { + /// When set to true, the opened catalog will only provide the information regarding packages installed from this catalog. + /// In this mode, no external resources should be required. + Boolean InstalledPackageInformationOnly; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + { + /// Authentication arguments used in authentication flow during package catalog operations if applicable. + /// This is user or caller input. + AuthenticationArguments AuthenticationArguments; + + /// Authentication info from the package catalog. + /// This is defined by individual package catalog. + AuthenticationInfo AuthenticationInfo { get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Updates the package catalog. + /// The progress value, represented as a double, indicates the percentage of update package catalog operation completion. + /// The progress range is from 0 to 100. + Windows.Foundation.IAsyncOperationWithProgress RefreshPackageCatalogAsync(); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// A callback invoked to validate the server certificate during Connect or ConnectAsync. + /// Only available to in-process callers; out-of-process callers will receive E_ACCESSDENIED on set. + /// If the BypassCertificatePinningForMicrosoftStore group policy is disabled, this cannot be set + /// for the MicrosoftStore catalog; attempting to do so produces APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY. + PackageCatalogConnectionValidationHandler ConnectionValidationHandler; + + /// Indicates whether the ConnectionValidationHandler can be set for this catalog reference. + /// Returns false if setting the handler would be blocked by policy (e.g., the + /// BypassCertificatePinningForMicrosoftStore group policy is disabled for the MicrosoftStore catalog). + Boolean IsConnectionValidationHandlerEnabled { get; }; + } + } + + /// Catalogs with PackageCatalogOrigin Predefined + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PredefinedPackageCatalog + { + OpenWindowsCatalog, + MicrosoftStore, + DesktopFrameworks, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + { + OpenWindowsCatalogFont, + }, + }; + + /// Local Catalogs with PackageCatalogOrigin Predefined + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum LocalPackageCatalog + { + InstalledPackages, + InstallingPackages + }; + + /// Options for creating a composite catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass CreateCompositePackageCatalogOptions + { + CreateCompositePackageCatalogOptions(); + + /// Create a composite catalog to allow searching a user defined or pre defined source + /// and a local source (Installed packages) together + IVector Catalogs { get; }; + /// Sets the default search behavior if the catalog is a composite catalog. + CompositeSearchBehavior CompositeSearchBehavior; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Create installed package catalog with required installed scope. + PackageInstallScope InstalledScope; + } + } + + /// Required install scope for the package. If the package does not have an installer that + /// supports the specified scope the Install call will fail with InstallResultStatus.NoApplicableInstallers + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallScope + { + /// An installer with any install scope is valid. + Any, + /// Only User install scope installers are valid + User, + /// Only System installers will be valid + System, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Both User and Unknown install scope installers are valid + UserOrUnknown, + /// Both System and Unknown install scope installers are valid + SystemOrUnknown, + } + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallMode + { + /// The default experience for the installer. Installer may show some UI. + Default, + /// Runs the installer in silent mode. This suppresses the installer's UI to the extent + /// possible (installer may still show some required UI). + Silent, + /// Runs the installer in interactive mode. + Interactive, + }; + + /// Options when installing a package. + /// Intended to allow full compatibility with the "winget install" command line interface. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass InstallOptions + { + InstallOptions(); + + /// Optionally specifies the version from the package to install. If unspecified, the CatalogPackage.DefaultInstallVersion + /// version is used. DefaultInstallVersion is the latest applicable version of the package. DefaultInstallVersion may be + /// empty if there's no applicable version. In that case, install attempts without setting this PackageVersionId + /// will return No Applicable Installer error code. + PackageVersionId PackageVersionId; + + /// Specifies alternate location to install package (if supported). + String PreferredInstallLocation; + /// User or Machine. + PackageInstallScope PackageInstallScope; + /// Silent, Interactive, or Default + PackageInstallMode PackageInstallMode; + /// Directs the logging to a log file. If provided, the installer must have write access to the file + String LogOutputPath; + /// Continues the install even if the hash in the catalog does not match the linked installer. + Boolean AllowHashMismatch; + /// A string that will be passed to the installer. + /// IMPLEMENTATION NOTE: maps to "--override" in the winget cmd line + String ReplacementInstallerArguments; + + /// Used by a caller to correlate the install with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + /// A string that will be passed to the source server if using a REST source + String AdditionalPackageCatalogArguments; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + { + /// The set of allowed Architectures, in preference order, that will be considered for + /// the install operation. Initially the vector contains the default allowed architectures + /// in the default preference order for the current system. It is allowed to have repeated + /// values in the list, to make prepending a preference override easier. Instances of an + /// architecture after the first will simply be ignored. + Windows.Foundation.Collections.IVector AllowedArchitectures { get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + /// Allow the upgrade to continue for upgrade packages with manifest versions Unknown. + Boolean AllowUpgradeToUnknownVersion; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Force the operation to continue upon non security related failures. + Boolean Force; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + /// A string that will be passed to the installer + /// IMPLEMENTATION NOTE: maps to "--custom" in the winget cmd line + String AdditionalInstallerArguments; + + /// Accept the package agreements required for installation. + Boolean AcceptPackageAgreements; + + /// Bypasses the Disabled Store Policy + Boolean BypassIsStoreClientBlockedPolicyCheck; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + { + /// Skip installing the dependencies for the package. + Boolean SkipDependencies; + + /// The package installer type. + PackageInstallerType InstallerType; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Authentication arguments used when downloading the package installer if authentication is required. + AuthenticationArguments AuthenticationArguments; + } + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + enum PackageUninstallMode + { + /// The default experience for the installer. Installer may show some UI. + Default, + /// Runs the installer in silent mode. This suppresses the installer's UI to the extent + /// possible (installer may still show some required UI). + Silent, + /// Runs the installer in interactive mode. + Interactive, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum PackageUninstallScope + { + /// Use default uninstall behavior. + Any, + /// Uninstall for current user. Currently only applicable to msix. + User, + /// Uninstall for all users. Currently only applicable to msix. + System, + }; + + /// Options when uninstalling a package. + /// Intended to allow full compatibility with the "winget uninstall" command line interface. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + runtimeclass UninstallOptions + { + UninstallOptions(); + + /// This property is not currently used. The version of CatalogPackage.InstalledVersion is used for uninstall. + PackageVersionId PackageVersionId; + + /// Silent, Interactive, or Default + PackageUninstallMode PackageUninstallMode; + + /// Directs the logging to a log file. If provided, the installer must have write access to the file + String LogOutputPath; + + /// Used by a caller to correlate the install with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Force the operation to continue upon non security related failures. + Boolean Force; + // The scope the uninstall will perform. Currently only applicable to msix. + PackageUninstallScope PackageUninstallScope; + } + } + + /// The Windows platform type. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + enum WindowsPlatform + { + /// An unknown platform + Unknown, + /// Windows.Universal + Universal, + /// Windows.Desktop + Desktop, + /// Windows.IoT + IoT, + /// Windows.Team + Team, + /// Windows.Holographic + Holographic, + }; + + /// Options when downloading a package. + /// Intended to allow full compatibility with the "winget download" command line interface. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + runtimeclass DownloadOptions + { + DownloadOptions(); + + /// Optionally specifies the version from the package to download. If unspecified the version matching + /// CatalogPackage.GetLatestVersion() is used. + PackageVersionId PackageVersionId; + + /// The package installer type. + PackageInstallerType InstallerType; + + /// The package installer scope. + PackageInstallScope Scope; + + /// The package installer architecture. + Windows.System.ProcessorArchitecture Architecture; + + /// The package installer locale. + String Locale; + + /// The directory where the installers are downloaded to. + String DownloadDirectory; + + /// Continues the download even if the hash in the catalog does not match the linked installer. + Boolean AllowHashMismatch; + + /// Skip downloading the dependencies for the package. + Boolean SkipDependencies; + + /// Accept the package agreements required for download. + Boolean AcceptPackageAgreements; + + /// Used by a caller to correlate the download with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Authentication arguments used when downloading the package installer if authentication is required. + AuthenticationArguments AuthenticationArguments; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + { + /// If the package is licensed from the Microsoft Store, setting this value to true will not attempt to download the license file. + Boolean SkipMicrosoftStoreLicense; + + /// The platform to download the package for. + WindowsPlatform Platform; + + /// When applicable, uses the provided value as the target OS version for the download. + String TargetOSVersion; + } + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairMode + { + /// The default experience for the installer. Installer may show some UI. + Default, + /// Runs the installer in silent mode. This suppresses the installer's UI to the extent + /// possible (installer may still show some required UI). + Silent, + /// Runs the installer in interactive mode. + Interactive, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairScope + { + /// Use default repair behavior. + Any, + /// Repair for current user. Currently only applicable to msix. + User, + /// Repair for all users. + System, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + runtimeclass RepairOptions + { + RepairOptions(); + + /// This property is not currently used. The version of CatalogPackage.InstalledVersion is used for repair. + PackageVersionId PackageVersionId; + + /// The package Repair scope. + PackageRepairScope PackageRepairScope; + + /// The package repair mode. + PackageRepairMode PackageRepairMode; + + /// Optional parameter specifying Accept the package agreements required for download. + Boolean AcceptPackageAgreements; + + /// Used by a caller to correlate the repair with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + + /// Continues the download even if the hash in the catalog does not match the linked installer used for repair. + Boolean AllowHashMismatch; + + /// Directs the logging to a log file. If provided, the installer must have write access to the file + String LogOutputPath; + + /// Force the operation to continue upon non security related failures. + Boolean Force; + + /// Bypasses the Disabled Store Policy + Boolean BypassIsStoreClientBlockedPolicyCheck; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Authentication arguments used when downloading the package installer if authentication is required. + AuthenticationArguments AuthenticationArguments; + } + } + + /// IMPLEMENTATION NOTE: Documentation from AppInstaller::Manifest::Documentation + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass Documentation + { + String DocumentLabel { get; }; + + String DocumentUrl { get; }; + } + + /// Icon resolution + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconResolution + { + Custom, + Square16, + Square20, + Square24, + Square30, + Square32, + Square36, + Square40, + Square48, + Square60, + Square64, + Square72, + Square80, + Square96, + Square256, + }; + + /// Icon file type + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconFileType + { + Unknown, + Jpeg, + Png, + Ico, + }; + + /// Icon theme + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconTheme + { + Unknown, + Default, + Light, + Dark, + HighContrast, + }; + + /// IMPLEMENTATION NOTE: Icon from AppInstaller::Manifest::Icon + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass Icon + { + String Url { get; }; + + IconFileType FileType{ get; }; + + IconResolution Resolution{ get; }; + + IconTheme Theme{ get; }; + + UInt8[] Sha256 { get; }; + } + + /// IMPLEMENTATION NOTE: SourceAgreement from AppInstaller::Manifest::SourceAgreement + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass SourceAgreement + { + String Label { get; }; + + String Text { get; }; + + String Url { get; }; + } + + /// IMPLEMENTATION NOTE: PackageAgreement from AppInstaller::Manifest::Agreement + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass PackageAgreement + { + String Label { get; }; + + String Text { get; }; + + String Url { get; }; + } + + /// IMPLEMENTATION NOTE: CatalogPackageMetadata from AppInstaller::Manifest::Localization + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass CatalogPackageMetadata + { + String Locale { get; }; + + String Publisher { get; }; + + String PublisherUrl { get; }; + + String PublisherSupportUrl { get; }; + + String PrivacyUrl { get; }; + + String Author { get; }; + + String PackageName { get; }; + + String PackageUrl { get; }; + + String License { get; }; + + String LicenseUrl { get; }; + + String Copyright { get; }; + + String CopyrightUrl { get; }; + + String ShortDescription { get; }; + + String Description { get; }; + + Windows.Foundation.Collections.IVectorView Tags { get; }; + + Windows.Foundation.Collections.IVectorView Agreements { get; }; + + Windows.Foundation.Collections.IVectorView Documentations { get; }; + + Windows.Foundation.Collections.IVectorView Icons { get; }; + + String ReleaseNotes { get; }; + + String ReleaseNotesUrl { get; }; + + String PurchaseUrl { get; }; + + String InstallationNotes { get; }; + } + + /// IMPLEMENTATION NOTE: AddPackageCatalogOptions + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass AddPackageCatalogOptions + { + AddPackageCatalogOptions(); + + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name; + + /// The SourceUri used when adding the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" + /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" + String SourceUri; + + /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" + /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". + /// For contoso sample on msdn "Microsoft.PreIndexed.Package" + String Type; + + /// The trust level of the catalog to add. + PackageCatalogTrustLevel TrustLevel; + + /// Custom header to pass to the catalog. + String CustomHeader; + + /// Excludes a source from discovery unless specified. + Boolean Explicit; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// The priority of this catalog. Higher values are sorted first. + Int32 Priority; + } + }; + + /// IMPLEMENTATION NOTE: AddPackageCatalogStatus + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + enum AddPackageCatalogStatus + { + Ok, + GroupPolicyError, + CatalogError, + InternalError, + InvalidOptions, + AccessDenied, + AuthenticationError, + }; + + /// IMPLEMENTATION NOTE: AddPackageCatalogResult + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass AddPackageCatalogResult + { + AddPackageCatalogStatus Status { get; }; + + /// Error codes + HRESULT ExtendedErrorCode { get; }; + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogOptions + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass RemovePackageCatalogOptions + { + RemovePackageCatalogOptions(); + + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name; + + /// By default, the value is 'false', resulting in the removal of the package catalog registration + /// from the winget Package catalogs list and the deletion of all associated system artifacts. This + /// mirrors the WinGet Source remove operation on a specific Package Catalog. + /// If set to 'true', it removes the package catalog registration from the Windows Package Catalogs + /// list without any cleanup, similar to the WinGet source reset operation on a specific Package + /// Catalog. + Boolean PreserveData; + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogStatus + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + enum RemovePackageCatalogStatus + { + Ok, + GroupPolicyError, + CatalogError, + InternalError, + AccessDenied, + InvalidOptions, + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogResult + /// Result of removing a package catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass RemovePackageCatalogResult + { + RemovePackageCatalogStatus Status { get; }; + + /// Error codes + HRESULT ExtendedErrorCode { get; }; + }; + + /// IMPLEMENTATION NOTE: EditPackageCatalogOptions + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + runtimeclass EditPackageCatalogOptions + { + EditPackageCatalogOptions(); + + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name; + + /// Editing the Explicit property has three states: true, false, and not specified (null). + Windows.Foundation.IReference Explicit; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// The priority of this catalog. Higher values are sorted first. + Windows.Foundation.IReference Priority; + } + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogStatus + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + enum EditPackageCatalogStatus + { + Ok, + GroupPolicyError, + CatalogError, + InternalError, + AccessDenied, + InvalidOptions, + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogResult + /// Result of editing a package catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + runtimeclass EditPackageCatalogResult + { + EditPackageCatalogStatus Status { get; }; + + /// Error codes + HRESULT ExtendedErrorCode { get; }; + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageManager + { + PackageManager(); + + /// Get the available catalogs. Each source will have a separate catalog. + /// This does not open the catalog. These catalogs can be used individually or merged with CreateCompositePackageCatalogAsync. + /// IMPLEMENTATION NOTE: This is a list of sources returned by Windows Package Manager source list + Windows.Foundation.Collections.IVectorView GetPackageCatalogs(); + /// Get a built in catalog + PackageCatalogReference GetPredefinedPackageCatalog(PredefinedPackageCatalog predefinedPackageCatalog); + /// Get a built in catalog + PackageCatalogReference GetLocalPackageCatalog(LocalPackageCatalog localPackageCatalog); + /// Get a catalog by a known name + PackageCatalogReference GetPackageCatalogByName(String catalogName); + /// Get a composite catalog to allow searching a user defined or pre defined source and a local source + /// (Installing, Installed) together at the same time. + PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Add a catalog to the Windows Package Catalogs. + /// The progress value, represented as a double, indicates the percentage of add package catalog operation completion. + /// The progress range is from 0 to 100. + Windows.Foundation.IAsyncOperationWithProgress AddPackageCatalogAsync(AddPackageCatalogOptions options); + + /// Unregisters a Package Catalog from the Windows Package Catalogs and eliminates the system artifacts based on the provided options. + /// The progress value, represented as a double, indicates the percentage of remove package catalog operation completion. + /// The progress range is from 0 to 100. + Windows.Foundation.IAsyncOperationWithProgress RemovePackageCatalogAsync(RemovePackageCatalogOptions options); + } + + /// Install the specified package + Windows.Foundation.IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + { + /// Get install progress + Windows.Foundation.IAsyncOperationWithProgress GetInstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + /// Upgrade the specified package + Windows.Foundation.IAsyncOperationWithProgress UpgradePackageAsync(CatalogPackage package, InstallOptions options); + + /// Uninstall the specified package + Windows.Foundation.IAsyncOperationWithProgress UninstallPackageAsync(CatalogPackage package, UninstallOptions options); + + /// Get uninstall progress + Windows.Foundation.IAsyncOperationWithProgress GetUninstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + { + // Download the specified package + Windows.Foundation.IAsyncOperationWithProgress DownloadPackageAsync(CatalogPackage package, DownloadOptions options); + + // Get download progress + Windows.Foundation.IAsyncOperationWithProgress GetDownloadProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + // Repair the specified package + Windows.Foundation.IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + { + // The version of the Windows Package Manager that is running. + String Version{ get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + { + /// Edit an existing Windows Package Catalog. + EditPackageCatalogResult EditPackageCatalog(EditPackageCatalogOptions options); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + { + /// Get all pins across all sources. + Windows.Foundation.Collections.IVectorView GetAllPins(); + + /// Get the pins associated with the specified package. + /// Returns pins for all sources the package is available from, and for the installed + /// package identity if InstalledVersion is present. + Windows.Foundation.Collections.IVectorView GetPins(CatalogPackage package); + + /// Add or update a pin for the specified package. + /// Requires Force = true to overwrite an existing pin of a different type. + PinPackageResult PinPackage(CatalogPackage package, PinPackageOptions options); + + /// Remove all pins associated with the specified package. + /// Returns PackagePinNotFound if no pin exists for the package. + PinPackageResult UnpinPackage(CatalogPackage package); + + /// Reset (remove) all pins. If packageCatalogReference is non-null, only pins from that + /// catalog are removed; otherwise all pins are removed. + PinPackageResult ResetAllPins(PackageCatalogReference packageCatalogReference); + } + } + + /// IMPLEMENTATION NOTE: Pinning::PinType from AppInstaller::Pinning + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + enum PackagePinType + { + /// Unknown pin type or not pinned. + Unknown, + /// Pinned by the manifest using the RequiresExplicitUpgrade field. + /// Behaves the same as Pinning. + PinnedByManifest, + /// The package is excluded from upgrade --all, unless --include-pinned is added. + /// upgrade is not blocked. + Pinning, + /// The package is pinned to a specific version range. + Gating, + /// The package is blocked from upgrade --all and upgrade . + /// User must unpin to allow update. + Blocking, + }; + + /// IMPLEMENTATION NOTE: Pinning::Pin from AppInstaller::Pinning + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + runtimeclass PackagePin + { + /// The package ID that the pin applies to (for available-package pins) or the + /// ProductCode/PackageFamilyName (for installed-package pins). + String PackageId { get; }; + + /// The source identifier the pin applies to. Empty for installed-package pins. + String SourceId { get; }; + + /// The type of the pin. + PackagePinType Type { get; }; + + /// The gated version range. Only meaningful when Type is Gating; empty otherwise. + String GatedVersion { get; }; + + /// The UTC date/time when the pin was added. Null if not set. + Windows.Foundation.IReference DateAdded { get; }; + + /// Optional note associated with the pin. May be empty. + String Note { get; }; + + /// True if this pin is keyed on the installed package identity + /// (ProductCode or PackageFamilyName) rather than available package ID + source. + Boolean IsForInstalledPackage { get; }; + }; + + /// Options for adding or updating a package pin. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + runtimeclass PinPackageOptions + { + PinPackageOptions(); + + /// The type of pin to create. Required. + PackagePinType PinType; + + /// The gated version range. Required when PinType is Gating; ignored otherwise. + /// SAMPLE VALUE: "1.0.*" or "<2.0" + String GatedVersion; + + /// When true, the pin is keyed on the installed package identity (ProductCode or + /// PackageFamilyName) instead of the available package ID and source. + /// Mirrors the winget pin add --installed flag. Default is false. + Boolean PinInstalledPackage; + + /// When true, an existing pin with a different type will be overwritten. + /// When false and a pin already exists with a different type, the operation returns + /// PinResultStatus::PackagePinAlreadyExists. Default is false. + Boolean Force; + + /// Optional note to store with the pin. + String Note; + }; + + /// Status of a pin operation. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + enum PinResultStatus + { + Ok, + BlockedByPolicy, + InternalError, + InvalidOptions, + /// Returned by UnpinPackage when no pin exists for the package. + PackagePinNotFound, + /// Returned by PinPackage when a pin with a different type already exists + /// and Force is false. + PackagePinAlreadyExists, + }; + + /// Result of a pin operation. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 30)] + runtimeclass PinPackageResult + { + PinResultStatus Status { get; }; + + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + }; + + /// Global settings for PackageManager operations. + /// This settings should be invoked prior to invocation of PackageManager class. + /// This settings is only exposed in in-proc Com invocation. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + runtimeclass PackageManagerSettings + { + PackageManagerSettings(); + + /// Sets caller name to be used in telemetry logging. Default value is the calling process name. + /// Call this before any PackageManager operations. + /// Returns true if successful, false if caller name is already set. + /// This is a one time setup, multiple calls will not override existing caller name. + Boolean SetCallerIdentifier(String callerIdentifier); + + /// Sets state name for state separation. If not set, state will be written in a default location and states may be affected by other callers. + /// Call this before any PackageManager operations. + /// Returns true if successful, false if state name is already set. + /// This is a one time setup, multiple calls will not override existing state name. + Boolean SetStateIdentifier(String stateIdentifier); + + /// Sets custom UserSettings. + /// Returns true if successful, false if settingsContent cannot be parsed or UserSettings is already created. + /// This is a one time setup, multiple calls will not override existing UserSettings. + Boolean SetUserSettings(String settingsContent); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + { + /// Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not. + /// This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if + /// there are active objects at all times. If set to true it will allow the unload when there are no + /// active objects. + /// Defaults to true. + Boolean CanUnloadPreference{ get; set; }; + + /// Gets or sets a value indicating whether the module should listen for termination signals (CTRL+C, window messages, package updates) + /// and begin the process of cancelling active operations and preventing new ones. + /// If set to false, the caller is responsible for handling these termination signals and cancelling active operations as necessary. + /// Set this to the desired state before any PackageManager operations. Changing it after the first operation for the process may have undefined behavior. + /// Defaults to true. + Boolean TerminationSignalMonitoring{ get; set; }; + } + } + + /// Force midl3 to generate vector marshalling info. + declare + { + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + } +} diff --git a/src/Microsoft.Management.Deployment/Public/ComClsids.h b/src/Microsoft.Management.Deployment/Public/ComClsids.h index 0eede1998a..b709392754 100644 --- a/src/Microsoft.Management.Deployment/Public/ComClsids.h +++ b/src/Microsoft.Management.Deployment/Public/ComClsids.h @@ -1,61 +1,61 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -// Clsids for out-of-proc com invocation -#if USE_PROD_CLSIDS -#define WINGET_OUTOFPROC_COM_CLSID_PackageManager "C53A4F16-787E-42A4-B304-29EFFB4BF597" -#define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "572DED96-9C60-4526-8F92-EE7D91D38C1A" -#define WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions "526534B8-7E46-47C8-8416-B1685C327D37" -#define WINGET_OUTOFPROC_COM_CLSID_InstallOptions "1095F097-EB96-453B-B4E6-1613637F3B14" -#define WINGET_OUTOFPROC_COM_CLSID_UninstallOptions "E1D9A11E-9F85-4D87-9C17-2B93143ADB8D" -#define WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter "D02C9DAF-99DC-429C-B503-4E504E4AB000" -#define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "73D763B7-2937-432F-A97A-D98A4A596126" -#define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "4CBABE76-7322-4BE4-9CEA-2589A80682DC" -#define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "BA580786-BDE3-4F6C-B8F3-44698AC8711A" -#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "0498F441-3097-455F-9CAF-148F28293865" -#define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "DB9D012D-00D7-47EE-8FB1-606E10AC4F51" -#define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "032B1C58-B975-469B-A013-E632B6ECE8D8" -#define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "A9F5E736-68CE-463C-BA6D-DE968F0CCE04" -#define WINGET_OUTOFPROC_COM_CLSID_PinPackageOptions "93409EF2-29D0-46D3-8085-13EDE73939C4" -#else -#define WINGET_OUTOFPROC_COM_CLSID_PackageManager "74CB3139-B7C5-4B9E-9388-E6616DEA288C" -#define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96" -#define WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions "EE160901-B317-4EA7-9CC6-5355C6D7D8A7" -#define WINGET_OUTOFPROC_COM_CLSID_InstallOptions "44FE0580-62F7-44D4-9E91-AA9614AB3E86" -#define WINGET_OUTOFPROC_COM_CLSID_UninstallOptions "AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C" -#define WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter "3F85B9F4-487A-4C48-9035-2903F8A6D9E8" -#define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "C9ED7917-66AB-4E31-A92A-F65F18EF7933" -#define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "8EF324ED-367C-4880-83E5-BB2ABD0B72F6" -#define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "6484A61D-50FA-41F0-B71E-F4370C6EB37C" -#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03" -#define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "D58C7E4C-70E6-476C-A5D4-80341ED80252" -#define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "87A96609-1A39-4955-BE72-7174E147B7DC" -#define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "29B19238-81AD-4A8E-A2FC-ADF17C38CAEB" -#define WINGET_OUTOFPROC_COM_CLSID_PinPackageOptions "B3A61CCB-A3D0-497D-B300-A904904EEA56" -#endif - -// Clsids only used in in-proc invocation -#define WINGET_INPROC_ONLY_COM_CLSID_PackageManagerSettings "80CF9D63-5505-4342-B9B4-BB87895CA8BB" - -namespace winrt::Microsoft::Management::Deployment -{ - // clsid constants for in-proc com invocation - const CLSID WINGET_INPROC_COM_CLSID_PackageManager = { 0x2DDE4456, 0x64D9, 0x4673, 0x8F, 0x7E, 0xA4, 0xF1, 0x9A, 0x2E, 0x6C, 0xC3 }; // 2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3 - const CLSID WINGET_INPROC_COM_CLSID_FindPackagesOptions = { 0x96B9A53A, 0x9228, 0x4DA0, 0xB0, 0x13, 0xBB, 0x1B, 0x20, 0x31, 0xAB, 0x3D }; // 96B9A53A-9228-4DA0-B013-BB1B2031AB3D - const CLSID WINGET_INPROC_COM_CLSID_CreateCompositePackageCatalogOptions = { 0x768318A6, 0x2EB5, 0x400D, 0x84, 0xD0, 0xDF, 0x35, 0x34, 0xC3, 0x0F, 0x5D }; // 768318A6-2EB5-400D-84D0-DF3534C30F5D - const CLSID WINGET_INPROC_COM_CLSID_InstallOptions = { 0xE2AF3BA8, 0x8A88, 0x4766, 0x9D, 0xDA, 0xAE, 0x40, 0x13, 0xAD, 0xE2, 0x86 }; // E2AF3BA8-8A88-4766-9DDA-AE4013ADE286 - const CLSID WINGET_INPROC_COM_CLSID_UninstallOptions = { 0x869CB959, 0xEB54, 0x425C, 0xA1, 0xE4, 0x1A, 0x1C, 0x29, 0x1C, 0x64, 0xE9 }; // 869CB959-EB54-425C-A1E4-1A1C291C64E9 - const CLSID WINGET_INPROC_COM_CLSID_PackageMatchFilter = { 0x57DC8962, 0x7343, 0x42CD, 0xB9, 0x1C, 0x04, 0xF6, 0xA2, 0x5D, 0xB1, 0xD0 }; // 57DC8962-7343-42CD-B91C-04F6A25DB1D0 - const CLSID WINGET_INPROC_COM_CLSID_PackageManagerSettings = { 0x80CF9D63, 0x5505, 0x4342, 0xB9, 0xB4, 0xBB, 0x87, 0x89, 0x5C, 0xA8, 0xBB }; // 80CF9D63-5505-4342-B9B4-BB87895CA8BB - const CLSID WINGET_INPROC_COM_CLSID_DownloadOptions = { 0x4288DF96, 0xFDC9, 0x4B68, 0xB4, 0x03, 0x19, 0x3D, 0xBB, 0xF5, 0x6A, 0x24 }; // 4288DF96-FDC9-4B68-B403-193DBBF56A24 - const CLSID WINGET_INPROC_COM_CLSID_AuthenticationArguments = { 0x8D593114, 0x1CF1, 0x43B9, 0x87, 0x22, 0x4D, 0xBB, 0x30, 0x10, 0x32, 0x96 }; // 8D593114-1CF1-43B9-8722-4DBB30103296 - const CLSID WINGET_INPROC_COM_CLSID_RepairOptions = { 0x30c024c4, 0x852c, 0x4dd4, 0x98, 0x10, 0x13, 0x48, 0xc5, 0x1e, 0xf9, 0xbb }; // {30C024C4-852C-4DD4-9810-1348C51EF9BB} - const CLSID WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions = { 0x24e6f1fa, 0xe4c3, 0x4acd, 0x96, 0x5d, 0xdf, 0x21, 0x3f, 0xd5, 0x8f, 0x15 }; // {24E6F1FA-E4C3-4ACD-965D-DF213FD58F15} - const CLSID WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions = { 0x1125d3a6, 0xe2ce, 0x479a, 0x91, 0xd5, 0x71, 0xa3, 0xf6, 0xf8, 0xb0, 0xb }; // {1125D3A6-E2CE-479A-91D5-71A3F6F8B00B} - const CLSID WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions = { 0xe8e12fe1, 0xab77, 0x40c4, 0xa5, 0x62, 0xe9, 0x1f, 0xb5, 0x1b, 0x4e, 0x82 }; // {E8E12FE1-AB77-40C4-A562-E91FB51B4E82} - const CLSID WINGET_INPROC_COM_CLSID_PinPackageOptions = { 0xba9bb1af, 0x4453, 0x4274, 0xbb, 0xf9, 0x4c, 0x17, 0x79, 0x4e, 0xfa, 0x8e }; // {BA9BB1AF-4453-4274-BBF9-4C17794EFA8E} - - CLSID GetRedirectedClsidFromInProcClsid(REFCLSID clsid); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +// Clsids for out-of-proc com invocation +#if USE_PROD_CLSIDS +#define WINGET_OUTOFPROC_COM_CLSID_PackageManager "C53A4F16-787E-42A4-B304-29EFFB4BF597" +#define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "572DED96-9C60-4526-8F92-EE7D91D38C1A" +#define WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions "526534B8-7E46-47C8-8416-B1685C327D37" +#define WINGET_OUTOFPROC_COM_CLSID_InstallOptions "1095F097-EB96-453B-B4E6-1613637F3B14" +#define WINGET_OUTOFPROC_COM_CLSID_UninstallOptions "E1D9A11E-9F85-4D87-9C17-2B93143ADB8D" +#define WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter "D02C9DAF-99DC-429C-B503-4E504E4AB000" +#define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "73D763B7-2937-432F-A97A-D98A4A596126" +#define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "4CBABE76-7322-4BE4-9CEA-2589A80682DC" +#define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "BA580786-BDE3-4F6C-B8F3-44698AC8711A" +#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "0498F441-3097-455F-9CAF-148F28293865" +#define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "DB9D012D-00D7-47EE-8FB1-606E10AC4F51" +#define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "032B1C58-B975-469B-A013-E632B6ECE8D8" +#define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "A9F5E736-68CE-463C-BA6D-DE968F0CCE04" +#define WINGET_OUTOFPROC_COM_CLSID_PinPackageOptions "93409EF2-29D0-46D3-8085-13EDE73939C4" +#else +#define WINGET_OUTOFPROC_COM_CLSID_PackageManager "74CB3139-B7C5-4B9E-9388-E6616DEA288C" +#define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96" +#define WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions "EE160901-B317-4EA7-9CC6-5355C6D7D8A7" +#define WINGET_OUTOFPROC_COM_CLSID_InstallOptions "44FE0580-62F7-44D4-9E91-AA9614AB3E86" +#define WINGET_OUTOFPROC_COM_CLSID_UninstallOptions "AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C" +#define WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter "3F85B9F4-487A-4C48-9035-2903F8A6D9E8" +#define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "C9ED7917-66AB-4E31-A92A-F65F18EF7933" +#define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "8EF324ED-367C-4880-83E5-BB2ABD0B72F6" +#define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "6484A61D-50FA-41F0-B71E-F4370C6EB37C" +#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03" +#define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "D58C7E4C-70E6-476C-A5D4-80341ED80252" +#define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "87A96609-1A39-4955-BE72-7174E147B7DC" +#define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "29B19238-81AD-4A8E-A2FC-ADF17C38CAEB" +#define WINGET_OUTOFPROC_COM_CLSID_PinPackageOptions "B3A61CCB-A3D0-497D-B300-A904904EEA56" +#endif + +// Clsids only used in in-proc invocation +#define WINGET_INPROC_ONLY_COM_CLSID_PackageManagerSettings "80CF9D63-5505-4342-B9B4-BB87895CA8BB" + +namespace winrt::Microsoft::Management::Deployment +{ + // clsid constants for in-proc com invocation + const CLSID WINGET_INPROC_COM_CLSID_PackageManager = { 0x2DDE4456, 0x64D9, 0x4673, 0x8F, 0x7E, 0xA4, 0xF1, 0x9A, 0x2E, 0x6C, 0xC3 }; // 2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3 + const CLSID WINGET_INPROC_COM_CLSID_FindPackagesOptions = { 0x96B9A53A, 0x9228, 0x4DA0, 0xB0, 0x13, 0xBB, 0x1B, 0x20, 0x31, 0xAB, 0x3D }; // 96B9A53A-9228-4DA0-B013-BB1B2031AB3D + const CLSID WINGET_INPROC_COM_CLSID_CreateCompositePackageCatalogOptions = { 0x768318A6, 0x2EB5, 0x400D, 0x84, 0xD0, 0xDF, 0x35, 0x34, 0xC3, 0x0F, 0x5D }; // 768318A6-2EB5-400D-84D0-DF3534C30F5D + const CLSID WINGET_INPROC_COM_CLSID_InstallOptions = { 0xE2AF3BA8, 0x8A88, 0x4766, 0x9D, 0xDA, 0xAE, 0x40, 0x13, 0xAD, 0xE2, 0x86 }; // E2AF3BA8-8A88-4766-9DDA-AE4013ADE286 + const CLSID WINGET_INPROC_COM_CLSID_UninstallOptions = { 0x869CB959, 0xEB54, 0x425C, 0xA1, 0xE4, 0x1A, 0x1C, 0x29, 0x1C, 0x64, 0xE9 }; // 869CB959-EB54-425C-A1E4-1A1C291C64E9 + const CLSID WINGET_INPROC_COM_CLSID_PackageMatchFilter = { 0x57DC8962, 0x7343, 0x42CD, 0xB9, 0x1C, 0x04, 0xF6, 0xA2, 0x5D, 0xB1, 0xD0 }; // 57DC8962-7343-42CD-B91C-04F6A25DB1D0 + const CLSID WINGET_INPROC_COM_CLSID_PackageManagerSettings = { 0x80CF9D63, 0x5505, 0x4342, 0xB9, 0xB4, 0xBB, 0x87, 0x89, 0x5C, 0xA8, 0xBB }; // 80CF9D63-5505-4342-B9B4-BB87895CA8BB + const CLSID WINGET_INPROC_COM_CLSID_DownloadOptions = { 0x4288DF96, 0xFDC9, 0x4B68, 0xB4, 0x03, 0x19, 0x3D, 0xBB, 0xF5, 0x6A, 0x24 }; // 4288DF96-FDC9-4B68-B403-193DBBF56A24 + const CLSID WINGET_INPROC_COM_CLSID_AuthenticationArguments = { 0x8D593114, 0x1CF1, 0x43B9, 0x87, 0x22, 0x4D, 0xBB, 0x30, 0x10, 0x32, 0x96 }; // 8D593114-1CF1-43B9-8722-4DBB30103296 + const CLSID WINGET_INPROC_COM_CLSID_RepairOptions = { 0x30c024c4, 0x852c, 0x4dd4, 0x98, 0x10, 0x13, 0x48, 0xc5, 0x1e, 0xf9, 0xbb }; // {30C024C4-852C-4DD4-9810-1348C51EF9BB} + const CLSID WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions = { 0x24e6f1fa, 0xe4c3, 0x4acd, 0x96, 0x5d, 0xdf, 0x21, 0x3f, 0xd5, 0x8f, 0x15 }; // {24E6F1FA-E4C3-4ACD-965D-DF213FD58F15} + const CLSID WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions = { 0x1125d3a6, 0xe2ce, 0x479a, 0x91, 0xd5, 0x71, 0xa3, 0xf6, 0xf8, 0xb0, 0xb }; // {1125D3A6-E2CE-479A-91D5-71A3F6F8B00B} + const CLSID WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions = { 0xe8e12fe1, 0xab77, 0x40c4, 0xa5, 0x62, 0xe9, 0x1f, 0xb5, 0x1b, 0x4e, 0x82 }; // {E8E12FE1-AB77-40C4-A562-E91FB51B4E82} + const CLSID WINGET_INPROC_COM_CLSID_PinPackageOptions = { 0xba9bb1af, 0x4453, 0x4274, 0xbb, 0xf9, 0x4c, 0x17, 0x79, 0x4e, 0xfa, 0x8e }; // {BA9BB1AF-4453-4274-BBF9-4C17794EFA8E} + + CLSID GetRedirectedClsidFromInProcClsid(REFCLSID clsid); +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs index cee14bfdbb..46bbe5fda9 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs @@ -1,92 +1,92 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Common.Command; - - /// - /// Searches configured sources for packages. - /// - public sealed class FinderPackageCommand : FinderExtendedCommand - { - /// - /// Initializes a new instance of the class. - /// - /// Caller cmdlet. - /// Package identifier. - /// Name of package. - /// Moniker of package. - /// Source to search. If null, all are searched. - /// Match against any field of a package. - /// Tag of the package. - /// Command of the package. - /// Max results to return. - public FinderPackageCommand( - PSCmdlet psCmdlet, - string id, - string name, - string moniker, - string source, - string[] query, - string tag, - string command, - uint count) - : base(psCmdlet) - { - // FinderCommand - this.Id = id; - this.Name = name; - this.Moniker = moniker; - this.Source = source; - this.Query = query; - - // FinderExtendedCommand - this.Tag = tag; - this.Command = command; - this.Count = count; - } - - /// - /// Process find package command. - /// - /// PSPackageFieldMatchOption. - public void Find(string psPackageFieldMatchOption) - { - var results = this.Execute( - () => this.FindPackages( - CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); - - for (var i = 0; i < results.Count; i++) - { - this.Write(StreamType.Object, new PSFoundCatalogPackage(results[i].CatalogPackage)); - } - } - - /// - /// Process get package command. - /// - /// PSPackageFieldMatchOption. - public void Get(string psPackageFieldMatchOption) - { - var results = this.Execute( - () => this.FindPackages( - CompositeSearchBehavior.LocalCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); - - for (var i = 0; i < results.Count; i++) - { - this.Write(StreamType.Object, new PSInstalledCatalogPackage(results[i].CatalogPackage)); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + + /// + /// Searches configured sources for packages. + /// + public sealed class FinderPackageCommand : FinderExtendedCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + /// Package identifier. + /// Name of package. + /// Moniker of package. + /// Source to search. If null, all are searched. + /// Match against any field of a package. + /// Tag of the package. + /// Command of the package. + /// Max results to return. + public FinderPackageCommand( + PSCmdlet psCmdlet, + string id, + string name, + string moniker, + string source, + string[] query, + string tag, + string command, + uint count) + : base(psCmdlet) + { + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + + // FinderExtendedCommand + this.Tag = tag; + this.Command = command; + this.Count = count; + } + + /// + /// Process find package command. + /// + /// PSPackageFieldMatchOption. + public void Find(string psPackageFieldMatchOption) + { + var results = this.Execute( + () => this.FindPackages( + CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); + + for (var i = 0; i < results.Count; i++) + { + this.Write(StreamType.Object, new PSFoundCatalogPackage(results[i].CatalogPackage)); + } + } + + /// + /// Process get package command. + /// + /// PSPackageFieldMatchOption. + public void Get(string psPackageFieldMatchOption) + { + var results = this.Execute( + () => this.FindPackages( + CompositeSearchBehavior.LocalCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); + + for (var i = 0; i < results.Count; i++) + { + this.Write(StreamType.Object, new PSInstalledCatalogPackage(results[i].CatalogPackage)); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs index 5528da6149..83d3e6887c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs @@ -1,250 +1,250 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - -#if NET - using WinRT; -#endif - - /// - /// Constructs instances of classes from the namespace. - /// - internal sealed class ManagementDeploymentFactory - { -#if USE_PROD_CLSIDS - private static readonly Guid PackageManagerClsid = Guid.Parse("C53A4F16-787E-42A4-B304-29EFFB4BF597"); - private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("572DED96-9C60-4526-8F92-EE7D91D38C1A"); - private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("526534B8-7E46-47C8-8416-B1685C327D37"); - private static readonly Guid InstallOptionsClsid = Guid.Parse("1095F097-EB96-453B-B4E6-1613637F3B14"); - private static readonly Guid UninstallOptionsClsid = Guid.Parse("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"); - private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); - private static readonly Guid DownloadOptionsClsid = Guid.Parse("4CBABE76-7322-4BE4-9CEA-2589A80682DC"); - private static readonly Guid RepairOptionsClsid = Guid.Parse("0498F441-3097-455F-9CAF-148F28293865"); - private static readonly Guid PinPackageOptionsClsid = Guid.Parse("93409EF2-29D0-46D3-8085-13EDE73939C4"); -#else - private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); - private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); - private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"); - private static readonly Guid InstallOptionsClsid = Guid.Parse("44FE0580-62F7-44D4-9E91-AA9614AB3E86"); - private static readonly Guid UninstallOptionsClsid = Guid.Parse("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"); - private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); - private static readonly Guid DownloadOptionsClsid = Guid.Parse("8EF324ED-367C-4880-83E5-BB2ABD0B72F6"); - private static readonly Guid RepairOptionsClsid = Guid.Parse("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"); - private static readonly Guid PinPackageOptionsClsid = Guid.Parse("B3A61CCB-A3D0-497D-B300-A904904EEA56"); -#endif - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? FindPackagesOptionsType = Type.GetTypeFromCLSID(FindPackagesOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? CreateCompositePackageCatalogOptionsType = Type.GetTypeFromCLSID(CreateCompositePackageCatalogOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? InstallOptionsType = Type.GetTypeFromCLSID(InstallOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? UninstallOptionsType = Type.GetTypeFromCLSID(UninstallOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? DownloadOptionsType = Type.GetTypeFromCLSID(DownloadOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? RepairOptionsType = Type.GetTypeFromCLSID(RepairOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? PinPackageOptionsType = Type.GetTypeFromCLSID(PinPackageOptionsClsid); - - // These GUIDs correspond to the CSWinRT interface IIDs generated for Microsoft.Management.Deployment.Projection - // and are auto-generated by the WinRT tool. - private static readonly Guid PackageManagerIid = Guid.Parse("B375E3B9-F2E0-5C93-87A7-B67497F7E593"); - private static readonly Guid FindPackagesOptionsIid = Guid.Parse("A5270EDD-7DA7-57A3-BACE-F2593553561F"); - private static readonly Guid CreateCompositePackageCatalogOptionsIid = Guid.Parse("21ABAA76-089D-51C5-A745-C85EEFE70116"); - private static readonly Guid InstallOptionsIid = Guid.Parse("6EE9DB69-AB48-5E72-A474-33A924CD23B3"); - private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); - private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); - private static readonly Guid DownloadOptionsIid = Guid.Parse("94C92C4B-43F5-5CA3-BBBE-9F432C9546BC"); - private static readonly Guid RepairOptionsIid = Guid.Parse("263F0546-2D7E-53A0-B8D1-75B74817FF18"); - private static readonly Guid PinPackageOptionsIid = Guid.Parse("8AB6949E-BB04-5F77-8B69-EF444D9C1635"); - - private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; - - private static readonly Lazy Lazy = new (() => new ManagementDeploymentFactory()); - - /// - /// Initializes static members of the class. - /// - static ManagementDeploymentFactory() - { - if (Utilities.UsesInProcWinget) - { - PackageManagerSettings settings = new PackageManagerSettings(); - settings.SetCallerIdentifier("PowerShellInProc"); - } - } - - private ManagementDeploymentFactory() - { - } - - /// - /// Gets the instance object. - /// - public static ManagementDeploymentFactory Instance - { - get { return Lazy.Value; } - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public PackageManager CreatePackageManager() - { - var result = Create(PackageManagerType, PackageManagerIid); - - if (!Utilities.UsesInProcWinget) - { - _ = CoAllowSetForegroundWindow(result, IntPtr.Zero); - } - - return result; - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public FindPackagesOptions CreateFindPackagesOptions() - { - return Create(FindPackagesOptionsType, FindPackagesOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() - { - return Create(CreateCompositePackageCatalogOptionsType, CreateCompositePackageCatalogOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// An instance. - public InstallOptions CreateInstallOptions() - { - return Create(InstallOptionsType, InstallOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public UninstallOptions CreateUninstallOptions() - { - return Create(UninstallOptionsType, UninstallOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public DownloadOptions CreateDownloadOptions() - { - return Create(DownloadOptionsType, DownloadOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public PackageMatchFilter CreatePackageMatchFilter() - { - return Create(PackageMatchFilterType, PackageMatchFilterIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public RepairOptions CreateRepairOptions() - { - return Create(RepairOptionsType, RepairOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public PinPackageOptions CreatePinPackageOptions() - { - return Create(PinPackageOptionsType, PinPackageOptionsIid); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static T Create(Type? type, in Guid iid) - where T : new() - { - if (type == null) - { - throw new ArgumentNullException(iid.ToString()); - } - - if (Utilities.UsesInProcWinget) - { - // This doesn't work on Windows PowerShell - // If we want to support it, we need something that loads the - // Microsoft.Management.Deployment.dll for .NET framework as CsWinRT - // does for .NET Core - return new T(); - } - - object? instance = null; - - if (Utilities.ExecutingAsAdministrator) - { - int hr = WinRTHelpers.ManualActivation(type.GUID, iid, 0, out instance); - - if (hr < 0) - { - if (hr == ErrorCode.FileNotFound || hr == ErrorCode.PackageNotRegisteredForUser) - { - throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled); - } - else - { - throw new COMException($"Failed to create instance: {hr}", hr); - } - } - } - else - { - instance = Activator.CreateInstance(type); - } - - if (instance == null) - { - throw new ArgumentNullException(); - } - -#if NET - IntPtr pointer = Marshal.GetIUnknownForObject(instance); - return MarshalInterface.FromAbi(pointer); -#else - return (T)instance; -#endif - } - - [DllImport("ole32.dll", ExactSpelling = true, PreserveSig = true)] - private static extern int CoAllowSetForegroundWindow([MarshalAs(UnmanagedType.IUnknown)] object pUnk, IntPtr reserved); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + +#if NET + using WinRT; +#endif + + /// + /// Constructs instances of classes from the namespace. + /// + internal sealed class ManagementDeploymentFactory + { +#if USE_PROD_CLSIDS + private static readonly Guid PackageManagerClsid = Guid.Parse("C53A4F16-787E-42A4-B304-29EFFB4BF597"); + private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("572DED96-9C60-4526-8F92-EE7D91D38C1A"); + private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("526534B8-7E46-47C8-8416-B1685C327D37"); + private static readonly Guid InstallOptionsClsid = Guid.Parse("1095F097-EB96-453B-B4E6-1613637F3B14"); + private static readonly Guid UninstallOptionsClsid = Guid.Parse("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"); + private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); + private static readonly Guid DownloadOptionsClsid = Guid.Parse("4CBABE76-7322-4BE4-9CEA-2589A80682DC"); + private static readonly Guid RepairOptionsClsid = Guid.Parse("0498F441-3097-455F-9CAF-148F28293865"); + private static readonly Guid PinPackageOptionsClsid = Guid.Parse("93409EF2-29D0-46D3-8085-13EDE73939C4"); +#else + private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); + private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); + private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"); + private static readonly Guid InstallOptionsClsid = Guid.Parse("44FE0580-62F7-44D4-9E91-AA9614AB3E86"); + private static readonly Guid UninstallOptionsClsid = Guid.Parse("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"); + private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); + private static readonly Guid DownloadOptionsClsid = Guid.Parse("8EF324ED-367C-4880-83E5-BB2ABD0B72F6"); + private static readonly Guid RepairOptionsClsid = Guid.Parse("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"); + private static readonly Guid PinPackageOptionsClsid = Guid.Parse("B3A61CCB-A3D0-497D-B300-A904904EEA56"); +#endif + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? FindPackagesOptionsType = Type.GetTypeFromCLSID(FindPackagesOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? CreateCompositePackageCatalogOptionsType = Type.GetTypeFromCLSID(CreateCompositePackageCatalogOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? InstallOptionsType = Type.GetTypeFromCLSID(InstallOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? UninstallOptionsType = Type.GetTypeFromCLSID(UninstallOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? DownloadOptionsType = Type.GetTypeFromCLSID(DownloadOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? RepairOptionsType = Type.GetTypeFromCLSID(RepairOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? PinPackageOptionsType = Type.GetTypeFromCLSID(PinPackageOptionsClsid); + + // These GUIDs correspond to the CSWinRT interface IIDs generated for Microsoft.Management.Deployment.Projection + // and are auto-generated by the WinRT tool. + private static readonly Guid PackageManagerIid = Guid.Parse("B375E3B9-F2E0-5C93-87A7-B67497F7E593"); + private static readonly Guid FindPackagesOptionsIid = Guid.Parse("A5270EDD-7DA7-57A3-BACE-F2593553561F"); + private static readonly Guid CreateCompositePackageCatalogOptionsIid = Guid.Parse("21ABAA76-089D-51C5-A745-C85EEFE70116"); + private static readonly Guid InstallOptionsIid = Guid.Parse("6EE9DB69-AB48-5E72-A474-33A924CD23B3"); + private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); + private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); + private static readonly Guid DownloadOptionsIid = Guid.Parse("94C92C4B-43F5-5CA3-BBBE-9F432C9546BC"); + private static readonly Guid RepairOptionsIid = Guid.Parse("263F0546-2D7E-53A0-B8D1-75B74817FF18"); + private static readonly Guid PinPackageOptionsIid = Guid.Parse("8AB6949E-BB04-5F77-8B69-EF444D9C1635"); + + private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; + + private static readonly Lazy Lazy = new (() => new ManagementDeploymentFactory()); + + /// + /// Initializes static members of the class. + /// + static ManagementDeploymentFactory() + { + if (Utilities.UsesInProcWinget) + { + PackageManagerSettings settings = new PackageManagerSettings(); + settings.SetCallerIdentifier("PowerShellInProc"); + } + } + + private ManagementDeploymentFactory() + { + } + + /// + /// Gets the instance object. + /// + public static ManagementDeploymentFactory Instance + { + get { return Lazy.Value; } + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public PackageManager CreatePackageManager() + { + var result = Create(PackageManagerType, PackageManagerIid); + + if (!Utilities.UsesInProcWinget) + { + _ = CoAllowSetForegroundWindow(result, IntPtr.Zero); + } + + return result; + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public FindPackagesOptions CreateFindPackagesOptions() + { + return Create(FindPackagesOptionsType, FindPackagesOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() + { + return Create(CreateCompositePackageCatalogOptionsType, CreateCompositePackageCatalogOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// An instance. + public InstallOptions CreateInstallOptions() + { + return Create(InstallOptionsType, InstallOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public UninstallOptions CreateUninstallOptions() + { + return Create(UninstallOptionsType, UninstallOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public DownloadOptions CreateDownloadOptions() + { + return Create(DownloadOptionsType, DownloadOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public PackageMatchFilter CreatePackageMatchFilter() + { + return Create(PackageMatchFilterType, PackageMatchFilterIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public RepairOptions CreateRepairOptions() + { + return Create(RepairOptionsType, RepairOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public PinPackageOptions CreatePinPackageOptions() + { + return Create(PinPackageOptionsType, PinPackageOptionsIid); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static T Create(Type? type, in Guid iid) + where T : new() + { + if (type == null) + { + throw new ArgumentNullException(iid.ToString()); + } + + if (Utilities.UsesInProcWinget) + { + // This doesn't work on Windows PowerShell + // If we want to support it, we need something that loads the + // Microsoft.Management.Deployment.dll for .NET framework as CsWinRT + // does for .NET Core + return new T(); + } + + object? instance = null; + + if (Utilities.ExecutingAsAdministrator) + { + int hr = WinRTHelpers.ManualActivation(type.GUID, iid, 0, out instance); + + if (hr < 0) + { + if (hr == ErrorCode.FileNotFound || hr == ErrorCode.PackageNotRegisteredForUser) + { + throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled); + } + else + { + throw new COMException($"Failed to create instance: {hr}", hr); + } + } + } + else + { + instance = Activator.CreateInstance(type); + } + + if (instance == null) + { + throw new ArgumentNullException(); + } + +#if NET + IntPtr pointer = Marshal.GetIUnknownForObject(instance); + return MarshalInterface.FromAbi(pointer); +#else + return (T)instance; +#endif + } + + [DllImport("ole32.dll", ExactSpelling = true, PreserveSig = true)] + private static extern int CoAllowSetForegroundWindow([MarshalAs(UnmanagedType.IUnknown)] object pUnk, IntPtr reserved); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs index 45e7e368f5..cb88793a53 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs @@ -1,188 +1,188 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using Microsoft.Management.Deployment; - using Newtonsoft.Json.Linq; - using Windows.System; - - /// - /// Extension methods for PS Enum wrappers for Microsoft.Management.Deployment enums. - /// - internal static class PSEnumHelpers - { - /// - /// Checks if the provided enum string value matches the 'Default' value for PS Enums. - /// - /// Enum string value. - /// Boolean value. - public static bool IsDefaultEnum(string value) - { - return string.Equals(value, "Default", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Converts PSPackageInstallMode string value to PackageInstallMode. - /// - /// PSPackageInstallMode to string value. - /// PackageInstallMode. - public static PackageInstallMode ToPackageInstallMode(string value) - { - return value switch - { - "Default" => PackageInstallMode.Default, - "Silent" => PackageInstallMode.Silent, - "Interactive" => PackageInstallMode.Interactive, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageInstallScope string value to PackageInstallScope. - /// - /// PSPackageInstallScope to string value. - /// PackageInstallScope. - public static PackageInstallScope ToPackageInstallScope(string value) - { - return value switch - { - "Any" => PackageInstallScope.Any, - "User" => PackageInstallScope.User, - "System" => PackageInstallScope.System, - "UserOrUnknown" => PackageInstallScope.UserOrUnknown, - "SystemOrUnknown" => PackageInstallScope.SystemOrUnknown, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSProcessorArchitecture string value to ProcessorArchitecture. - /// - /// PSProcessorArchitecture to string value. - /// ProcessorArchitecture. - public static ProcessorArchitecture ToProcessorArchitecture(string value) - { - return value switch - { - "X86" => ProcessorArchitecture.X86, - "Arm" => ProcessorArchitecture.Arm, - "X64" => ProcessorArchitecture.X64, - "Arm64" => ProcessorArchitecture.Arm64, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageUninstallMode string value to PackageUninstallMode. - /// - /// PSPackageUninstallMode string value. - /// PackageUninstallMode. - public static PackageUninstallMode ToPackageUninstallMode(string value) - { - return value switch - { - "Default" => PackageUninstallMode.Default, - "Silent" => PackageUninstallMode.Silent, - "Interactive" => PackageUninstallMode.Interactive, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageFieldMatchOption string value to PackageFieldMatchOption. - /// - /// PSPackageFieldMatchOption string value. - /// PackageFieldMatchOption. - public static PackageFieldMatchOption ToPackageFieldMatchOption(string value) - { - return value switch - { - "Equals" => PackageFieldMatchOption.Equals, - "EqualsCaseInsensitive" => PackageFieldMatchOption.EqualsCaseInsensitive, - "StartsWithCaseInsensitive" => PackageFieldMatchOption.StartsWithCaseInsensitive, - "ContainsCaseInsensitive" => PackageFieldMatchOption.ContainsCaseInsensitive, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageInstallerType string value to PackageInstallerType. - /// - /// PSPackageInstallerType string value. - /// PackageInstallerType. - public static PackageInstallerType ToPackageInstallerType(string value) - { - return value switch - { - "Unknown" => PackageInstallerType.Unknown, - "Inno" => PackageInstallerType.Inno, - "Wix" => PackageInstallerType.Wix, - "Msi" => PackageInstallerType.Msi, - "Nullsoft" => PackageInstallerType.Nullsoft, - "Zip" => PackageInstallerType.Zip, - "Msix" => PackageInstallerType.Msix, - "Exe" => PackageInstallerType.Exe, - "Burn" => PackageInstallerType.Burn, - "MSStore" => PackageInstallerType.MSStore, - "Portable" => PackageInstallerType.Portable, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageRepairMode string value to PackageRepairMode. - /// - /// PSPackageRepairMode string value. - /// PackageRepairMode. - public static PackageRepairMode ToPackageRepairMode(string value) - { - return value switch - { - "Default" => PackageRepairMode.Default, - "Silent" => PackageRepairMode.Silent, - "Interactive" => PackageRepairMode.Interactive, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackagePinType string value to PackagePinType. - /// - /// PSPackagePinType string value. - /// PackagePinType. - public static PackagePinType ToPackagePinType(string value) - { - return value switch - { - "Pinning" => PackagePinType.Pinning, - "Blocking" => PackagePinType.Blocking, - "Gating" => PackagePinType.Gating, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSWindowsPlatform string value to WindowsPlatform. - /// - /// PSWindowsPlatform string value. - /// WindowsPlatform. - public static WindowsPlatform ToWindowsPlatform(string value) - { - return value switch - { - "Default" => WindowsPlatform.Unknown, - "Universal" => WindowsPlatform.Universal, - "Desktop" => WindowsPlatform.Desktop, - "IoT" => WindowsPlatform.IoT, - "Team" => WindowsPlatform.Team, - "Holographic" => WindowsPlatform.Holographic, - _ => throw new InvalidOperationException(), - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using Microsoft.Management.Deployment; + using Newtonsoft.Json.Linq; + using Windows.System; + + /// + /// Extension methods for PS Enum wrappers for Microsoft.Management.Deployment enums. + /// + internal static class PSEnumHelpers + { + /// + /// Checks if the provided enum string value matches the 'Default' value for PS Enums. + /// + /// Enum string value. + /// Boolean value. + public static bool IsDefaultEnum(string value) + { + return string.Equals(value, "Default", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Converts PSPackageInstallMode string value to PackageInstallMode. + /// + /// PSPackageInstallMode to string value. + /// PackageInstallMode. + public static PackageInstallMode ToPackageInstallMode(string value) + { + return value switch + { + "Default" => PackageInstallMode.Default, + "Silent" => PackageInstallMode.Silent, + "Interactive" => PackageInstallMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageInstallScope string value to PackageInstallScope. + /// + /// PSPackageInstallScope to string value. + /// PackageInstallScope. + public static PackageInstallScope ToPackageInstallScope(string value) + { + return value switch + { + "Any" => PackageInstallScope.Any, + "User" => PackageInstallScope.User, + "System" => PackageInstallScope.System, + "UserOrUnknown" => PackageInstallScope.UserOrUnknown, + "SystemOrUnknown" => PackageInstallScope.SystemOrUnknown, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSProcessorArchitecture string value to ProcessorArchitecture. + /// + /// PSProcessorArchitecture to string value. + /// ProcessorArchitecture. + public static ProcessorArchitecture ToProcessorArchitecture(string value) + { + return value switch + { + "X86" => ProcessorArchitecture.X86, + "Arm" => ProcessorArchitecture.Arm, + "X64" => ProcessorArchitecture.X64, + "Arm64" => ProcessorArchitecture.Arm64, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageUninstallMode string value to PackageUninstallMode. + /// + /// PSPackageUninstallMode string value. + /// PackageUninstallMode. + public static PackageUninstallMode ToPackageUninstallMode(string value) + { + return value switch + { + "Default" => PackageUninstallMode.Default, + "Silent" => PackageUninstallMode.Silent, + "Interactive" => PackageUninstallMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageFieldMatchOption string value to PackageFieldMatchOption. + /// + /// PSPackageFieldMatchOption string value. + /// PackageFieldMatchOption. + public static PackageFieldMatchOption ToPackageFieldMatchOption(string value) + { + return value switch + { + "Equals" => PackageFieldMatchOption.Equals, + "EqualsCaseInsensitive" => PackageFieldMatchOption.EqualsCaseInsensitive, + "StartsWithCaseInsensitive" => PackageFieldMatchOption.StartsWithCaseInsensitive, + "ContainsCaseInsensitive" => PackageFieldMatchOption.ContainsCaseInsensitive, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageInstallerType string value to PackageInstallerType. + /// + /// PSPackageInstallerType string value. + /// PackageInstallerType. + public static PackageInstallerType ToPackageInstallerType(string value) + { + return value switch + { + "Unknown" => PackageInstallerType.Unknown, + "Inno" => PackageInstallerType.Inno, + "Wix" => PackageInstallerType.Wix, + "Msi" => PackageInstallerType.Msi, + "Nullsoft" => PackageInstallerType.Nullsoft, + "Zip" => PackageInstallerType.Zip, + "Msix" => PackageInstallerType.Msix, + "Exe" => PackageInstallerType.Exe, + "Burn" => PackageInstallerType.Burn, + "MSStore" => PackageInstallerType.MSStore, + "Portable" => PackageInstallerType.Portable, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageRepairMode string value to PackageRepairMode. + /// + /// PSPackageRepairMode string value. + /// PackageRepairMode. + public static PackageRepairMode ToPackageRepairMode(string value) + { + return value switch + { + "Default" => PackageRepairMode.Default, + "Silent" => PackageRepairMode.Silent, + "Interactive" => PackageRepairMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackagePinType string value to PackagePinType. + /// + /// PSPackagePinType string value. + /// PackagePinType. + public static PackagePinType ToPackagePinType(string value) + { + return value switch + { + "Pinning" => PackagePinType.Pinning, + "Blocking" => PackagePinType.Blocking, + "Gating" => PackagePinType.Gating, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSWindowsPlatform string value to WindowsPlatform. + /// + /// PSWindowsPlatform string value. + /// WindowsPlatform. + public static WindowsPlatform ToWindowsPlatform(string value) + { + return value switch + { + "Default" => WindowsPlatform.Unknown, + "Universal" => WindowsPlatform.Universal, + "Desktop" => WindowsPlatform.Desktop, + "IoT" => WindowsPlatform.IoT, + "Team" => WindowsPlatform.Team, + "Holographic" => WindowsPlatform.Holographic, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs index 0d1c593c34..98bf8d6bb0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs @@ -1,249 +1,249 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Windows.Foundation; - - /// - /// Wrapper for PackageManager that handles rpc disconnections. - /// The object is disconnected when the server is killed or on an update. - /// - internal sealed class PackageManagerWrapper - { - private static readonly Lazy Lazy = new (() => new PackageManagerWrapper()); - - private PackageManager packageManager = null!; - - private PackageManagerWrapper() - { - } - - /// - /// Gets the instance object. - /// - public static PackageManagerWrapper Instance - { - get { return Lazy.Value; } - } - - /// - /// Wrapper for InstallPackageAsync. - /// - /// The package to install. - /// The install options. - /// An async operation with progress. - public IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options) - { - return this.Execute( - () => this.packageManager.InstallPackageAsync(package, options), - false); - } - - /// - /// Wrapper for UpgradePackageAsync. - /// - /// The package to upgrade. - /// The install options. - /// An async operation with progress. - public IAsyncOperationWithProgress UpgradePackageAsync(CatalogPackage package, InstallOptions options) - { - return this.Execute( - () => this.packageManager.UpgradePackageAsync(package, options), - false); - } - - /// - /// Wrapper for UninstallPackageAsync. - /// - /// The package to uninstall. - /// The uninstall options. - /// An async operation with progress. - public IAsyncOperationWithProgress UninstallPackageAsync(CatalogPackage package, UninstallOptions options) - { - return this.Execute( - () => this.packageManager.UninstallPackageAsync(package, options), - false); - } - - /// - /// Wrapper for DownloadPackageAsync. - /// - /// The package to download. - /// The download options. - /// An async operation with progress. - public IAsyncOperationWithProgress DownloadPackageAsync(CatalogPackage package, DownloadOptions options) - { - return this.Execute( - () => this.packageManager.DownloadPackageAsync(package, options), - false); - } - - /// - /// Wrapper for RepairPackagesAsync. - /// - /// The package to repair. - /// The repair options. - /// An async operation with progress. - public IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options) - { - return this.Execute( - () => this.packageManager.RepairPackageAsync(package, options), - false); - } - - /// - /// Wrapper for GetPackageCatalogs. - /// - /// A list of PackageCatalogReferences. - public IReadOnlyList GetPackageCatalogs() - { - return this.Execute( - () => this.packageManager.GetPackageCatalogs(), - true); - } - - /// - /// Wrapper for GetPackageCatalogByName. - /// - /// The name of the source. - /// A PackageCatalogReference. - public PackageCatalogReference GetPackageCatalogByName(string source) - { - return this.Execute( - () => this.packageManager.GetPackageCatalogByName(source), - true); - } - - /// - /// Wrapper for CreateCompositePackageCatalog. - /// - /// CreateCompositePackageCatalogOptions. - /// A PackageCatalogReference. - public PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options) - { - return this.Execute( - () => this.packageManager.CreateCompositePackageCatalog(options), - false); - } - - /// - /// Wrapper for GetPins. - /// - /// The package to get pins for. - /// A read-only list of PackagePin objects. - public IReadOnlyList GetPins(CatalogPackage package) - { - return this.Execute( - () => this.packageManager.GetPins(package), - false); - } - - /// - /// Wrapper for GetAllPins. - /// - /// A read-only list of all PackagePin objects. - public IReadOnlyList GetAllPins() - { - return this.Execute( - () => this.packageManager.GetAllPins(), - false); - } - - /// - /// Wrapper for PinPackage. - /// - /// The package to pin. - /// The pin options. - /// A PinPackageResult. - public PinPackageResult PinPackage(CatalogPackage package, PinPackageOptions options) - { - return this.Execute( - () => this.packageManager.PinPackage(package, options), - false); - } - - /// - /// Wrapper for UnpinPackage. - /// - /// The package to unpin. - /// A PinPackageResult. - public PinPackageResult UnpinPackage(CatalogPackage package) - { - return this.Execute( - () => this.packageManager.UnpinPackage(package), - false); - } - - /// - /// Wrapper for ResetAllPins. - /// - /// The catalog reference to reset pins for. Pass null to reset all. - /// A PinPackageResult. - public PinPackageResult ResetAllPins(PackageCatalogReference? packageCatalogReference) - { - return this.Execute( - () => this.packageManager.ResetAllPins(packageCatalogReference!), - false); - } - - /// - /// Gets the version of the package manager that is running. - /// - /// The version string. - public string? GetVersion() - { - try - { - return this.Execute(() => this.packageManager.Version, true); - } - catch - { - return null; - } - } - - private TReturn Execute(Func func, bool canRetry) - { - if (Utilities.UsesInProcWinget && Utilities.ThreadIsSTA) - { - // If you failed here, then you didn't wrap your call in ManagementDeploymentCommand.Execute - throw new SingleThreadedApartmentException(); - } - - bool stopRetry = false; - while (true) - { - if (this.packageManager == null) - { - this.packageManager = ManagementDeploymentFactory.Instance.CreatePackageManager(); - } - - try - { - return func(); - } - catch (COMException ex) when (ex.HResult == ErrorCode.RpcServerUnavailable || ex.HResult == ErrorCode.RpcCallFailed) - { - this.packageManager = null!; - - if (stopRetry || !canRetry) - { - throw; - } - - stopRetry = true; - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Windows.Foundation; + + /// + /// Wrapper for PackageManager that handles rpc disconnections. + /// The object is disconnected when the server is killed or on an update. + /// + internal sealed class PackageManagerWrapper + { + private static readonly Lazy Lazy = new (() => new PackageManagerWrapper()); + + private PackageManager packageManager = null!; + + private PackageManagerWrapper() + { + } + + /// + /// Gets the instance object. + /// + public static PackageManagerWrapper Instance + { + get { return Lazy.Value; } + } + + /// + /// Wrapper for InstallPackageAsync. + /// + /// The package to install. + /// The install options. + /// An async operation with progress. + public IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options) + { + return this.Execute( + () => this.packageManager.InstallPackageAsync(package, options), + false); + } + + /// + /// Wrapper for UpgradePackageAsync. + /// + /// The package to upgrade. + /// The install options. + /// An async operation with progress. + public IAsyncOperationWithProgress UpgradePackageAsync(CatalogPackage package, InstallOptions options) + { + return this.Execute( + () => this.packageManager.UpgradePackageAsync(package, options), + false); + } + + /// + /// Wrapper for UninstallPackageAsync. + /// + /// The package to uninstall. + /// The uninstall options. + /// An async operation with progress. + public IAsyncOperationWithProgress UninstallPackageAsync(CatalogPackage package, UninstallOptions options) + { + return this.Execute( + () => this.packageManager.UninstallPackageAsync(package, options), + false); + } + + /// + /// Wrapper for DownloadPackageAsync. + /// + /// The package to download. + /// The download options. + /// An async operation with progress. + public IAsyncOperationWithProgress DownloadPackageAsync(CatalogPackage package, DownloadOptions options) + { + return this.Execute( + () => this.packageManager.DownloadPackageAsync(package, options), + false); + } + + /// + /// Wrapper for RepairPackagesAsync. + /// + /// The package to repair. + /// The repair options. + /// An async operation with progress. + public IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options) + { + return this.Execute( + () => this.packageManager.RepairPackageAsync(package, options), + false); + } + + /// + /// Wrapper for GetPackageCatalogs. + /// + /// A list of PackageCatalogReferences. + public IReadOnlyList GetPackageCatalogs() + { + return this.Execute( + () => this.packageManager.GetPackageCatalogs(), + true); + } + + /// + /// Wrapper for GetPackageCatalogByName. + /// + /// The name of the source. + /// A PackageCatalogReference. + public PackageCatalogReference GetPackageCatalogByName(string source) + { + return this.Execute( + () => this.packageManager.GetPackageCatalogByName(source), + true); + } + + /// + /// Wrapper for CreateCompositePackageCatalog. + /// + /// CreateCompositePackageCatalogOptions. + /// A PackageCatalogReference. + public PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options) + { + return this.Execute( + () => this.packageManager.CreateCompositePackageCatalog(options), + false); + } + + /// + /// Wrapper for GetPins. + /// + /// The package to get pins for. + /// A read-only list of PackagePin objects. + public IReadOnlyList GetPins(CatalogPackage package) + { + return this.Execute( + () => this.packageManager.GetPins(package), + false); + } + + /// + /// Wrapper for GetAllPins. + /// + /// A read-only list of all PackagePin objects. + public IReadOnlyList GetAllPins() + { + return this.Execute( + () => this.packageManager.GetAllPins(), + false); + } + + /// + /// Wrapper for PinPackage. + /// + /// The package to pin. + /// The pin options. + /// A PinPackageResult. + public PinPackageResult PinPackage(CatalogPackage package, PinPackageOptions options) + { + return this.Execute( + () => this.packageManager.PinPackage(package, options), + false); + } + + /// + /// Wrapper for UnpinPackage. + /// + /// The package to unpin. + /// A PinPackageResult. + public PinPackageResult UnpinPackage(CatalogPackage package) + { + return this.Execute( + () => this.packageManager.UnpinPackage(package), + false); + } + + /// + /// Wrapper for ResetAllPins. + /// + /// The catalog reference to reset pins for. Pass null to reset all. + /// A PinPackageResult. + public PinPackageResult ResetAllPins(PackageCatalogReference? packageCatalogReference) + { + return this.Execute( + () => this.packageManager.ResetAllPins(packageCatalogReference!), + false); + } + + /// + /// Gets the version of the package manager that is running. + /// + /// The version string. + public string? GetVersion() + { + try + { + return this.Execute(() => this.packageManager.Version, true); + } + catch + { + return null; + } + } + + private TReturn Execute(Func func, bool canRetry) + { + if (Utilities.UsesInProcWinget && Utilities.ThreadIsSTA) + { + // If you failed here, then you didn't wrap your call in ManagementDeploymentCommand.Execute + throw new SingleThreadedApartmentException(); + } + + bool stopRetry = false; + while (true) + { + if (this.packageManager == null) + { + this.packageManager = ManagementDeploymentFactory.Instance.CreatePackageManager(); + } + + try + { + return func(); + } + catch (COMException ex) when (ex.HResult == ErrorCode.RpcServerUnavailable || ex.HResult == ErrorCode.RpcCallFailed) + { + this.packageManager = null!; + + if (stopRetry || !canRetry) + { + throw; + } + + stopRetry = true; + } + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs index b33449cf42..a14665db5f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs @@ -1,81 +1,81 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using System; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Helpers; - - /// - /// InstalledCatalogPackage wrapper object for displaying to PowerShell. - /// - public sealed class PSInstalledCatalogPackage : PSCatalogPackage - { - private readonly Func isPinnedLookup; - private bool? isPinned; - - /// - /// Initializes a new instance of the class. - /// - /// The catalog package COM object. - /// - /// Optional lookup used to determine whether the package is pinned. When not supplied, - /// the lookup is resolved on demand through the package manager. - /// - internal PSInstalledCatalogPackage(CatalogPackage catalogPackage, Func? isPinnedLookup = null) - : base(catalogPackage) - { - this.isPinnedLookup = isPinnedLookup ?? this.DefaultIsPinnedLookup; - } - - /// - /// Gets the installed version of the catalog package. - /// - public string InstalledVersion - { - get { return this.CatalogPackageCOM.InstalledVersion.Version; } - } - - /// - /// Gets a value indicating whether the package is pinned. - /// - public bool IsPinned - { - get - { - if (!this.isPinned.HasValue) - { - this.isPinned = this.isPinnedLookup(this.CatalogPackageCOM); - } - - return this.isPinned.Value; - } - } - - private bool DefaultIsPinnedLookup(CatalogPackage catalogPackage) - { - return PackageManagerWrapper.Instance.GetPins(catalogPackage).Count > 0; - } - - /// - /// Compares versions. - /// - /// Version. - /// PSCompareResult. - public PSCompareResult CompareToVersion(string version) - { - return this.CatalogPackageCOM.InstalledVersion.CompareToVersion(version) switch - { - CompareResult.Unknown => PSCompareResult.Unknown, - CompareResult.Lesser => PSCompareResult.Lesser, - CompareResult.Equal => PSCompareResult.Equal, - CompareResult.Greater => PSCompareResult.Greater, - _ => throw new InvalidOperationException(), - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Helpers; + + /// + /// InstalledCatalogPackage wrapper object for displaying to PowerShell. + /// + public sealed class PSInstalledCatalogPackage : PSCatalogPackage + { + private readonly Func isPinnedLookup; + private bool? isPinned; + + /// + /// Initializes a new instance of the class. + /// + /// The catalog package COM object. + /// + /// Optional lookup used to determine whether the package is pinned. When not supplied, + /// the lookup is resolved on demand through the package manager. + /// + internal PSInstalledCatalogPackage(CatalogPackage catalogPackage, Func? isPinnedLookup = null) + : base(catalogPackage) + { + this.isPinnedLookup = isPinnedLookup ?? this.DefaultIsPinnedLookup; + } + + /// + /// Gets the installed version of the catalog package. + /// + public string InstalledVersion + { + get { return this.CatalogPackageCOM.InstalledVersion.Version; } + } + + /// + /// Gets a value indicating whether the package is pinned. + /// + public bool IsPinned + { + get + { + if (!this.isPinned.HasValue) + { + this.isPinned = this.isPinnedLookup(this.CatalogPackageCOM); + } + + return this.isPinned.Value; + } + } + + private bool DefaultIsPinnedLookup(CatalogPackage catalogPackage) + { + return PackageManagerWrapper.Instance.GetPins(catalogPackage).Count > 0; + } + + /// + /// Compares versions. + /// + /// Version. + /// PSCompareResult. + public PSCompareResult CompareToVersion(string version) + { + return this.CatalogPackageCOM.InstalledVersion.CompareToVersion(version) switch + { + CompareResult.Unknown => PSCompareResult.Unknown, + CompareResult.Lesser => PSCompareResult.Lesser, + CompareResult.Equal => PSCompareResult.Equal, + CompareResult.Greater => PSCompareResult.Greater, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index 8fecf43d74..e2d7eb38d8 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -1,1212 +1,1212 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# -.Synopsis - Pester tests related to the Microsoft.WinGet.Client PowerShell module. - The tests require the localhost web server to be running and serving the test data. - 'Invoke-Pester' should be called in an admin PowerShell window. -#> -[CmdletBinding()] -param( - # Whether to use production or developement targets. - [switch]$TargetProduction -) - -BeforeAll { - if ($TargetProduction) - { - $wingetExeName = "winget.exe" - } - else - { - $wingetExeName = "wingetdev.exe" - } - - $settingsFilePath = (ConvertFrom-Json (& $wingetExeName settings export)).userSettingsFile - $originalSettingsContent = Get-Content -Path $settingsFilePath -Raw - - $deviceGroupPolicyRoot = "HKLM:\Software\Policies\Microsoft\Windows" - $wingetPolicyKeyName = "AppInstaller" - $wingetGroupPolicyRegistryRoot = $deviceGroupPolicyRoot + "\" + $wingetPolicyKeyName - - Import-Module Microsoft.WinGet.Client - - function SetWinGetSettingsHelper($settings) { - $content = ConvertTo-Json $settings -Depth 4 - Set-Content -Path $settingsFilePath -Value $content - } - - function RestoreWinGetSettings() { - Set-Content -Path $settingsFilePath -Value $originalSettingsContent - } - - # Source Add requires admin privileges, this will only execute successfully in an elevated PowerShell. - function AddTestSource { - try { - Get-WinGetSource -Name 'TestSource' - } - catch { - Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -TrustLevel 'Trusted' - } - } - - # This is a workaround to an issue where the server takes longer than expected to terminate when - # running from PowerShell. This can cause other E2E tests to fail when attempting to reset the test source. - function RemoveTestSource { - try { - # Source Remove requires admin privileges, this will only execute successfully in an elevated PowerShell. - $testSource = Get-WinGetSource | Where-Object -Property 'Name' -eq 'TestSource' - if ($null -ne $testSource) - { - # Source Remove requires admin privileges - Remove-WinGetSource -Name 'TestSource' - } - } - catch { - # Non-admin - Start-Process -FilePath $wingetExeName -ArgumentList "source remove TestSource" - } - } - - function CreatePolicyKeyIfNotExists() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if(-Not($registryExists)) - { - New-Item -Path $deviceGroupPolicyRoot -Name $wingetPolicyKeyName - } - } - - function CleanupGroupPolicyKeyIfExists() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if($registryExists) - { - Remove-Item -Path $wingetGroupPolicyRegistryRoot -Recurse - } - } - - function CleanupGroupPolicies() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if($registryExists) - { - Remove-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name * - } - } - - function WaitForWindowsPackageManagerServer([bool]$force = $false) - { - $processes = Get-Process | Where-Object { $_.Name -eq "WindowsPackageManagerServer" } - foreach ($p in $processes) - { - if ($force) - { - Stop-Process $p - } - - $timeout = 300 - $secondsToWait = 5 - $time = 0 - while ($p.HasExited -eq $false) - { - $time += $secondsToWait - if ($time -ge $timeout ) - { - throw "Timeout waiting for $($p.Id) to exit" - } - Start-Sleep -Seconds 5 - } - } - } - - function GetRandomTestDirectory() - { - return Join-Path -Path $env:Temp -ChildPath "WingetPwshTest-$(New-Guid)" - } - - function Validate-WinGetResultCommonFields([psobject]$result, [psobject]$expected) { - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be $expected.Id - $result.Name | Should -Be $expected.Name - $result.Source | Should -Be $expected.Source - $result.Status | Should -Be $expected.Status - } - - function Validate-WinGetPackageOperationResult([psobject]$result, [psobject]$expected, [string]$operationType) - { - Validate-WinGetResultCommonFields $result $expected - $result.RebootRequired | Should -Be $expected.RebootRequired - - switch ($operationType) { - 'install' { - $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode - } - 'update' { - $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode - } - 'repair' { - $result.RepairErrorCode | Should -Be $expected.RepairErrorCode - } - 'uninstall' { - $result.UninstallerErrorCode | Should -Be $expected.UninstallerErrorCode - } - default { - throw "Unknown operation type: $operationType" - } - } - } -} - -Describe 'Get-WinGetVersion' { - - It 'Get-WinGetVersion' { - $version = Get-WinGetVersion - $version | Should -Not -BeNullOrEmpty -ErrorAction Stop - } -} - -Describe 'Reset-WinGetSource' { - BeforeAll { - AddTestSource - } - - # Requires admin - It 'Resets all sources' { - Reset-WinGetSource -All - } - - It 'Test source should be removed' { - { Get-WinGetSource -Name 'TestSource' } | Should -Throw - } -} - -Describe 'Get|Add|Reset-WinGetSource' { - - BeforeAll { - $ogSettings = @{ experimentalFeatures= @{sourcePriority=$true}} - SetWinGetSettingsHelper $ogSettings - - Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -TrustLevel 'Trusted' -Explicit -Priority 42 - } - - It 'Get Test source' { - $source = Get-WinGetSource -Name 'TestSource' - - $source | Should -Not -BeNullOrEmpty -ErrorAction Stop - $source.Name | Should -Be 'TestSource' - $source.Argument | Should -Be 'https://localhost:5001/TestKit/' - $source.Type | Should -Be 'Microsoft.PreIndexed.Package' - $source.TrustLevel | Should -Be 'Trusted' - $source.Explicit | Should -Be $true - $source.Priority | Should -Be 42 - } - - It 'Get fake source' { - { Get-WinGetSource -Name 'Fake' } | Should -Throw - } - - # This tests require admin - It 'Reset Test source' { - Reset-WinGetSource -Name TestSource - } - - AfterAll { - RemoveTestSource - RestoreWinGetSettings - } -} - -Describe 'Find-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - It 'Given no parameters, lists all available packages' { - $allPackages = Find-WinGetPackage -Source TestSource - $allPackages.Count | Should -BeGreaterThan 0 - } - - It 'Find by Id' { - $package = Find-WinGetPackage -Source 'TestSource' -Id 'AppInstallerTest.TestExampleInstaller' - - $package | Should -Not -BeNullOrEmpty -ErrorAction Stop - $package.Name | Should -Be 'TestExampleInstaller' - $package.Id | Should -Be 'AppInstallerTest.TestExampleInstaller' - $package.Version | Should -Be '1.2.3.4' - $package.Source | Should -Be 'TestSource' - } - - It 'Find by Name' { - $package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' - - $package | Should -Not -BeNullOrEmpty -ErrorAction Stop - $package[0].Name | Should -Be 'TestPortableExe' - $package[1].Name | Should -Be 'TestPortableExeWithCommand' - } - - It 'Find by Name sort by Version' { - $package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' | Sort-Object 'Version' - - $package | Should -Not -BeNullOrEmpty -ErrorAction Stop - $package[0].Name | Should -Be 'TestPortableExeWithCommand' - $package[1].Name | Should -Be 'TestPortableExe' - } - - It 'Find package and verify PackageVersionInfo' { - $package = Find-WinGetPackage -Source 'TestSource' -Id 'AppInstallerTest.TestPortableExe' -MatchOption Equals - - $package | Should -Not -BeNullOrEmpty -ErrorAction Stop - $package.AvailableVersions[0] | Should -Be '3.0.0.0' - $package.AvailableVersions[1] | Should -Be '2.0.0.0' - $package.AvailableVersions.Count | Should -Be 4 - - $packageVersionInfo = $package.GetPackageVersionInfo("3.0.0.0") - $packageVersionInfo.DisplayName | Should -Be 'TestPortableExe' - $packageVersionInfo.Id | Should -Be 'AppInstallerTest.TestPortableExe' - $packageVersionInfo.CompareToVersion("2.0.0.0") | Should -Be 'Greater' - $packageVersionInfo.CompareToVersion("4.0.0.0") | Should -Be 'Lesser' - } -} - -Describe 'Install|Update|Uninstall-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - BeforeEach { - $expectedExeInstallerResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestExeInstaller" - Name = "TestExeInstaller" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - UninstallerErrorCode = 0 - } - - $expectedPortableInstallerResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestPortableExe" - Name = "TestPortableExe" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install by Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' - Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' - } - - It 'Install by exact Name and Version' { - $result = Install-WinGetPackage -Name TestPortableExe -Version '2.0.0.0' -MatchOption Equals - Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'install' - } - - It 'Update by Id' { - $result = Update-WinGetPackage -Id AppInstallerTest.TestExeInstaller - Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'update' - } - - It 'Update by Name' { - $result = Update-WinGetPackage -Name TestPortableExe - Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'update' - } - - It 'Uninstall by Id' { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'uninstall' - } - - It 'Uninstall by Name' { - $result = Uninstall-WinGetPackage -Name TestPortableExe - Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'uninstall' - } - - AfterAll { - # Uninstall all test packages after each for proper cleanup. - $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller -MatchOption Equals - if ($testExe.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - } - - $testPortable = Get-WinGetPackage -Id AppInstallerTest.TestPortableExe -MatchOption Equals - if ($testPortable.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestPortableExe - } - } -} - -Describe 'Install|Repair|Uninstall-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - Context 'MSIX Repair Scenario' { - BeforeEach { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestMsixInstaller" - Name = "TestMsixInstaller" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install MSIX By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - Validate-WinGetPackageOperationResult $result $expectedResult 'install' - } - - It 'Repair MSIX By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - Validate-WinGetPackageOperationResult $result $expectedResult 'repair' - } - - It 'Uninstall MSIX By Id' { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' - } - } - - Context 'Burn installer "Modify" Repair Scenario' { - BeforeEach { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestModifyRepair" - Name = "TestModifyRepair" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install Burn Installer By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestModifyRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'install' - } - - It 'Repair Burn Installer By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.TestModifyRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'repair' - } - - It 'Uninstall Burn Installer By Id' { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' - } - } - - Context 'Exe Installer "Uninstaller" Repair Scenario' { - BeforeEach { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.UninstallerRepair" - Name = "UninstallerRepair" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install Exe Installer By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.UninstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'install' - } - - It 'Uninstaller Repair Exe Installer By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.UninstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'repair' - } - - It "Uninstall Exe Installer By Id" { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' - } - } - - Context 'Inno "Installer" Repair Scenario' { - BeforeEach { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestInstallerRepair" - Name = "TestInstallerRepair" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install Exe Installer By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'install' - } - - It 'Installer Repair Exe Installer By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'repair' - } - - It "Uninstall Exe Installer By Id" { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' - } - } - - AfterAll { - # Uninstall all test packages after each for proper cleanup. - $testMsix = Get-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - if ($testMsix.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - } - - $testBurn = Get-WinGetPackage -Id AppInstallerTest.TestModifyRepair - if ($testBurn.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair - } - - $testExe = Get-WinGetPackage -Id AppInstallerTest.UninstallerRepair - if ($testExe.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair - } - - $testInno = Get-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - if ($testInno.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - } - } -} - -Describe 'Install-WinGetPackage Source Priority' { - - It 'Install equal Priority' { - AddTestSource - Add-WinGetSource -Name 'dummyPackageSource' -Type 'Microsoft.Test.Configurable' -Arg '{"ContainsPackage":true}' - - { Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' } | Should -Throw - } - - It 'Install higher Priority' { - $ogSettings = @{ experimentalFeatures= @{sourcePriority=$true}} - SetWinGetSettingsHelper $ogSettings - - RemoveTestSource - Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -Priority 1 - Add-WinGetSource -Name 'dummyPackageSource' -Type 'Microsoft.Test.Configurable' -Arg '{"ContainsPackage":true}' - - $expectedExeInstallerResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestExeInstaller" - Name = "TestExeInstaller" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - UninstallerErrorCode = 0 - } - - $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' - Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' - } - - AfterEach { - $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller -MatchOption Equals - if ($testExe.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - } - - Remove-WinGetSource -Name 'dummyPackageSource' - RemoveTestSource - RestoreWinGetSettings - } -} - -Describe 'Get-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - It 'Install by Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' - } - - It 'Get package by Id' { - $result = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Name | Should -Be 'TestExeInstaller' - $result.Id | Should -Be 'AppInstallerTest.TestExeInstaller' - $result.InstalledVersion | Should -Be '1.0.0.0' - $result.Source | Should -Be 'TestSource' - $result.AvailableVersions[0] | Should -Be '2.0.0.0' - } - - It 'Get package by Name' { - $result = Get-WinGetPackage -Name TestExeInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Name | Should -Be 'TestExeInstaller' - $result.Id | Should -Be 'AppInstallerTest.TestExeInstaller' - $result.InstalledVersion | Should -Be '1.0.0.0' - $result.Source | Should -Be 'TestSource' - $result.AvailableVersions[0] | Should -Be '2.0.0.0' - } - - AfterAll { - # Uninstall all test packages after each for proper cleanup. - $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller - if ($testExe.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - } - } -} - -Describe 'Export-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - It 'Download by Id' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'Ok' - - # Download directory should be created and have exactly two files (installer and manifest file). - Test-Path -Path $testDirectory | Should -Be $true - (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 - } - - It 'Download by Locale' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -Locale 'zh-CN' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" - $result.Name | Should -Be "TestMultipleInstallers" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'Ok' - - Test-Path -Path $testDirectory | Should -Be $true - (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 - } - - It 'Download by InstallerType' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -InstallerType 'msi' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" - $result.Name | Should -Be "TestMultipleInstallers" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'Ok' - - Test-Path -Path $testDirectory | Should -Be $true - (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 - } - - It 'Download by InstallerType that does not exist' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -InstallerType 'zip' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'NoApplicableInstallers' - $result.ExtendedErrorCode | Should -Not -BeNullOrEmpty - Test-Path -Path $testDirectory | Should -Be $false - } - - It 'Download with short Version' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'Ok' - - # Download directory should be created and have exactly two files (installer and manifest file). - Test-Path -Path $testDirectory | Should -Be $true - (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 - } - - AfterEach { - if (Test-Path $testDirectory) { - Remove-Item $testDirectory -Force -Recurse - } - } -} - -Describe 'Get-WinGetUserSetting' { - - It 'Get setting' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $userSettings = Get-WinGetUserSetting - $userSettings | Should -Not -BeNullOrEmpty -ErrorAction Stop - $userSettings.Count | Should -Be 2 - $userSettings.visual.progressBar | Should -Be 'rainbow' - $userSettings.experimentalFeatures.experimentalArg | Should -Be $false - $userSettings.experimentalFeatures.experimentalCmd | Should -Be $true - } - - It 'Get settings. Bad json file' { - Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." - { Get-WinGetUserSetting } | Should -Throw - } - - AfterAll { - RestoreWinGetSettings - } -} - -Describe 'Test-WinGetUserSetting' { - - It 'Bad json file' { - Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." - - $inputSettings = @{ visual= @{ progressBar="retro"} } - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Equal' { - $ogSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - Test-WinGetUserSetting -UserSettings $ogSettings | Should -Be $true - } - - It 'Equal. Ignore schema' { - Set-Content -Path $settingsFilePath -Value '{ "$schema": "https://aka.ms/winget-settings.schema.json", "visual": { "progressBar": "retro" } }' - - $inputSettings = @{ visual= @{ progressBar="retro"} } - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $true - } - - It 'Not Equal string' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Not Equal bool' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Not Equal. More settings' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false }} - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Not Equal. More settings input' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Equal IgnoreNotSet' { - $ogSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - Test-WinGetUserSetting -UserSettings $ogSettings -IgnoreNotSet | Should -Be $true - } - - It 'Equal IgnoreNotSet. More settings' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false }} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $true - } - - It 'Not Equal IgnoreNotSet. More settings input' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false - } - - It 'Not Equal bool IgnoreNotSet' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false - } - - It 'Not Equal array IgnoreNotSet' { - $ogSettings = @{ installBehavior= @{ preferences= @{ architectures = @("x86", "x64")} }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ installBehavior= @{ preferences= @{ architectures = @("x86", "arm64")} }} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false - } - - It 'Not Equal wrong type IgnoreNotSet' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=4 ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false - } - - AfterAll { - RestoreWinGetSettings - } -} - -Describe 'Set-WinGetUserSetting' { - - It 'Overwrites' { - $ogSettings = @{ source= @{ autoUpdateIntervalInMinutes=3}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$false}} - $result = Set-WinGetUserSetting -UserSettings $inputSettings - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.'$schema' | Should -Not -BeNullOrEmpty - $result.visual | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.visual.progressBar | Should -Be "rainbow" - $result.experimentalFeatures | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.experimentalFeatures.experimentalArg | Should -Be $true - $result.experimentalFeatures.experimentalCmd | Should -Be $false - $result.source | Should -BeNullOrEmpty - } - - It 'Merge' { - $ogSettings = @{ source= @{ autoUpdateIntervalInMinutes=3}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$false}} - $result = Set-WinGetUserSetting -UserSettings $inputSettings -Merge - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.'$schema' | Should -Not -BeNullOrEmpty - $result.visual | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.visual.progressBar | Should -Be "rainbow" - $result.experimentalFeatures | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.experimentalFeatures.experimentalArg | Should -Be $true - $result.experimentalFeatures.experimentalCmd | Should -Be $false - $result.source | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.source.autoUpdateIntervalInMinutes | Should -Be 3 - } - - It 'Schema.' { - Set-Content -Path $settingsFilePath -Value '{ "$schema": "https://aka.ms/winget-settings.schema.json", "visual": { "progressBar": "retro" } }' - - $inputSettings = @{ visual= @{ progressBar="retro"} } - $result = Set-WinGetUserSetting -UserSettings $inputSettings - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.'$schema' | Should -Not -BeNullOrEmpty - $result.visual.progressBar | Should -Be "retro" - } - - It 'Overwrites Bad json file' { - Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." - - $inputSettings = @{ visual= @{ progressBar="retro"} } - $result = Set-WinGetUserSetting -UserSettings $inputSettings - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.'$schema' | Should -Not -BeNullOrEmpty - $result.visual.progressBar | Should -Be "retro" - } - - It 'Overwrites Bad json file' { - Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." - - $inputSettings = @{ visual= @{ progressBar="retro"} } - { Set-WinGetUserSetting -UserSettings $inputSettings -Merge } | Should -Throw - } - - AfterAll { - RestoreWinGetSettings - } -} - -Describe 'Get|Enable|Disable-WinGetSetting' { - - It 'Get-WinGetSetting' { - $settings = Get-WinGetSetting - $settings | Should -Not -BeNullOrEmpty -ErrorAction Stop - $settings.'$schema' | Should -Not -BeNullOrEmpty - $settings.adminSettings | Should -Not -BeNullOrEmpty - $settings.userSettingsFile | Should -Be $settingsFilePath - } - - # This tests require admin - It 'Enable|Disable' { - $settings = Get-WinGetSetting - $settings | Should -Not -BeNullOrEmpty -ErrorAction Stop - $settings.adminSettings | Should -Not -BeNullOrEmpty - $settings.adminSettings.LocalManifestFiles | Should -Be $false - - Enable-WinGetSetting -Name LocalManifestFiles - - $afterEnable = Get-WinGetSetting - $afterEnable | Should -Not -BeNullOrEmpty -ErrorAction Stop - $afterEnable.adminSettings | Should -Not -BeNullOrEmpty - $afterEnable.adminSettings.LocalManifestFiles | Should -Be $true - - Disable-WingetSetting -Name LocalManifestFiles - - $afterDisable = Get-WinGetSetting - $afterDisable | Should -Not -BeNullOrEmpty -ErrorAction Stop - $afterDisable.adminSettings | Should -Not -BeNullOrEmpty - $afterDisable.adminSettings.LocalManifestFiles | Should -Be $false - } -} - -Describe 'Test-GroupPolicies' { - BeforeAll { - CleanupGroupPolicies - CreatePolicyKeyIfNotExists - } - - It "Disable WinGetPolicy and run Get-WinGetVersion" { - $policyKeyValueName = "EnableAppInstaller" - - Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 - $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName - $registryKey | Should -Not -BeNullOrEmpty - $registryKey.EnableAppInstaller | Should -Be 0 - - { Get-WinGetVersion } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager" - - CleanupGroupPolicies - } - - It "Disable EnableWindowsPackageManagerCommandLineInterfaces Policy and run Get-WinGetVersion" { - $policyKeyValueName = "EnableWindowsPackageManagerCommandLineInterfaces" - - Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 - $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName - $registryKey | Should -Not -BeNullOrEmpty - $registryKey.EnableWindowsPackageManagerCommandLineInterfaces | Should -Be 0 - - { Get-WinGetVersion } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager command line interfaces" - - CleanupGroupPolicies - } - - AfterAll { - CleanupGroupPolicies - CleanupGroupPolicyKeyIfExists - } -} - -Describe 'WindowsPackageManagerServer' -Skip:($PSEdition -eq "Desktop") { - - BeforeEach { - AddTestSource - WaitForWindowsPackageManagerServer $true - } - - # When WindowsPackageManagerServer dies, we should not fail. - It 'Forced termination' { - $source = Get-WinGetSource -Name 'TestSource' - $source | Should -Not -BeNullOrEmpty - $source.Name | Should -Be 'TestSource' - - $process = Get-Process -Name "WindowsPackageManagerServer" - $process | Should -Not -BeNullOrEmpty - - # At least one is running. - $process | Where-Object { $_.HasExited -eq $false } | Should -Not -BeNullOrEmpty - - WaitForWindowsPackageManagerServer $true - - # From the ones we got, at least one exited - $process | Where-Object { $_.HasExited -eq $true } | Should -Not -BeNullOrEmpty - - $source2 = Get-WinGetSource -Name 'TestSource' - $source2 | Should -Not -BeNullOrEmpty - $source2.Name | Should -Be 'TestSource' - - $process2 = Get-Process -Name "WindowsPackageManagerServer" - $process2 | Should -Not -BeNullOrEmpty - $process2.Id | Should -Not -Be $process.Id - } - - # The Microsoft.WinGet.Client has static proxy objects of WindowsPackageManagerServer - # This tests does all the Microsoft.WinGet.Client calls in a different pwsh instance. - It 'Graceful termination' { - $typeTable = [System.Management.Automation.Runspaces.TypeTable]::LoadDefaultTypeFiles() - $oopRunspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateOutOfProcessRunspace($typeTable) - $oopRunspace.Open() - $oopPwsh = [PowerShell]::Create() - $oopPwsh.Runspace = $oopRunspace - $oopPwshPid = $oopPwsh.AddScript("`$PID").Invoke() - $oopPwshProcess = Get-Process -Id $oopPwshPid - $oopPwshProcess.HasExited | Should -Be $false - - $source = $oopPwsh.AddScript("Get-WinGetSource -Name TestSource").Invoke() - $source | Should -Not -BeNullOrEmpty - $source.Name | Should -Be 'TestSource' - - $wingetProcess = Get-Process -Name "WindowsPackageManagerServer" - $wingetProcess | Should -Not -BeNullOrEmpty - - # At least one is running. - $wingetProcess | Where-Object { $_.HasExited -eq $false } | Should -Not -BeNullOrEmpty - - $oopRunspace.Close() - - Start-Sleep -Seconds 30 - $oopPwshProcess.HasExited | Should -Be $true - - # From the ones we got, at least one exited - WaitForWindowsPackageManagerServer - $wingetProcess | Where-Object { $_.HasExited -eq $true } | Should -Not -BeNullOrEmpty - } -} - -Describe 'Add|Get|Remove|Reset-WinGetPin' { - - BeforeAll { - AddTestSource - } - - AfterAll { - Reset-WinGetPin -Force | Out-Null - RemoveTestSource - } - - Context 'Add-WinGetPin' { - - AfterEach { - Reset-WinGetPin -Force | Out-Null - } - - It 'Add pinning pin by Id' { - $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - - $result | Should -Not -BeNullOrEmpty - $result.Status | Should -Be 'Ok' - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -Not -BeNullOrEmpty - $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' - $pin.Type | Should -Be 'Pinning' - } - - It 'Add blocking pin by Id' { - $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Blocking - - $result | Should -Not -BeNullOrEmpty - $result.Status | Should -Be 'Ok' - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -Not -BeNullOrEmpty - $pin.Type | Should -Be 'Blocking' - } - - It 'Add gating pin with GatedVersion' { - $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -GatedVersion '<2.0.0.0' - - $result | Should -Not -BeNullOrEmpty - $result.Status | Should -Be 'Ok' - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -Not -BeNullOrEmpty - $pin.Type | Should -Be 'Gating' - $pin.GatedVersion | Should -Be '<2.0.0.0' - } - - It 'Add pin with Note' { - $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Note 'test note' - - $result | Should -Not -BeNullOrEmpty - $result.Status | Should -Be 'Ok' - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -Not -BeNullOrEmpty - $pin.Note | Should -Be 'test note' - } - - It 'Add with -Blocking and -GatedVersion throws' { - { Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Blocking -GatedVersion '<2.0.0.0' } | Should -Throw - } - - It 'Add with -WhatIf does not create pin' { - Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -WhatIf - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -BeNullOrEmpty - } - } - - Context 'Get-WinGetPin' { - - BeforeAll { - Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null - } - - AfterAll { - Reset-WinGetPin -Force | Out-Null - } - - It 'Get all pins returns non-empty list' { - $pins = Get-WinGetPin - $pins | Should -Not -BeNullOrEmpty - } - - It 'Get pin by Id' { - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - - $pin | Should -Not -BeNullOrEmpty - $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' - } - - It 'Get pin scoped to Source' { - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - - $pin | Should -Not -BeNullOrEmpty - $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' - $pin.SourceId | Should -Not -BeNullOrEmpty - } - - It 'Get pin via pipeline from Find-WinGetPackage' { - $pin = Find-WinGetPackage -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -MatchOption Equals | Get-WinGetPin - - $pin | Should -Not -BeNullOrEmpty - $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' - } - } - - Context 'Remove-WinGetPin' { - - BeforeEach { - Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null - } - - AfterEach { - Reset-WinGetPin -Force | Out-Null - } - - It 'Remove pin by Id' { - $result = Remove-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - - $result | Should -Not -BeNullOrEmpty - $result.Status | Should -Be 'Ok' - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -BeNullOrEmpty - } - - It 'Remove pin via pipeline from Get-WinGetPin' { - Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Remove-WinGetPin - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -BeNullOrEmpty - } - - It 'Remove with -WhatIf does not remove pin' { - Remove-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -WhatIf - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -Not -BeNullOrEmpty - } - } - - Context 'Reset-WinGetPin' { - - BeforeEach { - Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null - } - - It 'Reset all pins with -Force' { - $result = Reset-WinGetPin -Force - - $result | Should -Not -BeNullOrEmpty - $result.Status | Should -Be 'Ok' - - $pins = Get-WinGetPin - $pins | Should -BeNullOrEmpty - } - - It 'Reset with -Source scopes to that source' { - $result = Reset-WinGetPin -Source 'TestSource' -Force - - $result | Should -Not -BeNullOrEmpty - $result.Status | Should -Be 'Ok' - - $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' - $pin | Should -BeNullOrEmpty - } - - It 'Reset with -WhatIf does not remove pins' { - Reset-WinGetPin -WhatIf - - $pins = Get-WinGetPin - $pins | Should -Not -BeNullOrEmpty - } - - AfterEach { - Reset-WinGetPin -Force | Out-Null - } - } -} - -AfterAll { - RestoreWinGetSettings - RemoveTestSource -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Pester tests related to the Microsoft.WinGet.Client PowerShell module. + The tests require the localhost web server to be running and serving the test data. + 'Invoke-Pester' should be called in an admin PowerShell window. +#> +[CmdletBinding()] +param( + # Whether to use production or developement targets. + [switch]$TargetProduction +) + +BeforeAll { + if ($TargetProduction) + { + $wingetExeName = "winget.exe" + } + else + { + $wingetExeName = "wingetdev.exe" + } + + $settingsFilePath = (ConvertFrom-Json (& $wingetExeName settings export)).userSettingsFile + $originalSettingsContent = Get-Content -Path $settingsFilePath -Raw + + $deviceGroupPolicyRoot = "HKLM:\Software\Policies\Microsoft\Windows" + $wingetPolicyKeyName = "AppInstaller" + $wingetGroupPolicyRegistryRoot = $deviceGroupPolicyRoot + "\" + $wingetPolicyKeyName + + Import-Module Microsoft.WinGet.Client + + function SetWinGetSettingsHelper($settings) { + $content = ConvertTo-Json $settings -Depth 4 + Set-Content -Path $settingsFilePath -Value $content + } + + function RestoreWinGetSettings() { + Set-Content -Path $settingsFilePath -Value $originalSettingsContent + } + + # Source Add requires admin privileges, this will only execute successfully in an elevated PowerShell. + function AddTestSource { + try { + Get-WinGetSource -Name 'TestSource' + } + catch { + Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -TrustLevel 'Trusted' + } + } + + # This is a workaround to an issue where the server takes longer than expected to terminate when + # running from PowerShell. This can cause other E2E tests to fail when attempting to reset the test source. + function RemoveTestSource { + try { + # Source Remove requires admin privileges, this will only execute successfully in an elevated PowerShell. + $testSource = Get-WinGetSource | Where-Object -Property 'Name' -eq 'TestSource' + if ($null -ne $testSource) + { + # Source Remove requires admin privileges + Remove-WinGetSource -Name 'TestSource' + } + } + catch { + # Non-admin + Start-Process -FilePath $wingetExeName -ArgumentList "source remove TestSource" + } + } + + function CreatePolicyKeyIfNotExists() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if(-Not($registryExists)) + { + New-Item -Path $deviceGroupPolicyRoot -Name $wingetPolicyKeyName + } + } + + function CleanupGroupPolicyKeyIfExists() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if($registryExists) + { + Remove-Item -Path $wingetGroupPolicyRegistryRoot -Recurse + } + } + + function CleanupGroupPolicies() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if($registryExists) + { + Remove-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name * + } + } + + function WaitForWindowsPackageManagerServer([bool]$force = $false) + { + $processes = Get-Process | Where-Object { $_.Name -eq "WindowsPackageManagerServer" } + foreach ($p in $processes) + { + if ($force) + { + Stop-Process $p + } + + $timeout = 300 + $secondsToWait = 5 + $time = 0 + while ($p.HasExited -eq $false) + { + $time += $secondsToWait + if ($time -ge $timeout ) + { + throw "Timeout waiting for $($p.Id) to exit" + } + Start-Sleep -Seconds 5 + } + } + } + + function GetRandomTestDirectory() + { + return Join-Path -Path $env:Temp -ChildPath "WingetPwshTest-$(New-Guid)" + } + + function Validate-WinGetResultCommonFields([psobject]$result, [psobject]$expected) { + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be $expected.Id + $result.Name | Should -Be $expected.Name + $result.Source | Should -Be $expected.Source + $result.Status | Should -Be $expected.Status + } + + function Validate-WinGetPackageOperationResult([psobject]$result, [psobject]$expected, [string]$operationType) + { + Validate-WinGetResultCommonFields $result $expected + $result.RebootRequired | Should -Be $expected.RebootRequired + + switch ($operationType) { + 'install' { + $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode + } + 'update' { + $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode + } + 'repair' { + $result.RepairErrorCode | Should -Be $expected.RepairErrorCode + } + 'uninstall' { + $result.UninstallerErrorCode | Should -Be $expected.UninstallerErrorCode + } + default { + throw "Unknown operation type: $operationType" + } + } + } +} + +Describe 'Get-WinGetVersion' { + + It 'Get-WinGetVersion' { + $version = Get-WinGetVersion + $version | Should -Not -BeNullOrEmpty -ErrorAction Stop + } +} + +Describe 'Reset-WinGetSource' { + BeforeAll { + AddTestSource + } + + # Requires admin + It 'Resets all sources' { + Reset-WinGetSource -All + } + + It 'Test source should be removed' { + { Get-WinGetSource -Name 'TestSource' } | Should -Throw + } +} + +Describe 'Get|Add|Reset-WinGetSource' { + + BeforeAll { + $ogSettings = @{ experimentalFeatures= @{sourcePriority=$true}} + SetWinGetSettingsHelper $ogSettings + + Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -TrustLevel 'Trusted' -Explicit -Priority 42 + } + + It 'Get Test source' { + $source = Get-WinGetSource -Name 'TestSource' + + $source | Should -Not -BeNullOrEmpty -ErrorAction Stop + $source.Name | Should -Be 'TestSource' + $source.Argument | Should -Be 'https://localhost:5001/TestKit/' + $source.Type | Should -Be 'Microsoft.PreIndexed.Package' + $source.TrustLevel | Should -Be 'Trusted' + $source.Explicit | Should -Be $true + $source.Priority | Should -Be 42 + } + + It 'Get fake source' { + { Get-WinGetSource -Name 'Fake' } | Should -Throw + } + + # This tests require admin + It 'Reset Test source' { + Reset-WinGetSource -Name TestSource + } + + AfterAll { + RemoveTestSource + RestoreWinGetSettings + } +} + +Describe 'Find-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + It 'Given no parameters, lists all available packages' { + $allPackages = Find-WinGetPackage -Source TestSource + $allPackages.Count | Should -BeGreaterThan 0 + } + + It 'Find by Id' { + $package = Find-WinGetPackage -Source 'TestSource' -Id 'AppInstallerTest.TestExampleInstaller' + + $package | Should -Not -BeNullOrEmpty -ErrorAction Stop + $package.Name | Should -Be 'TestExampleInstaller' + $package.Id | Should -Be 'AppInstallerTest.TestExampleInstaller' + $package.Version | Should -Be '1.2.3.4' + $package.Source | Should -Be 'TestSource' + } + + It 'Find by Name' { + $package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' + + $package | Should -Not -BeNullOrEmpty -ErrorAction Stop + $package[0].Name | Should -Be 'TestPortableExe' + $package[1].Name | Should -Be 'TestPortableExeWithCommand' + } + + It 'Find by Name sort by Version' { + $package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' | Sort-Object 'Version' + + $package | Should -Not -BeNullOrEmpty -ErrorAction Stop + $package[0].Name | Should -Be 'TestPortableExeWithCommand' + $package[1].Name | Should -Be 'TestPortableExe' + } + + It 'Find package and verify PackageVersionInfo' { + $package = Find-WinGetPackage -Source 'TestSource' -Id 'AppInstallerTest.TestPortableExe' -MatchOption Equals + + $package | Should -Not -BeNullOrEmpty -ErrorAction Stop + $package.AvailableVersions[0] | Should -Be '3.0.0.0' + $package.AvailableVersions[1] | Should -Be '2.0.0.0' + $package.AvailableVersions.Count | Should -Be 4 + + $packageVersionInfo = $package.GetPackageVersionInfo("3.0.0.0") + $packageVersionInfo.DisplayName | Should -Be 'TestPortableExe' + $packageVersionInfo.Id | Should -Be 'AppInstallerTest.TestPortableExe' + $packageVersionInfo.CompareToVersion("2.0.0.0") | Should -Be 'Greater' + $packageVersionInfo.CompareToVersion("4.0.0.0") | Should -Be 'Lesser' + } +} + +Describe 'Install|Update|Uninstall-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + BeforeEach { + $expectedExeInstallerResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestExeInstaller" + Name = "TestExeInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + UninstallerErrorCode = 0 + } + + $expectedPortableInstallerResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestPortableExe" + Name = "TestPortableExe" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install by Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' + } + + It 'Install by exact Name and Version' { + $result = Install-WinGetPackage -Name TestPortableExe -Version '2.0.0.0' -MatchOption Equals + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'install' + } + + It 'Update by Id' { + $result = Update-WinGetPackage -Id AppInstallerTest.TestExeInstaller + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'update' + } + + It 'Update by Name' { + $result = Update-WinGetPackage -Name TestPortableExe + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'update' + } + + It 'Uninstall by Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'uninstall' + } + + It 'Uninstall by Name' { + $result = Uninstall-WinGetPackage -Name TestPortableExe + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'uninstall' + } + + AfterAll { + # Uninstall all test packages after each for proper cleanup. + $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller -MatchOption Equals + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller + } + + $testPortable = Get-WinGetPackage -Id AppInstallerTest.TestPortableExe -MatchOption Equals + if ($testPortable.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestPortableExe + } + } +} + +Describe 'Install|Repair|Uninstall-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + Context 'MSIX Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestMsixInstaller" + Name = "TestMsixInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install MSIX By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Repair MSIX By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It 'Uninstall MSIX By Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + + Context 'Burn installer "Modify" Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestModifyRepair" + Name = "TestModifyRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install Burn Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestModifyRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Repair Burn Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestModifyRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It 'Uninstall Burn Installer By Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + + Context 'Exe Installer "Uninstaller" Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.UninstallerRepair" + Name = "UninstallerRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install Exe Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.UninstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Uninstaller Repair Exe Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.UninstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It "Uninstall Exe Installer By Id" { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + + Context 'Inno "Installer" Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestInstallerRepair" + Name = "TestInstallerRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install Exe Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Installer Repair Exe Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It "Uninstall Exe Installer By Id" { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + + AfterAll { + # Uninstall all test packages after each for proper cleanup. + $testMsix = Get-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + if ($testMsix.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + } + + $testBurn = Get-WinGetPackage -Id AppInstallerTest.TestModifyRepair + if ($testBurn.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair + } + + $testExe = Get-WinGetPackage -Id AppInstallerTest.UninstallerRepair + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair + } + + $testInno = Get-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + if ($testInno.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + } + } +} + +Describe 'Install-WinGetPackage Source Priority' { + + It 'Install equal Priority' { + AddTestSource + Add-WinGetSource -Name 'dummyPackageSource' -Type 'Microsoft.Test.Configurable' -Arg '{"ContainsPackage":true}' + + { Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' } | Should -Throw + } + + It 'Install higher Priority' { + $ogSettings = @{ experimentalFeatures= @{sourcePriority=$true}} + SetWinGetSettingsHelper $ogSettings + + RemoveTestSource + Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -Priority 1 + Add-WinGetSource -Name 'dummyPackageSource' -Type 'Microsoft.Test.Configurable' -Arg '{"ContainsPackage":true}' + + $expectedExeInstallerResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestExeInstaller" + Name = "TestExeInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + UninstallerErrorCode = 0 + } + + $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' + } + + AfterEach { + $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller -MatchOption Equals + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller + } + + Remove-WinGetSource -Name 'dummyPackageSource' + RemoveTestSource + RestoreWinGetSettings + } +} + +Describe 'Get-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + It 'Install by Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.InstallerErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Get package by Id' { + $result = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Name | Should -Be 'TestExeInstaller' + $result.Id | Should -Be 'AppInstallerTest.TestExeInstaller' + $result.InstalledVersion | Should -Be '1.0.0.0' + $result.Source | Should -Be 'TestSource' + $result.AvailableVersions[0] | Should -Be '2.0.0.0' + } + + It 'Get package by Name' { + $result = Get-WinGetPackage -Name TestExeInstaller + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Name | Should -Be 'TestExeInstaller' + $result.Id | Should -Be 'AppInstallerTest.TestExeInstaller' + $result.InstalledVersion | Should -Be '1.0.0.0' + $result.Source | Should -Be 'TestSource' + $result.AvailableVersions[0] | Should -Be '2.0.0.0' + } + + AfterAll { + # Uninstall all test packages after each for proper cleanup. + $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller + } + } +} + +Describe 'Export-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + It 'Download by Id' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + # Download directory should be created and have exactly two files (installer and manifest file). + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by Locale' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -Locale 'zh-CN' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" + $result.Name | Should -Be "TestMultipleInstallers" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by InstallerType' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -InstallerType 'msi' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" + $result.Name | Should -Be "TestMultipleInstallers" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by InstallerType that does not exist' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -InstallerType 'zip' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'NoApplicableInstallers' + $result.ExtendedErrorCode | Should -Not -BeNullOrEmpty + Test-Path -Path $testDirectory | Should -Be $false + } + + It 'Download with short Version' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + # Download directory should be created and have exactly two files (installer and manifest file). + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + AfterEach { + if (Test-Path $testDirectory) { + Remove-Item $testDirectory -Force -Recurse + } + } +} + +Describe 'Get-WinGetUserSetting' { + + It 'Get setting' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $userSettings = Get-WinGetUserSetting + $userSettings | Should -Not -BeNullOrEmpty -ErrorAction Stop + $userSettings.Count | Should -Be 2 + $userSettings.visual.progressBar | Should -Be 'rainbow' + $userSettings.experimentalFeatures.experimentalArg | Should -Be $false + $userSettings.experimentalFeatures.experimentalCmd | Should -Be $true + } + + It 'Get settings. Bad json file' { + Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." + { Get-WinGetUserSetting } | Should -Throw + } + + AfterAll { + RestoreWinGetSettings + } +} + +Describe 'Test-WinGetUserSetting' { + + It 'Bad json file' { + Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." + + $inputSettings = @{ visual= @{ progressBar="retro"} } + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Equal' { + $ogSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + Test-WinGetUserSetting -UserSettings $ogSettings | Should -Be $true + } + + It 'Equal. Ignore schema' { + Set-Content -Path $settingsFilePath -Value '{ "$schema": "https://aka.ms/winget-settings.schema.json", "visual": { "progressBar": "retro" } }' + + $inputSettings = @{ visual= @{ progressBar="retro"} } + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $true + } + + It 'Not Equal string' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Not Equal bool' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Not Equal. More settings' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false }} + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Not Equal. More settings input' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Equal IgnoreNotSet' { + $ogSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + Test-WinGetUserSetting -UserSettings $ogSettings -IgnoreNotSet | Should -Be $true + } + + It 'Equal IgnoreNotSet. More settings' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false }} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $true + } + + It 'Not Equal IgnoreNotSet. More settings input' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false + } + + It 'Not Equal bool IgnoreNotSet' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false + } + + It 'Not Equal array IgnoreNotSet' { + $ogSettings = @{ installBehavior= @{ preferences= @{ architectures = @("x86", "x64")} }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ installBehavior= @{ preferences= @{ architectures = @("x86", "arm64")} }} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false + } + + It 'Not Equal wrong type IgnoreNotSet' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=4 ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false + } + + AfterAll { + RestoreWinGetSettings + } +} + +Describe 'Set-WinGetUserSetting' { + + It 'Overwrites' { + $ogSettings = @{ source= @{ autoUpdateIntervalInMinutes=3}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$false}} + $result = Set-WinGetUserSetting -UserSettings $inputSettings + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.'$schema' | Should -Not -BeNullOrEmpty + $result.visual | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.visual.progressBar | Should -Be "rainbow" + $result.experimentalFeatures | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.experimentalFeatures.experimentalArg | Should -Be $true + $result.experimentalFeatures.experimentalCmd | Should -Be $false + $result.source | Should -BeNullOrEmpty + } + + It 'Merge' { + $ogSettings = @{ source= @{ autoUpdateIntervalInMinutes=3}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$false}} + $result = Set-WinGetUserSetting -UserSettings $inputSettings -Merge + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.'$schema' | Should -Not -BeNullOrEmpty + $result.visual | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.visual.progressBar | Should -Be "rainbow" + $result.experimentalFeatures | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.experimentalFeatures.experimentalArg | Should -Be $true + $result.experimentalFeatures.experimentalCmd | Should -Be $false + $result.source | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.source.autoUpdateIntervalInMinutes | Should -Be 3 + } + + It 'Schema.' { + Set-Content -Path $settingsFilePath -Value '{ "$schema": "https://aka.ms/winget-settings.schema.json", "visual": { "progressBar": "retro" } }' + + $inputSettings = @{ visual= @{ progressBar="retro"} } + $result = Set-WinGetUserSetting -UserSettings $inputSettings + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.'$schema' | Should -Not -BeNullOrEmpty + $result.visual.progressBar | Should -Be "retro" + } + + It 'Overwrites Bad json file' { + Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." + + $inputSettings = @{ visual= @{ progressBar="retro"} } + $result = Set-WinGetUserSetting -UserSettings $inputSettings + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.'$schema' | Should -Not -BeNullOrEmpty + $result.visual.progressBar | Should -Be "retro" + } + + It 'Overwrites Bad json file' { + Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." + + $inputSettings = @{ visual= @{ progressBar="retro"} } + { Set-WinGetUserSetting -UserSettings $inputSettings -Merge } | Should -Throw + } + + AfterAll { + RestoreWinGetSettings + } +} + +Describe 'Get|Enable|Disable-WinGetSetting' { + + It 'Get-WinGetSetting' { + $settings = Get-WinGetSetting + $settings | Should -Not -BeNullOrEmpty -ErrorAction Stop + $settings.'$schema' | Should -Not -BeNullOrEmpty + $settings.adminSettings | Should -Not -BeNullOrEmpty + $settings.userSettingsFile | Should -Be $settingsFilePath + } + + # This tests require admin + It 'Enable|Disable' { + $settings = Get-WinGetSetting + $settings | Should -Not -BeNullOrEmpty -ErrorAction Stop + $settings.adminSettings | Should -Not -BeNullOrEmpty + $settings.adminSettings.LocalManifestFiles | Should -Be $false + + Enable-WinGetSetting -Name LocalManifestFiles + + $afterEnable = Get-WinGetSetting + $afterEnable | Should -Not -BeNullOrEmpty -ErrorAction Stop + $afterEnable.adminSettings | Should -Not -BeNullOrEmpty + $afterEnable.adminSettings.LocalManifestFiles | Should -Be $true + + Disable-WingetSetting -Name LocalManifestFiles + + $afterDisable = Get-WinGetSetting + $afterDisable | Should -Not -BeNullOrEmpty -ErrorAction Stop + $afterDisable.adminSettings | Should -Not -BeNullOrEmpty + $afterDisable.adminSettings.LocalManifestFiles | Should -Be $false + } +} + +Describe 'Test-GroupPolicies' { + BeforeAll { + CleanupGroupPolicies + CreatePolicyKeyIfNotExists + } + + It "Disable WinGetPolicy and run Get-WinGetVersion" { + $policyKeyValueName = "EnableAppInstaller" + + Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 + $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName + $registryKey | Should -Not -BeNullOrEmpty + $registryKey.EnableAppInstaller | Should -Be 0 + + { Get-WinGetVersion } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager" + + CleanupGroupPolicies + } + + It "Disable EnableWindowsPackageManagerCommandLineInterfaces Policy and run Get-WinGetVersion" { + $policyKeyValueName = "EnableWindowsPackageManagerCommandLineInterfaces" + + Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 + $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName + $registryKey | Should -Not -BeNullOrEmpty + $registryKey.EnableWindowsPackageManagerCommandLineInterfaces | Should -Be 0 + + { Get-WinGetVersion } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager command line interfaces" + + CleanupGroupPolicies + } + + AfterAll { + CleanupGroupPolicies + CleanupGroupPolicyKeyIfExists + } +} + +Describe 'WindowsPackageManagerServer' -Skip:($PSEdition -eq "Desktop") { + + BeforeEach { + AddTestSource + WaitForWindowsPackageManagerServer $true + } + + # When WindowsPackageManagerServer dies, we should not fail. + It 'Forced termination' { + $source = Get-WinGetSource -Name 'TestSource' + $source | Should -Not -BeNullOrEmpty + $source.Name | Should -Be 'TestSource' + + $process = Get-Process -Name "WindowsPackageManagerServer" + $process | Should -Not -BeNullOrEmpty + + # At least one is running. + $process | Where-Object { $_.HasExited -eq $false } | Should -Not -BeNullOrEmpty + + WaitForWindowsPackageManagerServer $true + + # From the ones we got, at least one exited + $process | Where-Object { $_.HasExited -eq $true } | Should -Not -BeNullOrEmpty + + $source2 = Get-WinGetSource -Name 'TestSource' + $source2 | Should -Not -BeNullOrEmpty + $source2.Name | Should -Be 'TestSource' + + $process2 = Get-Process -Name "WindowsPackageManagerServer" + $process2 | Should -Not -BeNullOrEmpty + $process2.Id | Should -Not -Be $process.Id + } + + # The Microsoft.WinGet.Client has static proxy objects of WindowsPackageManagerServer + # This tests does all the Microsoft.WinGet.Client calls in a different pwsh instance. + It 'Graceful termination' { + $typeTable = [System.Management.Automation.Runspaces.TypeTable]::LoadDefaultTypeFiles() + $oopRunspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateOutOfProcessRunspace($typeTable) + $oopRunspace.Open() + $oopPwsh = [PowerShell]::Create() + $oopPwsh.Runspace = $oopRunspace + $oopPwshPid = $oopPwsh.AddScript("`$PID").Invoke() + $oopPwshProcess = Get-Process -Id $oopPwshPid + $oopPwshProcess.HasExited | Should -Be $false + + $source = $oopPwsh.AddScript("Get-WinGetSource -Name TestSource").Invoke() + $source | Should -Not -BeNullOrEmpty + $source.Name | Should -Be 'TestSource' + + $wingetProcess = Get-Process -Name "WindowsPackageManagerServer" + $wingetProcess | Should -Not -BeNullOrEmpty + + # At least one is running. + $wingetProcess | Where-Object { $_.HasExited -eq $false } | Should -Not -BeNullOrEmpty + + $oopRunspace.Close() + + Start-Sleep -Seconds 30 + $oopPwshProcess.HasExited | Should -Be $true + + # From the ones we got, at least one exited + WaitForWindowsPackageManagerServer + $wingetProcess | Where-Object { $_.HasExited -eq $true } | Should -Not -BeNullOrEmpty + } +} + +Describe 'Add|Get|Remove|Reset-WinGetPin' { + + BeforeAll { + AddTestSource + } + + AfterAll { + Reset-WinGetPin -Force | Out-Null + RemoveTestSource + } + + Context 'Add-WinGetPin' { + + AfterEach { + Reset-WinGetPin -Force | Out-Null + } + + It 'Add pinning pin by Id' { + $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' + $pin.Type | Should -Be 'Pinning' + } + + It 'Add blocking pin by Id' { + $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Blocking + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + $pin.Type | Should -Be 'Blocking' + } + + It 'Add gating pin with GatedVersion' { + $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -GatedVersion '<2.0.0.0' + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + $pin.Type | Should -Be 'Gating' + $pin.GatedVersion | Should -Be '<2.0.0.0' + } + + It 'Add pin with Note' { + $result = Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Note 'test note' + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + $pin.Note | Should -Be 'test note' + } + + It 'Add with -Blocking and -GatedVersion throws' { + { Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -Blocking -GatedVersion '<2.0.0.0' } | Should -Throw + } + + It 'Add with -WhatIf does not create pin' { + Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -WhatIf + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -BeNullOrEmpty + } + } + + Context 'Get-WinGetPin' { + + BeforeAll { + Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null + } + + AfterAll { + Reset-WinGetPin -Force | Out-Null + } + + It 'Get all pins returns non-empty list' { + $pins = Get-WinGetPin + $pins | Should -Not -BeNullOrEmpty + } + + It 'Get pin by Id' { + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + + $pin | Should -Not -BeNullOrEmpty + $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' + } + + It 'Get pin scoped to Source' { + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + + $pin | Should -Not -BeNullOrEmpty + $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' + $pin.SourceId | Should -Not -BeNullOrEmpty + } + + It 'Get pin via pipeline from Find-WinGetPackage' { + $pin = Find-WinGetPackage -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -MatchOption Equals | Get-WinGetPin + + $pin | Should -Not -BeNullOrEmpty + $pin.PackageId | Should -Be 'AppInstallerTest.TestExeInstaller' + } + } + + Context 'Remove-WinGetPin' { + + BeforeEach { + Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null + } + + AfterEach { + Reset-WinGetPin -Force | Out-Null + } + + It 'Remove pin by Id' { + $result = Remove-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -BeNullOrEmpty + } + + It 'Remove pin via pipeline from Get-WinGetPin' { + Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Remove-WinGetPin + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -BeNullOrEmpty + } + + It 'Remove with -WhatIf does not remove pin' { + Remove-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' -WhatIf + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -Not -BeNullOrEmpty + } + } + + Context 'Reset-WinGetPin' { + + BeforeEach { + Add-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' | Out-Null + } + + It 'Reset all pins with -Force' { + $result = Reset-WinGetPin -Force + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pins = Get-WinGetPin + $pins | Should -BeNullOrEmpty + } + + It 'Reset with -Source scopes to that source' { + $result = Reset-WinGetPin -Source 'TestSource' -Force + + $result | Should -Not -BeNullOrEmpty + $result.Status | Should -Be 'Ok' + + $pin = Get-WinGetPin -Id 'AppInstallerTest.TestExeInstaller' -Source 'TestSource' + $pin | Should -BeNullOrEmpty + } + + It 'Reset with -WhatIf does not remove pins' { + Reset-WinGetPin -WhatIf + + $pins = Get-WinGetPin + $pins | Should -Not -BeNullOrEmpty + } + + AfterEach { + Reset-WinGetPin -Force | Out-Null + } + } +} + +AfterAll { + RestoreWinGetSettings + RemoveTestSource +} From 19406e9ab22d83f03912316aaa446898565dea75 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 14:44:19 -0500 Subject: [PATCH 60/60] Fix release notes --- doc/ReleaseNotes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 8955dd2f15..187e3a490e 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -5,7 +5,8 @@ ### Pinning improvements - `winget pin add` now supports an optional `--note` value for pin metadata. -- `winget pin show` was added to display detailed pin information for a package. +- `winget pin add` now tracks the date that a pin was added or updated. +- `winget pin list` now supports an optional `--details` argument for showing extended pin data. - The PowerShell pin cmdlets now expose the new pinning capabilities and metadata. ## Bug Fixes