winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 7cea3d9582f9bd68a4406c73f07309b1df79f120
parent 8a006549c4aa0dd13cc17f6185ce30a1f4c2e71b
Author: Flor Chacón <14323496+florelis@users.noreply.github.com>
Date:   Thu, 14 Mar 2024 16:10:38 -0700

Add support for proxies #190 (#4203)

For #190
See spec on #4152 
See #1776 for a related PR for the feature with the core implementation
for proxies in wininet

This PR adds basic support for using proxies. Most of the changes are
for enabling the configuration and blocking of the feature. This feature
will be gated behind an experimental feature setting

* Added Group Policy and Admin settings for enabling/disabling the use
of proxy CLI arguments and for setting a default proxy.
  + Pending: Internal review for new Group Policy
+ Extended `AdminSettings` to support settings with string values,
instead of only bool flags. The implementation is mostly a copy of the
bool case. In the future we should look back at it to reduce duplication
of code.
+ Added a `set` subcommand to `settings` that can set the admin settings
* Added CLI arguments to select a proxy on each different invocation of
winget, or to disable the use of a default one.
* Updated calls to wininet and cpprestsdk to use the provided proxy, and
added plumbing to get the arguments from the command line to the point
of use.
* Changed the flow around downloads to force winget to use proxies if
available.

Manually tested on a VM using mitmproxy
Pending: Adding automated tests tests.

Co-authored-by: yao-msft <50888816+yao-msft@users.noreply.github.com>
Diffstat:
Mdoc/Settings.md | 11+++++++++++
Mdoc/admx/DesktopAppInstaller.admx | 17+++++++++++++++++
Mdoc/admx/en-US/DesktopAppInstaller.adml | 20+++++++++++++++++++-
Mschemas/JSON/settings/settings.export.schema.0.1.json | 20+++++++++++++++++++-
Mschemas/JSON/settings/settings.schema.0.2.json | 9+++++++--
Msrc/AppInstallerCLICore/Argument.cpp | 21++++++++++++++++++---
Msrc/AppInstallerCLICore/Argument.h | 12++++++++----
Msrc/AppInstallerCLICore/Command.cpp | 2+-
Msrc/AppInstallerCLICore/Commands/DownloadCommand.cpp | 4++--
Msrc/AppInstallerCLICore/Commands/RootCommand.cpp | 10+++++++++-
Msrc/AppInstallerCLICore/Commands/SettingsCommand.cpp | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/AppInstallerCLICore/Commands/SettingsCommand.h | 32++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/Core.cpp | 7-------
Msrc/AppInstallerCLICore/ExecutionArgs.h | 6++++++
Msrc/AppInstallerCLICore/ExecutionContext.cpp | 18++++++++++++++++++
Msrc/AppInstallerCLICore/ExecutionContextData.h | 1+
Msrc/AppInstallerCLICore/Resources.h | 18+++++++++++++++---
Msrc/AppInstallerCLICore/Workflows/ArchiveFlow.cpp | 2+-
Msrc/AppInstallerCLICore/Workflows/DownloadFlow.cpp | 16++++++++++++----
Msrc/AppInstallerCLICore/Workflows/SettingsFlow.cpp | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/AppInstallerCLICore/Workflows/SettingsFlow.h | 12++++++++++++
Msrc/AppInstallerCLIE2ETests/GroupPolicyHelper.cs | 6++++++
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 44++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/AdminSettings.cpp | 34++++++++++++++++++++++++++++------
Msrc/AppInstallerCLITests/CustomHeader.cpp | 14+++++++-------
Msrc/AppInstallerCLITests/GroupPolicy.cpp | 1+
Msrc/AppInstallerCLITests/InstallFlow.cpp | 4++--
Msrc/AppInstallerCLITests/RestClient.cpp | 20++++++++++----------
Msrc/AppInstallerCLITests/RestInterface_1_1.cpp | 20++++++++++----------
Msrc/AppInstallerCLITests/RestInterface_1_4.cpp | 4++--
Msrc/AppInstallerCLITests/RestInterface_1_5.cpp | 2+-
Msrc/AppInstallerCLITests/RestInterface_1_6.cpp | 2+-
Msrc/AppInstallerCLITests/RestInterface_1_7.cpp | 14+++++++-------
Msrc/AppInstallerCLITests/TestSettings.h | 1+
Msrc/AppInstallerCLITests/pch.h | 2++
Msrc/AppInstallerCommonCore/AdminSettings.cpp | 258++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 2++
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters | 6++++++
Msrc/AppInstallerCommonCore/Deployment.cpp | 2++
Msrc/AppInstallerCommonCore/Downloader.cpp | 37++++++++++++++++++++++++++-----------
Msrc/AppInstallerCommonCore/ExperimentalFeature.cpp | 4++++
Msrc/AppInstallerCommonCore/HttpStream/HttpClientWrapper.cpp | 1+
Msrc/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.h | 3+--
Msrc/AppInstallerCommonCore/Manifest/MsixManifestValidation.cpp | 4++--
Asrc/AppInstallerCommonCore/NetworkSettings.cpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Public/AppInstallerDownloader.h | 15+++++----------
Msrc/AppInstallerCommonCore/Public/winget/AdminSettings.h | 38+++++++++++++++++++++++++++++++-------
Msrc/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h | 1+
Asrc/AppInstallerCommonCore/Public/winget/NetworkSettings.h | 34++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Public/winget/UserSettings.h | 2++
Msrc/AppInstallerCommonCore/UserSettings.cpp | 1+
Msrc/AppInstallerRepositoryCore/Rest/RestClient.cpp | 19++++++++++---------
Msrc/AppInstallerRepositoryCore/Rest/RestClient.h | 12+++++++++---
Msrc/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp | 2+-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_0/Interface.h | 2+-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h | 2+-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp | 4++--
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h | 2+-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp | 4++--
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h | 2+-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp | 4++--
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h | 2+-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp | 4++--
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_7/Interface.h | 2+-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_7/RestInterface_1_7.cpp | 4++--
Msrc/AppInstallerRepositoryCore/Rest/Schema/HttpClientHelper.cpp | 16+++++++++++++++-
Msrc/AppInstallerRepositoryCore/Rest/Schema/HttpClientHelper.h | 1+
Msrc/AppInstallerRepositoryCore/SourceList.cpp | 2+-
Msrc/AppInstallerRepositoryCore/pch.h | 36+++++++++++++++++++-----------------
Msrc/AppInstallerSharedLib/GroupPolicy.cpp | 10++++++++++
Msrc/AppInstallerSharedLib/Public/AppInstallerLanguageUtilities.h | 29+++++++++++++++++++++++++++++
Msrc/AppInstallerSharedLib/Public/winget/GroupPolicy.h | 3+++
Msrc/AppInstallerSharedLib/Public/winget/Resources.h | 1+
Msrc/Microsoft.Management.Deployment/pch.h | 1+
Msrc/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.Designer.cs | 9+++++++++
Msrc/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.resx | 3+++
Msrc/WinGetSchemas/WinGetSchemas.vcxitems | 1+
Msrc/WinGetSchemas/WinGetSchemas.vcxitems.filters | 3+++
Msrc/WinGetUtil/pch.h | 1+
79 files changed, 1003 insertions(+), 220 deletions(-)

diff --git a/doc/Settings.md b/doc/Settings.md @@ -312,3 +312,14 @@ You can enable the feature as shown below. "configuration03": true }, ``` + +### proxy + +This feature enables the use of web proxies. +You can enable the feature as shown below. + +```json + "experimentalFeatures": { + "proxy": true + }, +``` diff --git a/doc/admx/DesktopAppInstaller.admx b/doc/admx/DesktopAppInstaller.admx @@ -166,5 +166,22 @@ <decimal value="0" /> </disabledValue> </policy> + <policy name="EnableWindowsPackageManagerProxyCommandLineOptions" class="Machine" displayName="$(string.EnableWindowsPackageManagerProxyCommandLineOptions)" explainText="$(string.EnableWindowsPackageManagerProxyCommandLineOptionsExplanation)" key="Software\Policies\Microsoft\Windows\AppInstaller" valueName="EnableWindowsPackageManagerProxyCommandLineOptions"> + <parentCategory ref="AppInstaller" /> + <supportedOn ref="windows:SUPPORTED_Windows_10_0_RS5" /> + <enabledValue> + <decimal value="1" /> + </enabledValue> + <disabledValue> + <decimal value="0" /> + </disabledValue> + </policy> + <policy name="WindowsPackageManagerDefaultProxy" class="Machine" displayName="$(string.WindowsPackageManagerDefaultProxy)" explainText="$(string.WindowsPackageManagerDefaultProxyExplanation)" presentation="$(presentation.WindowsPackageManagerDefaultProxy)" key="Software\Policies\Microsoft\Windows\AppInstaller"> + <parentCategory ref="AppInstaller" /> + <supportedOn ref="windows:SUPPORTED_Windows_10_0_RS5" /> + <elements> + <text id="WindowsPackageManagerDefaultProxy" valueName="DefaultProxy" /> + </elements> + </policy> </policies> </policyDefinitions> diff --git a/doc/admx/en-US/DesktopAppInstaller.adml b/doc/admx/en-US/DesktopAppInstaller.adml @@ -100,7 +100,7 @@ If you disable or do not configure this setting, users will not be able to insta If you disable this policy, users will not be able execute the Windows Package Manager CLI, and PowerShell cmdlets. - If you enable, or do not configuring this policy, users will be able to execute the Windows Package Manager CLI commands, and PowerShell cmdlets. (Provided “Enable App Installer” policy is not disabled). + If you enable, or do not configure this policy, users will be able to execute the Windows Package Manager CLI commands, and PowerShell cmdlets. (Provided “Enable App Installer” policy is not disabled). This policy does not override the “Enable App Installer” policy.</string> <string id="EnableWindowsPackageManagerConfiguration">Enable Windows Package Manager Configuration</string> @@ -109,6 +109,19 @@ If you disable or do not configure this setting, users will not be able to insta If you enable or do not configure this setting, users will be able to use the Windows Package Manager configuration feature. If you disable this setting, users will not be able to use the Windows Package Manager configuration feature.</string> + <string id="EnableWindowsPackageManagerProxyCommandLineOptions">Enable Windows Package Manager Proxy command line options</string> + <string id="EnableWindowsPackageManagerProxyCommandLineOptionsExplanation"> + This policy controls whether the Windows Package Manager usage of proxy can be configured by users through the command line. + + If you enable this setting, users will be able to configure the Windows Package Manager's use of proxy through the command line. + + If you disable or do not configure this setting, users will not be able to to configure the Windows Package Manager's use of proxy through the command line.</string> + <string id="WindowsPackageManagerDefaultProxy">Set Windows Package Manager Default Proxy</string> + <string id="WindowsPackageManagerDefaultProxyExplanation">This policy controls the default proxy used by the Windows Package Manager. + +If you disable or do not configure this setting, no proxy will be used by default. + +If you enable this setting, the specified proxy will be used by default.</string> </stringTable> <presentationTable> <presentation id="SourceAutoUpdateInterval"> @@ -120,6 +133,11 @@ If you disable this setting, users will not be able to use the Windows Package M <presentation id="AllowedSources"> <listBox refId="AllowedSources" required="false">Allowed Sources: </listBox> </presentation> + <presentation id="WindowsPackageManagerDefaultProxy"> + <textBox refId="WindowsPackageManagerDefaultProxy"> + <label>Default Proxy</label> + </textBox> + </presentation> </presentationTable> </resources> </policyDefinitionResources> diff --git a/schemas/JSON/settings/settings.export.schema.0.1.json b/schemas/JSON/settings/settings.export.schema.0.1.json @@ -16,8 +16,26 @@ "description": "Enable installing local manifests.", "type": "boolean", "default": false + }, + "InstallerHashOverride": { + "description": "Enable overriding installer hash validation.", + "type": "boolean", + "default": false + }, + "LocalArchiveMalwareScanOverride": { + "description": "Enable overriding malware scan for local archives.", + "type": "boolean", + "default": false + }, + "ProxyCommandLineOptions": { + "description": "Enable using command line options for proxy.", + "type": "boolean", + "default": false + }, + "DefaultProxy": { + "description": "Default proxy.", + "type": "string" } - } }, "UserSettingsFile": { "description": "Path for the winget's user settings file.", diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json @@ -266,8 +266,13 @@ "type": "boolean", "default": false }, - "configuration": { - "description": "Enable support for configuration", + "configuration03": { + "description": "Enable support for configuration schema 0.3", + "type": "boolean", + "default": false + }, + "proxy": { + "description": "Enable support for proxies", "type": "boolean", "default": false } diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -151,6 +151,10 @@ namespace AppInstaller::CLI 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: @@ -233,6 +237,11 @@ namespace AppInstaller::CLI 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' }; @@ -268,7 +277,7 @@ namespace AppInstaller::CLI 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::AdminSetting::LocalManifestFiles }; + 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: @@ -308,7 +317,7 @@ namespace AppInstaller::CLI 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::AdminSetting::InstallerHashOverride }; + 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: @@ -324,7 +333,7 @@ namespace AppInstaller::CLI case Args::Type::SkipDependencies: return Argument{ type, Resource::String::SkipDependenciesArgumentDescription, ArgumentType::Flag, false }; case Args::Type::IgnoreLocalArchiveMalwareScan: - return Argument{ type, Resource::String::IgnoreLocalArchiveMalwareScanArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::LocalArchiveMalwareScanOverride, Settings::AdminSetting::LocalArchiveMalwareScanOverride }; + 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: @@ -379,6 +388,10 @@ namespace AppInstaller::CLI 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::Proxy: + return Argument{ type, Resource::String::ProxyArgumentDescription, ArgumentType::Standard, ExperimentalFeature::Feature::Proxy, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; + case Args::Type::NoProxy: + return Argument{ type, Resource::String::NoProxyArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::Proxy, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; default: THROW_HR(E_UNEXPECTED); } @@ -394,6 +407,8 @@ namespace AppInstaller::CLI args.push_back(ForType(Args::Type::RetroStyle)); args.push_back(ForType(Args::Type::VerboseLogs)); 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)); } std::string Argument::GetUsageString() const diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h @@ -85,6 +85,7 @@ namespace AppInstaller::CLI PurgePreserve = 0x4, PinType = 0x8, StubType = 0x10, + Proxy = 0x20, // This must always be at the end Max @@ -211,7 +212,7 @@ namespace AppInstaller::CLI Argument::Visibility GetVisibility() const; Settings::ExperimentalFeature::Feature Feature() const { return m_feature; } Settings::TogglePolicy::Policy GroupPolicy() const { return m_groupPolicy; } - Settings::AdminSetting AdminSetting() const { return m_adminSetting; } + Settings::BoolAdminSetting AdminSetting() const { return m_adminSetting; } Argument& SetRequired(bool required) { m_required = required; return *this; } Argument& SetCountLimit(size_t countLimit) { m_countLimit = countLimit; return *this; } @@ -237,12 +238,15 @@ namespace AppInstaller::CLI Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required, Settings::ExperimentalFeature::Feature feature) : m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required), m_feature(feature) {} - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::ExperimentalFeature::Feature feature, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_feature(feature), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} + ArgumentCommon m_argCommon; Resource::StringId m_desc; bool m_required = false; @@ -251,6 +255,6 @@ namespace AppInstaller::CLI size_t m_countLimit = 1; Settings::ExperimentalFeature::Feature m_feature = Settings::ExperimentalFeature::Feature::None; Settings::TogglePolicy::Policy m_groupPolicy = Settings::TogglePolicy::Policy::None; - Settings::AdminSetting m_adminSetting = Settings::AdminSetting::Unknown; + Settings::BoolAdminSetting m_adminSetting = Settings::BoolAdminSetting::Unknown; }; } diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -637,7 +637,7 @@ namespace AppInstaller::CLI throw GroupPolicyException(arg.GroupPolicy()); } - if (arg.AdminSetting() != AdminSetting::Unknown && !Settings::IsAdminSettingEnabled(arg.AdminSetting()) && execArgs.Contains(arg.ExecArgType())) + if (arg.AdminSetting() != BoolAdminSetting::Unknown && !Settings::IsAdminSettingEnabled(arg.AdminSetting()) && execArgs.Contains(arg.ExecArgType())) { auto setting = Settings::AdminSettingToString(arg.AdminSetting()); AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " disabled by admin setting " << setting); diff --git a/src/AppInstallerCLICore/Commands/DownloadCommand.cpp b/src/AppInstallerCLICore/Commands/DownloadCommand.cpp @@ -36,8 +36,8 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::CustomHeader), Argument::ForType(Args::Type::AuthenticationMode), Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Execution::Args::Type::AcceptPackageAgreements), - Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::AcceptPackageAgreements), + Argument::ForType(Args::Type::AcceptSourceAgreements), }; } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -124,13 +124,21 @@ namespace AppInstaller::CLI Execution::TableOutput<2> adminSettingsTable{ context.Reporter, { Resource::String::AdminSettingHeader, Resource::String::StateHeader } }; // Output the admin settings. - for (const auto& setting : Settings::GetAllAdminSettings()) + for (const auto& setting : Settings::GetAllBoolAdminSettings()) { adminSettingsTable.OutputLine({ std::string{ AdminSettingToString(setting)}, Resource::LocString{ IsAdminSettingEnabled(setting) ? Resource::String::StateEnabled : Resource::String::StateDisabled } }); } + for (const auto& setting : Settings::GetAllStringAdminSettings()) + { + auto settingValue = GetAdminSetting(setting); + adminSettingsTable.OutputLine({ + std::string{ AdminSettingToString(setting)}, + settingValue ? Utility::LocIndString{ settingValue.value() } : Resource::LocString{ Resource::String::StateDisabled } + }); + } adminSettingsTable.Complete(); } diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -20,6 +20,8 @@ namespace AppInstaller::CLI { return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({ std::make_unique<SettingsExportCommand>(FullName()), + std::make_unique<SettingsSetCommand>(FullName()), + std::make_unique<SettingsResetCommand>(FullName()), }); } @@ -49,21 +51,20 @@ namespace AppInstaller::CLI void SettingsCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const { // Get admin setting string for all available options except Unknown - using AdminSetting_t = std::underlying_type_t<AdminSetting>; std::vector<Utility::LocIndString> adminSettingList; - for (AdminSetting_t i = 1 + static_cast<AdminSetting_t>(AdminSetting::Unknown); i < static_cast<AdminSetting_t>(AdminSetting::Max); ++i) + for (auto setting : GetAllSequentialEnumValues(BoolAdminSetting::Unknown)) { - adminSettingList.emplace_back(AdminSettingToString(static_cast<AdminSetting>(i))); + adminSettingList.emplace_back(AdminSettingToString(setting)); } Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); - if (execArgs.Contains(Execution::Args::Type::AdminSettingEnable) && AdminSetting::Unknown == StringToAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingEnable))) + if (execArgs.Contains(Execution::Args::Type::AdminSettingEnable) && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingEnable))) { throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AdminSettingEnable).Name, validOptions)); } - if (execArgs.Contains(Execution::Args::Type::AdminSettingDisable) && AdminSetting::Unknown == StringToAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingDisable))) + if (execArgs.Contains(Execution::Args::Type::AdminSettingDisable) && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingDisable))) { throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AdminSettingDisable).Name, validOptions)); } @@ -110,4 +111,103 @@ namespace AppInstaller::CLI context << Workflow::ExportSettings; } + + std::vector<Argument> SettingsSetCommand::GetArguments() const + { + return { + Argument { Execution::Args::Type::SettingName, Resource::String::SettingNameArgumentDescription, ArgumentType::Positional, true }, + Argument { Execution::Args::Type::SettingValue, Resource::String::SettingValueArgumentDescription, ArgumentType::Positional, true }, + }; + } + + Resource::LocString SettingsSetCommand::ShortDescription() const + { + return { Resource::String::SettingsSetCommandShortDescription }; + } + + Resource::LocString SettingsSetCommand::LongDescription() const + { + return { Resource::String::SettingsSetCommandLongDescription }; + } + + Utility::LocIndView SettingsSetCommand::HelpLink() const + { + return s_SettingsCommand_HelpLink; + } + + void SettingsSetCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + // Get admin setting string for all available options except Unknown + std::vector<Utility::LocIndString> adminSettingList; + for (auto setting : GetAllSequentialEnumValues(StringAdminSetting::Unknown)) + { + adminSettingList.emplace_back(AdminSettingToString(setting)); + } + + Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); + + if (StringAdminSetting::Unknown == StringToStringAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName))) + { + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name, validOptions)); + } + } + + void SettingsSetCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::SetAdminSetting; + } + + std::vector<Argument> SettingsResetCommand::GetArguments() const + { + return { + Argument { Execution::Args::Type::SettingName, Resource::String::SettingNameArgumentDescription, ArgumentType::Positional, true }, + }; + } + + Resource::LocString SettingsResetCommand::ShortDescription() const + { + return { Resource::String::SettingsResetCommandShortDescription }; + } + + Resource::LocString SettingsResetCommand::LongDescription() const + { + return { Resource::String::SettingsResetCommandLongDescription }; + } + + Utility::LocIndView SettingsResetCommand::HelpLink() const + { + return s_SettingsCommand_HelpLink; + } + + void SettingsResetCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + // Get admin setting string for all available options except Unknown. + // We accept both bool and string settings + std::vector<Utility::LocIndString> adminSettingList; + for (auto setting : GetAllSequentialEnumValues(BoolAdminSetting::Unknown)) + { + adminSettingList.emplace_back(AdminSettingToString(setting)); + } + for (auto setting : GetAllSequentialEnumValues(StringAdminSetting::Unknown)) + { + adminSettingList.emplace_back(AdminSettingToString(setting)); + } + + Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); + + if (StringAdminSetting::Unknown == StringToStringAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName)) + && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName))) + { + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name, validOptions)); + } + } + + void SettingsResetCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::ResetAdminSetting; + } } diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.h b/src/AppInstallerCLICore/Commands/SettingsCommand.h @@ -34,4 +34,36 @@ namespace AppInstaller::CLI protected: void ExecuteInternal(Execution::Context& context) const override; }; + + struct SettingsSetCommand final : public Command + { + SettingsSetCommand(std::string_view parent) : Command("set", {}, parent, Settings::TogglePolicy::Policy::Settings) {} + + std::vector<Argument> GetArguments() const override; + + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct SettingsResetCommand final : public Command + { + SettingsResetCommand(std::string_view parent) : Command("reset", {}, parent, Settings::TogglePolicy::Policy::Settings) {} + + std::vector<Argument> GetArguments() const override; + + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; } diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp @@ -119,13 +119,6 @@ namespace AppInstaller::CLI Logging::Telemetry().LogCommand(command->FullName()); command->ParseArguments(invocation, context.Args); - - // Change logging level to Info if Verbose not requested - if (context.Args.Contains(Execution::Args::Type::VerboseLogs)) - { - Logging::Log().SetLevel(Logging::Level::Verbose); - } - context.UpdateForArgs(); context.SetExecutingCommand(command.get()); command->ValidateArguments(context.Args); diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -89,6 +89,8 @@ namespace AppInstaller::CLI::Execution // Setting Command AdminSettingEnable, AdminSettingDisable, + SettingName, + SettingValue, // Upgrade command All, // Used in Update command to update all installed packages to latest @@ -140,6 +142,10 @@ namespace AppInstaller::CLI::Execution 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 diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -9,6 +9,7 @@ #include <winget/Checkpoint.h> #include <winget/Reboot.h> #include <winget/UserSettings.h> +#include <winget/NetworkSettings.h> using namespace AppInstaller::Checkpoints; @@ -349,6 +350,23 @@ namespace AppInstaller::CLI::Execution void Context::UpdateForArgs() { + // Change logging level to Info if Verbose not requested + if (Args.Contains(Args::Type::VerboseLogs)) + { + Logging::Log().SetLevel(Logging::Level::Verbose); + } + + // Set proxy + if (Args.Contains(Args::Type::Proxy)) + { + Network().SetProxyUri(std::string{ Args.GetArg(Args::Type::Proxy) }); + } + else if (Args.Contains(Args::Type::NoProxy)) + { + Network().SetProxyUri(std::nullopt); + } + + // Set visual style if (Args.Contains(Args::Type::NoVT)) { Reporter.SetStyle(VisualStyle::NoVT); diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h @@ -276,6 +276,7 @@ namespace AppInstaller::CLI::Execution using value_t = std::string; }; + template<> struct DataMapping<Data::RepairString> { diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -339,15 +339,16 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotFound); WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSpecified); WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); WINGET_DEFINE_RESOURCE_STRINGID(NoAdminRepairForUserScopePackage); + WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage); WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledPackageFound); WINGET_DEFINE_RESOURCE_STRINGID(NoPackageFound); WINGET_DEFINE_RESOURCE_STRINGID(NoPackageSelectionArgumentProvided); WINGET_DEFINE_RESOURCE_STRINGID(NoPackagesFoundInImportFile); - WINGET_DEFINE_RESOURCE_STRINGID(Notes); + WINGET_DEFINE_RESOURCE_STRINGID(NoProxyArgumentDescription); 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); @@ -414,6 +415,7 @@ namespace AppInstaller::CLI::Resource 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); @@ -437,6 +439,8 @@ namespace AppInstaller::CLI::Resource 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(ResumeCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdArgumentDescription); @@ -457,14 +461,22 @@ namespace AppInstaller::CLI::Resource 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); @@ -589,8 +601,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicableReason); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeAvailableForPinned); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByPinCount); 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); diff --git a/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp b/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp @@ -29,7 +29,7 @@ namespace AppInstaller::CLI::Workflow else { if (context.Args.Contains(Execution::Args::Type::IgnoreLocalArchiveMalwareScan) && - Settings::IsAdminSettingEnabled(Settings::AdminSetting::LocalArchiveMalwareScanOverride)) + Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride)) { AICLI_LOG(CLI, Warning, << "Archive scan detected malware. Proceeding due to --ignore-local-archive-malware-scan"); context.Reporter.Warn() << Resource::String::ArchiveFailedMalwareScanOverridden << std::endl; diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -7,13 +7,16 @@ #include <AppInstallerRuntime.h> #include <AppInstallerMsixInfo.h> #include <winget/AdminSettings.h> +#include <winget/GroupPolicy.h> #include <winget/ManifestYamlWriter.h> +#include <winget/NetworkSettings.h> namespace AppInstaller::CLI::Workflow { using namespace AppInstaller::Manifest; using namespace AppInstaller::Repository; using namespace AppInstaller::Utility; + using namespace AppInstaller::Settings; using namespace std::string_view_literals; namespace @@ -211,14 +214,18 @@ namespace AppInstaller::CLI::Workflow context << DownloadInstallerFile; break; case InstallerTypeEnum::Msix: - if (installer.SignatureSha256.empty() || installerDownloadOnly) + // If the signature hash is provided in the manifest and we are doing an install, + // we can just verify signature hash without a full download and do a streaming install. + // Even if we have the signature hash, we still do a full download if InstallerDownloadOnly + // flag is set, or if we need to use a proxy (as deployment APIs won't use proxy for us). + if (installer.SignatureSha256.empty() + || installerDownloadOnly + || Network().GetProxyUri()) { - // If InstallerDownloadOnly flag is set, always download the installer file. context << DownloadInstallerFile; } else { - // Signature hash provided. No download needed. Just verify signature hash. context << GetMsixSignatureHash; } break; @@ -386,6 +393,7 @@ namespace AppInstaller::CLI::Workflow { const auto& installer = context.Get<Execution::Data::Installer>().value(); + // Signature hash is only used for streaming installs, which don't use proxy Msix::MsixInfo msixInfo(installer.Url); auto signatureHash = msixInfo.GetSignatureHash(); @@ -422,7 +430,7 @@ namespace AppInstaller::CLI::Workflow { context.Reporter.Error() << Resource::String::InstallerHashMismatchAdminBlock << std::endl; } - else if (!Settings::IsAdminSettingEnabled(Settings::AdminSetting::InstallerHashOverride)) + else if (!Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::InstallerHashOverride)) { context.Reporter.Error() << Resource::String::InstallerHashMismatchError << std::endl; } diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp b/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp @@ -23,12 +23,22 @@ namespace AppInstaller::CLI::Workflow root["userSettingsFile"] = UserSettings::SettingsFilePath().u8string(); } - void AddAdminSetting(AdminSetting setting) + void AddAdminSetting(BoolAdminSetting setting) { auto str = std::string{ Settings::AdminSettingToString(setting) }; root["adminSettings"][str] = Settings::IsAdminSettingEnabled(setting); } + void AddAdminSetting(StringAdminSetting setting) + { + auto name = std::string{ Settings::AdminSettingToString(setting) }; + auto value = Settings::GetAdminSetting(setting); + if (value) + { + root["adminSettings"][name] = value.value(); + } + } + std::string ToJsonString() const { Json::StreamWriterBuilder writerBuilder; @@ -44,7 +54,7 @@ namespace AppInstaller::CLI::Workflow void EnableAdminSetting(Execution::Context& context) { auto adminSettingString = context.Args.GetArg(Execution::Args::Type::AdminSettingEnable); - AdminSetting adminSetting = Settings::StringToAdminSetting(adminSettingString); + BoolAdminSetting adminSetting = Settings::StringToBoolAdminSetting(adminSettingString); if (Settings::EnableAdminSetting(adminSetting)) { context.Reporter.Info() << Resource::String::AdminSettingEnabled(AdminSettingToString(adminSetting)) << std::endl; @@ -58,7 +68,7 @@ namespace AppInstaller::CLI::Workflow void DisableAdminSetting(Execution::Context& context) { auto adminSettingString = context.Args.GetArg(Execution::Args::Type::AdminSettingDisable); - AdminSetting adminSetting = Settings::StringToAdminSetting(adminSettingString); + BoolAdminSetting adminSetting = Settings::StringToBoolAdminSetting(adminSettingString); if (Settings::DisableAdminSetting(adminSetting)) { context.Reporter.Info() << Resource::String::AdminSettingDisabled(AdminSettingToString(adminSetting)) << std::endl; @@ -69,6 +79,40 @@ namespace AppInstaller::CLI::Workflow } } + void SetAdminSetting(Execution::Context& context) + { + auto adminSettingName = context.Args.GetArg(Execution::Args::Type::SettingName); + auto adminSettingValue = context.Args.GetArg(Execution::Args::Type::SettingValue); + StringAdminSetting adminSetting = Settings::StringToStringAdminSetting(adminSettingName); + if (Settings::SetAdminSetting(adminSetting, adminSettingValue)) + { + context.Reporter.Info() << Resource::String::SetAdminSettingSucceeded(LocIndString{ adminSettingName }, LocIndString{ adminSettingValue }) << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::SetAdminSettingFailed(LocIndString{ adminSettingName }) << std::endl; + } + } + + void ResetAdminSetting(Execution::Context& context) + { + auto adminSettingName = context.Args.GetArg(Execution::Args::Type::SettingName); + + // Try as both bool and string setting as we don't know the type + auto boolAdminSetting = Settings::StringToBoolAdminSetting(adminSettingName); + auto stringAdminSetting = Settings::StringToStringAdminSetting(adminSettingName); + + if ((boolAdminSetting != Settings::BoolAdminSetting::Unknown && Settings::DisableAdminSetting(boolAdminSetting)) + || (stringAdminSetting != Settings::StringAdminSetting::Unknown && Settings::ResetAdminSetting(stringAdminSetting))) + { + context.Reporter.Info() << Resource::String::ResetAdminSettingSucceeded(LocIndString{ adminSettingName }) << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::ResetAdminSettingFailed(LocIndString{ adminSettingName }) << std::endl; + } + } + void OpenUserSetting(Execution::Context& context) { // Show warnings only when the setting command is executed. @@ -129,7 +173,12 @@ namespace AppInstaller::CLI::Workflow { ExportSettingsJson exportSettingsJson; - for (const auto& setting : GetAllAdminSettings()) + for (const auto& setting : GetAllBoolAdminSettings()) + { + exportSettingsJson.AddAdminSetting(setting); + } + + for (const auto& setting : GetAllStringAdminSettings()) { exportSettingsJson.AddAdminSetting(setting); } diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.h b/src/AppInstallerCLICore/Workflows/SettingsFlow.h @@ -17,6 +17,18 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void DisableAdminSetting(Execution::Context& context); + // Sets the value of an admin setting. + // Required Args: SettingName, SettingValue + // Inputs: None + // Outputs: None + void SetAdminSetting(Execution::Context& context); + + // Resets an admin setting to the default. + // Required Args: SettingName + // Inputs: None + // Outputs: None + void ResetAdminSetting(Execution::Context& context); + // Opens the user settings. // Required Args: None // Inputs: None diff --git a/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs b/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs @@ -119,6 +119,11 @@ namespace AppInstallerCLIE2ETests public static GroupPolicyHelper EnableConfiguration { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerConfiguration"); /// <summary> + /// Gets the Enable Windows Package Manager proxy command line options policy. + /// </summary> + public static GroupPolicyHelper EnableProxyCommandLineOptions { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerProxyCommandLineOptions"); + + /// <summary> /// Gets the Enable auto update interval policy. /// </summary> public static GroupPolicyHelper SourceAutoUpdateInterval { get; private set; } = new GroupPolicyHelper("SourceAutoUpdateInterval", "SourceAutoUpdateInterval"); @@ -138,6 +143,7 @@ namespace AppInstallerCLIE2ETests SourceAutoUpdateInterval, EnableWinGetCommandLineInterfaces, EnableConfiguration, + EnableProxyCommandLineOptions, }; /// <summary> diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -2817,4 +2817,48 @@ Please specify one of them using the --source option to proceed.</value> <data name="APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED" xml:space="preserve"> <value>The SQLite connection was terminated to prevent corruption.</value> </data> + <data name="PolicyEnableProxyCommandLineOptions" xml:space="preserve"> + <value>Enable Windows Package Manager proxy command line options</value> + <comment>Describes a Group Policy that can enable the use of the --proxy option to set a proxy</comment> + </data> + <data name="ProxyArgumentDescription" xml:space="preserve"> + <value>Set a proxy to use for this execution</value> + </data> + <data name="NoProxyArgumentDescription" xml:space="preserve"> + <value>Disable the use of proxy for this execution</value> + </data> + <data name="ResetAdminSettingFailed" xml:space="preserve"> + <value>Cannot reset {0}. This setting is controlled by policy. For more information contact your system administrator.</value> + <comment>{Locked="{0}"} The value will be replaced with the feature name</comment> + </data> + <data name="ResetAdminSettingSucceeded" xml:space="preserve"> + <value>Reset admin setting '{0}'.</value> + <comment>{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.</comment> + </data> + <data name="SetAdminSettingFailed" xml:space="preserve"> + <value>Cannot set {0}. This setting is controlled by policy. For more information contact your system administrator.</value> + <comment>{Locked="{0}"} The value will be replaced with the feature name</comment> + </data> + <data name="SetAdminSettingSucceeded" xml:space="preserve"> + <value>Set admin setting '{0}' to '{1}'.</value> + <comment>{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 </comment> + </data> + <data name="SettingNameArgumentDescription" xml:space="preserve"> + <value>Name of the setting to modify</value> + </data> + <data name="SettingValueArgumentDescription" xml:space="preserve"> + <value>Value to set for the setting.</value> + </data> + <data name="SettingsResetCommandLongDescription" xml:space="preserve"> + <value>Resets an admin setting to its default value.</value> + </data> + <data name="SettingsResetCommandShortDescription" xml:space="preserve"> + <value>Resets an admin setting to its default value.</value> + </data> + <data name="SettingsSetCommandLongDescription" xml:space="preserve"> + <value>Sets the value of an admin setting.</value> + </data> + <data name="SettingsSetCommandShortDescription" xml:space="preserve"> + <value>Sets the value of an admin setting.</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/AdminSettings.cpp b/src/AppInstallerCLITests/AdminSettings.cpp @@ -16,21 +16,21 @@ TEST_CASE("AdminSetting_Enable", "[adminSettings]") { GroupPolicyTestOverride policies; policies.SetState(TogglePolicy::Policy::LocalManifestFiles, PolicyState::NotConfigured); - REQUIRE(EnableAdminSetting(AdminSetting::LocalManifestFiles)); + REQUIRE(EnableAdminSetting(BoolAdminSetting::LocalManifestFiles)); } SECTION("Enabled") { GroupPolicyTestOverride policies; policies.SetState(TogglePolicy::Policy::LocalManifestFiles, PolicyState::Enabled); - REQUIRE(EnableAdminSetting(AdminSetting::LocalManifestFiles)); + REQUIRE(EnableAdminSetting(BoolAdminSetting::LocalManifestFiles)); } SECTION("Disabled") { GroupPolicyTestOverride policies; policies.SetState(TogglePolicy::Policy::LocalManifestFiles, PolicyState::Disabled); - REQUIRE_FALSE(EnableAdminSetting(AdminSetting::LocalManifestFiles)); + REQUIRE_FALSE(EnableAdminSetting(BoolAdminSetting::LocalManifestFiles)); } } } @@ -43,21 +43,42 @@ TEST_CASE("AdminSetting_Disable", "[adminSettings]") { GroupPolicyTestOverride policies; policies.SetState(TogglePolicy::Policy::LocalManifestFiles, PolicyState::NotConfigured); - REQUIRE(DisableAdminSetting(AdminSetting::LocalManifestFiles)); + REQUIRE(DisableAdminSetting(BoolAdminSetting::LocalManifestFiles)); } SECTION("Enabled") { GroupPolicyTestOverride policies; policies.SetState(TogglePolicy::Policy::LocalManifestFiles, PolicyState::Enabled); - REQUIRE_FALSE(DisableAdminSetting(AdminSetting::LocalManifestFiles)); + REQUIRE_FALSE(DisableAdminSetting(BoolAdminSetting::LocalManifestFiles)); } SECTION("Disabled") { GroupPolicyTestOverride policies; policies.SetState(TogglePolicy::Policy::LocalManifestFiles, PolicyState::Disabled); - REQUIRE(DisableAdminSetting(AdminSetting::LocalManifestFiles)); + REQUIRE(DisableAdminSetting(BoolAdminSetting::LocalManifestFiles)); } } } + +TEST_CASE("AdminSetting_AllSettingsAreImplemented", "[adminSettings]") +{ + for (auto adminSetting : GetAllBoolAdminSettings()) + { + // If we forget to add it to the conversion, it returns Unknown/None + REQUIRE(AdminSettingToString(adminSetting) != AdminSettingToString(BoolAdminSetting::Unknown)); + REQUIRE(StringToBoolAdminSetting(AdminSettingToString(adminSetting)) != BoolAdminSetting::Unknown); + REQUIRE(GetAdminSettingPolicy(adminSetting) != TogglePolicy::Policy::None); + + GroupPolicyTestOverride policies; + policies.SetState(GetAdminSettingPolicy(adminSetting), PolicyState::NotConfigured); + + // We should be able to configure the state. + // If we forget to add it, it won't persist. + REQUIRE(EnableAdminSetting(adminSetting)); + REQUIRE(IsAdminSettingEnabled(adminSetting)); + REQUIRE(DisableAdminSetting(adminSetting)); + REQUIRE_FALSE(IsAdminSettingEnabled(adminSetting)); + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/CustomHeader.cpp b/src/AppInstallerCLITests/CustomHeader.cpp @@ -54,7 +54,7 @@ TEST_CASE("RestClient_CustomHeader", "[RestSource][CustomHeader]") std::optional<std::string> customHeader = "Testing custom header"; auto header = std::make_pair<>(CustomHeaderName, JSON::GetUtilityString(customHeader.value())); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, {}, std::move(helper)); + RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, std::move(helper), {}); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -66,7 +66,7 @@ TEST_CASE("RestSourceSearch_CustomHeader", "[RestSource][CustomHeader]") std::unordered_map<utility::string_t, utility::string_t> headers; headers.emplace(CustomHeaderName, customHeader); - V1_1::Interface v1_1{ "https://restsource.com/api", {}, headers, std::move(helper) }; + V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper) , {}, headers}; Schema::IRestClient::SearchResult searchResponse = v1_1.Search({}); REQUIRE(searchResponse.Matches.size() == 1); Schema::IRestClient::Package package = searchResponse.Matches.at(0); @@ -80,7 +80,7 @@ TEST_CASE("RestSourceSearch_WhitespaceCustomHeader", "[RestSource][CustomHeader] std::unordered_map<utility::string_t, utility::string_t> headers; headers.emplace(CustomHeaderName, customHeader); - V1_1::Interface v1_1{ "https://restsource.com/api", {}, headers, std::move(helper) }; + V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper), {}, headers }; Schema::IRestClient::SearchResult searchResponse = v1_1.Search({}); REQUIRE(searchResponse.Matches.size() == 1); } @@ -93,7 +93,7 @@ TEST_CASE("RestSourceSearch_NoCustomHeader", "[RestSource][CustomHeader]") std::unordered_map<utility::string_t, utility::string_t> headers; headers.emplace(CustomHeaderName, customHeader); - V1_1::Interface v1_1{ "https://restsource.com/api", {}, {}, std::move(helper) }; + V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper), {}, {} }; REQUIRE_THROWS_HR(v1_1.Search({}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INTERNAL_ERROR); } @@ -103,7 +103,7 @@ TEST_CASE("RestSourceSearch_CustomHeaderExceedingSize", "[RestSource][CustomHead auto header = std::make_pair<>(CustomHeaderName, JSON::GetUtilityString(customHeader)); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; - REQUIRE_THROWS_HR(RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, {}, std::move(helper)), + REQUIRE_THROWS_HR(RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, std::move(helper), {}), APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH); } @@ -121,7 +121,7 @@ TEST_CASE("RestClient_CustomUserAgentHeader", "[RestSource][CustomHeader]") std::string testCaller = "TestCaller"; auto header = std::make_pair<>(web::http::header_names::user_agent, JSON::GetUtilityString(Runtime::GetUserAgent(testCaller))); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), {}, testCaller, {}, std::move(helper)); + RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), {}, testCaller, std::move(helper), {}); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -138,6 +138,6 @@ TEST_CASE("RestClient_DefaultUserAgentHeader", "[RestSource][CustomHeader]") auto header = std::make_pair<>(web::http::header_names::user_agent, JSON::GetUtilityString(Runtime::GetDefaultUserAgent())); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), {}, {}, {}, std::move(helper)); + RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), {}, {}, std::move(helper), {}); REQUIRE(client.GetSourceIdentifier() == "Source123"); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/GroupPolicy.cpp b/src/AppInstallerCLITests/GroupPolicy.cpp @@ -376,6 +376,7 @@ TEST_CASE("GroupPolicy_AllEnabled", "[groupPolicy]") SetRegistryValue(policiesKey.get(), BypassCertificatePinningForMicrosoftStoreValueName, 1); SetRegistryValue(policiesKey.get(), EnableWindowsPackageManagerCommandLineInterfaces, 1); SetRegistryValue(policiesKey.get(), ConfigurationPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), ProxyCommandLineOptionsPolicyValueName, 1); GroupPolicy groupPolicy{ policiesKey.get() }; for (const auto& policy : TogglePolicy::GetAllPolicies()) diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp @@ -450,7 +450,7 @@ TEST_CASE("InstallFlow_Zip_ArchiveScanOverride_AdminSettingDisabled", "[InstallF context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); context.Args.AddArg(Execution::Args::Type::IgnoreLocalArchiveMalwareScan); - DisableAdminSetting(AppInstaller::Settings::AdminSetting::LocalArchiveMalwareScanOverride); + DisableAdminSetting(AppInstaller::Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride); TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); @@ -478,7 +478,7 @@ TEST_CASE("InstallFlow_Zip_ArchiveScanOverride_AdminSettingEnabled", "[InstallFl context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); context.Args.AddArg(Execution::Args::Type::IgnoreLocalArchiveMalwareScan); - EnableAdminSetting(AppInstaller::Settings::AdminSetting::LocalArchiveMalwareScanOverride); + EnableAdminSetting(AppInstaller::Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride); TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); diff --git a/src/AppInstallerCLITests/RestClient.cpp b/src/AppInstallerCLITests/RestClient.cpp @@ -47,11 +47,11 @@ TEST_CASE("GetSupportedInterface", "[RestSource]") IRestClient::Information info{ "TestId", { "1.0.0" } }; Version version{ "1.0.0" }; - REQUIRE(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, version)->GetVersion() == version); + REQUIRE(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, version, {})->GetVersion() == version); // Update this test to next version so that we don't forget to add to supported versions before rest e2e tests are available. Version invalid{ "1.8.0" }; - REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, invalid), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); + REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, invalid, {}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Silent; @@ -60,12 +60,12 @@ TEST_CASE("GetSupportedInterface", "[RestSource]") // GetSupportedInterface throws on unknown authentication type. IRestClient::Information infoWithUnknownAuthenticationType{ "TestId", { "1.7.0" } }; infoWithUnknownAuthenticationType.Authentication.Type = Authentication::AuthenticationType::Unknown; - REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithUnknownAuthenticationType, authArgs, version_1_7), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithUnknownAuthenticationType, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); // GetSupportedInterface throws on invalid authentication info. IRestClient::Information infoWithInvalidAuthenticationInfo{ "TestId", { "1.7.0" } }; infoWithInvalidAuthenticationInfo.Authentication.Type = Authentication::AuthenticationType::MicrosoftEntraId; - REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithInvalidAuthenticationInfo, authArgs, version_1_7), APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO); + REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithInvalidAuthenticationInfo, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO); } TEST_CASE("GetInformation_Success", "[RestSource]") @@ -277,7 +277,7 @@ TEST_CASE("RestClientCreate_UnsupportedVersion", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, {}, std::move(helper)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); } TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") @@ -297,7 +297,7 @@ TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Silent; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(authArgs), std::move(helper)), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper), std::move(authArgs)), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); } TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") @@ -320,7 +320,7 @@ TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Unknown; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(authArgs), std::move(helper)), E_UNEXPECTED); + REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper), std::move(authArgs)), E_UNEXPECTED); } TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") @@ -335,7 +335,7 @@ TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, {}, std::move(helper)); + RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper), {}); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -372,7 +372,7 @@ TEST_CASE("RestClientCreate_1.1_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, {}, std::move(helper)); + RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper)); REQUIRE(client.GetSourceIdentifier() == "Source123"); auto information = client.GetSourceInformation(); REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); @@ -438,7 +438,7 @@ TEST_CASE("RestClientCreate_1.7_Success", "[RestSource]") Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Silent; HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(authArgs), std::move(helper)); + RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper), std::move(authArgs)); REQUIRE(client.GetSourceIdentifier() == "Source123"); auto information = client.GetSourceInformation(); REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); diff --git a/src/AppInstallerCLITests/RestInterface_1_1.cpp b/src/AppInstallerCLITests/RestInterface_1_1.cpp @@ -310,7 +310,7 @@ TEST_CASE("Search_BadResponse_UnsupportedPackageMatchFields", "[RestSource][Inte })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; AppInstaller::Repository::SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; request.Filters.emplace_back(std::move(filter)); @@ -326,7 +326,7 @@ TEST_CASE("Search_BadResponse_RequiredPackageMatchFields", "[RestSource][Interfa })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; AppInstaller::Repository::SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; request.Filters.emplace_back(std::move(filter)); @@ -342,7 +342,7 @@ TEST_CASE("GetManifests_BadResponse_UnsupportedQueryParameters", "[RestSource][I })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; REQUIRE_THROWS_HR(v1_1.GetManifests("Foo"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); } @@ -355,7 +355,7 @@ TEST_CASE("GetManifests_BadResponse_RequiredQueryParameters", "[RestSource][Inte })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; REQUIRE_THROWS_HR(v1_1.GetManifests("Foo"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); } @@ -375,7 +375,7 @@ TEST_CASE("Search_BadRequest_UnsupportedPackageMatchFields", "[RestSource][Inter })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; AppInstaller::Repository::SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Moniker, MatchType::Exact, "Foo" }; request.Filters.emplace_back(std::move(filter)); @@ -398,7 +398,7 @@ TEST_CASE("Search_GoodRequest_OnlyMarketRequired", "[RestSource][Interface_1_1]" })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; AppInstaller::Repository::SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; request.Filters.emplace_back(std::move(filter)); @@ -443,7 +443,7 @@ TEST_CASE("GetManifests_BadRequest_UnsupportedQueryParameters", "[RestSource][In })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; REQUIRE_THROWS_HR(v1_1.GetManifestByVersion("Foo", "1.0", "beta"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); } @@ -479,7 +479,7 @@ TEST_CASE("GetManifests_GoodRequest_OnlyMarketRequired", "[RestSource][Interface IRestClient::Information info = GetTestSourceInformation(); info.UnsupportedQueryParameters.clear(); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, info, {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), info, {} }; auto manifestResult = v1_1.GetManifestByVersion("Foo", "5.0.0", ""); REQUIRE(manifestResult.has_value()); const Manifest& manifest = manifestResult.value(); @@ -526,7 +526,7 @@ TEST_CASE("GetManifests_GoodResponse_MSStoreType", "[RestSource][Interface_1_1]" })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(msstoreInstallerResponse)) }; - Interface v1_1{ TestRestUriString, GetTestSourceInformation(), {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; std::vector<Manifest> manifests = v1_1.GetManifests("Foo.Bar"); REQUIRE(manifests.size() == 1); @@ -542,7 +542,7 @@ TEST_CASE("GetManifests_GoodResponse_V1_1", "[RestSource][Interface_1_1]") GoodManifest_AllFields sampleManifest; utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, {}, {}, std::move(helper) }; + Interface v1_1{ TestRestUriString, std::move(helper), {} }; std::vector<Manifest> manifests = v1_1.GetManifests("Foo.Bar"); REQUIRE(manifests.size() == 1); diff --git a/src/AppInstallerCLITests/RestInterface_1_4.cpp b/src/AppInstallerCLITests/RestInterface_1_4.cpp @@ -361,7 +361,7 @@ TEST_CASE("GetManifests_GoodResponse_V1_4", "[RestSource][Interface_1_4]") GoodManifest_AllFields sampleManifest; utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_4{ TestRestUriString, {}, {}, std::move(helper) }; + Interface v1_4{ TestRestUriString, std::move(helper), {} }; std::vector<Manifest> manifests = v1_4.GetManifests("Foo.Bar"); REQUIRE(manifests.size() == 1); @@ -404,7 +404,7 @@ TEST_CASE("Search_GoodResponse_V1_4", "[RestSource][Interface_1_4]") })delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_4{ TestRestUriString, {}, {}, std::move(helper) }; + Interface v1_4{ TestRestUriString, std::move(helper), {} }; Schema::IRestClient::SearchResult searchResponse = v1_4.Search({}); REQUIRE(searchResponse.Matches.size() == 1); Schema::IRestClient::Package package = searchResponse.Matches.at(0); diff --git a/src/AppInstallerCLITests/RestInterface_1_5.cpp b/src/AppInstallerCLITests/RestInterface_1_5.cpp @@ -387,7 +387,7 @@ TEST_CASE("GetManifests_GoodResponse_V1_5", "[RestSource][Interface_1_5]") GoodManifest_AllFields sampleManifest; utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_5{ TestRestUriString, {}, {}, std::move(helper) }; + Interface v1_5{ TestRestUriString, std::move(helper), {} }; std::vector<Manifest> manifests = v1_5.GetManifests("Foo.Bar"); REQUIRE(manifests.size() == 1); diff --git a/src/AppInstallerCLITests/RestInterface_1_6.cpp b/src/AppInstallerCLITests/RestInterface_1_6.cpp @@ -389,7 +389,7 @@ TEST_CASE("GetManifests_GoodResponse_V1_6", "[RestSource][Interface_1_6]") GoodManifest_AllFields sampleManifest; utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_6{ TestRestUriString, {}, {}, std::move(helper) }; + Interface v1_6{ TestRestUriString, std::move(helper), {} }; std::vector<Manifest> manifests = v1_6.GetManifests("Foo.Bar"); REQUIRE(manifests.size() == 1); diff --git a/src/AppInstallerCLITests/RestInterface_1_7.cpp b/src/AppInstallerCLITests/RestInterface_1_7.cpp @@ -457,7 +457,7 @@ TEST_CASE("GetManifests_GoodRequest_Authentication", "[RestSource][Interface_1_7 // GetManifest should succeed with expected value. HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, GetTestSourceInformation(), {}, GetTestAuthenticationArguments(), std::move(helper) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; auto manifestResult = v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""); REQUIRE(manifestResult.has_value()); const auto& manifest = manifestResult.value(); @@ -482,7 +482,7 @@ TEST_CASE("GetManifests_BadRequest_AuthenticationFailed", "[RestSource][Interfac // GetManifest should fail with authentication failure HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, GetTestSourceInformation(), {}, GetTestAuthenticationArguments(), std::move(helper) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; REQUIRE_THROWS_HR(v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""), APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED); } @@ -504,7 +504,7 @@ TEST_CASE("GetManifests_BadRequest_InvalidAuthenticationToken", "[RestSource][In // GetManifest should fail with access denied HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, GetTestSourceInformation(), {}, GetTestAuthenticationArguments(), std::move(helper) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; REQUIRE_THROWS_HR(v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""), HTTP_E_STATUS_DENIED); } @@ -526,7 +526,7 @@ TEST_CASE("Search_GoodRequest_Authentication", "[RestSource][Interface_1_7]") // Search should succeed with expected value. HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, GetTestSourceInformation(), {}, GetTestAuthenticationArguments(), std::move(helper) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; request.Filters.emplace_back(std::move(filter)); @@ -553,7 +553,7 @@ TEST_CASE("Search_BadRequest_AuthenticationFailed", "[RestSource][Interface_1_7] // Search should fail with authentication failure HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, GetTestSourceInformation(), {}, GetTestAuthenticationArguments(), std::move(helper) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; request.Filters.emplace_back(std::move(filter)); @@ -578,7 +578,7 @@ TEST_CASE("Search_BadRequest_InvalidAuthenticationToken", "[RestSource][Interfac // Search should fail with access denied HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, GetTestSourceInformation(), {}, GetTestAuthenticationArguments(), std::move(helper) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; SearchRequest request; PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; request.Filters.emplace_back(std::move(filter)); @@ -590,7 +590,7 @@ TEST_CASE("GetManifests_GoodResponse_V1_7", "[RestSource][Interface_1_7]") GoodManifest_AllFields sampleManifest; utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_7{ TestRestUriString, {}, {}, {}, std::move(helper) }; + Interface v1_7{ TestRestUriString, std::move(helper), {} }; std::vector<AppInstaller::Manifest::Manifest> manifests = v1_7.GetManifests("Foo.Bar"); REQUIRE(manifests.size() == 1); diff --git a/src/AppInstallerCLITests/TestSettings.h b/src/AppInstallerCLITests/TestSettings.h @@ -22,6 +22,7 @@ namespace TestCommon const std::wstring BypassCertificatePinningForMicrosoftStoreValueName = L"EnableBypassCertificatePinningForMicrosoftStore"; const std::wstring EnableWindowsPackageManagerCommandLineInterfaces = L"EnableWindowsPackageManagerCommandLineInterfaces"; const std::wstring ConfigurationPolicyValueName = L"EnableWindowsPackageManagerConfiguration"; + const std::wstring ProxyCommandLineOptionsPolicyValueName = L"EnableWindowsPackageManagerProxyCommandLineOptions"; const std::wstring SourceUpdateIntervalPolicyValueName = L"SourceAutoUpdateInterval"; const std::wstring SourceUpdateIntervalPolicyOldValueName = L"SourceAutoUpdateIntervalInMinutes"; diff --git a/src/AppInstallerCLITests/pch.h b/src/AppInstallerCLITests/pch.h @@ -19,6 +19,8 @@ #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Globalization.h> #include <winrt/Windows.Management.Deployment.h> +#include <winrt/Windows.Web.Http.h> +#include <wrl/client.h> #include <wil/filesystem.h> #include <wil/resource.h> diff --git a/src/AppInstallerCommonCore/AdminSettings.cpp b/src/AppInstallerCommonCore/AdminSettings.cpp @@ -19,6 +19,9 @@ namespace AppInstaller::Settings constexpr Utility::LocIndView s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore = "BypassCertificatePinningForMicrosoftStore"_liv; constexpr Utility::LocIndView s_AdminSettingsYaml_InstallerHashOverride = "InstallerHashOverride"_liv; constexpr Utility::LocIndView s_AdminSettingsYaml_LocalArchiveMalwareScanOverride = "LocalArchiveMalwareScanOverride"_liv; + constexpr Utility::LocIndView s_AdminSettingsYaml_ProxyCommandLineOptions = "ProxyCommandLineOptions"_liv; + + constexpr Utility::LocIndView s_AdminSettingsYaml_DefaultProxy = "DefaultProxy"_liv; // Attempts to read a single scalar value from the node. template<typename Value> @@ -42,20 +45,30 @@ namespace AppInstaller::Settings bool BypassCertificatePinningForMicrosoftStore = false; bool InstallerHashOverride = false; bool LocalArchiveMalwareScanOverride = false; + bool ProxyCommandLineOptions = false; + + std::optional<std::string> DefaultProxy; }; struct AdminSettingsInternal { AdminSettingsInternal(); - void SetAdminSetting(AdminSetting setting, bool enabled); + void SetAdminSetting(BoolAdminSetting setting, bool enabled); + void SetAdminSetting(StringAdminSetting setting, const std::optional<std::string>& value); - bool GetAdminSettingBoolValue(AdminSetting setting) const; + bool GetAdminSettingValue(BoolAdminSetting setting) const; + std::optional<std::string> GetAdminSettingValue(StringAdminSetting setting) const; private: void LoadAdminSettings(); [[nodiscard]] bool SaveAdminSettings(); + // Sets the value of an admin setting using the given function and then saves the changes. + // Encapsulates the retry and reload logic. + // Stops if the value cannot be set, as indicated by the return value of setValue() + void SetAdminSettingAndSave(std::function<bool()> setValue); + Stream m_settingStream; AdminSettingValues m_settingValues; }; @@ -65,25 +78,12 @@ namespace AppInstaller::Settings LoadAdminSettings(); } - void AdminSettingsInternal::SetAdminSetting(AdminSetting setting, bool enabled) + void AdminSettingsInternal::SetAdminSettingAndSave(std::function<bool()> setValue) { for (size_t i = 0; i < 10; ++i) { - switch (setting) + if (!setValue()) { - case AdminSetting::LocalManifestFiles: - m_settingValues.LocalManifestFiles = enabled; - break; - case AdminSetting::BypassCertificatePinningForMicrosoftStore: - m_settingValues.BypassCertificatePinningForMicrosoftStore = enabled; - break; - case AdminSetting::InstallerHashOverride: - m_settingValues.InstallerHashOverride = enabled; - break; - case AdminSetting::LocalArchiveMalwareScanOverride: - m_settingValues.LocalArchiveMalwareScanOverride = enabled; - break; - default: return; } @@ -99,23 +99,78 @@ namespace AppInstaller::Settings THROW_HR_MSG(E_UNEXPECTED, "Too many attempts at SaveAdminSettings"); } - bool AdminSettingsInternal::GetAdminSettingBoolValue(AdminSetting setting) const + void AdminSettingsInternal::SetAdminSetting(BoolAdminSetting setting, bool enabled) + { + SetAdminSettingAndSave([&]() + { + switch (setting) + { + case BoolAdminSetting::LocalManifestFiles: + m_settingValues.LocalManifestFiles = enabled; + return true; + case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: + m_settingValues.BypassCertificatePinningForMicrosoftStore = enabled; + return true; + case BoolAdminSetting::InstallerHashOverride: + m_settingValues.InstallerHashOverride = enabled; + return true; + case BoolAdminSetting::LocalArchiveMalwareScanOverride: + m_settingValues.LocalArchiveMalwareScanOverride = enabled; + return true; + case BoolAdminSetting::ProxyCommandLineOptions: + m_settingValues.ProxyCommandLineOptions = enabled; + return true; + default: + return false; + } + }); + } + + void AdminSettingsInternal::SetAdminSetting(StringAdminSetting setting, const std::optional<std::string>& value) + { + SetAdminSettingAndSave([&]() + { + switch (setting) + { + case StringAdminSetting::DefaultProxy: + m_settingValues.DefaultProxy = value; + return true; + default: + return false; + } + }); + } + + bool AdminSettingsInternal::GetAdminSettingValue(BoolAdminSetting setting) const { switch (setting) { - case AdminSetting::LocalManifestFiles: + case BoolAdminSetting::LocalManifestFiles: return m_settingValues.LocalManifestFiles; - case AdminSetting::BypassCertificatePinningForMicrosoftStore: + case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: return m_settingValues.BypassCertificatePinningForMicrosoftStore; - case AdminSetting::InstallerHashOverride: + case BoolAdminSetting::InstallerHashOverride: return m_settingValues.InstallerHashOverride; - case AdminSetting::LocalArchiveMalwareScanOverride: + case BoolAdminSetting::LocalArchiveMalwareScanOverride: return m_settingValues.LocalArchiveMalwareScanOverride; + case BoolAdminSetting::ProxyCommandLineOptions: + return m_settingValues.ProxyCommandLineOptions; default: return false; } } + std::optional<std::string> AdminSettingsInternal::GetAdminSettingValue(StringAdminSetting setting) const + { + switch (setting) + { + case StringAdminSetting::DefaultProxy: + return m_settingValues.DefaultProxy; + default: + return std::nullopt; + } + } + void AdminSettingsInternal::LoadAdminSettings() { auto stream = m_settingStream.Get(); @@ -154,6 +209,13 @@ namespace AppInstaller::Settings TryReadScalar<bool>(document, s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore, m_settingValues.BypassCertificatePinningForMicrosoftStore); TryReadScalar<bool>(document, s_AdminSettingsYaml_InstallerHashOverride, m_settingValues.InstallerHashOverride); TryReadScalar<bool>(document, s_AdminSettingsYaml_LocalArchiveMalwareScanOverride, m_settingValues.LocalArchiveMalwareScanOverride); + TryReadScalar<bool>(document, s_AdminSettingsYaml_ProxyCommandLineOptions, m_settingValues.ProxyCommandLineOptions); + + std::string defaultProxy; + if (TryReadScalar<std::string>(document, s_AdminSettingsYaml_DefaultProxy, defaultProxy)) + { + m_settingValues.DefaultProxy.emplace(std::move(defaultProxy)); + } } bool AdminSettingsInternal::SaveAdminSettings() @@ -164,74 +226,128 @@ namespace AppInstaller::Settings out << YAML::Key << s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore << YAML::Value << m_settingValues.BypassCertificatePinningForMicrosoftStore; out << YAML::Key << s_AdminSettingsYaml_InstallerHashOverride << YAML::Value << m_settingValues.InstallerHashOverride; out << YAML::Key << s_AdminSettingsYaml_LocalArchiveMalwareScanOverride << YAML::Value << m_settingValues.LocalArchiveMalwareScanOverride; + out << YAML::Key << s_AdminSettingsYaml_ProxyCommandLineOptions << YAML::Value << m_settingValues.ProxyCommandLineOptions; + + if (m_settingValues.DefaultProxy) + { + out << YAML::Key << s_AdminSettingsYaml_DefaultProxy << YAML::Value << m_settingValues.DefaultProxy.value(); + } + out << YAML::EndMap; return m_settingStream.Set(out.str()); } + + auto GetPolicyStateForSetting(BoolAdminSetting setting) + { + auto policy = GetAdminSettingPolicy(setting); + return GroupPolicies().GetState(policy); + } + + std::optional<std::reference_wrapper<const std::string>> GetPolicyStateForSetting(StringAdminSetting setting) + { + switch (setting) + { + case AppInstaller::Settings::StringAdminSetting::DefaultProxy: + return GroupPolicies().GetValueRef<ValuePolicy::DefaultProxy>(); + default: + return std::nullopt; + } + } } - AdminSetting StringToAdminSetting(std::string_view in) + BoolAdminSetting StringToBoolAdminSetting(std::string_view in) { - AdminSetting result = AdminSetting::Unknown; + BoolAdminSetting result = BoolAdminSetting::Unknown; if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_LocalManifestFiles, in)) { - result = AdminSetting::LocalManifestFiles; + result = BoolAdminSetting::LocalManifestFiles; } else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore, in)) { - result = AdminSetting::BypassCertificatePinningForMicrosoftStore; + result = BoolAdminSetting::BypassCertificatePinningForMicrosoftStore; } else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_InstallerHashOverride, in)) { - result = AdminSetting::InstallerHashOverride; + result = BoolAdminSetting::InstallerHashOverride; } else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_LocalArchiveMalwareScanOverride, in)) { - result = AdminSetting::LocalArchiveMalwareScanOverride; + result = BoolAdminSetting::LocalArchiveMalwareScanOverride; + } + else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_ProxyCommandLineOptions, in)) + { + result = BoolAdminSetting::ProxyCommandLineOptions; + } + + return result; + } + + StringAdminSetting StringToStringAdminSetting(std::string_view in) + { + StringAdminSetting result = StringAdminSetting::Unknown; + + if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_DefaultProxy, in)) + { + result = StringAdminSetting::DefaultProxy; } return result; } - Utility::LocIndView AdminSettingToString(AdminSetting setting) + Utility::LocIndView AdminSettingToString(BoolAdminSetting setting) { switch (setting) { - case AdminSetting::LocalManifestFiles: + case BoolAdminSetting::LocalManifestFiles: return s_AdminSettingsYaml_LocalManifestFiles; - case AdminSetting::BypassCertificatePinningForMicrosoftStore: + case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: return s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore; - case AdminSetting::InstallerHashOverride: + case BoolAdminSetting::InstallerHashOverride: return s_AdminSettingsYaml_InstallerHashOverride; - case AdminSetting::LocalArchiveMalwareScanOverride: + case BoolAdminSetting::LocalArchiveMalwareScanOverride: return s_AdminSettingsYaml_LocalArchiveMalwareScanOverride; + case BoolAdminSetting::ProxyCommandLineOptions: + return s_AdminSettingsYaml_ProxyCommandLineOptions; + default: + return "Unknown"_liv; + } + } + + Utility::LocIndView AdminSettingToString(StringAdminSetting setting) + { + switch (setting) + { + case StringAdminSetting::DefaultProxy: + return s_AdminSettingsYaml_DefaultProxy; default: return "Unknown"_liv; } } - TogglePolicy::Policy GetAdminSettingPolicy(AdminSetting setting) + TogglePolicy::Policy GetAdminSettingPolicy(BoolAdminSetting setting) { switch (setting) { - case AdminSetting::LocalManifestFiles: + case BoolAdminSetting::LocalManifestFiles: return TogglePolicy::Policy::LocalManifestFiles; - case AdminSetting::BypassCertificatePinningForMicrosoftStore: + case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: return TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore; - case AdminSetting::InstallerHashOverride: + case BoolAdminSetting::InstallerHashOverride: return TogglePolicy::Policy::HashOverride; - case AdminSetting::LocalArchiveMalwareScanOverride: + case BoolAdminSetting::LocalArchiveMalwareScanOverride: return TogglePolicy::Policy::LocalArchiveMalwareScanOverride; + case BoolAdminSetting::ProxyCommandLineOptions: + return TogglePolicy::Policy::ProxyCommandLineOptions; default: return TogglePolicy::Policy::None; } } - bool EnableAdminSetting(AdminSetting setting) + bool EnableAdminSetting(BoolAdminSetting setting) { - auto policy = GetAdminSettingPolicy(setting); - if (GroupPolicies().GetState(policy) == PolicyState::Disabled) + if (GetPolicyStateForSetting(setting) == PolicyState::Disabled) { return false; } @@ -241,10 +357,9 @@ namespace AppInstaller::Settings return true; } - bool DisableAdminSetting(AdminSetting setting) + bool DisableAdminSetting(BoolAdminSetting setting) { - auto policy = GetAdminSettingPolicy(setting); - if (GroupPolicies().GetState(policy) == PolicyState::Enabled) + if (GetPolicyStateForSetting(setting) == PolicyState::Enabled) { return false; } @@ -254,31 +369,64 @@ namespace AppInstaller::Settings return true; } - bool IsAdminSettingEnabled(AdminSetting setting) + bool IsAdminSettingEnabled(BoolAdminSetting setting) { // Check for a policy that overrides this setting. - auto policy = GetAdminSettingPolicy(setting); - auto policyState = GroupPolicies().GetState(policy); + auto policyState = GetPolicyStateForSetting(setting); if (policyState != PolicyState::NotConfigured) { return policyState == PolicyState::Enabled; } AdminSettingsInternal adminSettingsInternal; - return adminSettingsInternal.GetAdminSettingBoolValue(setting); + return adminSettingsInternal.GetAdminSettingValue(setting); + } + + bool SetAdminSetting(StringAdminSetting setting, std::string_view value) + { + if (GetPolicyStateForSetting(setting)) + { + return false; + } + + AdminSettingsInternal adminSettingsInternal; + adminSettingsInternal.SetAdminSetting(setting, std::string{ value }); + return true; } - std::vector<AdminSetting> GetAllAdminSettings() + bool ResetAdminSetting(StringAdminSetting setting) { - std::vector<AdminSetting> result; - using AdminSetting_t = std::underlying_type_t<AdminSetting>; + if (GetPolicyStateForSetting(setting)) + { + return false; + } - // Skip Unknown. - for (AdminSetting_t i = 1 + static_cast<AdminSetting_t>(AdminSetting::Unknown); i < static_cast<AdminSetting_t>(AdminSetting::Max); ++i) + AdminSettingsInternal adminSettingsInternal; + adminSettingsInternal.SetAdminSetting(setting, std::nullopt); + return true; + } + + std::optional<std::string> GetAdminSetting(StringAdminSetting setting) + { + // Check for a policy that overrides this setting. + auto policyState = GetPolicyStateForSetting(setting); + if (policyState) { - result.emplace_back(static_cast<AdminSetting>(i));; + return policyState.value(); } - return result; + AdminSettingsInternal adminSettingsInternal; + return adminSettingsInternal.GetAdminSettingValue(setting); + } + + std::vector<BoolAdminSetting> GetAllBoolAdminSettings() + { + return GetAllSequentialEnumValues(BoolAdminSetting::Unknown); } + + std::vector<StringAdminSetting> GetAllStringAdminSettings() + { + return GetAllSequentialEnumValues(StringAdminSetting::Unknown); + } + } diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -435,6 +435,7 @@ <ClInclude Include="Public\winget\MSStore.h" /> <ClInclude Include="Public\winget\NameNormalization.h" /> <ClInclude Include="Public\winget\Filesystem.h" /> + <ClInclude Include="Public\winget\NetworkSettings.h" /> <ClInclude Include="Public\winget\PackageDependenciesValidationUtil.h" /> <ClInclude Include="Public\winget\Pin.h" /> <ClInclude Include="Public\winget\Reboot.h" /> @@ -481,6 +482,7 @@ <ClCompile Include="MsixManifest.cpp" /> <ClCompile Include="MSStore.cpp" /> <ClCompile Include="NameNormalization.cpp" /> + <ClCompile Include="NetworkSettings.cpp" /> <ClCompile Include="PackageDependenciesValidationUtil.cpp" /> <ClCompile Include="Pin.cpp" /> <ClCompile Include="Progress.cpp" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -189,6 +189,9 @@ <ClInclude Include="Public\winget\Authentication.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\NetworkSettings.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -338,6 +341,9 @@ <ClCompile Include="Authentication\WebAccountManagerAuthenticator.cpp"> <Filter>Authentication</Filter> </ClCompile> + <ClCompile Include="NetworkSettings.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp @@ -114,6 +114,7 @@ namespace AppInstaller::Deployment PackageManager packageManager; // In the event of a failure we want to ensure that the package is not left on the system. + // No need for proxy as Deployment won't use it anyways. Msix::MsixInfo packageInfo{ uri }; std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide(); std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide); @@ -218,6 +219,7 @@ namespace AppInstaller::Deployment PackageManager packageManager; // In the event of a failure we want to ensure that the package is not left on the system. + // No need for proxy as Deployment won't use it anyways. Msix::MsixInfo packageInfo{ uri }; std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide(); std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide); diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp @@ -9,6 +9,7 @@ #include "Public/AppInstallerLogging.h" #include "Public/AppInstallerTelemetry.h" #include "Public/winget/UserSettings.h" +#include "Public/winget/NetworkSettings.h" #include "Public/winget/Filesystem.h" #include "DODownloader.h" #include "HttpStream/HttpRandomAccessStream.h" @@ -103,12 +104,29 @@ namespace AppInstaller::Utility AICLI_LOG(Core, Info, << "WinINet downloading from url: " << url); auto agentWide = Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent().get()); - wil::unique_hinternet session(InternetOpen( - agentWide.c_str(), - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, - NULL, - 0)); + wil::unique_hinternet session; + + const auto& proxyUri = Network().GetProxyUri(); + if (proxyUri) + { + AICLI_LOG(Core, Info, << "Using proxy " << proxyUri.value()); + session.reset(InternetOpen( + agentWide.c_str(), + INTERNET_OPEN_TYPE_PROXY, + Utility::ConvertToUTF16(proxyUri.value()).c_str(), + NULL, + 0)); + } + else + { + session.reset(InternetOpen( + agentWide.c_str(), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0)); + } + THROW_LAST_ERROR_IF_NULL_MSG(session, "InternetOpen() failed."); auto urlWide = Utility::ConvertToUTF16(url); @@ -224,6 +242,7 @@ namespace AppInstaller::Utility std::map<std::string, std::string> GetHeaders(std::string_view url) { + // TODO: Use proxy info. HttpClient does not support using a custom proxy, only using the system-wide one. AICLI_LOG(Core, Verbose, << "Retrieving headers from url: " << url); HttpBaseProtocolFilter filter; @@ -296,11 +315,7 @@ namespace AppInstaller::Utility // - WinGetUtil :: Intentionally not using DO at this time if (type == DownloadType::Installer) { - // Determine whether to try DO first or not, as this is the only choice currently supported. - InstallerDownloader setting = User().Get<Setting::NetworkDownloader>(); - - if (setting == InstallerDownloader::Default || - setting == InstallerDownloader::DeliveryOptimization) + if (Network().GetInstallerDownloader() == InstallerDownloader::DeliveryOptimization) { try { diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -44,6 +44,8 @@ namespace AppInstaller::Settings return userSettings.Get<Setting::EFResume>(); case ExperimentalFeature::Feature::Configuration03: return userSettings.Get<Setting::EFConfiguration03>(); + case ExperimentalFeature::Feature::Proxy: + return userSettings.Get<Setting::EFProxy>(); default: THROW_HR(E_UNEXPECTED); } @@ -77,6 +79,8 @@ namespace AppInstaller::Settings return ExperimentalFeature{ "Resume", "resume", "https://aka.ms/winget-settings", Feature::Resume }; case Feature::Configuration03: return ExperimentalFeature{ "Configuration Schema 0.3", "configuration03", "https://aka.ms/winget-settings", Feature::Configuration03 }; + case Feature::Proxy: + return ExperimentalFeature{ "Proxy", "proxy", "https://aka.ms/winget-settings", Feature::Proxy }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.cpp b/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.cpp @@ -22,6 +22,7 @@ namespace AppInstaller::Utility::HttpStream { std::future<std::shared_ptr<HttpClientWrapper>> HttpClientWrapper::CreateAsync(const Uri& uri) { + // TODO: Use proxy info. HttpClient does not support using a custom proxy, only using the system-wide one. std::shared_ptr<HttpClientWrapper> instance = std::make_shared<HttpClientWrapper>(); // Use an HTTP filter to disable the default caching behavior and use the Most Recent caching behavior instead diff --git a/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.h b/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.h @@ -19,8 +19,7 @@ namespace AppInstaller::Utility::HttpStream winrt::Windows::Storage::Streams::IInputStream> { public: - winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::Streams::IRandomAccessStream> InitializeAsync( - const winrt::Windows::Foundation::Uri& uri); + winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::Streams::IRandomAccessStream> InitializeAsync(const winrt::Windows::Foundation::Uri& uri); uint64_t Size() const; void Size(uint64_t value); uint64_t Position() const; diff --git a/src/AppInstallerCommonCore/Manifest/MsixManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/MsixManifestValidation.cpp @@ -59,8 +59,8 @@ namespace AppInstaller::Manifest { AICLI_LOG(Core, Info, << "Start downloading installer"); auto tempFile = Runtime::GetNewTempFilePath(); - ProgressCallback callback; - Utility::Download(installerUrl, tempFile, Utility::DownloadType::Installer, callback); + ProgressCallback emptyCallback; + Utility::Download(installerUrl, tempFile, Utility::DownloadType::Installer, emptyCallback); m_downloadedInstallers.push_back(tempFile); return tempFile; } diff --git a/src/AppInstallerCommonCore/NetworkSettings.cpp b/src/AppInstallerCommonCore/NetworkSettings.cpp @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/NetworkSettings.h" +#include "winget/AdminSettings.h" +#include "AppInstallerLogging.h" + +namespace AppInstaller::Settings +{ + void NetworkSettings::SetProxyUri(const std::optional<std::string>& proxyUri) + { + AICLI_LOG(Core, Info, << "Setting proxy"); + + if (proxyUri) + { + m_proxyUri = proxyUri.value(); + AICLI_LOG(Core, Info, << "New value for proxy is " << m_proxyUri.value()); + } + else + { + m_proxyUri.reset(); + AICLI_LOG(Core, Info, << "Proxy will not be used"); + } + } + + InstallerDownloader NetworkSettings::GetInstallerDownloader() const + { + // The default is DeliveryOptimization. + // We only use WinINet if specified by settings, or if we want to use proxy (as DO does not support that) + InstallerDownloader setting = User().Get<Setting::NetworkDownloader>(); + + if (setting != InstallerDownloader::WinInet && m_proxyUri) + { + AICLI_LOG(Core, Info, << "Forcing use of wininet for download as DO does not support proxy"); + return InstallerDownloader::WinInet; + } + else // Default or DO + { + return InstallerDownloader::DeliveryOptimization; + } + } + + NetworkSettings::NetworkSettings() + { + // Get the default proxy + m_proxyUri = GetAdminSetting(StringAdminSetting::DefaultProxy); + AICLI_LOG(Core, Info, << "Default proxy is " << (m_proxyUri ? m_proxyUri.value() : "not set")); + } + + NetworkSettings& NetworkSettings::Instance() + { + static NetworkSettings networkSettings; + return networkSettings; + } + + NetworkSettings& Network() + { + return NetworkSettings::Instance(); + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h @@ -4,11 +4,6 @@ #include <AppInstallerErrors.h> #include <AppInstallerProgress.h> -#include <winrt/Windows.Web.Http.h> - -#include <urlmon.h> -#include <wrl/client.h> - #include <chrono> #include <filesystem> #include <map> @@ -56,27 +51,27 @@ namespace AppInstaller::Utility // url: The url to be downloaded from. http->https redirection is allowed. // dest: The stream to be downloaded to. // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. - // downloadIdentifier: Optional. Currently only used by DO to identify the download. + // downloadInfo: Optional. Currently only used by DO to identify the download. std::optional<std::vector<BYTE>> DownloadToStream( const std::string& url, std::ostream& dest, DownloadType type, IProgressCallback& progress, bool computeHash = false, - std::optional<DownloadInfo> info = {}); + std::optional<DownloadInfo> downloadInfo = {}); // Downloads a file from the given URL and places it in the given location. // url: The url to be downloaded from. http->https redirection is allowed. // dest: The path to local file to be downloaded to. // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. - // downloadIdentifier: Optional. Currently only used by DO to identify the download. + // downloadInfo: Optional. Currently only used by DO to identify the download. std::optional<std::vector<BYTE>> Download( const std::string& url, const std::filesystem::path& dest, DownloadType type, IProgressCallback& progress, bool computeHash = false, - std::optional<DownloadInfo> info = {}); + std::optional<DownloadInfo> downloadInfo = {}); // Gets the headers for the given URL. std::map<std::string, std::string> GetHeaders(std::string_view url); @@ -100,7 +95,7 @@ namespace AppInstaller::Utility HRESULT ApplyMotwUsingIAttachmentExecuteIfApplicable(const std::filesystem::path& filePath, const std::string& source, URLZONE zoneIfScanFailure); // Function to read-only create a stream from a uri string (url address or file system path) - Microsoft::WRL::ComPtr<IStream> GetReadOnlyStreamFromURI(std::string_view uriStr); + ::Microsoft::WRL::ComPtr<IStream> GetReadOnlyStreamFromURI(std::string_view uriStr); // Gets the retry after value in terms of a delay in seconds. std::chrono::seconds GetRetryAfter(const std::wstring& retryAfter); diff --git a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h @@ -4,28 +4,52 @@ #include <string> +#ifndef AICLI_DISABLE_TEST_HOOKS +#include "winget/GroupPolicy.h" +#endif + namespace AppInstaller::Settings { // Enum of admin settings. - enum class AdminSetting + enum class BoolAdminSetting : size_t { Unknown = 0, LocalManifestFiles, BypassCertificatePinningForMicrosoftStore, InstallerHashOverride, LocalArchiveMalwareScanOverride, + ProxyCommandLineOptions, + Max, + }; + + enum class StringAdminSetting : size_t + { + Unknown = 0, + DefaultProxy, Max, }; - AdminSetting StringToAdminSetting(std::string_view in); + BoolAdminSetting StringToBoolAdminSetting(std::string_view in); + StringAdminSetting StringToStringAdminSetting(std::string_view in); - Utility::LocIndView AdminSettingToString(AdminSetting setting); + Utility::LocIndView AdminSettingToString(BoolAdminSetting setting); + Utility::LocIndView AdminSettingToString(StringAdminSetting setting); - bool EnableAdminSetting(AdminSetting setting); + // Returns true if the value is set. + // Group policy overriding the setting can prevent the value from being set + bool EnableAdminSetting(BoolAdminSetting setting); + bool DisableAdminSetting(BoolAdminSetting setting); + bool SetAdminSetting(StringAdminSetting setting, std::string_view value); + bool ResetAdminSetting(StringAdminSetting setting); - bool DisableAdminSetting(AdminSetting setting); + bool IsAdminSettingEnabled(BoolAdminSetting setting); + std::optional<std::string> GetAdminSetting(StringAdminSetting setting); - bool IsAdminSettingEnabled(AdminSetting setting); + std::vector<BoolAdminSetting> GetAllBoolAdminSettings(); + std::vector<StringAdminSetting> GetAllStringAdminSettings(); - std::vector<AdminSetting> GetAllAdminSettings(); +#ifndef AICLI_DISABLE_TEST_HOOKS + // Only exposed for tests to validate that all admin settings have a corresponding policy + TogglePolicy::Policy GetAdminSettingPolicy(BoolAdminSetting setting); +#endif } diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h @@ -25,6 +25,7 @@ namespace AppInstaller::Settings DirectMSI = 0x1, Resume = 0x2, Configuration03 = 0x4, + Proxy = 0x8, 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/NetworkSettings.h b/src/AppInstallerCommonCore/Public/winget/NetworkSettings.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include <optional> +#include <string> +#include "winget/UserSettings.h" + +namespace AppInstaller::Settings +{ + // Network related settings. + // Merges information from user settings, admin settings, command line, and group policy. + // TODO: This is currently a process global. It should be converted to a thread local + // (like telemetry) once we expose configuring a proxy through the COM API + struct NetworkSettings + { + static NetworkSettings& Instance(); + + const std::optional<std::string> GetProxyUri() const { return m_proxyUri; } + // Sets the proxy URI; may do nothing depending on admin settings and group policy + void SetProxyUri(const std::optional<std::string>& proxyUri); + + InstallerDownloader GetInstallerDownloader() const; + + protected: + NetworkSettings(); + ~NetworkSettings() = default; + + std::optional<std::string> m_proxyUri; + }; + + NetworkSettings& Network(); +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -72,6 +72,7 @@ namespace AppInstaller::Settings EFDirectMSI, EFResume, EFConfiguration03, + EFProxy, // Telemetry TelemetryDisable, // Install behavior @@ -151,6 +152,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFResume, bool, bool, false, ".experimentalFeatures.resume"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFConfiguration03, bool, bool, false, ".experimentalFeatures.configuration03"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFProxy, bool, bool, false, ".experimentalFeatures.proxy"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 @@ -261,6 +261,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) WINGET_VALIDATE_PASS_THROUGH(EFResume) WINGET_VALIDATE_PASS_THROUGH(EFConfiguration03) + WINGET_VALIDATE_PASS_THROUGH(EFProxy) WINGET_VALIDATE_PASS_THROUGH(AnonymizePathForDisplay) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -148,37 +148,38 @@ namespace AppInstaller::Repository::Rest const HttpClientHelper::HttpRequestHeaders& additionalHeaders, const IRestClient::Information& information, const Authentication::AuthenticationArguments& authArgs, - const Version& version) + const Version& version, + const HttpClientHelper& helper) { if (version == Version_1_0_0) { - return std::make_unique<Schema::V1_0::Interface>(api); + return std::make_unique<Schema::V1_0::Interface>(api, helper); } else if (version == Version_1_1_0) { - return std::make_unique<Schema::V1_1::Interface>(api, information, additionalHeaders); + return std::make_unique<Schema::V1_1::Interface>(api, helper, information, additionalHeaders); } else if (version == Version_1_4_0) { - return std::make_unique<Schema::V1_4::Interface>(api, information, additionalHeaders); + return std::make_unique<Schema::V1_4::Interface>(api, helper, information, additionalHeaders); } else if (version == Version_1_5_0) { - return std::make_unique<Schema::V1_5::Interface>(api, information, additionalHeaders); + return std::make_unique<Schema::V1_5::Interface>(api, helper, information, additionalHeaders); } else if (version == Version_1_6_0) { - return std::make_unique<Schema::V1_6::Interface>(api, information, additionalHeaders); + return std::make_unique<Schema::V1_6::Interface>(api, helper, information, additionalHeaders); } else if (version == Version_1_7_0) { - return std::make_unique<Schema::V1_7::Interface>(api, information, additionalHeaders, authArgs); + return std::make_unique<Schema::V1_7::Interface>(api, helper, information, additionalHeaders, authArgs); } THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); } - RestClient RestClient::Create(const std::string& restApi, std::optional<std::string> customHeader, std::string_view caller, const Authentication::AuthenticationArguments& authArgs, const HttpClientHelper& helper) + RestClient RestClient::Create(const std::string& restApi, std::optional<std::string> customHeader, std::string_view caller, const HttpClientHelper& helper, const Authentication::AuthenticationArguments& authArgs) { utility::string_t restEndpoint = RestHelper::GetRestAPIBaseUri(restApi); THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !RestHelper::IsValidUri(restEndpoint)); @@ -189,7 +190,7 @@ namespace AppInstaller::Repository::Rest std::optional<Version> latestCommonVersion = GetLatestCommonVersion(information.ServerSupportedVersions, WingetSupportedContracts); THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !latestCommonVersion); - std::unique_ptr<Schema::IRestClient> supportedInterface = GetSupportedInterface(utility::conversions::to_utf8string(restEndpoint), headers, information, authArgs, latestCommonVersion.value()); + std::unique_ptr<Schema::IRestClient> supportedInterface = GetSupportedInterface(utility::conversions::to_utf8string(restEndpoint), headers, information, authArgs, latestCommonVersion.value(), helper); return RestClient{ std::move(supportedInterface), information.SourceIdentifier }; } } diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.h b/src/AppInstallerRepositoryCore/Rest/RestClient.h @@ -30,12 +30,18 @@ namespace AppInstaller::Repository::Rest static std::optional<AppInstaller::Utility::Version> GetLatestCommonVersion(const std::vector<std::string>& serverSupportedVersions, const std::set<AppInstaller::Utility::Version>& wingetSupportedVersions); // Responsible for getting the source information contracts with minimal validation. Does not try to create a rest interface out of it. - static Schema::IRestClient::Information GetInformation(const std::string& restApi, std::optional<std::string> customHeader, std::string_view caller, const Schema::HttpClientHelper& helper = {}); + static Schema::IRestClient::Information GetInformation(const std::string& restApi, std::optional<std::string> customHeader, std::string_view caller, const Schema::HttpClientHelper& helper); - static std::unique_ptr<Schema::IRestClient> GetSupportedInterface(const std::string& restApi, const Schema::HttpClientHelper::HttpRequestHeaders& additionalHeaders, const Schema::IRestClient::Information& information, const Authentication::AuthenticationArguments& authArgs, const AppInstaller::Utility::Version& version); + static std::unique_ptr<Schema::IRestClient> GetSupportedInterface( + const std::string& restApi, + const Schema::HttpClientHelper::HttpRequestHeaders& additionalHeaders, + const Schema::IRestClient::Information& information, + const Authentication::AuthenticationArguments& authArgs, + const AppInstaller::Utility::Version& version, + const Schema::HttpClientHelper& helper); // Creates the rest client. Full validation performed (just as opening the source) - static RestClient Create(const std::string& restApi, std::optional<std::string> customHeader, std::string_view caller, const Authentication::AuthenticationArguments& authArgs = {}, const Schema::HttpClientHelper& helper = {}); + static RestClient Create(const std::string& restApi, std::optional<std::string> customHeader, std::string_view caller, const Schema::HttpClientHelper& helper, const Authentication::AuthenticationArguments& authArgs = {}); private: RestClient(std::unique_ptr<Schema::IRestClient> supportedInterface, std::string sourceIdentifier); diff --git a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp @@ -50,7 +50,7 @@ namespace AppInstaller::Repository::Rest std::shared_ptr<ISource> Open(IProgressCallback&) override { Initialize(); - RestClient restClient = RestClient::Create(m_details.Arg, m_customHeader, m_caller, m_authArgs, m_httpClientHelper); + RestClient restClient = RestClient::Create(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper, m_authArgs); return std::make_shared<RestSource>(m_details, m_information, std::move(restClient)); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Interface.h @@ -10,7 +10,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0 // Interface to this schema version exposed through IRestClient. struct Interface : public IRestClient { - Interface(const std::string& restApi, const HttpClientHelper& httpClientHelper = {}); + Interface(const std::string& restApi, const Schema::HttpClientHelper& helper); Interface(const Interface&) = delete; Interface& operator=(const Interface&) = delete; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h @@ -8,7 +8,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_1 // Interface to this schema version exposed through IRestClient. struct Interface : public V1_0::Interface { - Interface(const std::string& restApi, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, const HttpClientHelper& httpClientHelper = {}); + Interface(const std::string& restApi, const Schema::HttpClientHelper& helper, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); Interface(const Interface&) = delete; Interface& operator=(const Interface&) = delete; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp @@ -26,9 +26,9 @@ namespace AppInstaller::Repository::Rest::Schema::V1_1 Interface::Interface( const std::string& restApi, + const HttpClientHelper& httpClientHelper, IRestClient::Information information, - const HttpClientHelper::HttpRequestHeaders& additionalHeaders, - const HttpClientHelper& httpClientHelper) : V1_0::Interface(restApi, httpClientHelper), m_information(std::move(information)) + const HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_0::Interface(restApi, httpClientHelper), m_information(std::move(information)) { m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_1_0.ToString()); diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h @@ -8,7 +8,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_4 // Interface to this schema version exposed through IRestClient. struct Interface : public V1_1::Interface { - Interface(const std::string& restApi, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, const HttpClientHelper& httpClientHelper = {}); + Interface(const std::string& restApi, const Schema::HttpClientHelper& helper, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); Interface(const Interface&) = delete; Interface& operator=(const Interface&) = delete; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp @@ -11,9 +11,9 @@ namespace AppInstaller::Repository::Rest::Schema::V1_4 { Interface::Interface( const std::string& restApi, + const HttpClientHelper& httpClientHelper, IRestClient::Information information, - const HttpClientHelper::HttpRequestHeaders& additionalHeaders, - const HttpClientHelper& httpClientHelper) : V1_1::Interface(restApi, std::move(information), additionalHeaders, httpClientHelper) + const HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_1::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) { m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_4_0.ToString()); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h @@ -8,7 +8,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_5 // Interface to this schema version exposed through IRestClient. struct Interface : public V1_4::Interface { - Interface(const std::string& restApi, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, const HttpClientHelper& httpClientHelper = {}); + Interface(const std::string& restApi, const Schema::HttpClientHelper& helper, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); Interface(const Interface&) = delete; Interface& operator=(const Interface&) = delete; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp @@ -11,9 +11,9 @@ namespace AppInstaller::Repository::Rest::Schema::V1_5 { Interface::Interface( const std::string& restApi, + const HttpClientHelper& httpClientHelper, IRestClient::Information information, - const HttpClientHelper::HttpRequestHeaders& additionalHeaders, - const HttpClientHelper& httpClientHelper) : V1_4::Interface(restApi, std::move(information), additionalHeaders, httpClientHelper) + const HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_4::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) { m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_5_0.ToString()); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h @@ -8,7 +8,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_6 // Interface to this schema version exposed through IRestClient. struct Interface : public V1_5::Interface { - Interface(const std::string& restApi, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, const HttpClientHelper& httpClientHelper = {}); + Interface(const std::string& restApi, const Schema::HttpClientHelper& helper, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); Interface(const Interface&) = delete; Interface& operator=(const Interface&) = delete; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp @@ -11,9 +11,9 @@ namespace AppInstaller::Repository::Rest::Schema::V1_6 { Interface::Interface( const std::string& restApi, + const HttpClientHelper& httpClientHelper, IRestClient::Information information, - const HttpClientHelper::HttpRequestHeaders& additionalHeaders, - const HttpClientHelper& httpClientHelper) : V1_5::Interface(restApi, std::move(information), additionalHeaders, httpClientHelper) + const HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_5::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) { m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_6_0.ToString()); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Interface.h @@ -8,7 +8,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_7 // Interface to this schema version exposed through IRestClient. struct Interface : public V1_6::Interface { - Interface(const std::string& restApi, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}, const HttpClientHelper& httpClientHelper = {}); + Interface(const std::string& restApi, const Schema::HttpClientHelper& helper, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); Interface(const Interface&) = delete; Interface& operator=(const Interface&) = delete; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/RestInterface_1_7.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/RestInterface_1_7.cpp @@ -11,10 +11,10 @@ namespace AppInstaller::Repository::Rest::Schema::V1_7 { Interface::Interface( const std::string& restApi, + const HttpClientHelper& httpClientHelper, IRestClient::Information information, const HttpClientHelper::HttpRequestHeaders& additionalHeaders, - Authentication::AuthenticationArguments authArgs, - const HttpClientHelper& httpClientHelper) : V1_6::Interface(restApi, std::move(information), additionalHeaders, httpClientHelper), m_authArgs(std::move(authArgs)) + Authentication::AuthenticationArguments authArgs) : V1_6::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders), m_authArgs(std::move(authArgs)) { m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_7_0.ToString()); diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/HttpClientHelper.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/HttpClientHelper.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "HttpClientHelper.h" +#include <winget/NetworkSettings.h> namespace AppInstaller::Repository::Rest::Schema { @@ -42,7 +43,20 @@ namespace AppInstaller::Repository::Rest::Schema } } - HttpClientHelper::HttpClientHelper(std::shared_ptr<web::http::http_pipeline_stage> stage) : m_defaultRequestHandlerStage(std::move(stage)) {} + HttpClientHelper::HttpClientHelper(std::shared_ptr<web::http::http_pipeline_stage> stage) + : m_defaultRequestHandlerStage(std::move(stage)) + { + const auto& proxyUri = Settings::Network().GetProxyUri(); + if (proxyUri) + { + AICLI_LOG(Repo, Info, << "Setting proxy for REST HTTP Client helper to " << proxyUri.value()); + m_clientConfig.set_proxy(web::web_proxy{ Utility::ConvertToUTF16(proxyUri.value()) }); + } + else + { + AICLI_LOG(Repo, Info, << "REST HTTP Client helper does not use proxy"); + } + } pplx::task<web::http::http_response> HttpClientHelper::Post( const utility::string_t& uri, diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/HttpClientHelper.h b/src/AppInstallerRepositoryCore/Rest/Schema/HttpClientHelper.h @@ -26,6 +26,7 @@ namespace AppInstaller::Repository::Rest::Schema std::optional<web::json::value> HandleGet(const utility::string_t& uri, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}) const; void SetPinningConfiguration(const Certificates::PinningConfiguration& configuration); + protected: std::optional<web::json::value> ValidateAndExtractResponse(const web::http::http_response& response) const; diff --git a/src/AppInstallerRepositoryCore/SourceList.cpp b/src/AppInstallerRepositoryCore/SourceList.cpp @@ -316,7 +316,7 @@ namespace AppInstaller::Repository details.TrustLevel = SourceTrustLevel::Trusted; details.SupportInstalledSearchCorrelation = false; - if (!Settings::IsAdminSettingEnabled(Settings::AdminSetting::BypassCertificatePinningForMicrosoftStore)) + if (!Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::BypassCertificatePinningForMicrosoftStore)) { using namespace AppInstaller::Certificates; diff --git a/src/AppInstallerRepositoryCore/pch.h b/src/AppInstallerRepositoryCore/pch.h @@ -20,23 +20,6 @@ #include <wil/result_macros.h> #pragma warning( pop ) -#include <AppInstallerDateTime.h> -#include <AppInstallerDownloader.h> -#include <AppInstallerErrors.h> -#include <AppInstallerLogging.h> -#include <AppInstallerRuntime.h> -#include <AppInstallerSHA256.h> -#include <AppInstallerStrings.h> -#include <AppInstallerSynchronization.h> -#include <AppInstallerTelemetry.h> -#include <AppInstallerVersions.h> -#include <winget/ExtensionCatalog.h> -#include <winget/ExperimentalFeature.h> -#include <winget/Locale.h> -#include <winget/Settings.h> -#include <winget/UserSettings.h> -#include <winget/Yaml.h> - #include <winsqlite/winsqlite3.h> #include <winrt/Windows.ApplicationModel.h> @@ -44,6 +27,7 @@ #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Management.Deployment.h> #include <winrt/Windows.Storage.h> +#include <winrt/Windows.Web.Http.h> #include <wrl/client.h> #include <algorithm> @@ -77,3 +61,20 @@ #include <cpprest/json.h> #include <cpprest/uri_builder.h> #pragma warning( pop ) + +#include <AppInstallerDateTime.h> +#include <AppInstallerDownloader.h> +#include <AppInstallerErrors.h> +#include <AppInstallerLogging.h> +#include <AppInstallerRuntime.h> +#include <AppInstallerSHA256.h> +#include <AppInstallerStrings.h> +#include <AppInstallerSynchronization.h> +#include <AppInstallerTelemetry.h> +#include <AppInstallerVersions.h> +#include <winget/ExtensionCatalog.h> +#include <winget/ExperimentalFeature.h> +#include <winget/Locale.h> +#include <winget/Settings.h> +#include <winget/UserSettings.h> +#include <winget/Yaml.h>+ \ No newline at end of file diff --git a/src/AppInstallerSharedLib/GroupPolicy.cpp b/src/AppInstallerSharedLib/GroupPolicy.cpp @@ -224,6 +224,13 @@ namespace AppInstaller::Settings namespace details { +#define POLICY_MAPPING_DEFAULT_READ(_policy_) \ + std::optional<typename ValuePolicyMapping<_policy_>::value_t> ValuePolicyMapping<_policy_>::ReadAndValidate(const Registry::Key& policiesKey) \ + { \ + using Mapping = ValuePolicyMapping<_policy_>; \ + return GetRegistryValueData<Mapping::ValueType>(policiesKey, Mapping::ValueName); \ + } + #define POLICY_MAPPING_DEFAULT_LIST_READ(_policy_) \ std::optional<typename ValuePolicyMapping<_policy_>::value_t> ValuePolicyMapping<_policy_>::ReadAndValidate(const Registry::Key& policiesKey) \ { \ @@ -232,6 +239,7 @@ namespace AppInstaller::Settings POLICY_MAPPING_DEFAULT_LIST_READ(ValuePolicy::AdditionalSources); POLICY_MAPPING_DEFAULT_LIST_READ(ValuePolicy::AllowedSources); + POLICY_MAPPING_DEFAULT_READ(ValuePolicy::DefaultProxy); std::nullopt_t ValuePolicyMapping<ValuePolicy::None>::ReadAndValidate(const Registry::Key&) { @@ -298,6 +306,8 @@ namespace AppInstaller::Settings return TogglePolicy(policy, "EnableWindowsPackageManagerCommandLineInterfaces"sv, String::PolicyEnableWindowsPackageManagerCommandLineInterfaces); case TogglePolicy::Policy::Configuration: return TogglePolicy(policy, "EnableWindowsPackageManagerConfiguration"sv, String::PolicyEnableWinGetConfiguration); + case TogglePolicy::Policy::ProxyCommandLineOptions: + return TogglePolicy(policy, "EnableWindowsPackageManagerProxyCommandLineOptions"sv, String::PolicyEnableProxyCommandLineOptions); default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerSharedLib/Public/AppInstallerLanguageUtilities.h b/src/AppInstallerSharedLib/Public/AppInstallerLanguageUtilities.h @@ -9,6 +9,7 @@ #include <type_traits> #include <utility> #include <variant> +#include <vector> namespace AppInstaller { @@ -144,6 +145,34 @@ namespace AppInstaller std::map<Enum, typename Variant::variant_t> m_data; }; + + template<typename E> + std::vector<E> GetAllSequentialEnumValues(E initialToSkip) + { + std::vector<E> result; + using underlying_t = std::underlying_type_t<E>; + + for (underlying_t i = 1 + static_cast<underlying_t>(initialToSkip); i < static_cast<underlying_t>(E::Max); ++i) + { + result.emplace_back(static_cast<E>(i)); + } + + return result; + } + + template<typename E> + std::vector<E> GetAllExponentialEnumValues(E initialToSkip) + { + std::vector<E> result; + using underlying_t = std::underlying_type_t<E>; + + for (underlying_t i = 1 + static_cast<underlying_t>(initialToSkip); i < static_cast<underlying_t>(E::Max); i <<= 1) + { + result.emplace_back(static_cast<E>(i)); + } + + return result; + } } // Enable enums to be output generically (as their integral value). diff --git a/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h b/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h @@ -22,6 +22,7 @@ namespace AppInstaller::Settings SourceAutoUpdateIntervalInMinutes, AdditionalSources, AllowedSources, + DefaultProxy, Max, }; @@ -45,6 +46,7 @@ namespace AppInstaller::Settings BypassCertificatePinningForMicrosoftStore, WinGetCommandLineInterfaces, Configuration, + ProxyCommandLineOptions, Max, }; @@ -146,6 +148,7 @@ namespace AppInstaller::Settings ) POLICY_MAPPING_VALUE_SPECIALIZATION(ValuePolicy::SourceAutoUpdateIntervalInMinutes, uint32_t, "SourceAutoUpdateInterval"sv, Registry::Value::Type::DWord); + POLICY_MAPPING_VALUE_SPECIALIZATION(ValuePolicy::DefaultProxy, std::string, "DefaultProxy"sv, Registry::Value::Type::String); POLICY_MAPPING_LIST_SPECIALIZATION(ValuePolicy::AdditionalSources, SourceFromPolicy, "AdditionalSources"sv); POLICY_MAPPING_LIST_SPECIALIZATION(ValuePolicy::AllowedSources, SourceFromPolicy, "AllowedSources"sv); diff --git a/src/AppInstallerSharedLib/Public/winget/Resources.h b/src/AppInstallerSharedLib/Public/winget/Resources.h @@ -59,6 +59,7 @@ namespace AppInstaller WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableBypassCertificatePinningForMicrosoftStore); WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWindowsPackageManagerCommandLineInterfaces); WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWinGetConfiguration); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableProxyCommandLineOptions); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidFieldFormat); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidFieldValue); diff --git a/src/Microsoft.Management.Deployment/pch.h b/src/Microsoft.Management.Deployment/pch.h @@ -4,6 +4,7 @@ #include <unknwn.h> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> +#include <winrt/Windows.Web.Http.h> #include <ostream> #include <string> diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.Designer.cs b/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.Designer.cs @@ -151,6 +151,15 @@ namespace Microsoft.WinGet.SharedLib.Resources { } /// <summary> + /// Looks up a localized string similar to Enable Windows Package Manager proxy command line options. + /// </summary> + internal static string PolicyEnableProxyCommandLineOptions { + get { + return ResourceManager.GetString("PolicyEnableProxyCommandLineOptions", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Enable Windows Package Manager command line interfaces. /// </summary> internal static string PolicyEnableWindowsPackageManagerCommandLineInterfaces { diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.resx b/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.resx @@ -147,6 +147,9 @@ <data name="PolicyEnableMSStoreSource" xml:space="preserve"> <value>Enable Windows App Installer Microsoft Store Source</value> </data> + <data name="PolicyEnableProxyCommandLineOptions" xml:space="preserve"> + <value>Enable Windows Package Manager proxy command line options</value> + </data> <data name="PolicyEnableWindowsPackageManagerCommandLineInterfaces" xml:space="preserve"> <value>Enable Windows Package Manager command line interfaces</value> </data> diff --git a/src/WinGetSchemas/WinGetSchemas.vcxitems b/src/WinGetSchemas/WinGetSchemas.vcxitems @@ -16,6 +16,7 @@ <ItemGroup> <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\packages\packages.schema.1.0.json" /> <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\packages\packages.schema.2.0.json" /> + <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\settings\settings.export.schema.0.1.json" /> <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\settings\settings.schema.0.2.json" /> </ItemGroup> <ItemGroup> diff --git a/src/WinGetSchemas/WinGetSchemas.vcxitems.filters b/src/WinGetSchemas/WinGetSchemas.vcxitems.filters @@ -18,6 +18,9 @@ <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\packages\packages.schema.2.0.json"> <Filter>packages</Filter> </None> + <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\settings\settings.export.schema.0.1.json"> + <Filter>settings</Filter> + </None> </ItemGroup> <ItemGroup> <ClInclude Include="$(MSBuildThisFileDirectory)resource.h" /> diff --git a/src/WinGetUtil/pch.h b/src/WinGetUtil/pch.h @@ -19,3 +19,4 @@ #include <string> #include <winrt/Windows.Foundation.h> +#include <winrt/Windows.Web.Http.h>