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:
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());