diff --git a/src/Microsoft.Management.Configuration.Processor/Constants/PowerShellConstants.cs b/src/Microsoft.Management.Configuration.Processor/Constants/PowerShellConstants.cs index 0b8a07a31c..58b90b3464 100644 --- a/src/Microsoft.Management.Configuration.Processor/Constants/PowerShellConstants.cs +++ b/src/Microsoft.Management.Configuration.Processor/Constants/PowerShellConstants.cs @@ -26,6 +26,7 @@ internal static class Modules public const string PSDesiredStateConfigurationMinVersion = "2.0.6"; public const string PowerShellGet = "PowerShellGet"; public const string PowerShellGetMinVersion = "2.2.5"; + public const string PSDesiredStateConfigurationMaxVersion = "2.*"; } internal static class Commands diff --git a/src/Microsoft.Management.Configuration.Processor/DscModules/DscModuleV2.cs b/src/Microsoft.Management.Configuration.Processor/DscModules/DscModuleV2.cs index 3632686efd..ccec1eca65 100644 --- a/src/Microsoft.Management.Configuration.Processor/DscModules/DscModuleV2.cs +++ b/src/Microsoft.Management.Configuration.Processor/DscModules/DscModuleV2.cs @@ -11,8 +11,10 @@ namespace Microsoft.Management.Configuration.Processor.DscModule using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation; + using System.Text; using Microsoft.Management.Configuration.Processor.DscResourcesInfo; using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Extensions; using Microsoft.Management.Configuration.Processor.Helpers; using Microsoft.PowerShell.Commands; using Windows.Foundation.Collections; @@ -39,7 +41,8 @@ public DscModuleV2() { this.ModuleSpecification = PowerShellHelpers.CreateModuleSpecification( Modules.PSDesiredStateConfiguration, - minVersion: Modules.PSDesiredStateConfigurationMinVersion); + minVersion: Modules.PSDesiredStateConfigurationMinVersion, + maxVersion: Modules.PSDesiredStateConfigurationMaxVersion); } /// @@ -95,9 +98,15 @@ public ValueSet InvokeGetResource( var getResult = pwsh.AddCommand(this.InvokeDscResourceCmd) .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) .AddParameter(Parameters.Method, DscMethods.Get) - .Invoke() + .InvokeAndStopOnError() .FirstOrDefault(); + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceGetException(name, moduleSpecification, errorMessage); + } + if (getResult is null) { throw new InvokeDscResourceGetException(name, moduleSpecification); @@ -132,9 +141,15 @@ public bool InvokeTestResource( dynamic? testResult = pwsh.AddCommand(this.InvokeDscResourceCmd) .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) .AddParameter(Parameters.Method, DscMethods.Test) - .Invoke() + .InvokeAndStopOnError() .FirstOrDefault(); + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceTestException(name, moduleSpecification, errorMessage); + } + if (testResult is null || !TypeHelpers.PropertyWithTypeExists(testResult, InDesiredState)) { @@ -156,9 +171,15 @@ public bool InvokeSetResource( dynamic? setResult = pwsh.AddCommand(this.InvokeDscResourceCmd) .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) .AddParameter(Parameters.Method, DscMethods.Set) - .Invoke() + .InvokeAndStopOnError() .FirstOrDefault(); + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceSetException(name, moduleSpecification, errorMessage); + } + if (setResult is null || !TypeHelpers.PropertyWithTypeExists(setResult, RebootRequired)) { @@ -173,11 +194,7 @@ private static Dictionary PrepareInvokeParameters( ValueSet settings, ModuleSpecification? moduleSpecification) { - var properties = new Hashtable(); - foreach (var setting in settings) - { - properties.Add(setting.Key, setting.Value); - } + Hashtable properties = settings.ToHashtable(); var parameters = new Dictionary() { diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/ImportModuleException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/ImportModuleException.cs index 4fba2d1821..05c8f541a0 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/ImportModuleException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/ImportModuleException.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------------- -// +// // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // // ----------------------------------------------------------------------------- diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceGetException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceGetException.cs index 9d867ef41f..fd57d0ef99 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceGetException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceGetException.cs @@ -20,7 +20,21 @@ internal class InvokeDscResourceGetException : Exception /// Resource name. /// Optional module. public InvokeDscResourceGetException(string resourceName, ModuleSpecification? module) - : base($"Failed when calling `Get` for resource: {resourceName} [{module?.ToString() ?? ""}]") + : base(CreateMessage(resourceName, module, null)) + { + this.HResult = ErrorCodes.WinGetConfigUnitInvokeGet; + this.ResourceName = resourceName; + this.Module = module; + } + + /// + /// Initializes a new instance of the class. + /// + /// Resource name. + /// Optional module. + /// Message. + public InvokeDscResourceGetException(string resourceName, ModuleSpecification? module, string message) + : base(CreateMessage(resourceName, module, message)) { this.HResult = ErrorCodes.WinGetConfigUnitInvokeGet; this.ResourceName = resourceName; @@ -36,5 +50,16 @@ public InvokeDscResourceGetException(string resourceName, ModuleSpecification? m /// Gets the module, if any. /// public ModuleSpecification? Module { get; } + + private static string CreateMessage(string resourceName, ModuleSpecification? module, string? message) + { + string result = $"Failed when calling `Get` for resource: {resourceName} [{module?.ToString() ?? ""}]"; + if (message != null) + { + result += $" Message: '{message}'"; + } + + return result; + } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceSetException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceSetException.cs index e2e97c65cd..5e5f0ff394 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceSetException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceSetException.cs @@ -20,7 +20,21 @@ internal class InvokeDscResourceSetException : Exception /// Resource name. /// Optional module. public InvokeDscResourceSetException(string resourceName, ModuleSpecification? module) - : base($"Failed when calling `Set` for resource: {resourceName} [{module?.ToString() ?? ""}]") + : base(CreateMessage(resourceName, module, null)) + { + this.HResult = ErrorCodes.WinGetConfigUnitInvokeSet; + this.ResourceName = resourceName; + this.Module = module; + } + + /// + /// Initializes a new instance of the class. + /// + /// Resource name. + /// Optional module. + /// Message. + public InvokeDscResourceSetException(string resourceName, ModuleSpecification? module, string message) + : base(CreateMessage(resourceName, module, message)) { this.HResult = ErrorCodes.WinGetConfigUnitInvokeSet; this.ResourceName = resourceName; @@ -36,5 +50,16 @@ public InvokeDscResourceSetException(string resourceName, ModuleSpecification? m /// Gets the module, if any. /// public ModuleSpecification? Module { get; } + + private static string CreateMessage(string resourceName, ModuleSpecification? module, string? message) + { + string result = $"Failed when calling `Set` for resource: {resourceName} [{module?.ToString() ?? ""}]"; + if (message != null) + { + result += $" Message: '{message}'"; + } + + return result; + } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceTestException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceTestException.cs index afc440ea82..d2d7d78e21 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceTestException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceTestException.cs @@ -20,7 +20,21 @@ internal class InvokeDscResourceTestException : Exception /// Resource name. /// Optional module. public InvokeDscResourceTestException(string resourceName, ModuleSpecification? module) - : base($"Failed when calling `Test` for resource: {resourceName} [{module?.ToString() ?? ""}]") + : base(CreateMessage(resourceName, module, null)) + { + this.HResult = ErrorCodes.WinGetConfigUnitInvokeTest; + this.ResourceName = resourceName; + this.Module = module; + } + + /// + /// Initializes a new instance of the class. + /// + /// Resource name. + /// Optional module. + /// Message. + public InvokeDscResourceTestException(string resourceName, ModuleSpecification? module, string message) + : base(CreateMessage(resourceName, module, message)) { this.HResult = ErrorCodes.WinGetConfigUnitInvokeTest; this.ResourceName = resourceName; @@ -36,5 +50,16 @@ public InvokeDscResourceTestException(string resourceName, ModuleSpecification? /// Gets the module, if any. /// public ModuleSpecification? Module { get; } + + private static string CreateMessage(string resourceName, ModuleSpecification? module, string? message) + { + string result = $"Failed when calling `Test` for resource: {resourceName} [{module?.ToString() ?? ""}]"; + if (message != null) + { + result += $" Message: '{message}'"; + } + + return result; + } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/PowerShellExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/PowerShellExtensions.cs index e9f0f223e4..c64da9b92e 100644 --- a/src/Microsoft.Management.Configuration.Processor/Extensions/PowerShellExtensions.cs +++ b/src/Microsoft.Management.Configuration.Processor/Extensions/PowerShellExtensions.cs @@ -9,6 +9,8 @@ namespace Microsoft.Management.Configuration.Processor.Extensions using System.Collections.ObjectModel; using System.Management.Automation; using System.Text; + using Microsoft.Management.Configuration.Processor.Exceptions; + using System.Xml.Linq; using Microsoft.PowerShell.Commands; /// @@ -28,11 +30,7 @@ public static Collection InvokeAndStopOnError(this PowerShell pwsh) ErrorActionPreference = ActionPreference.Stop, }; - var result = pwsh.Invoke(null, settings); - - pwsh.ValidateErrorStream(); - - return result; + return pwsh.Invoke(null, settings); } /// @@ -48,18 +46,15 @@ public static Collection InvokeAndStopOnError(this PowerShell pwsh) ErrorActionPreference = ActionPreference.Stop, }; - var result = pwsh.Invoke(null, settings); - - pwsh.ValidateErrorStream(); - - return result; + return pwsh.Invoke(null, settings); } /// - /// Throws if there are streams in the stream error. + /// Gets the error stream message if any. /// /// PowerShell. - public static void ValidateErrorStream(this PowerShell pwsh) + /// Error message. Null if none. + public static string? GetErrorMessage(this PowerShell pwsh) { if (pwsh.HadErrors) { @@ -69,8 +64,10 @@ public static void ValidateErrorStream(this PowerShell pwsh) psStreamBuilder.AppendLine(line.ToString()); } - throw new WriteErrorException(psStreamBuilder.ToString()); + return psStreamBuilder.ToString(); } + + return null; } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs new file mode 100644 index 0000000000..a516fef489 --- /dev/null +++ b/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Extensions +{ + using System.Collections; + using Windows.Foundation.Collections; + + /// + /// Extensions for ValueSet. + /// + internal static class ValueSetExtensions + { + /// + /// Extension method to transform a ValueSet to a Hashtable. + /// + /// Value set. + /// A hashtable. + public static Hashtable ToHashtable(this ValueSet valueSet) + { + var hashtable = new Hashtable(); + + foreach (var keyValuePair in valueSet) + { + if (keyValuePair.Value is ValueSet) + { + ValueSet innerValueSet = (ValueSet)keyValuePair.Value; + hashtable.Add(keyValuePair.Key, innerValueSet.ToHashtable()); + } + else + { + hashtable.Add(keyValuePair.Key, keyValuePair.Value); + } + } + + return hashtable; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellHelpers.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellHelpers.cs index a9379afec4..0a8cac4d38 100644 --- a/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellHelpers.cs +++ b/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellHelpers.cs @@ -15,6 +15,8 @@ namespace Microsoft.Management.Configuration.Processor.Helpers /// internal static class PowerShellHelpers { + private const string MaxRange = "999999999"; + /// /// Creates a module specification object. /// @@ -56,7 +58,14 @@ minVersion is null && if (!string.IsNullOrEmpty(maxVersion)) { - moduleInfo.Add(Parameters.MaximumVersion, maxVersion); + // For some reason, the constructor of ModuleSpecification that takes + // a hashtable calls ModuleCmdletBase.GetMaximumVersion. This method will + // validate the max version and replace * for 999999999 only if its the last + // char in the string. But then the returned value is not assigned to the + // ModuleSpecification's MaximumVersion property. If we want to set a + // MaximumVersion with a wildcard and pass this to Install-Module it will + // fail with "Cannot convert value 'x.*' to type 'System.Version'." + moduleInfo.Add(Parameters.MaximumVersion, maxVersion.Replace("*", MaxRange)); } if (!string.IsNullOrEmpty(guid)) diff --git a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psd1 b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psd1 index 050fb5be5a..e4d2652725 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psd1 +++ b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psd1 @@ -24,6 +24,7 @@ DscResourcesToExport = @( 'SimpleTestResource' 'SimpleTestResourceThrows' 'SimpleTestResourceError' + 'SimpleTestResourceTypes' ) HelpInfoURI = 'https://www.contoso.com/help' diff --git a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psm1 b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psm1 index bcb6fe0705..893609a4a3 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psm1 +++ b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psm1 @@ -168,4 +168,95 @@ class SimpleTestResourceError { Write-Error "Error in Set" } -} \ No newline at end of file +} + +[DscResource()] +class SimpleTestResourceTypes +{ + [DscProperty(Key)] + [string] $key + + [DscProperty()] + [boolean] $boolProperty + + [DscProperty()] + [int] $intProperty; + + [DscProperty()] + [double] $doubleProperty; + + [DscProperty()] + [char] $charProperty; + + [DscProperty()] + [Hashtable] $hashtableProperty; + + [SimpleTestResourceTypes] Get() + { + $result = @{ + key = "SimpleTestResourceTypesKey" + boolProperty = $false + intProperty = 0 + doubleProperty = 0.0 + charProperty = 'z' + hashtableProperty = @{} + } + return $result + } + + [bool] Test() + { + # Because we can't get the error stream from a class based resource, I throw so is easier to know if + # there's something wrong. + if ($this.boolProperty -ne $true) + { + throw "Failed boolProperty" + } + + if ($this.intProperty -ne 3) + { + throw "Failed intProperty. Got $($this.intProperty)" + } + + if ($this.doubleProperty -ne -9.876) + { + throw "Failed doubleProperty Got $($this.doubleProperty)" + } + + if ($this.charProperty -ne 'f') + { + throw "Failed charProperty Got $($this.charProperty)" + } + + if ($this.hashtableProperty.ContainsKey("secretStringKey")) + { + if ($this.hashtableProperty["secretStringKey"] -ne "secretCode") + { + throw "Failed comparing value of `$hashtableProperty.secretStringKey Got $($this.hashtableProperty["secretStringKey"])" + } + } + else + { + throw "Failed finding secretStringKey in hashtableProperty" + } + + if ($this.hashtableProperty.ContainsKey("secretIntKey")) + { + if ($this.hashtableProperty["secretIntKey"] -ne 123456) + { + throw "Failed comparing value of `$hashtableProperty.secretIntKey Got $($this.hashtableProperty["secretIntKey"])" + } + } + else + { + throw "Failed finding secretIntKey in hashtableProperty" + } + + return $true + } + + [void] Set() + { + # no-op + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs index abfa0b27bb..d1d18b7f71 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs @@ -19,6 +19,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using Microsoft.Management.Configuration.UnitTests.Helpers; using Microsoft.PowerShell.Commands; using Moq; + using Windows.Foundation.Collections; using Windows.Security.Cryptography.Certificates; using Xunit; using Xunit.Abstractions; @@ -642,6 +643,43 @@ public void GetUnitProcessorDetails_Load() Times.Exactly(2)); } + /// + /// This tests uses SimpleTestResourceTypes Test to validate the resource got the correct types + /// from the processor. + /// + [Fact] + public void CreateUnitProcessor_TestTypes() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + var setProcessor = new ConfigurationSetProcessor(processorEnv, new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + UnitName = "SimpleTestResourceTypes", + Intent = ConfigurationUnitIntent.Assert, + }; + + unit.Directives.Add("module", "xSimpleTestResource"); + unit.Directives.Add("version", "0.0.0.1"); + + var hashtableProperty = new ValueSet + { + { "secretStringKey", "secretCode" }, + { "secretIntKey", "123456" }, + }; + + unit.Settings.Add("boolProperty", true); + unit.Settings.Add("intProperty", 3); + unit.Settings.Add("doubleProperty", -9.876); + unit.Settings.Add("charProperty", 'f'); + unit.Settings.Add("hashtableProperty", hashtableProperty); + + var unitProcessor = setProcessor.CreateUnitProcessor(unit, null); + + unitProcessor.TestSettings(); + } + private ConfigurationUnit CreteConfigurationUnit() { var unit = new ConfigurationUnit(); diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs index 95e861a22a..28b285a76b 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs @@ -61,7 +61,7 @@ public void GetAllDscResources_Test() /// Module. /// Expected DSC resources. [Theory] - [InlineData(TestModule.SimpleTestResourceModuleName, 4)] + [InlineData(TestModule.SimpleTestResourceModuleName, 5)] [InlineData("MyReallyFakeModule", 0)] public void GetDscResourcesInModule_Test(string module, int expectedResources) { @@ -111,7 +111,7 @@ public void GetDscResourcesInModule_VersionTest() PowerShellHelpers.CreateModuleSpecification( TestModule.SimpleTestResourceModuleName, version: TestModule.SimpleTestResourceVersion)); - Assert.Equal(4, ogResources.Count); + Assert.Equal(5, ogResources.Count); } { @@ -121,7 +121,7 @@ public void GetDscResourcesInModule_VersionTest() PowerShellHelpers.CreateModuleSpecification( TestModule.SimpleTestResourceModuleName, version: newVersion)); - Assert.Equal(4, newVersionResources.Count); + Assert.Equal(5, newVersionResources.Count); } { @@ -556,5 +556,33 @@ public void InvokeResource_MultipleVersions() version: newVersion)); } } + + /// + /// Calls Invoke-DscResource invalid arguments. + /// + /// Setting value. + /// Expected reboot required. + [Fact] + public void InvokeSetResource_InvalidArguments() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + + var settings = new ValueSet() + { + { "Fake", "please dont add it" }, + }; + + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var e = Assert.Throws(() => dscModule.InvokeSetResource( + pwsh, + settings, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + + Assert.Contains("The property 'Fake' cannot be found on this object.", e.Message); + } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs index e8f642efdb..378efdf1e5 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs @@ -6,9 +6,14 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { + using System; using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Moq; + using Windows.Security.Authentication.OnlineId; using Xunit; using Xunit.Abstractions; using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; @@ -209,7 +214,7 @@ public void HostedEnvironment_CleanupPSModulePath() psModulePathExpected = "CleanupPSModulePathPath2;CleanupPSModulePathPath3"; psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.Equal(psModulePath, psModulePath); + Assert.Equal(psModulePathExpected, psModulePath); } } }