Skip to content

Commit

Permalink
Export command for winget settings. (#2719)
Browse files Browse the repository at this point in the history
  • Loading branch information
msftrubengu authored Dec 13, 2022
1 parent 92d91ad commit 050056a
Show file tree
Hide file tree
Showing 15 changed files with 262 additions and 10 deletions.
43 changes: 43 additions & 0 deletions schemas/JSON/settings/settings.export.schema.0.1.json
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Commands/RootCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
43 changes: 36 additions & 7 deletions src/AppInstallerCLICore/Commands/SettingsCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}
14 changes: 14 additions & 0 deletions src/AppInstallerCLICore/Commands/SettingsCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
};
}
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
43 changes: 43 additions & 0 deletions src/AppInstallerCLICore/Workflows/SettingsFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/Workflows/SettingsFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
9 changes: 9 additions & 0 deletions src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
Expand Up @@ -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>
33 changes: 33 additions & 0 deletions src/AppInstallerCLITests/TestCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
6 changes: 6 additions & 0 deletions src/AppInstallerCLITests/TestCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
46 changes: 45 additions & 1 deletion src/AppInstallerCLITests/WorkFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

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);
}
}
2 changes: 2 additions & 0 deletions src/AppInstallerCLITests/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCommonCore/Public/AppInstallerRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/AppInstallerCommonCore/Public/winget/AdminSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 050056a

Please sign in to comment.