winget-cli

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

commit f97417f2ea850757d0a3337be877ac35f371a9ea
parent 39d132beb39980c957aa9c760b8734ac7fe5ea92
Author: Ryan <69221034+ryfu-msft@users.noreply.github.com>
Date:   Tue, 26 Nov 2024 15:40:30 -0500

Add default module root setting for configuration (#4974)


Diffstat:
Mdoc/Settings.md | 15+++++++++++++++
Mschemas/JSON/settings/settings.schema.0.2.json | 11+++++++++++
Msrc/AppInstallerCLICore/ConfigurationCommon.cpp | 11+++++++++--
Msrc/AppInstallerCLIE2ETests/ConfigureCommand.cs | 25+++++++++++++++++++++++++
Msrc/AppInstallerCLIE2ETests/Constants.cs | 1+
Msrc/AppInstallerCLIE2ETests/ErrorCommand.cs | 14+++++++++++---
Msrc/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs | 20++++++++++++++++++++
Msrc/AppInstallerCLITests/UserSettings.cpp | 15+++++++++++++++
Msrc/AppInstallerCommonCore/Public/winget/UserSettings.h | 4++++
Msrc/AppInstallerCommonCore/UserSettings.cpp | 5+++++
10 files changed, 116 insertions(+), 5 deletions(-)

diff --git a/doc/Settings.md b/doc/Settings.md @@ -200,6 +200,21 @@ The `purgePortablePackage` behavior affects the default behavior for uninstallin }, ``` +## Configure Behavior + +The `configureBehavior` settings affect the default behavior of applying a configuration. + +### Default Module Root +The `defaultModuleRoot` behavior affects the default root directory where modules are installed to. Defaults to `%LOCALAPPDATA%/Microsoft/WinGet/Configuration/Modules` if value is not set or is invalid. + +> Note: This setting value must be an absolute path. + +```json + "configureBehavior": { + "defaultModuleRoot": "C:/Program Files/Modules/" + }, +``` + ## Telemetry The `telemetry` settings control whether winget writes ETW events that may be sent to Microsoft on a default installation of Windows. diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json @@ -198,6 +198,17 @@ } } }, + "ConfigureBehavior": { + "description": "Configure settings", + "type": "object", + "properties": { + "defaultModuleRoot": { + "description": "The default root directory where PowerShell modules are installed to when applying a configuration.", + "type": "string", + "maxLength": 32767 + } + } + }, "DownloadBehavior": { "description": "Download settings", "type": "object", diff --git a/src/AppInstallerCLICore/ConfigurationCommon.cpp b/src/AppInstallerCLICore/ConfigurationCommon.cpp @@ -21,7 +21,7 @@ namespace AppInstaller::CLI struct ModulePathInfo { SetProcessorFactory::PwshConfigurationProcessorLocation location; - std::optional<std::string_view> customLocation; + std::optional<std::string> customLocation; }; ModulePathInfo GetModulePathInfo(Execution::Args& execArgs) @@ -44,10 +44,17 @@ namespace AppInstaller::CLI } else { - return { SetProcessorFactory::PwshConfigurationProcessorLocation::Custom, execArgs.GetArg(Execution::Args::Type::ConfigurationModulePath) }; + return { SetProcessorFactory::PwshConfigurationProcessorLocation::Custom, std::string(execArgs.GetArg(Execution::Args::Type::ConfigurationModulePath)) }; } } + std::filesystem::path defaultModuleRoot = Settings::User().Get<Settings::Setting::ConfigureDefaultModuleRoot>(); + + if (!defaultModuleRoot.empty()) + { + return { SetProcessorFactory::PwshConfigurationProcessorLocation::Custom, defaultModuleRoot.u8string() }; + } + return { SetProcessorFactory::PwshConfigurationProcessorLocation::WinGetModulePath, {} }; } } diff --git a/src/AppInstallerCLIE2ETests/ConfigureCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureCommand.cs @@ -77,6 +77,31 @@ namespace AppInstallerCLIE2ETests } /// <summary> + /// Simple test to confirm that the module was installed to the location specified in the DefaultModuleRoot settings. + /// </summary> + [Test] + public void ConfigureFromTestRepo_DefaultModuleRootSetting() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + string moduleTestDir = TestCommon.GetRandomTestDir(); + WinGetSettingsHelper.ConfigureConfigureBehavior(Constants.DefaultModuleRoot, moduleTestDir); + + string args = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo_Location.yml"); + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, args); + + WinGetSettingsHelper.ConfigureConfigureBehavior(Constants.DefaultModuleRoot, string.Empty); + bool moduleExists = Directory.Exists(Path.Combine(moduleTestDir, Constants.SimpleTestModuleName)); + if (moduleExists) + { + // Clean test directory to avoid impacting other tests. + Directory.Delete(moduleTestDir, true); + } + + Assert.AreEqual(0, result.ExitCode); + Assert.True(moduleExists); + } + + /// <summary> /// Simple test to confirm that the module was installed in the right location. /// </summary> /// <param name="location">Location to pass.</param> diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -125,6 +125,7 @@ namespace AppInstallerCLIE2ETests public const string PortablePackageMachineRoot = "portablePackageMachineRoot"; public const string InstallBehaviorScope = "scope"; public const string InstallerTypes = "installerTypes"; + public const string DefaultModuleRoot = "defaultModuleRoot"; // Configuration public const string PSGalleryName = "PSGallery"; diff --git a/src/AppInstallerCLIE2ETests/ErrorCommand.cs b/src/AppInstallerCLIE2ETests/ErrorCommand.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ErrorCommand.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -15,6 +15,15 @@ namespace AppInstallerCLIE2ETests public class ErrorCommand { /// <summary> + /// Reset settings file to avoid affecting output from error command. + /// </summary> + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.InitializeWingetSettings(); + } + + /// <summary> /// Tests 0. /// </summary> [Test] @@ -127,4 +136,4 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE")); } } -}- \ No newline at end of file +} diff --git a/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs b/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs @@ -73,6 +73,12 @@ namespace AppInstallerCLIE2ETests.Helpers { } }, + { + "configureBehavior", + new Hashtable() + { + } + }, }; // Run winget one time to initialize settings directory @@ -109,6 +115,20 @@ namespace AppInstallerCLIE2ETests.Helpers } /// <summary> + /// Configure the configuration behavior. + /// </summary> + /// <param name="settingName">Setting name.</param> + /// <param name="value">Setting value.</param> + public static void ConfigureConfigureBehavior(string settingName, string value) + { + JObject settingsJson = GetJsonSettingsObject("configureBehavior"); + var configureBehavior = settingsJson["configureBehavior"]; + configureBehavior[settingName] = value; + + SetWingetSettings(settingsJson); + } + + /// <summary> /// Configure the install behavior preferences. /// </summary> /// <param name="settingName">Setting name.</param> diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp @@ -526,6 +526,21 @@ TEST_CASE("SettingsDownloadDefaultDirectory", "[settings]") } } +TEST_CASE("SettingsConfigureDefaultModuleRoot", "[settings]") +{ + auto again = DeleteUserSettingsFiles(); + + SECTION("Valid path") + { + std::string_view json = R"({ "configureBehavior": { "defaultModuleRoot": "C:/Foo/Bar" } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get<Setting::ConfigureDefaultModuleRoot>() == "C:/Foo/Bar"); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } +} + TEST_CASE("SettingsArchiveExtractionMethod", "[settings]") { auto again = DeleteUserSettingsFiles(); diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -108,6 +108,8 @@ namespace AppInstaller::Settings UninstallPurgePortablePackage, // Download behavior DownloadDefaultDirectory, + // Configure behavior + ConfigureDefaultModuleRoot, // Interactivity InteractivityDisable, #ifndef AICLI_DISABLE_TEST_HOOKS @@ -185,6 +187,8 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortablePackage, bool, bool, false, ".uninstallBehavior.purgePortablePackage"sv); // Download behavior SETTINGMAPPING_SPECIALIZATION(Setting::DownloadDefaultDirectory, std::string, std::filesystem::path, {}, ".downloadBehavior.defaultDownloadDirectory"sv); + // Configure behavior + SETTINGMAPPING_SPECIALIZATION(Setting::ConfigureDefaultModuleRoot, std::string, std::filesystem::path, {}, ".configureBehavior.defaultModuleRoot"sv); // Network SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDownloader, std::string, InstallerDownloader, InstallerDownloader::Default, ".network.downloader"sv); diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp @@ -401,6 +401,11 @@ namespace AppInstaller::Settings return ValidatePathValue(value); } + WINGET_VALIDATE_SIGNATURE(ConfigureDefaultModuleRoot) + { + return ValidatePathValue(value); + } + WINGET_VALIDATE_SIGNATURE(NetworkDownloader) { static constexpr std::string_view s_downloader_default = "default";