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:
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";