commit 050056a6ceb46dcd62127da112255dceed8114f1 parent 92d91adfdd8b2051247fe689f24146159274b46f Author: Ruben Guerrero <rubengu@microsoft.com> Date: Mon, 12 Dec 2022 22:11:52 -0800 Export command for winget settings. (#2719) Diffstat:
15 files changed, 262 insertions(+), 11 deletions(-)
diff --git a/schemas/JSON/settings/settings.export.schema.0.1.json b/schemas/JSON/settings/settings.export.schema.0.1.json @@ -0,0 +1,43 @@ +{ + "$id": "https://aka.ms/winget-settings-export.schema.json", + "$schema": "https://json-schema.org/draft/2019-09/schema#", + "title": "Microsoft's Windows Package Manager Settings Export Schema", + "definitions": { + "AdminSettings": { + "description": "Administrator settings", + "type": "object", + "properties": { + "BypassCertificatePinningForMicrosoftStore": { + "description": "Bypass Certificate Pinning For Microsoft Store", + "type": "boolean", + "default": false + }, + "LocalManifestFiles": { + "description": "Enable installing local manifests.", + "type": "boolean", + "default": false + } + } + }, + "UserSettingsFile": { + "description": "Path for the winget's user settings file.", + "type": "string", + "maxLength": 32767 + } + }, + "allOf": [ + { + "properties": { + "adminSettings": { "$ref": "#/definitions/AdminSettings" } + }, + "additionalItems": true + }, + { + "properties": { + "userSettingsFile": { "$ref": "#/definitions/UserSettingsFile" } + }, + "additionalItems": true + } + ], + "additionalProperties": true +} diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -193,6 +193,7 @@ namespace AppInstaller::CLI }; info << std::endl << Resource::String::Logs << ": "_liv << Runtime::GetPathTo(Runtime::PathName::DefaultLogLocationForDisplay).u8string() << std::endl; + info << std::endl << Resource::String::UserSettings << ": "_liv << Runtime::GetPathTo(Runtime::PathName::UserSettingsFileLocationForDisplay).u8string() << std::endl; info << std::endl; diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -16,14 +16,22 @@ namespace AppInstaller::CLI constexpr Utility::LocIndView s_ArgumentName_Enable = "enable"_liv; constexpr Utility::LocIndView s_ArgumentName_Disable = "disable"_liv; constexpr Utility::LocIndView s_ArgName_EnableAndDisable = "enable|disable"_liv; + static constexpr std::string_view s_SettingsCommand_HelpLink = "https://aka.ms/winget-settings"sv; } - std::vector<Argument> SettingsCommand::GetArguments() const - { - return { - Argument{ s_ArgumentName_Enable, Argument::NoAlias, Execution::Args::Type::AdminSettingEnable, Resource::String::AdminSettingEnableDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument{ s_ArgumentName_Disable, Argument::NoAlias, Execution::Args::Type::AdminSettingDisable, Resource::String::AdminSettingDisableDescription, ArgumentType::Standard, Argument::Visibility::Help }, - }; + std::vector<std::unique_ptr<Command>> SettingsCommand::GetCommands() const + { + return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({ + std::make_unique<SettingsExportCommand>(FullName()), + }); + } + + std::vector<Argument> SettingsCommand::GetArguments() const + { + return { + Argument{ s_ArgumentName_Enable, Argument::NoAlias, Execution::Args::Type::AdminSettingEnable, Resource::String::AdminSettingEnableDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ s_ArgumentName_Disable, Argument::NoAlias, Execution::Args::Type::AdminSettingDisable, Resource::String::AdminSettingDisableDescription, ArgumentType::Standard, Argument::Visibility::Help }, + }; } Resource::LocString SettingsCommand::ShortDescription() const @@ -38,7 +46,7 @@ namespace AppInstaller::CLI std::string SettingsCommand::HelpLink() const { - return "https://aka.ms/winget-settings"; + return std::string{ s_SettingsCommand_HelpLink }; } void SettingsCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const @@ -79,4 +87,25 @@ namespace AppInstaller::CLI context << Workflow::OpenUserSetting; } } + + Resource::LocString SettingsExportCommand::ShortDescription() const + { + return { Resource::String::SettingsExportCommandShortDescription }; + } + + Resource::LocString SettingsExportCommand::LongDescription() const + { + return { Resource::String::SettingsExportCommandLongDescription }; + } + + std::string SettingsExportCommand::HelpLink() const + { + return std::string{ s_SettingsCommand_HelpLink }; + } + + void SettingsExportCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::ExportSettings; + } } diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.h b/src/AppInstallerCLICore/Commands/SettingsCommand.h @@ -9,6 +9,7 @@ namespace AppInstaller::CLI { SettingsCommand(std::string_view parent) : Command("settings", { "config" }, parent, Settings::TogglePolicy::Policy::Settings) {} + std::vector<std::unique_ptr<Command>> GetCommands() const override; std::vector<Argument> GetArguments() const override; virtual Resource::LocString ShortDescription() const override; @@ -20,4 +21,17 @@ namespace AppInstaller::CLI void ValidateArgumentsInternal(Execution::Args& execArgs) const override; void ExecuteInternal(Execution::Context& context) const override; }; + + struct SettingsExportCommand final : public Command + { + SettingsExportCommand(std::string_view parent) : Command("export", parent) {} + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -296,6 +296,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningField); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningValue); @@ -421,6 +423,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); WINGET_DEFINE_RESOURCE_STRINGID(Usage); + WINGET_DEFINE_RESOURCE_STRINGID(UserSettings); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandReportDependencies); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandShortDescription); diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp b/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp @@ -12,6 +12,35 @@ namespace AppInstaller::CLI::Workflow using namespace AppInstaller::Settings; using namespace AppInstaller::Utility; + namespace + { + struct ExportSettingsJson + { + ExportSettingsJson() + { + root["$schema"] = "https://aka.ms/winget-settings-export.schema.json"; + root["adminSettings"] = Json::ValueType::objectValue; + root["userSettingsFile"] = Runtime::GetPathTo(Runtime::PathName::UserSettingsFileLocation).u8string(); + } + + void AddAdminSetting(AdminSetting setting) + { + auto str = std::string{ Settings::AdminSettingToString(setting) }; + root["adminSettings"][str] = Settings::IsAdminSettingEnabled(setting); + } + + std::string ToJsonString() const + { + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = ""; + return Json::writeString(writerBuilder, root); + } + + private: + Json::Value root{ Json::ValueType::objectValue }; + }; + } + void EnableAdminSetting(Execution::Context& context) { std::string_view adminSettingString = context.Args.GetArg(Execution::Args::Type::AdminSettingEnable); @@ -99,4 +128,18 @@ namespace AppInstaller::CLI::Workflow ShellExecuteW(nullptr, nullptr, L"notepad", filePathUTF16.c_str(), nullptr, SW_SHOW); } } + + void ExportSettings(Execution::Context& context) + { + ExportSettingsJson exportSettingsJson; + using AdminSetting_t = std::underlying_type_t<AdminSetting>; + + // Skip Unknown. + for (AdminSetting_t i = 1 + static_cast<AdminSetting_t>(AdminSetting::Unknown); i < static_cast<AdminSetting_t>(AdminSetting::Max); ++i) + { + exportSettingsJson.AddAdminSetting(static_cast<AdminSetting>(i)); + } + + context.Reporter.Info() << exportSettingsJson.ToJsonString() << std::endl; + } } diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.h b/src/AppInstallerCLICore/Workflows/SettingsFlow.h @@ -22,4 +22,10 @@ namespace AppInstaller::CLI::Workflow // Inputs: None // Outputs: None void OpenUserSetting(Execution::Context& context); + + // Lists the state of settings. + // Required Args: None + // Inputs: None + // Outputs: None + void ExportSettings(Execution::Context& context); } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1526,4 +1526,13 @@ Please specify one of them using the --source option to proceed.</value> <data name="PinAddBlockingArgumentDescription" xml:space="preserve"> <value>Block from updating until the pin is removed, preventing override arguments</value> </data> + <data name="SettingsExportCommandLongDescription" xml:space="preserve"> + <value>Export settings as JSON</value> + </data> + <data name="SettingsExportCommandShortDescription" xml:space="preserve"> + <value>Export settings</value> + </data> + <data name="UserSettings" xml:space="preserve"> + <value>User Settings</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestCommon.cpp b/src/AppInstallerCLITests/TestCommon.cpp @@ -326,4 +326,37 @@ namespace TestCommon return AppInstaller::Msix::GetPackageReader(stream.Get(), &packageReader) && SUCCEEDED(packageReader->GetManifest(manifestReader)); } + + std::string RemoveConsoleFormat(const std::string& str) + { + // We are looking something that starts with "\x1b[0m" + if (!str.empty() && str[0] == '\x1b') + { + // Find first m + auto pos = str.find("m"); + if (pos != std::string::npos) + { + return str.substr(pos + 1); + } + } + + return str; + } + + Json::Value ConvertToJson(const std::string& content) + { + auto contentClean = RemoveConsoleFormat(content); + + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); + std::string error; + + if (!reader->parse(contentClean.c_str(), contentClean.c_str() + contentClean.size(), &root, &error)) + { + throw error; + } + + return root; + } } diff --git a/src/AppInstallerCLITests/TestCommon.h b/src/AppInstallerCLITests/TestCommon.h @@ -145,4 +145,10 @@ namespace TestCommon // Get manifest reader from a msix file path bool GetMsixPackageManifestReader(std::string_view testFileName, IAppxManifestReader** manifestReader); + + // Removes console format + std::string RemoveConsoleFormat(const std::string& str); + + // Convert to Json::Value + Json::Value ConvertToJson(const std::string& content); } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -3924,4 +3924,48 @@ TEST_CASE("InstallFlow_FoundInstalledAndUpgradeNotAvailable", "[UpdateFlow][work REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UpdateNotApplicable).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); -}- \ No newline at end of file +} + +TEST_CASE("Export_Settings", "[Settings][workflow]") +{ + RemoveSetting(Stream::AdminSettings); + + { + // No admin settings, local manifest should be false. + std::ostringstream exportOutput; + TestContext context{ exportOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + SettingsExportCommand settingsExportCommand({}); + settingsExportCommand.Execute(context); + + auto json = ConvertToJson(exportOutput.str()); + REQUIRE(!json.isNull()); + REQUIRE_FALSE(json["adminSettings"]["LocalManifestFiles"].asBool()); + + auto userSettingsFileValue = std::string(json["userSettingsFile"].asCString()); + REQUIRE(userSettingsFileValue.find("settings.json") != std::string::npos); + } + + { + // Enable local manifest and verify export works. + std::ostringstream settingsOutput; + TestContext context{ settingsOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::AdminSettingEnable, "LocalManifestFiles"sv); + context.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); + SettingsCommand settings({}); + settings.Execute(context); + + std::ostringstream exportOutput; + TestContext context2{ exportOutput, std::cin }; + auto previousThreadGlobals2 = context2.SetForCurrentThread(); + SettingsExportCommand settingsExportCommand({}); + settingsExportCommand.Execute(context2); + auto json = ConvertToJson(exportOutput.str()); + REQUIRE(!json.isNull()); + REQUIRE(json["adminSettings"]["LocalManifestFiles"].asBool()); + + auto userSettingsFileValue = std::string(json["userSettingsFile"].asCString()); + REQUIRE(userSettingsFileValue.find("settings.json") != std::string::npos); + } +} diff --git a/src/AppInstallerCLITests/pch.h b/src/AppInstallerCLITests/pch.h @@ -12,6 +12,8 @@ #include <catch.hpp> +#include <json/json.h> + #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Globalization.h> diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -62,6 +62,10 @@ namespace AppInstaller::Runtime PortableLinksUserLocation, // The location where symlinks to portable packages are stored under machine scope. PortableLinksMachineLocation, + // The location of the user settings json file. + UserSettingsFileLocation, + // The location of the user settings json file, anonymized using environment variables. + UserSettingsFileLocationForDisplay, }; // The principal that an ACE applies to. diff --git a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h @@ -9,9 +9,10 @@ namespace AppInstaller::Settings // Enum of admin settings. enum class AdminSetting { - Unknown, + Unknown = 0, LocalManifestFiles, BypassCertificatePinningForMicrosoftStore, + Max, }; AdminSetting StringToAdminSetting(std::string_view in); diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp @@ -23,7 +23,7 @@ namespace AppInstaller::Runtime constexpr std::string_view s_DefaultTempDirectory = "WinGet"sv; constexpr std::string_view s_AppDataDir_Settings = "Settings"sv; constexpr std::string_view s_AppDataDir_State = "State"sv; - constexpr std::string_view s_SecureSettings_Base = "Microsoft/WinGet"sv; + constexpr std::string_view s_SecureSettings_Base = "Microsoft\\WinGet"sv; constexpr std::string_view s_SecureSettings_UserRelative = "settings"sv; constexpr std::string_view s_SecureSettings_Relative_Unpackaged = "win"sv; constexpr std::string_view s_PortablePackageUserRoot_Base = "Microsoft"sv; @@ -547,6 +547,13 @@ namespace AppInstaller::Runtime case PathName::PortableLinksMachineLocation: result = GetPathDetailsCommon(path); break; + case PathName::UserSettingsFileLocation: + result.Path = UserSettings::SettingsFilePath(); + break; + case PathName::UserSettingsFileLocationForDisplay: + result.Path = UserSettings::SettingsFilePath(); + ReplaceCommonPathPrefix(result.Path, GetKnownFolderPath(FOLDERID_LocalAppData), "%LOCALAPPDATA%"); + break; default: THROW_HR(E_UNEXPECTED); } @@ -617,6 +624,13 @@ namespace AppInstaller::Runtime case PathName::PortableLinksMachineLocation: result = GetPathDetailsCommon(path); break; + case PathName::UserSettingsFileLocation: + result.Path = UserSettings::SettingsFilePath(); + break; + case PathName::UserSettingsFileLocationForDisplay: + result.Path = UserSettings::SettingsFilePath(); + ReplaceCommonPathPrefix(result.Path, GetKnownFolderPath(FOLDERID_LocalAppData), "%LOCALAPPDATA%"); + break; default: THROW_HR(E_UNEXPECTED); }