diff --git a/doc/Settings.md b/doc/Settings.md
index b2a7cd98f9..f0d09d3b2d 100644
--- a/doc/Settings.md
+++ b/doc/Settings.md
@@ -253,7 +253,8 @@ You can enable the feature as shown below.
"openLogsArgument": true
},
```
-### Dependencies
+
+### dependencies
Experimental feature with the aim of managing dependencies, as of now it only shows package dependency information. You can enable the feature as shown below.
@@ -262,3 +263,14 @@ Experimental feature with the aim of managing dependencies, as of now it only sh
"dependencies": true
},
```
+
+### pinning
+
+This feature enables the ability to pin packages to prevent the Windows Package Manager from updating them.
+You can enable the feature as shown below.
+
+```json
+ "experimentalFeatures": {
+ "pinning": true
+ },
+```
\ No newline at end of file
diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
index a78da26639..effaa5d8b7 100644
--- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
+++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
@@ -249,6 +249,7 @@
+
@@ -283,6 +284,7 @@
+
@@ -299,6 +301,7 @@
+
diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
index 5ddf0b00df..53e912fc82 100644
--- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
+++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
@@ -188,6 +188,12 @@
Header Files
+
+ Commands
+
+
+ Workflows
+
@@ -343,6 +349,9 @@
Source Files
+
+ Source Files
+
diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp
new file mode 100644
index 0000000000..5cdedb26b0
--- /dev/null
+++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp
@@ -0,0 +1,243 @@
+// 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::Utility::literals;
+ using namespace std::string_view_literals;
+
+ static constexpr std::string_view s_PinCommand_HelpLink = "https://aka.ms/winget-command-pin"sv;
+
+ 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 };
+ }
+
+ std::string PinCommand::HelpLink() const
+ {
+ return std::string{ 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{ "version"_liv, 'v', Args::Type::GatedVersion, Resource::String::GatedVersionArgumentDescription, ArgumentType::Standard },
+ Argument::ForType(Args::Type::Source),
+ Argument::ForType(Args::Type::CustomHeader),
+ Argument::ForType(Args::Type::AcceptSourceAgreements),
+ Argument::ForType(Args::Type::Force),
+ Argument{ "blocking"_liv, Argument::NoAlias, Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag },
+ };
+ }
+
+ 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
+ {
+ switch (valueType)
+ {
+ case Args::Type::Query:
+ case Args::Type::Id:
+ case Args::Type::Name:
+ case Args::Type::Moniker:
+ case Args::Type::Source:
+ context <<
+ Workflow::CompleteWithSingleSemanticsForValue(valueType);
+ break;
+ }
+ }
+
+ std::string PinAddCommand::HelpLink() const
+ {
+ return std::string{ s_PinCommand_HelpLink };
+ }
+
+ void PinAddCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const
+ {
+ if (execArgs.Contains(Execution::Args::Type::GatedVersion) && execArgs.Contains(Execution::Args::Type::BlockingPin))
+ {
+ throw CommandException(Resource::String::BothGatedVersionAndBlockingFlagProvided);
+ }
+
+ }
+
+ void PinAddCommand::ExecuteInternal(Execution::Context& context) const
+ {
+ // TODO
+ Command::ExecuteInternal(context);
+ }
+
+ 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::AcceptSourceAgreements),
+ };
+ }
+
+ 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
+ {
+ switch (valueType)
+ {
+ case Args::Type::Query:
+ case Args::Type::Id:
+ case Args::Type::Name:
+ case Args::Type::Moniker:
+ case Args::Type::Source:
+ context <<
+ Workflow::CompleteWithSingleSemanticsForValue(valueType);
+ break;
+ }
+ }
+
+ std::string PinRemoveCommand::HelpLink() const
+ {
+ return std::string{ s_PinCommand_HelpLink };
+ }
+
+ void PinRemoveCommand::ExecuteInternal(Execution::Context& context) const
+ {
+ // TODO
+ Command::ExecuteInternal(context);
+ }
+
+ 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::AcceptSourceAgreements),
+ };
+ }
+
+ 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
+ {
+ switch (valueType)
+ {
+ case Args::Type::Query:
+ case Args::Type::Id:
+ case Args::Type::Name:
+ case Args::Type::Moniker:
+ case Args::Type::Source:
+ context <<
+ Workflow::CompleteWithSingleSemanticsForValue(valueType);
+ break;
+ }
+ }
+
+ std::string PinListCommand::HelpLink() const
+ {
+ return std::string{ s_PinCommand_HelpLink };
+ }
+
+ void PinListCommand::ExecuteInternal(Execution::Context& context) const
+ {
+ // TODO
+ Command::ExecuteInternal(context);
+ }
+
+ std::vector PinResetCommand::GetArguments() const
+ {
+ return {
+ Argument::ForType(Args::Type::Force),
+ };
+ }
+
+ Resource::LocString PinResetCommand::ShortDescription() const
+ {
+ return { Resource::String::PinResetCommandShortDescription };
+ }
+
+ Resource::LocString PinResetCommand::LongDescription() const
+ {
+ return { Resource::String::PinResetCommandLongDescription };
+ }
+
+ std::string PinResetCommand::HelpLink() const
+ {
+ return std::string{ s_PinCommand_HelpLink };
+ }
+
+ void PinResetCommand::ExecuteInternal(Execution::Context& context) const
+ {
+ // TODO
+ Command::ExecuteInternal(context);
+ }
+}
diff --git a/src/AppInstallerCLICore/Commands/PinCommand.h b/src/AppInstallerCLICore/Commands/PinCommand.h
new file mode 100644
index 0000000000..8740971b9d
--- /dev/null
+++ b/src/AppInstallerCLICore/Commands/PinCommand.h
@@ -0,0 +1,90 @@
+// 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, Settings::ExperimentalFeature::Feature::Pinning) {}
+
+ std::vector> GetCommands() const override;
+
+ Resource::LocString ShortDescription() const override;
+ Resource::LocString LongDescription() const override;
+
+ std::string 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;
+
+ std::string 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;
+
+ std::string 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;
+
+ std::string 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;
+
+ std::string HelpLink() const override;
+
+ protected:
+ void ExecuteInternal(Execution::Context& context) const override;
+ };
+}
diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp
index f07358655e..ebf081e4df 100644
--- a/src/AppInstallerCLICore/Commands/RootCommand.cpp
+++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp
@@ -18,6 +18,7 @@
#include "CompleteCommand.h"
#include "ExportCommand.h"
#include "ImportCommand.h"
+#include "PinCommand.h"
#include "Resources.h"
#include "TableOutput.h"
@@ -127,6 +128,7 @@ namespace AppInstaller::CLI
std::make_unique(FullName()),
std::make_unique(FullName()),
std::make_unique(FullName()),
+ std::make_unique(FullName()),
});
}
diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h
index fd3d196b72..d0df99a98f 100644
--- a/src/AppInstallerCLICore/ExecutionArgs.h
+++ b/src/AppInstallerCLICore/ExecutionArgs.h
@@ -87,6 +87,10 @@ namespace AppInstaller::CLI::Execution
// Show command
ListVersions, // Used in Show command to list all available versions of an app
+ // Pin command
+ GatedVersion, // Differs from Version in that this supports wildcards
+ BlockingPin,
+
// Common arguments
NoVT, // Disable VirtualTerminal outputs
RetroStyle, // Makes progress display as retro
diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h
index 9ac6f3d06d..1122c02259 100644
--- a/src/AppInstallerCLICore/Resources.h
+++ b/src/AppInstallerCLICore/Resources.h
@@ -36,6 +36,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableSubcommands);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableUpgrades);
+ WINGET_DEFINE_RESOURCE_STRINGID(BothGatedVersionAndBlockingFlagProvided);
WINGET_DEFINE_RESOURCE_STRINGID(BothManifestAndSearchQueryProvided);
WINGET_DEFINE_RESOURCE_STRINGID(BothPurgeAndPreserveFlagsProvided);
WINGET_DEFINE_RESOURCE_STRINGID(Cancelled);
@@ -96,6 +97,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(FilesRemainInInstallDirectory);
WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError);
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);
@@ -237,6 +239,17 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled);
WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies);
WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandShortDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PoliciesDisabled);
WINGET_DEFINE_RESOURCE_STRINGID(PoliciesEnabled);
WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy);
diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h
new file mode 100644
index 0000000000..504ed5782e
--- /dev/null
+++ b/src/AppInstallerCLICore/Workflows/PinFlow.h
@@ -0,0 +1,8 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#pragma once
+#include "ExecutionContext.h"
+
+namespace AppInstaller::CLI::Workflow
+{
+}
diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
index 947caa549a..73044e944f 100644
--- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
+++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
@@ -1485,4 +1485,45 @@ Please specify one of them using the --source option to proceed.
Cannot disable %1. This setting is controlled by policy. For more information contact your system administrator.
{Locked="%1"} The value will be replaced with the feature name
+
+ Add a new pin. A pin can limit the Windows Package Manager from updating a package to specific ranges of versions, or it can prevent it from updating the package altogether. A pinned package may still update on its own and be updated from outside the Windows Package Manager. By default, a pinned package can be updated 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 updating a package to specific ranges of versions, or it can prevent it from updating the package altogether. A pinned package may still update on its own and be updated 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
+
+
+ Both version and '--blocking' arguments provided
+ {Locked="--blocking"}
+
+
+ Version to which to pin the package. The wildcard '*' can be used as the last version part
+
+
+ Block from updating until the pin is removed, preventing override arguments
+
\ No newline at end of file
diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp
index 49067ae966..22bcec667a 100644
--- a/src/AppInstallerCommonCore/ExperimentalFeature.cpp
+++ b/src/AppInstallerCommonCore/ExperimentalFeature.cpp
@@ -46,6 +46,8 @@ namespace AppInstaller::Settings
return userSettings.Get();
case ExperimentalFeature::Feature::OpenLogsArgument:
return userSettings.Get();
+ case ExperimentalFeature::Feature::Pinning:
+ return userSettings.Get();
default:
THROW_HR(E_UNEXPECTED);
}
@@ -81,6 +83,8 @@ namespace AppInstaller::Settings
return ExperimentalFeature{ "Zip Installation", "zipInstall", "https://aka.ms/winget-settings", Feature::ZipInstall };
case Feature::OpenLogsArgument:
return ExperimentalFeature{ "Open Logs Argument", "openLogsArgument", "https://aka.ms/winget-settings", Feature::OpenLogsArgument };
+ case Feature::Pinning:
+ return ExperimentalFeature{ "Package Pinning", "pinning", "https://aka.ms/winget-settings", Feature::Pinning};
default:
THROW_HR(E_UNEXPECTED);
}
diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h
index 76ac6e7523..f34b24d2c3 100644
--- a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h
+++ b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h
@@ -25,6 +25,7 @@ namespace AppInstaller::Settings
DirectMSI = 0x2,
ZipInstall = 0x4,
OpenLogsArgument = 0x8,
+ Pinning = 0x10,
Max, // This MUST always be after all experimental features
// Features listed after Max will not be shown with the features command
diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h
index b6e4a9b2cf..ec14844426 100644
--- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h
+++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h
@@ -79,6 +79,7 @@ namespace AppInstaller::Settings
EFDirectMSI,
EFZipInstall,
EFOpenLogsArgument,
+ EFPinning,
// Telemetry
TelemetryDisable,
// Install behavior
@@ -148,6 +149,7 @@ namespace AppInstaller::Settings
SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::EFZipInstall, bool, bool, false, ".experimentalFeatures.zipInstall"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::EFOpenLogsArgument, bool, bool, false, ".experimentalFeatures.openLogsArgument"sv);
+ SETTINGMAPPING_SPECIALIZATION(Setting::EFPinning, bool, bool, false, ".experimentalFeatures.pinning"sv);
// Telemetry
SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv);
// Install behavior
diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp
index ac842a19e6..c97d086868 100644
--- a/src/AppInstallerCommonCore/UserSettings.cpp
+++ b/src/AppInstallerCommonCore/UserSettings.cpp
@@ -249,6 +249,7 @@ namespace AppInstaller::Settings
WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI)
WINGET_VALIDATE_PASS_THROUGH(EFZipInstall)
WINGET_VALIDATE_PASS_THROUGH(EFOpenLogsArgument)
+ WINGET_VALIDATE_PASS_THROUGH(EFPinning)
WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable)
WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable)
WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump)