diff --git a/schemas/JSON/settings/settings.export.schema.0.1.json b/schemas/JSON/settings/settings.export.schema.0.1.json new file mode 100644 index 0000000000..17d0e8d5e4 --- /dev/null +++ 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 index ebf081e4df..c9864bc1f6 100644 --- 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 index 3b8e112d2d..cb1aa953d0 100644 --- 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 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> SettingsCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + }); + } + + std::vector 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 index 2bd2aeeb18..112b41105d 100644 --- 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> GetCommands() const override; std::vector 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 index 1122c02259..7b17b3737a 100644 --- 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 index 59bc361c6c..f6fba40a0b 100644 --- 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; + + // Skip Unknown. + for (AdminSetting_t i = 1 + static_cast(AdminSetting::Unknown); i < static_cast(AdminSetting::Max); ++i) + { + exportSettingsJson.AddAdminSetting(static_cast(i)); + } + + context.Reporter.Info() << exportSettingsJson.ToJsonString() << std::endl; + } } diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.h b/src/AppInstallerCLICore/Workflows/SettingsFlow.h index 578ff34b98..e40a0051c9 100644 --- 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 index 73044e944f..4306966168 100644 --- 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. Block from updating until the pin is removed, preventing override arguments + + Export settings as JSON + + + Export settings + + + User Settings + \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestCommon.cpp b/src/AppInstallerCLITests/TestCommon.cpp index f2520865dc..f0728d4de6 100644 --- 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 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 index 31bb57ad26..77358d4dd3 100644 --- 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 index f971306326..c08073cfd4 100644 --- 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 index 0a0be3f741..328fa5c20f 100644 --- a/src/AppInstallerCLITests/pch.h +++ b/src/AppInstallerCLITests/pch.h @@ -12,6 +12,8 @@ #include +#include + #include #include #include diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h index 451a2fc33b..336b749fbc 100644 --- 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 index 67301c722c..d172f497d7 100644 --- 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 index e7a8429502..cd4f730b4a 100644 --- 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); }