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
6 changes: 0 additions & 6 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -371,12 +371,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:
Expand Down
70 changes: 61 additions & 9 deletions src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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
{
Expand Down Expand Up @@ -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()) };
Expand Down Expand Up @@ -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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always apply it without testing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the wingetpackage dsc resource, it tests it (in the resource implementation) before applying. So I'm just applying it to save 2 calls.

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;
Expand All @@ -1561,7 +1614,6 @@ namespace AppInstaller::CLI::Workflow
continue;
}
}
*/

for (const auto& resourceInfo : resources.ResourceInfos)
{
Expand Down
6 changes: 0 additions & 6 deletions src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
24 changes: 24 additions & 0 deletions src/AppInstallerCLIE2ETests/ConfigureCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,30 @@ public void ConfigureFindUnitProcessors()
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
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLIE2ETests/ConfigureExportCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ public void ExportAll()
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"));
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand Down Expand Up @@ -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();
Expand All @@ -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());
Expand Down
Loading