winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit c9572d13d94032fe01444c91bef504ff348ec248
parent bba4994c747dee8b6d558bfb3d7e5677b2188c9d
Author: KEINOS <github+fork-qiita-news@keinos.com>
Date:   Fri, 13 Jun 2025 17:26:05 +0000

Merge remote-tracking branch 'upstream/master'

Diffstat:
Mazure-pipelines.yml | 6------
Msrc/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj | 6------
Msrc/AppInstallerCLIE2ETests/ConfigureCommand.cs | 24++++++++++++++++++++++++
Msrc/AppInstallerCLIE2ETests/ConfigureExportCommand.cs | 3+++
Asrc/AppInstallerCLIE2ETests/TestData/Configuration/RunCommandOnSet.yml | 15+++++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp | 29++++++++++++++++++++++++-----
7 files changed, 127 insertions(+), 26 deletions(-)

diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -357,12 +357,6 @@ jobs: displayName: Clean up Sysinternals PsTools condition: succeededOrFailed() - # Install required DSC modules until export all command can handle auto acquisition - - pwsh: | - Install-Module -Name Microsoft.Windows.Settings -AllowPrerelease -Force - displayName: Install Required DSC Modules for Tests - condition: succeededOrFailed() - - task: PowerShell@2 displayName: Run Unit Tests Packaged inputs: diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -53,7 +53,6 @@ namespace AppInstaller::CLI::Workflow 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_WinGetAdminSettings_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/AdminSettings"; - constexpr std::wstring_view s_UnitType_PowerShellModuleGet = L"PowerShellGet/PSModule"; constexpr std::wstring_view s_Module_WinGetClient = L"Microsoft.WinGet.DSC"; @@ -65,7 +64,8 @@ namespace AppInstaller::CLI::Workflow constexpr std::wstring_view s_Setting_WinGetSource_Arg = L"argument"; constexpr std::wstring_view s_Setting_WinGetSource_Type = L"type"; - constexpr std::wstring_view s_Setting_PowerShellGet_ModuleName = L"name"; + constexpr std::wstring_view s_Predefined_PowerShell_PackageId = L"Microsoft.PowerShell"; + constexpr std::wstring_view s_Predefined_PowerShell_PackageSource = L"winget"; struct PredefinedResourceInfo { @@ -1246,17 +1246,58 @@ namespace AppInstaller::CLI::Workflow return unit; } - ConfigurationUnit CreatePowerShellModuleGetUnit(const std::wstring& moduleName) + ConfigurationUnit CreatePowerShellPackageUnit() { - ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(s_UnitType_PowerShellModuleGet, Utility::ConvertToUTF8(moduleName)); + ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(s_UnitType_WinGetPackage_DSCv3, "Microsoft.PowerShell"); ValueSet settings; - settings.Insert(s_Setting_PowerShellGet_ModuleName, PropertyValue::CreateString(moduleName)); + settings.Insert(s_Setting_WinGetPackage_Id, PropertyValue::CreateString(s_Predefined_PowerShell_PackageId)); + settings.Insert(s_Setting_WinGetPackage_Source, PropertyValue::CreateString(s_Predefined_PowerShell_PackageSource)); unit.Settings(settings); return unit; } + ValueSet CreateValueSetFromStringVector(const std::vector<std::wstring>& values) + { + ValueSet result; + size_t index = 0; + + for (const auto& value : values) + { + std::wostringstream strstr; + strstr << index++; + result.Insert(strstr.str(), PropertyValue::CreateString(value)); + } + + result.Insert(L"treatAsArray", PropertyValue::CreateBoolean(true)); + return result; + } + + // TODO: This is a workaround unit to ensure v2 dsc resource modules. Move to dsc v3 resource when available. + ConfigurationUnit CreateRequiredModuleUnit(std::wstring_view moduleName, const ConfigurationUnit& dependentUnit) + { + std::wstring moduleNameString{ moduleName }; + + ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(L"Microsoft.DSC.Transitional/RunCommandOnSet", Utility::ConvertToUTF8(moduleName)); + + ValueSet settings; + settings.Insert(L"executable", PropertyValue::CreateString(L"pwsh")); + std::vector<std::wstring> arguments = + { + L"-NoProfile", + L"-NoLogo", + L"-Command", + L"if (-not (Get-Module -ListAvailable -Name " + moduleNameString + L")) { Install-Module -Name " + moduleNameString + L" -Confirm:$False -Force -AllowPrerelease -AllowClobber }" + }; + settings.Insert(L"arguments", CreateValueSetFromStringVector(arguments)); + unit.Settings(settings); + + unit.Dependencies().Append(dependentUnit.Identifier()); + + return unit; + } + std::wstring GetWinGetSourceUnitType(const ConfigurationContext& configContext) { Utility::Version schemaVersion = { Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) }; @@ -1536,15 +1577,27 @@ namespace AppInstaller::CLI::Workflow { ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>(); + // PowerShell package needs to be present for certain predefined modules to work. + ConfigurationUnit powerShellPackageUnit = CreatePowerShellPackageUnit(); + configContext.Set().Units().Append(powerShellPackageUnit); + + // Apply the unit to make sure it's on the system. + context.Reporter.Info() << Resource::String::ConfigurationExportInstallRequiredModule(Utility::LocIndView{ "Microsoft PowerShell Package" }) << std::endl; + auto applyPowerShellResult = ApplyUnit(context, powerShellPackageUnit); + if (FAILED(applyPowerShellResult.ResultInformation().ResultCode())) + { + AICLI_LOG(Config, Warning, << "Failed to ensure module. [Microsoft PowerShell Package] Related settings may not be exported."); + LogFailedGetConfigurationUnitDetails(powerShellPackageUnit, applyPowerShellResult.ResultInformation()); + context.Reporter.Warn() << Resource::String::ConfigurationExportInstallRequiredModuleFailed << std::endl; + } + for (const auto& resources : PredefinedResourcesForExport()) { std::optional<ConfigurationUnit> requiredModuleUnit; - /* The PowershellGet/PSModule does not work under dsc v3 adaptor yet. - * Uncomment if still applicable after the issue is fixed. if (!resources.RequiredModule.empty()) { - requiredModuleUnit = CreatePowerShellModuleGetUnit(resources.RequiredModule); + requiredModuleUnit = CreateRequiredModuleUnit(resources.RequiredModule, powerShellPackageUnit); // Apply the unit to make sure it's on the system. context.Reporter.Info() << Resource::String::ConfigurationExportInstallRequiredModule(Utility::LocIndView{ Utility::ConvertToUTF8(resources.RequiredModule) }) << std::endl; @@ -1561,7 +1614,6 @@ namespace AppInstaller::CLI::Workflow continue; } } - */ for (const auto& resourceInfo : resources.ResourceInfos) { diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -49,12 +49,6 @@ </ItemGroup> <ItemGroup> - <None Remove="TestData\Configuration\ShowDetails_TestRepo_0_3.yml" /> - <None Remove="TestData\Configuration\WithParameters_0_3.yml" /> - <None Remove="TestData\empty" /> - <None Remove="TestData\Manifests\TestUpgradeAddsDependency.1.0.yaml" /> - <None Remove="TestData\Manifests\TestUpgradeAddsDependency.2.0.yaml" /> - <None Remove="TestData\Manifests\TestUpgradeAddsDependencyDependent.1.0.yaml" /> <Content Include="..\..\doc\admx\DesktopAppInstaller.admx" Link="TestData\DesktopAppInstaller.admx"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> diff --git a/src/AppInstallerCLIE2ETests/ConfigureCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureCommand.cs @@ -390,6 +390,30 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(0, result.ExitCode); } + /// <summary> + /// RunCommandOnSet test. + /// </summary> + [Test] + public void RunCommandOnSetResourceTest() + { + var testDir = TestCommon.GetRandomTestDir(); + var testConfigFile = Path.Combine(testDir, "RunCommandOnSet.yml"); + File.Copy(TestCommon.GetTestDataFile("Configuration\\RunCommandOnSet.yml"), testConfigFile); + + var content = File.ReadAllText(testConfigFile); + content = content.Replace("<PathToBeReplaced>", testDir); + File.WriteAllText(testConfigFile, content); + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, testConfigFile, timeOut: 300000); + Assert.AreEqual(0, result.ExitCode); + + // Verify test file created. + string targetFilePath = Path.Combine(testDir, "TestFile.txt"); + FileAssert.Exists(targetFilePath); + string testContent = File.ReadAllText(targetFilePath); + Assert.True(testContent.Contains("TestContent")); + } + private void DeleteResourceArtifacts() { // Delete all .txt files in the test directory; they are placed there by the tests diff --git a/src/AppInstallerCLIE2ETests/ConfigureExportCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureExportCommand.cs @@ -154,7 +154,10 @@ namespace AppInstallerCLIE2ETests var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}", timeOut: 1200000); Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); + Assert.True(showResult.StdOut.Contains("Microsoft.PowerShell")); + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/UserSettingsFile")); + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/AdminSettings")); Assert.True(showResult.StdOut.Contains("Microsoft.Windows.Settings/WindowsSettings")); Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/RunCommandOnSet.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/RunCommandOnSet.yml @@ -0,0 +1,15 @@ +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: Test RunCommandOnSet + type: Microsoft.DSC.Transitional/RunCommandOnSet + properties: + executable: pwsh + arguments: + - -NoProfile + - -NoLogo + - -Command + - | + Set-Content -Path <PathToBeReplaced>\TestFile.txt -Value 'TestContent' diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp @@ -13,6 +13,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation { namespace { + constexpr std::wstring_view s_ResourceType_RunCommandOnSet = L"Microsoft.DSC.Transitional/RunCommandOnSet"; + std::string GetNormalizedIdentifier(hstring identifier) { using namespace AppInstaller::Utility; @@ -33,6 +35,17 @@ namespace winrt::Microsoft::Management::Configuration::implementation { return intent == ConfigurationUnitIntent::Apply || intent == ConfigurationUnitIntent::Unknown; } + + // Check if a unit should always be applied. No TestSettings is needed. + bool ShouldApplyAlways(const Configuration::ConfigurationUnit& unit) + { + if (AppInstaller::Utility::CaseInsensitiveEquals(s_ResourceType_RunCommandOnSet, unit.Type())) + { + return true; + } + + return false; + } } ConfigurationSetApplyProcessor::ConfigurationSetApplyProcessor( @@ -454,14 +467,15 @@ namespace winrt::Microsoft::Management::Configuration::implementation } else { - ITestSettingsResult testSettingsResult = unitProcessor.TestSettings(); + ITestSettingsResult testSettingsResult = nullptr; + bool applyAlways = ShouldApplyAlways(unitProcessor.Unit()); - if (testSettingsResult.TestResult() == ConfigurationTestResult::Positive) + if (!applyAlways) { - unitInfo.Result->PreviouslyInDesiredState(true); - result = true; + testSettingsResult = unitProcessor.TestSettings(); } - else if (testSettingsResult.TestResult() == ConfigurationTestResult::Negative) + + if (applyAlways || testSettingsResult.TestResult() == ConfigurationTestResult::Negative) { // Just in case testing took a while, check for cancellation before moving on to applying m_progress.ThrowIfCancelled(); @@ -477,6 +491,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation unitInfo.ResultInformation->Initialize(applySettingsResult.ResultInformation()); } } + else if (testSettingsResult.TestResult() == ConfigurationTestResult::Positive) + { + unitInfo.Result->PreviouslyInDesiredState(true); + result = true; + } else if (testSettingsResult.TestResult() == ConfigurationTestResult::Failed) { unitInfo.ResultInformation->Initialize(testSettingsResult.ResultInformation());