diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 5c6730fd72..30b40e6999 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -302,6 +302,7 @@ + @@ -390,6 +391,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index 29ce6a719a..ce771bdb4e 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -290,6 +290,9 @@ Commands\Configuration + + Commands\Configuration + @@ -547,6 +550,9 @@ Commands\Configuration + + Commands\Configuration + diff --git a/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.cpp b/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.cpp new file mode 100644 index 0000000000..761ee8d523 --- /dev/null +++ b/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.cpp @@ -0,0 +1,294 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscAdminSettingsResource.h" +#include "DscComposableObject.h" +#include "Resources.h" +#include + +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI +{ + namespace AdminSettingsDetails + { + // The base for admin settings. + struct IAdminSetting + { + virtual ~IAdminSetting() = default; + + // Gets the name of the setting. + virtual Utility::LocIndView SettingName() const = 0; + + // Tests the value. + // Returns true if in the desired state; false if not. + virtual bool Test() const = 0; + + // Sets the value. + // Returns true if the value could be set; false if not. + virtual bool Set() const = 0; + }; + + // A boolean based admin setting + struct AdminSetting_Bool : public IAdminSetting + { + AdminSetting_Bool(Settings::BoolAdminSetting setting, bool value) : m_setting(setting), m_value(value) {} + + Utility::LocIndView SettingName() const override + { + return Settings::AdminSettingToString(m_setting); + } + + bool Test() const override + { + return Settings::IsAdminSettingEnabled(m_setting) == m_value; + } + + bool Set() const override + { + return m_value ? Settings::EnableAdminSetting(m_setting) : Settings::DisableAdminSetting(m_setting); + } + + private: + Settings::BoolAdminSetting m_setting; + bool m_value; + }; + + // A string based admin setting + struct AdminSetting_String : public IAdminSetting + { + AdminSetting_String(Settings::StringAdminSetting setting, std::optional value) : m_setting(setting), m_value(std::move(value)) {} + + Utility::LocIndView SettingName() const override + { + return Settings::AdminSettingToString(m_setting); + } + + bool Test() const override + { + return Settings::GetAdminSetting(m_setting) == m_value; + } + + bool Set() const override + { + return m_value ? Settings::SetAdminSetting(m_setting, m_value.value()) : Settings::ResetAdminSetting(m_setting); + } + + private: + Settings::StringAdminSetting m_setting; + std::optional m_value; + }; + } + + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(SettingsProperty, Json::Value, Settings, "settings", Resource::String::DscResourcePropertyDescriptionAdminSettingsSettings); + + using AdminSettingsResourceObject = DscComposableObject; + + struct AdminSettingsFunctionData + { + AdminSettingsFunctionData() = default; + + AdminSettingsFunctionData(const std::optional& json) : + Input(json) + { + } + + const AdminSettingsResourceObject Input; + AdminSettingsResourceObject Output; + std::vector> InputSettings; + + // Converts the input settings into the appropriate settings object. + void ParseSettings() + { + if (Input.Settings()) + { + const Json::Value& inputSettings = Input.Settings().value(); + for (const auto& property : inputSettings.getMemberNames()) + { + auto boolSetting = Settings::StringToBoolAdminSetting(property); + if (boolSetting != Settings::BoolAdminSetting::Unknown) + { + bool value = inputSettings[property].asBool(); + AICLI_LOG(Config, Info, << "Bool admin setting: " << property << " => " << (value ? "true" : "false")); + InputSettings.emplace_back(std::make_unique(boolSetting, value)); + continue; + } + + auto stringSetting = Settings::StringToStringAdminSetting(property); + if (stringSetting != Settings::StringAdminSetting::Unknown) + { + const auto& propertyNode = inputSettings[property]; + std::optional value; + + if (propertyNode.isNull()) + { + AICLI_LOG(Config, Info, << "String admin setting: " << property << " => null"); + } + else + { + value = propertyNode.asString(); + AICLI_LOG(Config, Info, << "String admin setting: " << property << " => `" << value.value() << "`"); + } + + InputSettings.emplace_back(std::make_unique(stringSetting, std::move(value))); + continue; + } + + AICLI_LOG(Config, Warning, << "Unknown admin setting: " << property); + } + } + } + + // Fills the Output object with the current state + void Get() + { + Json::Value adminSettings{ Json::objectValue }; + + for (const auto& setting : Settings::GetAllBoolAdminSettings()) + { + auto str = std::string{ Settings::AdminSettingToString(setting) }; + adminSettings[str] = Settings::IsAdminSettingEnabled(setting); + } + + for (const auto& setting : Settings::GetAllStringAdminSettings()) + { + auto name = std::string{ Settings::AdminSettingToString(setting) }; + auto value = Settings::GetAdminSetting(setting); + if (value) + { + adminSettings[name] = value.value(); + } + } + + Output.Settings(std::move(adminSettings)); + } + + // Determines if the current Output values match the Input values state. + bool Test() + { + for (const auto& setting : InputSettings) + { + if (!setting->Test()) + { + return false; + } + } + + return true; + } + + // Sets all of the input settings. + void Set() + { + for (const auto& setting : InputSettings) + { + if (!setting->Test()) + { + if (!setting->Set()) + { + auto message = Resource::String::DisabledByGroupPolicy(setting->SettingName()); + THROW_HR_MSG(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, "%hs", message.get().c_str()); + } + } + } + } + + Json::Value DiffJson(bool inDesiredState) + { + Json::Value result{ Json::ValueType::arrayValue }; + + if (!inDesiredState) + { + result.append(std::string{ SettingsProperty::Name() }); + } + + return result; + } + }; + } + + DscAdminSettingsResource::DscAdminSettingsResource(std::string_view parent) : + DscCommandBase(parent, "admin-settings", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::ReturnsStateAndDiff) + { + } + + Resource::LocString DscAdminSettingsResource::ShortDescription() const + { + return Resource::String::DscAdminSettingsResourceShortDescription; + } + + Resource::LocString DscAdminSettingsResource::LongDescription() const + { + return Resource::String::DscAdminSettingsResourceLongDescription; + } + + std::string DscAdminSettingsResource::ResourceType() const + { + return "AdminSettings"; + } + + void DscAdminSettingsResource::ResourceFunctionGet(Execution::Context& context) const + { + AdminSettingsFunctionData data; + data.Get(); + WriteJsonOutputLine(context, data.Output.ToJson()); + } + + void DscAdminSettingsResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + AdminSettingsFunctionData data{ json }; + + data.ParseSettings(); + + bool inDesiredState = data.Test(); + if (!inDesiredState) + { + Workflow::EnsureRunningAsAdmin(context); + + if (context.IsTerminated()) + { + return; + } + + data.Set(); + } + + data.Get(); + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson(inDesiredState)); + } + } + + void DscAdminSettingsResource::ResourceFunctionTest(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + AdminSettingsFunctionData data{ json }; + + data.ParseSettings(); + + data.Get(); + data.Output.InDesiredState(data.Test()); + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson(data.Output.InDesiredState().value())); + } + } + + void DscAdminSettingsResource::ResourceFunctionExport(Execution::Context& context) const + { + ResourceFunctionGet(context); + } + + void DscAdminSettingsResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, AdminSettingsResourceObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.h b/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.h new file mode 100644 index 0000000000..0ad9330cb1 --- /dev/null +++ b/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.h @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A resource for managing admin settings. + struct DscAdminSettingsResource : public DscCommandBase + { + DscAdminSettingsResource(std::string_view parent); + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionTest(Execution::Context& context) const override; + void ResourceFunctionExport(Execution::Context& context) const override; + void ResourceFunctionSchema(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscCommand.cpp b/src/AppInstallerCLICore/Commands/DscCommand.cpp index a0e9a57673..3b88245e83 100644 --- a/src/AppInstallerCLICore/Commands/DscCommand.cpp +++ b/src/AppInstallerCLICore/Commands/DscCommand.cpp @@ -5,6 +5,7 @@ #include "DscPackageResource.h" #include "DscUserSettingsFileResource.h" #include "DscSourceResource.h" +#include "DscAdminSettingsResource.h" #ifndef AICLI_DISABLE_TEST_HOOKS #include "DscTestFileResource.h" @@ -33,6 +34,7 @@ namespace AppInstaller::CLI std::make_unique(FullName()), std::make_unique(FullName()), std::make_unique(FullName()), + std::make_unique(FullName()), #ifndef AICLI_DISABLE_TEST_HOOKS std::make_unique(FullName()), std::make_unique(FullName()), diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h index 3b3fa7ddc5..45b55dc011 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -162,7 +162,7 @@ namespace AppInstaller::CLI static void FromJson(Derived* self, const Json::Value* value, bool ignoreFieldRequirements) { - if (value) + if (value && !value->isNull()) { self->m_value = GetJsonTypeValue::Get(*value); } diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index 70620f5bca..e27b69e26c 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -162,7 +162,8 @@ namespace AppInstaller::CLI std::vector SettingsResetCommand::GetArguments() const { return { - Argument { Execution::Args::Type::SettingName, Resource::String::SettingNameArgumentDescription, ArgumentType::Positional, true }, + Argument { Execution::Args::Type::SettingName, Resource::String::SettingNameArgumentDescription, ArgumentType::Positional }, + Argument { Execution::Args::Type::All, Resource::String::ResetAllAdminSettingsArgumentDescription, ArgumentType::Flag }, }; } @@ -183,6 +184,21 @@ namespace AppInstaller::CLI void SettingsResetCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const { + if (execArgs.Contains(Execution::Args::Type::All)) + { + if (execArgs.Contains(Execution::Args::Type::SettingName)) + { + throw CommandException(Resource::String::MultipleExclusiveArgumentsProvided("all|setting"_liv)); + } + + return; + } + + if (!execArgs.Contains(Execution::Args::Type::SettingName)) + { + throw CommandException(Resource::String::RequiredArgError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name)); + } + // Get admin setting string for all available options except Unknown. // We accept both bool and string settings std::vector adminSettingList; @@ -208,6 +224,6 @@ namespace AppInstaller::CLI { context << Workflow::EnsureRunningAsAdmin << - Workflow::ResetAdminSetting; + (context.Args.Contains(Execution::Args::Type::All) ? Workflow::ResetAllAdminSettings : Workflow::ResetAdminSetting); } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 4d01bf8705..1b651a3219 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -223,6 +223,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(Downloading); WINGET_DEFINE_RESOURCE_STRINGID(DscCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(DscCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionGet); @@ -239,6 +241,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionExist); WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionInDesiredState); WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAcceptAgreements); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAdminSettingsSettings); WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageId); WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageSource); WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageVersion); @@ -571,6 +574,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ReservedFilenameError); WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingFailed); WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsSucceeded); WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdArgumentDescription); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp index 39051083aa..280570275b 100644 --- a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -52,6 +52,7 @@ namespace AppInstaller::CLI::Workflow constexpr std::wstring_view s_UnitType_WinGetPackage_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Package"; constexpr std::wstring_view s_UnitType_WinGetSource_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Source"; constexpr std::wstring_view s_UnitType_WinGetUserSettingsFile_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/UserSettingsFile"; + constexpr std::wstring_view s_UnitType_WinGetAdminSettings_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/AdminSettings"; constexpr std::wstring_view s_UnitType_PowerShellModuleGet = L"PowerShellGet/PSModule"; constexpr std::wstring_view s_Module_WinGetClient = L"Microsoft.WinGet.DSC"; @@ -86,7 +87,7 @@ namespace AppInstaller::CLI::Workflow std::vector PredefinedResourcesForExport() { return { - { {}, { { s_UnitType_WinGetUserSettingsFile_DSCv3 } } }, + { {}, { { s_UnitType_WinGetUserSettingsFile_DSCv3 }, { s_UnitType_WinGetAdminSettings_DSCv3, true } } }, { L"Microsoft.Windows.Settings", { { L"Microsoft.Windows.Settings/WindowsSettings", true } } }, }; } diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp b/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp index 49eca405cd..f730d5208d 100644 --- a/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp @@ -113,6 +113,12 @@ namespace AppInstaller::CLI::Workflow } } + void ResetAllAdminSettings(Execution::Context& context) + { + Settings::ResetAllAdminSettings(); + context.Reporter.Info() << Resource::String::ResetAllAdminSettingsSucceeded << std::endl; + } + void OpenUserSetting(Execution::Context& context) { // Show warnings only when the setting command is executed. diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.h b/src/AppInstallerCLICore/Workflows/SettingsFlow.h index 50998295a5..c937e88adf 100644 --- a/src/AppInstallerCLICore/Workflows/SettingsFlow.h +++ b/src/AppInstallerCLICore/Workflows/SettingsFlow.h @@ -29,6 +29,12 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void ResetAdminSetting(Execution::Context& context); + // Resets all admin settings to the default. + // Required Args: None + // Inputs: None + // Outputs: None + void ResetAllAdminSettings(Execution::Context& context); + // Opens the user settings. // Required Args: None // Inputs: None diff --git a/src/AppInstallerCLIE2ETests/DSCv3AdminSettingsResourceCommand.cs b/src/AppInstallerCLIE2ETests/DSCv3AdminSettingsResourceCommand.cs new file mode 100644 index 0000000000..4f547f94cb --- /dev/null +++ b/src/AppInstallerCLIE2ETests/DSCv3AdminSettingsResourceCommand.cs @@ -0,0 +1,372 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.Collections.Generic; + using System.Text.Json; + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `Configure` command tests. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public class DSCv3AdminSettingsResourceCommand : DSCv3ResourceTestBase + { + private const string AdminSettingsResource = "admin-settings"; + private const string SettingsPropertyName = "settings"; + + // Bool settings + private const string BypassCertificatePinningForMicrosoftStore = "BypassCertificatePinningForMicrosoftStore"; + private const string InstallerHashOverride = "InstallerHashOverride"; + private const string LocalArchiveMalwareScanOverride = "LocalArchiveMalwareScanOverride"; + private const string LocalManifestFiles = "LocalManifestFiles"; + private const string ProxyCommandLineOptions = "ProxyCommandLineOptions"; + + // String settings + private const string DefaultProxy = "DefaultProxy"; + + // Not a setting + private const string NotAnAdminSettingName = "NotAnAdminSetting"; + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("dsc3", true); + EnsureTestResourcePresence(); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + WinGetSettingsHelper.ConfigureFeature("dsc3", false); + ResetAllSettings(); + GroupPolicyHelper.DeleteExistingPolicies(); + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + ResetAllSettings(); + GroupPolicyHelper.DeleteExistingPolicies(); + } + + /// + /// Calls `get` on the `admin-settings` resource with the value not present. + /// + /// The resource function to invoke. + [TestCase(GetFunction)] + [TestCase(ExportFunction)] + public void AdminSettings_Get(string function) + { + var result = RunDSCv3Command(AdminSettingsResource, function, null); + AssertSuccessfulResourceRun(ref result); + + AdminSettingsResourceData output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsNotNull(output.Settings); + Assert.IsTrue(output.Settings.ContainsKey(LocalManifestFiles)); + Assert.IsFalse(output.Settings.ContainsKey(DefaultProxy)); + Assert.IsFalse(output.Settings.ContainsKey(NotAnAdminSettingName)); + } + + /// + /// Calls `test` on the `admin-settings` resource with a bool setting. + /// + /// The setting to test. + [TestCase(BypassCertificatePinningForMicrosoftStore)] + [TestCase(InstallerHashOverride)] + [TestCase(LocalArchiveMalwareScanOverride)] + [TestCase(LocalManifestFiles)] + [TestCase(ProxyCommandLineOptions)] + public void AdminSettings_Test_BoolSetting(string settingName) + { + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + + resourceData.Settings[settingName] = true; + var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfBoolSetting(ref result, settingName, false, true); + + resourceData.Settings[settingName] = false; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfBoolSetting(ref result, settingName, false, false); + + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {settingName}").ExitCode); + + resourceData.Settings[settingName] = false; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfBoolSetting(ref result, settingName, true, false); + + resourceData.Settings[settingName] = true; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfBoolSetting(ref result, settingName, true, true); + } + + /// + /// Calls `test` on the `admin-settings` resource with a string setting. + /// + /// The setting to test. + [TestCase(DefaultProxy)] + public void AdminSettings_Test_StringSetting(string settingName) + { + const string testValue = "A string to test"; + const string differentTestValue = "A different value"; + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + + resourceData.Settings[settingName] = null; + var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, null, null); + + resourceData.Settings[settingName] = testValue; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, null, testValue); + + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings set", $"{settingName} \"{testValue}\"").ExitCode); + + resourceData.Settings[settingName] = null; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, testValue, null); + + resourceData.Settings[settingName] = testValue; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, testValue, testValue); + + resourceData.Settings[settingName] = differentTestValue; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, testValue, differentTestValue); + } + + /// + /// Calls `test` on the `admin-settings` resource with a complex input. + /// + [Test] + public void AdminSettings_Test_Complex() + { + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {LocalArchiveMalwareScanOverride}").ExitCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {LocalManifestFiles}").ExitCode); + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + resourceData.Settings[LocalArchiveMalwareScanOverride] = true; + resourceData.Settings[BypassCertificatePinningForMicrosoftStore] = false; + resourceData.Settings[DefaultProxy] = null; + + var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsTrue(output.InDesiredState); + Assert.IsNotNull(output.Settings); + + AssertDiffState(diff, []); + } + + /// + /// Calls `set` on the `admin-settings` resource with a bool setting. + /// + /// The setting to test. + [TestCase(BypassCertificatePinningForMicrosoftStore)] + [TestCase(InstallerHashOverride)] + [TestCase(LocalArchiveMalwareScanOverride)] + [TestCase(LocalManifestFiles)] + [TestCase(ProxyCommandLineOptions)] + public void AdminSettings_Set_BoolSetting(string settingName) + { + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + + resourceData.Settings[settingName] = true; + var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfBoolSetting(ref result, settingName, false, true); + + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfBoolSetting(ref result, settingName, true, true); + + resourceData.Settings[settingName] = false; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfBoolSetting(ref result, settingName, true, false); + + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfBoolSetting(ref result, settingName, false, false); + } + + /// + /// Calls `set` on the `admin-settings` resource with a string setting. + /// + /// The setting to test. + [TestCase(DefaultProxy)] + public void AdminSettings_Set_StringSetting(string settingName) + { + const string testValue = "A string to test"; + const string differentTestValue = "A different value"; + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + + resourceData.Settings[settingName] = null; + var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, null, null); + + resourceData.Settings[settingName] = testValue; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, null, testValue); + + resourceData.Settings[settingName] = testValue; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, testValue, testValue); + + resourceData.Settings[settingName] = differentTestValue; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, testValue, differentTestValue); + + resourceData.Settings[settingName] = null; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, differentTestValue, null); + } + + /// + /// Calls `set` on the `admin-settings` resource with a complex input. + /// + [Test] + public void AdminSettings_Set_Complex() + { + const string testValue = "A string to test"; + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + resourceData.Settings[LocalArchiveMalwareScanOverride] = true; + resourceData.Settings[BypassCertificatePinningForMicrosoftStore] = false; + resourceData.Settings[DefaultProxy] = testValue; + + var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsNotNull(output.Settings); + Assert.AreEqual(JsonValueKind.True, output.Settings[LocalArchiveMalwareScanOverride].AsValue().GetValueKind()); + Assert.AreEqual(JsonValueKind.False, output.Settings[BypassCertificatePinningForMicrosoftStore].AsValue().GetValueKind()); + Assert.AreEqual(testValue, output.Settings[DefaultProxy].AsValue().GetValue()); + + AssertDiffState(diff, [ SettingsPropertyName ]); + } + + /// + /// Calls `set` on the `admin-settings` resource attempting to change a setting with group policy enabled. + /// + [Test] + public void AdminSettings_Set_GroupPolicyBlocked() + { + GroupPolicyHelper.EnableHashOverride.Disable(); + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + resourceData.Settings[InstallerHashOverride] = true; + + var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + + private static void ResetAllSettings() + { + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings reset", "--all").ExitCode); + } + + private static void AssertTestOfBoolSetting(ref TestCommon.RunCommandResult result, string settingName, bool expectedState, bool testState) + { + AssertSuccessfulResourceRun(ref result); + + bool inDesiredState = expectedState == testState; + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.AreEqual(inDesiredState, output.InDesiredState); + Assert.IsNotNull(output.Settings); + Assert.IsTrue(output.Settings.ContainsKey(settingName)); + Assert.AreEqual(expectedState ? JsonValueKind.True : JsonValueKind.False, output.Settings[settingName].AsValue().GetValueKind()); + + AssertDiffState(diff, inDesiredState ? [] : [ SettingsPropertyName ]); + } + + private static void AssertSetOfBoolSetting(ref TestCommon.RunCommandResult result, string settingName, bool previousState, bool desiredState) + { + AssertSuccessfulResourceRun(ref result); + + bool inDesiredState = previousState == desiredState; + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsNotNull(output.Settings); + Assert.IsTrue(output.Settings.ContainsKey(settingName)); + Assert.AreEqual(desiredState ? JsonValueKind.True : JsonValueKind.False, output.Settings[settingName].AsValue().GetValueKind()); + + AssertDiffState(diff, inDesiredState ? [] : [ SettingsPropertyName ]); + } + + private static void AssertTestOfStringSetting(ref TestCommon.RunCommandResult result, string settingName, string expectedState, string testState) + { + AssertSuccessfulResourceRun(ref result); + + bool inDesiredState = expectedState == testState; + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.AreEqual(inDesiredState, output.InDesiredState); + Assert.IsNotNull(output.Settings); + if (expectedState != null) + { + Assert.IsTrue(output.Settings.ContainsKey(settingName)); + Assert.AreEqual(expectedState, output.Settings[settingName].AsValue().GetValue()); + } + else + { + Assert.IsFalse(output.Settings.ContainsKey(settingName)); + } + + AssertDiffState(diff, inDesiredState ? [] : [SettingsPropertyName]); + } + + private static void AssertSetOfStringSetting(ref TestCommon.RunCommandResult result, string settingName, string previousState, string desiredState) + { + AssertSuccessfulResourceRun(ref result); + + bool inDesiredState = previousState == desiredState; + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsNotNull(output.Settings); + if (desiredState != null) + { + Assert.IsTrue(output.Settings.ContainsKey(settingName)); + Assert.AreEqual(desiredState, output.Settings[settingName].AsValue().GetValue()); + } + else + { + Assert.IsFalse(output.Settings.ContainsKey(settingName)); + } + + AssertDiffState(diff, inDesiredState ? [] : [SettingsPropertyName]); + } + + private class AdminSettingsResourceData + { + [JsonPropertyName(InDesiredStatePropertyName)] + public bool? InDesiredState { get; set; } + + public JsonObject Settings { get; set; } + } + } +} diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index c53c35b31a..530fe634f2 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3355,4 +3355,19 @@ Please specify one of them using the --source option to proceed. Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through --processor-path argument. {Locked="dsc.exe","--processor-path"} + + An object containing the administrator settings and their values. + + + Manage administrator settings + + + Allows management of administrator settings via the DSC v3 command line interface protocol. See the help link for details. + + + Resets all admin settings + + + All admin settings reset. + diff --git a/src/AppInstallerCommonCore/AdminSettings.cpp b/src/AppInstallerCommonCore/AdminSettings.cpp index 89a23d64ea..6d2d8634c1 100644 --- a/src/AppInstallerCommonCore/AdminSettings.cpp +++ b/src/AppInstallerCommonCore/AdminSettings.cpp @@ -60,6 +60,8 @@ namespace AppInstaller::Settings bool GetAdminSettingValue(BoolAdminSetting setting) const; std::optional GetAdminSettingValue(StringAdminSetting setting) const; + void Reset(); + private: void LoadAdminSettings(); [[nodiscard]] bool SaveAdminSettings(); @@ -171,6 +173,11 @@ namespace AppInstaller::Settings } } + void AdminSettingsInternal::Reset() + { + m_settingStream.Remove(); + } + void AdminSettingsInternal::LoadAdminSettings() { auto stream = m_settingStream.Get(); @@ -406,6 +413,11 @@ namespace AppInstaller::Settings return true; } + void ResetAllAdminSettings() + { + AdminSettingsInternal{}.Reset(); + } + std::optional GetAdminSetting(StringAdminSetting setting) { // Check for a policy that overrides this setting. diff --git a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h index d46fafdf01..b36b19789e 100644 --- a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h @@ -38,6 +38,7 @@ namespace AppInstaller::Settings bool DisableAdminSetting(BoolAdminSetting setting); bool SetAdminSetting(StringAdminSetting setting, std::string_view value); bool ResetAdminSetting(StringAdminSetting setting); + void ResetAllAdminSettings(); bool IsAdminSettingEnabled(BoolAdminSetting setting); std::optional GetAdminSetting(StringAdminSetting setting);