Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/AppInstallerCLI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,7 @@ Global
CertificateResources\CertificateResources.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
COMServer\COMServer.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
PureLib\PureLib.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4
CertificateResources\CertificateResources.vcxitems*{5eb88068-5fb9-4e69-89b2-72dbc5e068f9}*SharedItemsImports = 4
binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ namespace AppInstaller::CLI
Command(name, aliases, parent, visibility, Settings::ExperimentalFeature::Feature::None) {}
Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature) :
Command(name, {}, parent, Command::Visibility::Show, feature) {}
Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature, CommandOutputFlags outputFlags) :
Command(name, {}, parent, Command::Visibility::Show, feature, Settings::TogglePolicy::Policy::None, outputFlags) {}
Command(std::string_view name, std::vector<std::string_view> aliases, std::string_view parent, Settings::ExperimentalFeature::Feature feature) :
Command(name, aliases, parent, Command::Visibility::Show, feature) {}
Command(std::string_view name, std::vector<std::string_view> aliases, std::string_view parent, Settings::TogglePolicy::Policy groupPolicy) :
Expand Down
54 changes: 53 additions & 1 deletion src/AppInstallerCLICore/Commands/DscCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,22 @@

namespace AppInstaller::CLI
{
namespace
{
Argument GetOutputFileArgument()
{
return { Execution::Args::Type::OutputFile, Resource::String::OutputDirectoryArgumentDescription, ArgumentType::Standard };
}
}

DscCommand::DscCommand(std::string_view parent) :
Command(StaticName(), parent)
{
}

std::vector<std::unique_ptr<Command>> DscCommand::GetCommands() const
{
// These should all derive from DscCommandBase
return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({
std::make_unique<DscPackageResource>(FullName()),
std::make_unique<DscSourceResource>(FullName()),
Expand All @@ -26,6 +40,16 @@ namespace AppInstaller::CLI
});
}

std::vector<Argument> DscCommand::GetArguments() const
{
std::vector<Argument> result;

result.emplace_back(Execution::Args::Type::DscResourceFunctionManifest, Resource::String::DscResourceFunctionDescriptionManifest, ArgumentType::Flag);
result.emplace_back(GetOutputFileArgument());

return result;
}

Resource::LocString DscCommand::ShortDescription() const
{
return { Resource::String::DscCommandShortDescription };
Expand All @@ -43,6 +67,34 @@ namespace AppInstaller::CLI

void DscCommand::ExecuteInternal(Execution::Context& context) const
{
OutputHelp(context.Reporter);
if (context.Args.Contains(Execution::Args::Type::DscResourceFunctionManifest))
{
std::filesystem::path outputDirectory{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) };
std::filesystem::create_directories(outputDirectory);

std::string filePrefix = Utility::ToLower(DscCommandBase::ModuleName());

for (const auto& command : GetCommands())
{
DscCommandBase* commandBase = static_cast<DscCommandBase*>(command.get());

std::filesystem::path outputPath = outputDirectory;
outputPath /= std::string{ filePrefix }.append(".").append(commandBase->Name()).append(".dsc.resource.json");
commandBase->WriteManifest(context, outputPath);
}
}
else
{
OutputHelp(context.Reporter);
}
}

void DscCommand::ValidateArgumentsInternal(Execution::Args& args) const
{
if (args.Contains(Execution::Args::Type::DscResourceFunctionManifest) &&
!args.Contains(Execution::Args::Type::OutputFile))
{
throw CommandException(Resource::String::RequiredArgError(GetOutputFileArgument().Name()));
}
}
}
4 changes: 3 additions & 1 deletion src/AppInstallerCLICore/Commands/DscCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ namespace AppInstaller::CLI
{
struct DscCommand final : public Command
{
DscCommand(std::string_view parent) : Command(StaticName(), parent, Settings::ExperimentalFeature::Feature::ConfigurationDSCv3) {}
DscCommand(std::string_view parent);

static constexpr std::string_view StaticName() { return "dscv3"sv; };

std::vector<std::unique_ptr<Command>> GetCommands() const override;
std::vector<Argument> GetArguments() const override;

Resource::LocString ShortDescription() const override;
Resource::LocString LongDescription() const override;
Expand All @@ -21,5 +22,6 @@ namespace AppInstaller::CLI

protected:
void ExecuteInternal(Execution::Context& context) const override;
void ValidateArgumentsInternal(Execution::Args& args) const override;
};
}
22 changes: 15 additions & 7 deletions src/AppInstallerCLICore/Commands/DscCommandBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ namespace AppInstaller::CLI
{
namespace
{
constexpr std::string_view s_WingetModuleName = "Microsoft.WinGet"sv;

std::string GetFunctionManifestString(DscFunctions function)
{
THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function));
Expand Down Expand Up @@ -167,7 +165,7 @@ namespace AppInstaller::CLI
}

DscCommandBase::DscCommandBase(std::string_view parent, std::string_view resourceName, DscResourceKind kind, DscFunctions functions, DscFunctionModifiers modifiers) :
Command(resourceName, parent, CommandOutputFlags::IgnoreSettingsWarnings), m_kind(kind), m_functions(functions), m_modifiers(modifiers)
Command(resourceName, parent, Settings::ExperimentalFeature::Feature::ConfigurationDSCv3, CommandOutputFlags::IgnoreSettingsWarnings), m_kind(kind), m_functions(functions), m_modifiers(modifiers)
{
// Limits on current implementation
THROW_HR_IF(E_NOTIMPL, kind != DscResourceKind::Resource);
Expand Down Expand Up @@ -224,14 +222,14 @@ namespace AppInstaller::CLI

WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_METHOD);

void DscCommandBase::ResourceFunctionManifest(Execution::Context& context) const
void DscCommandBase::WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const
{
Json::Value json{ Json::ValueType::objectValue };

// TODO: Move to release schema when released (there should be an aka.ms link as well, but it wasn't active yet)
//json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/resource/manifest.json";
json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json";
json["type"] = std::string{ s_WingetModuleName } + '/' + ResourceType();
json["type"] = std::string{ ModuleName() } + '/' + ResourceType();
json["description"] = LongDescription().get();
json["version"] = Runtime::GetClientVersion().get();

Expand All @@ -253,9 +251,9 @@ namespace AppInstaller::CLI
writerBuilder.settings_["indentation"] = " ";
std::string jsonString = Json::writeString(writerBuilder, json);

if (context.Args.Contains(Execution::Args::Type::OutputFile))
if (!filePath.empty())
{
std::ofstream stream{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)), std::ios::binary };
std::ofstream stream{ filePath, std::ios::binary };
stream.write(jsonString.c_str(), jsonString.length());
}
else
Expand All @@ -264,6 +262,16 @@ namespace AppInstaller::CLI
}
}

void DscCommandBase::ResourceFunctionManifest(Execution::Context& context) const
{
std::filesystem::path path;
if (context.Args.Contains(Execution::Args::Type::OutputFile))
{
path = std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) };
}
WriteManifest(context, path);
}

#undef WINGET_DSC_FUNCTION_METHOD

std::optional<Json::Value> DscCommandBase::GetJsonFromInput(Execution::Context& context, bool terminateContextOnError) const
Expand Down
17 changes: 17 additions & 0 deletions src/AppInstallerCLICore/Commands/DscCommandBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
#include <json/json.h>
#include <optional>

#ifndef AICLI_DISABLE_TEST_HOOKS
#define WINGET_DSCV3_MODULE_NAME "Microsoft.WinGet.Dev"
#define WINGET_DSCV3_MODULE_NAME_WIDE L"Microsoft.WinGet.Dev"
#else
#define WINGET_DSCV3_MODULE_NAME "Microsoft.WinGet"
#define WINGET_DSCV3_MODULE_NAME_WIDE L"Microsoft.WinGet"
#endif

namespace AppInstaller::CLI
{
// The kind of resource that this command is implementing.
Expand Down Expand Up @@ -75,6 +83,15 @@ namespace AppInstaller::CLI

Utility::LocIndView HelpLink() const override;

static constexpr std::string_view ModuleName()
{
return WINGET_DSCV3_MODULE_NAME;
}

// Writes the manifest for the command to the file path.
// If the path is empty, writes the manifest to the output stream.
void WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const;

protected:
void ExecuteInternal(Execution::Context& context) const override;

Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatchHelp);
WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoSourceDefined);
WINGET_DEFINE_RESOURCE_STRINGID(Options);
WINGET_DEFINE_RESOURCE_STRINGID(OutputDirectoryArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(OutputFileArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(OverwritingExistingFileAtMessage);
Expand Down
44 changes: 31 additions & 13 deletions src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "Public/ConfigurationSetProcessorFactoryRemoting.h"
#include "ConfigurationCommon.h"
#include "ConfigurationWingetDscModuleUnitValidation.h"
#include "Commands/DscCommandBase.h"
#include <AppInstallerDateTime.h>
#include <AppInstallerDownloader.h>
#include <AppInstallerErrors.h>
Expand Down Expand Up @@ -47,8 +48,9 @@ namespace AppInstaller::CLI::Workflow
constexpr std::wstring_view s_Unit_WinGetPackage = L"WinGetPackage";
constexpr std::wstring_view s_Unit_WinGetSource = L"WinGetSource";

constexpr std::wstring_view s_UnitType_WinGetPackage_DSCv3 = L"Microsoft.WinGet/Package";
constexpr std::wstring_view s_UnitType_WinGetSource_DSCv3 = L"Microsoft.WinGet/Source";
constexpr std::wstring_view s_UnitType_WinGetPackage_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Package";
constexpr std::wstring_view s_UnitType_WinGetSource_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Source";
constexpr std::wstring_view s_UnitType_WinGetUserSettingsFile_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/UserSettingsFile";
constexpr std::wstring_view s_UnitType_PowerShellModuleGet = L"PowerShellGet/PSModule";

constexpr std::wstring_view s_Module_WinGetClient = L"Microsoft.WinGet.DSC";
Expand All @@ -66,19 +68,33 @@ namespace AppInstaller::CLI::Workflow
struct PredefinedResource
{
// RequiredModule could be empty, meaning no required modules needed.
std::wstring RequiredModule;
std::wstring_view RequiredModule;

std::vector<std::wstring> UnitTypes;
std::vector<std::wstring_view> UnitTypes;
};

static const PredefinedResource s_PredefinedResourcesForExport[] = {
{ std::wstring{ s_Module_WinGetClient }, { L"Microsoft.WinGet.DSC/WinGetUserSettings" } },
{ L"Microsoft.Windows.Developer", { L"Microsoft.Windows.Developer/DeveloperMode", L"Microsoft.Windows.Developer/EnableDarkMode", L"Microsoft.Windows.Developer/ShowSecondsInClock", L"Microsoft.Windows.Developer/Taskbar", L"Microsoft.Windows.Developer/WindowsExplorer" }},
};
std::vector<PredefinedResource> PredefinedResourcesForExport()
{
return {
{ {}, { s_UnitType_WinGetUserSettingsFile_DSCv3 } },
{ L"Microsoft.Windows.Developer", { L"Microsoft.Windows.Developer/DeveloperMode", L"Microsoft.Windows.Developer/EnableDarkMode", L"Microsoft.Windows.Developer/ShowSecondsInClock", L"Microsoft.Windows.Developer/Taskbar", L"Microsoft.Windows.Developer/WindowsExplorer" } },
};
}

static const std::wstring s_PackageSettingsExclusionList[] = {
L"Microsoft.WinGet/", L"Microsoft.DSC.Debug/", L"Microsoft.DSC/", L"Microsoft.DSC.Transitional/", L"Microsoft.Windows/RebootPending",
L"Microsoft.Windows/Registry", L"Microsoft.Windows/WMI", L"Microsoft.Windows/WindowsPowerShell", L"Microsoft/OSInfo"
std::vector<std::wstring_view> PackageSettingsExclusionList()
{
return {
L"Microsoft.WinGet/",
L"Microsoft.WinGet.Dev/",
L"Microsoft.DSC.Debug/",
L"Microsoft.DSC/",
L"Microsoft.DSC.Transitional/",
L"Microsoft.Windows/RebootPending",
L"Microsoft.Windows/Registry",
L"Microsoft.Windows/WMI",
L"Microsoft.Windows/WindowsPowerShell",
L"Microsoft/OSInfo"
};
};

Logging::Level ConvertLevel(DiagnosticLevel level)
Expand Down Expand Up @@ -1475,7 +1491,7 @@ namespace AppInstaller::CLI::Workflow
{
ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();

for (const auto& resources : s_PredefinedResourcesForExport)
for (const auto& resources : PredefinedResourcesForExport())
{
std::optional<ConfigurationUnit> requiredModuleUnit;

Expand Down Expand Up @@ -1538,11 +1554,13 @@ namespace AppInstaller::CLI::Workflow
context.Reporter.Warn() << Resource::String::ConfigurationExportFailedToGetUnitProcessors << std::endl;
}

auto exclusionList = PackageSettingsExclusionList();

// Filter out processors in exclusion list.
for (auto itr = unitProcessors.begin(); itr != unitProcessors.end(); /* itr incremented in the logic */)
{
bool processorRemoved = false;
for (const auto& exclusionItem : anon::s_PackageSettingsExclusionList)
for (const auto& exclusionItem : exclusionList)
{
if (Utility::CaseInsensitiveStartsWith(itr->UnitType(), exclusionItem))
{
Expand Down
7 changes: 3 additions & 4 deletions src/AppInstallerCLIE2ETests/ConfigureCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ public class ConfigureCommand
/// </summary>
public static void EnsureTestResourcePresence()
{
DSCv3ResourceTestBase.EnsureTestResourcePresence("test-file");
DSCv3ResourceTestBase.EnsureTestResourcePresence("test-json");
DSCv3ResourceTestBase.EnsureTestResourcePresence();
}

/// <summary>
Expand Down Expand Up @@ -330,12 +329,12 @@ public void DSCv3_Export()
var exportDir = TestCommon.GetRandomTestDir();
var exportFile = Path.Combine(exportDir, "exported.yml");

result = TestCommon.RunAICLICommand("test config-export-units", $"-o {exportFile} --resource Microsoft.WinGet/TestJSON --verbose");
result = TestCommon.RunAICLICommand("test config-export-units", $"-o {exportFile} --resource Microsoft.WinGet.Dev/TestJSON --verbose");
Assert.AreEqual(0, result.ExitCode);

Assert.True(File.Exists(exportFile));
string exportText = File.ReadAllText(exportFile);
Assert.True(exportText.Contains("Microsoft.WinGet/TestJSON"));
Assert.True(exportText.Contains("Microsoft.WinGet.Dev/TestJSON"));
Assert.True(exportText.Contains(propertyName1));
Assert.True(exportText.Contains(propertyName2));
Assert.True(exportText.Contains(propertyValue1));
Expand Down
Loading