commit a43fa8b38410477541df5e8b8fd0a4c6baaefbc5 parent c32d376b8bc428eacf5669d3d08585b52bd8b2e4 Author: JohnMcPMS <johnmcp@microsoft.com> Date: Tue, 11 Feb 2025 11:43:54 -0800 Refactor configuration processor (#5194) ## Change Split the PowerShell specific code from the more general configuration interface implementation. The goal is to make it easier to reuse some of the existing infrastructure with a new configuration processing engine. No functional change is intended from this change. Diffstat:
56 files changed, 3919 insertions(+), 3654 deletions(-)
diff --git a/src/Microsoft.Management.Configuration.Processor/Constants/PowerShellConstants.cs b/src/Microsoft.Management.Configuration.Processor/Constants/PowerShellConstants.cs @@ -1,81 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="PowerShellConstants.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Constants -{ - /// <summary> - /// Constants related to PowerShell. - /// </summary> - internal static class PowerShellConstants - { -#pragma warning disable SA1600 // ElementsMustBeDocumented - public const string Core = "Core"; - - internal static class Variables - { - public const string PSEdition = "PSEdition"; - public const string Error = "Error"; - public const string PSModulePath = "env:PSModulePath"; - } - - internal static class Modules - { - public const string PSDesiredStateConfiguration = "PSDesiredStateConfiguration"; - public const string PSDesiredStateConfigurationMinVersion = "2.0.7"; - public const string PowerShellGet = "PowerShellGet"; - public const string PowerShellGetMinVersion = "2.2.5"; - public const string PSDesiredStateConfigurationMaxVersion = "2.*"; - } - - internal static class Commands - { - public const string FindDscResource = "Find-DscResource"; - public const string GetAuthenticodeSignature = "Get-AuthenticodeSignature"; - public const string GetChildItem = "Get-ChildItem"; - public const string GetCommand = "Get-Command"; - public const string GetDscResource = "Get-DscResource"; - public const string GetInstalledModule = "Get-InstalledModule"; - public const string GetModule = "Get-Module"; - public const string ImportModule = "Import-Module"; - public const string InstallModule = "Install-Module"; - public const string InvokeDscResource = "Invoke-DscResource"; - public const string SaveModule = "Save-Module"; - public const string FindModule = "Find-Module"; - public const string ImportCliXml = "Import-CliXml"; - } - - internal static class Parameters - { - public const string AllowPrerelease = "AllowPrerelease"; - public const string Force = "Force"; - public const string FullyQualifiedName = "FullyQualifiedName"; - public const string Guid = "GUID"; - public const string InputObject = "InputObject"; - public const string ListAvailable = "ListAvailable"; - public const string MaximumVersion = "MaximumVersion"; - public const string Method = "Method"; - public const string MinimumVersion = "MinimumVersion"; - public const string Module = "Module"; - public const string ModuleName = "ModuleName"; - public const string ModuleVersion = "ModuleVersion"; - public const string Name = "Name"; - public const string Path = "Path"; - public const string Property = "Property"; - public const string Recurse = "Recurse"; - public const string Repository = "Repository"; - public const string RequiredVersion = "RequiredVersion"; - public const string Scope = "Scope"; - } - - internal static class DscMethods - { - public const string Get = "Get"; - public const string Set = "Set"; - public const string Test = "Test"; - } -#pragma warning restore SA1600 // ElementsMustBeDocumented - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/DscModules/DscModuleV2.cs b/src/Microsoft.Management.Configuration.Processor/DscModules/DscModuleV2.cs @@ -1,295 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="DscModuleV2.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DscModule -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Management.Automation; - 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; - using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; - - /// <summary> - /// PSDesiredStateConfiguration v2. - /// </summary> - internal class DscModuleV2 : IDscModule - { - private const string InDesiredState = "InDesiredState"; - private const string RebootRequired = "RebootRequired"; - - private static readonly IEnumerable<string> ExclusionResourcesParentPath = new string[] - { - @"C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources", - @"C:\Program Files\WindowsPowerShell\Modules\PackageManagement\1.0.0.1", - }; - - /// <summary> - /// Initializes a new instance of the <see cref="DscModuleV2"/> class. - /// </summary> - public DscModuleV2() - { - this.ModuleSpecification = PowerShellHelpers.CreateModuleSpecification( - Modules.PSDesiredStateConfiguration, - minVersion: Modules.PSDesiredStateConfigurationMinVersion, - maxVersion: Modules.PSDesiredStateConfigurationMaxVersion); - } - - /// <inheritdoc/> - public ModuleSpecification ModuleSpecification { get; private init; } - - /// <inheritdoc/> - public string GetDscResourceCmd { get; } = Commands.GetDscResource; - - /// <inheritdoc/> - public string InvokeDscResourceCmd { get; } = Commands.InvokeDscResource; - - /// <inheritdoc/> - public IReadOnlyList<DscResourceInfoInternal> GetAllDscResources(PowerShell pwsh) - { - return this.GetDscResources(pwsh, null, null); - } - - /// <inheritdoc/> - public IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule( - PowerShell pwsh, - ModuleSpecification moduleSpecification) - { - return this.GetDscResources(pwsh, null, moduleSpecification); - } - - /// <inheritdoc/> - public DscResourceInfoInternal? GetDscResource( - PowerShell pwsh, - string name, - ModuleSpecification? moduleSpecification) - { - var dscResourceInfos = this.GetDscResources(pwsh, name, moduleSpecification); - - if (dscResourceInfos.Count == 0) - { - return null; - } - else if (dscResourceInfos.Count > 1) - { - throw new GetDscResourceMultipleMatches(name, moduleSpecification); - } - - return dscResourceInfos[0]; - } - - /// <inheritdoc/> - public ValueSet InvokeGetResource( - PowerShell pwsh, - ValueSet settings, - string name, - ModuleSpecification? moduleSpecification) - { - PSObject? getResult = null; - - try - { - getResult = pwsh.AddCommand(this.InvokeDscResourceCmd) - .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) - .AddParameter(Parameters.Method, DscMethods.Get) - .InvokeAndStopOnError() - .FirstOrDefault(); - } - catch (System.Exception ex) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, ex); - } - - string? errorMessage = pwsh.GetErrorMessage(); - if (errorMessage is not null) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, errorMessage); - } - - if (getResult is null) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification); - } - - // Script based resource. - if (getResult.BaseObject is Hashtable) - { - Hashtable hashTable = (Hashtable)getResult.BaseObject; - var resultSettings = new ValueSet(); - foreach (DictionaryEntry entry in hashTable) - { - resultSettings.Add(entry.Key as string, entry.Value); - } - - return resultSettings; - } - - // Class-based resource. - return TypeHelpers.GetAllPropertiesValues(getResult.BaseObject); - } - - /// <inheritdoc/> - public bool InvokeTestResource( - PowerShell pwsh, - ValueSet settings, - string name, - ModuleSpecification? moduleSpecification) - { - // Returned type is InvokeDscResourceTestResult which is a PowerShell classed defined - // in PSDesiredStateConfiguration.psm1. - dynamic? testResult = null; - - try - { - testResult = pwsh.AddCommand(this.InvokeDscResourceCmd) - .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) - .AddParameter(Parameters.Method, DscMethods.Test) - .InvokeAndStopOnError() - .FirstOrDefault(); - } - catch (System.Exception ex) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, ex); - } - - string? errorMessage = pwsh.GetErrorMessage(); - if (errorMessage is not null) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, errorMessage); - } - - if (testResult is null || - !TypeHelpers.PropertyWithTypeExists<bool>(testResult, InDesiredState)) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification); - } - - return testResult?.InDesiredState; - } - - /// <inheritdoc/> - public bool InvokeSetResource( - PowerShell pwsh, - ValueSet settings, - string name, - ModuleSpecification? moduleSpecification) - { - // Returned type is InvokeDscResourceSetResult which is a PowerShell classed defined - // in PSDesiredStateConfiguration.psm1. - dynamic? setResult = null; - - try - { - setResult = pwsh.AddCommand(this.InvokeDscResourceCmd) - .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) - .AddParameter(Parameters.Method, DscMethods.Set) - .InvokeAndStopOnError() - .FirstOrDefault(); - } - catch (System.Exception ex) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, ex); - } - - string? errorMessage = pwsh.GetErrorMessage(); - if (errorMessage is not null) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, errorMessage, pwsh.ContainsPropertyError()); - } - - if (setResult is null || - !TypeHelpers.PropertyWithTypeExists<bool>(setResult, RebootRequired)) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification); - } - - return setResult?.RebootRequired; - } - - private static Dictionary<string, object> PrepareInvokeParameters( - string name, - ValueSet settings, - ModuleSpecification? moduleSpecification) - { - Hashtable properties = settings.ToHashtable(); - - var parameters = new Dictionary<string, object>() - { - { Parameters.Name, name }, - { Parameters.Property, properties }, - }; - - if (moduleSpecification is not null) - { - parameters.Add(Parameters.ModuleName, moduleSpecification); - } - - return parameters; - } - - private IReadOnlyList<DscResourceInfoInternal> GetDscResources( - PowerShell pwsh, - string? name, - ModuleSpecification? moduleSpecification) - { - pwsh.AddCommand(this.GetDscResourceCmd); - - if (name is not null) - { - pwsh.AddParameter(Parameters.Name, name); - } - - if (moduleSpecification is not null) - { - pwsh.AddParameter(Parameters.Module, moduleSpecification); - } - - try - { - var resources = pwsh.Invoke(); - return this.ConvertToDscResourceInfoInternal(resources); - } - catch (RuntimeException e) - { - // Detect easily this. - if (e.ErrorRecord.FullyQualifiedErrorId == "ExceptionWhenSetting,GetResourceFromKeyword") - { - throw new GetDscResourceModuleConflict(name, moduleSpecification, e); - } - - throw; - } - } - - private List<DscResourceInfoInternal> ConvertToDscResourceInfoInternal(Collection<PSObject> psObjects) - { - var result = new List<DscResourceInfoInternal>(); - foreach (dynamic psObject in psObjects) - { - var dscResourceInfo = new DscResourceInfoInternal(psObject); - - // Explicitly don't support old DSC resources from v1 PSDesiredStateConfiguration. - // Even if the Windows System32 Windows PowerShell module path is removed they - // will show up. - if (ExclusionResourcesParentPath.Any(e => dscResourceInfo.ParentPath!.StartsWith(e))) - { - continue; - } - - result.Add(dscResourceInfo); - } - - return result; - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/DscModules/IDscModule.cs b/src/Microsoft.Management.Configuration.Processor/DscModules/IDscModule.cs @@ -1,89 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="IDscModule.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DscModule -{ - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - - /// <summary> - /// Interface for defining a PSDesiredStateConfiguration module. - /// </summary> - internal interface IDscModule - { - /// <summary> - /// Gets the module specification. - /// </summary> - ModuleSpecification ModuleSpecification { get; } - - /// <summary> - /// Gets the name of the Get-DscResource Cmdlet. - /// </summary> - string GetDscResourceCmd { get; } - - /// <summary> - /// Gets the name of the Invoke-DscResource Cmdlet. - /// </summary> - string InvokeDscResourceCmd { get; } - - /// <summary> - /// Gets all DSC resource. - /// </summary> - /// <param name="pwsh">PowerShell.</param> - /// <returns>A list with the DSC resource.</returns> - IReadOnlyList<DscResourceInfoInternal> GetAllDscResources(PowerShell pwsh); - - /// <summary> - /// Gets all resources in a module. - /// </summary> - /// <param name="pwsh">PowerShell.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>List of resources of that module and version.</returns> - IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule(PowerShell pwsh, ModuleSpecification moduleSpecification); - - /// <summary> - /// Gets a DSC Resource. - /// </summary> - /// <param name="pwsh">PowerShell.</param> - /// <param name="name">Name.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>DSC Resource from that module and version.</returns> - DscResourceInfoInternal? GetDscResource(PowerShell pwsh, string name, ModuleSpecification? moduleSpecification); - - /// <summary> - /// Calls Invoke-DscResource -Method Get from this module. - /// </summary> - /// <param name="pwsh">PowerShell.</param> - /// <param name="settings">Settings.</param> - /// <param name="name">Name.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>Properties of resource.</returns> - ValueSet InvokeGetResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// <summary> - /// Calls Invoke-DscResource -Method Test from this module. - /// </summary> - /// <param name="pwsh">PowerShell.</param> - /// <param name="settings">Settings.</param> - /// <param name="name">Name.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>Is in desired state.</returns> - bool InvokeTestResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// <summary> - /// Calls Invoke-DscResource -Method Set from this module. - /// </summary> - /// <param name="pwsh">PowerShell.</param> - /// <param name="settings">Settings.</param> - /// <param name="name">Name.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>If a reboot is required.</returns> - bool InvokeSetResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/DscResourcesInfo/DscResourceInfoInternal.cs b/src/Microsoft.Management.Configuration.Processor/DscResourcesInfo/DscResourceInfoInternal.cs @@ -1,157 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="DscResourceInfoInternal.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DscResourcesInfo -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.Helpers; - - /// <summary> - /// Contains a DSC resource information. - /// This object should be initialized with a Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo. - /// That object is the result of Get-DscResource and is not the same as System.Management.Automation.DscResourceInfo. - /// The type is defined in a psd1 in the PSDesiredStateConfiguration. This file is based on that. - /// If Invoke-DscResource gets support for passing the DscResourceInfo, we could keep the original object as member - /// to then pass it in code. - /// </summary> - internal class DscResourceInfoInternal - { - private const string DscResourceInfoFullName = "Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo"; - - /// <summary> - /// Initializes a new instance of the <see cref="DscResourceInfoInternal"/> class. - /// </summary> - /// <param name="info">Dynamic object. Expected DscResourceInfo.</param> - public DscResourceInfoInternal(dynamic info) - { - if (info.GetType().FullName != DscResourceInfoFullName) - { - throw new ArgumentException(); - } - - this.ResourceType = info.ResourceType; - this.Name = info.Name; - this.FriendlyName = info.FriendlyName; - this.Module = info.Module; - this.Path = info.Path; - this.ParentPath = info.ParentPath; - this.ImplementedAs = Enum.Parse<ImplementedAsTypeInternal>(info.ImplementedAs.ToString()); - this.CompanyName = info.CompanyName; - - if (info.Module is not null) - { - this.ModuleName = this.Module.Name; - this.Version = this.Module.Version; - } - - foreach (object property in info.Properties) - { - this.Properties.Add(new DscResourcePropertyInfoInternal(property)); - } - } - - /// <summary> - /// Initializes a new instance of the <see cref="DscResourceInfoInternal"/> class. - /// Used for unit tests. - /// </summary> - /// <param name="name">Resource name.</param> - /// <param name="moduleName">Module name.</param> - /// <param name="version">Version.</param> - internal DscResourceInfoInternal(string name, string? moduleName, Version? version) - { - this.Name = name; - this.ModuleName = moduleName; - this.Version = version; - } - - /// <summary> - /// Gets or sets resource type name. - /// </summary> - public string? ResourceType { get; set; } - - /// <summary> - /// Gets or sets Name of the resource. This name is used to access the resource. - /// </summary> - public string Name { get; set; } - - /// <summary> - /// Gets the normalized resource name. - /// </summary> - public string NormalizedName - { - get { return StringHelpers.Normalize(this.Name); } - } - - /// <summary> - /// Gets or sets friendly name defined for the resource. - /// </summary> - public string? FriendlyName { get; set; } - - /// <summary> - /// Gets or sets module which implements the resource. This could point to parent module, if the DSC resource is implemented. - /// by one of nested modules. - /// </summary> - public PSModuleInfo? Module { get; set; } - - /// <summary> - /// Gets or sets name of the module which implements the resource. - /// </summary> - public string? ModuleName { get; set; } - - /// <summary> - /// Gets the normalized module name. - /// </summary> - public string? NormalizedModuleName - { - get { return (this.ModuleName is not null) ? StringHelpers.Normalize(this.ModuleName) : null; } - } - - /// <summary> - /// Gets or sets version of the module which implements the resource. - /// </summary> - public Version? Version { get; set; } - - /// <summary> - /// Gets or sets of the file which implements the resource. For the resources which are defined using - /// MOF file, this will be path to a module which resides in the same folder where schema.mof file is present. - /// For composite resources, this will be the module which implements the resource. - /// </summary> - public string? Path { get; set; } - - /// <summary> - /// Gets or sets parent folder, where the resource is defined - /// It is the folder containing either the implementing module(=Path) or folder containing ".schema.mof". - /// For native providers, Path will be null and only ParentPath will be present. - /// </summary> - public string? ParentPath { get; set; } - - /// <summary> - /// Gets or sets a value which indicate how DSC resource is implemented. - /// </summary> - public ImplementedAsTypeInternal? ImplementedAs { get; set; } - - /// <summary> - /// Gets or sets company which owns this resource. - /// </summary> - public string? CompanyName { get; set; } - - /// <summary> - /// Gets properties of the resource. - /// </summary> - public List<DscResourcePropertyInfoInternal> Properties { get; private set; } = new List<DscResourcePropertyInfoInternal>(); - - /// <summary> - /// Updates properties of the resource. - /// </summary> - /// <param name="properties">Updated properties.</param> - public void UpdateProperties(List<DscResourcePropertyInfoInternal> properties) - { - this.Properties = properties; - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/DscResourcesInfo/DscResourcePropertyInfoInternal.cs b/src/Microsoft.Management.Configuration.Processor/DscResourcesInfo/DscResourcePropertyInfoInternal.cs @@ -1,56 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="DscResourcePropertyInfoInternal.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DscResourcesInfo -{ - using System; - using System.Collections.Generic; - - /// <summary> - /// Contains a DSC resource property information. - /// </summary> - internal sealed class DscResourcePropertyInfoInternal - { - private const string DscResourcePropertyInfoFullName = "Microsoft.PowerShell.DesiredStateConfiguration.DscResourcePropertyInfo"; - - /// <summary> - /// Initializes a new instance of the <see cref="DscResourcePropertyInfoInternal"/> class. - /// </summary> - /// <param name="dscPropertyInfo">Dynamic DSC property info.</param> - public DscResourcePropertyInfoInternal(dynamic dscPropertyInfo) - { - if (dscPropertyInfo.GetType().FullName != DscResourcePropertyInfoFullName) - { - throw new ArgumentException(); - } - - this.Name = dscPropertyInfo.Name; - this.PropertyType = dscPropertyInfo.PropertyType; - this.IsMandatory = dscPropertyInfo.IsMandatory; - this.Values = dscPropertyInfo.Values; - } - - /// <summary> - /// Gets or sets name of the property. - /// </summary> - public string Name { get; set; } - - /// <summary> - /// Gets or sets type of the property. - /// </summary> - public string PropertyType { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the property is mandatory or not. - /// </summary> - public bool IsMandatory { get; set; } - - /// <summary> - /// Gets Values for a resource property. - /// </summary> - public List<string> Values { get; private set; } = new List<string>(); - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/DscResourcesInfo/ImplementedAsTypeInternal.cs b/src/Microsoft.Management.Configuration.Processor/DscResourcesInfo/ImplementedAsTypeInternal.cs @@ -1,34 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="ImplementedAsTypeInternal.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DscResourcesInfo -{ - /// <summary> - /// Enumerated values for DSC resource implementation type. - /// </summary> - internal enum ImplementedAsTypeInternal - { - /// <summary> - /// DSC resource implementation type not known. - /// </summary> - None = 0, - - /// <summary> - /// DSC resource is implemented using PowerShell module. - /// </summary> - PowerShell = 1, - - /// <summary> - /// DSC resource is implemented using a CIM provider. - /// </summary> - Binary = 2, - - /// <summary> - /// DSC resource is a composite and implemented using configuration keyword. - /// </summary> - Composite = 3, - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/PowerShellExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/PowerShellExtensions.cs @@ -1,97 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="PowerShellExtensions.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Extensions -{ - using System.Collections.ObjectModel; - using System.Linq; - using System.Management.Automation; - using System.Text; - - /// <summary> - /// Extensions methods for <see cref="PowerShell"/> class. - /// </summary> - internal static class PowerShellExtensions - { - /// <summary> - /// Calls Invoke with <see cref="ActionPreference.Stop"/> to stop execution of commands. - /// </summary> - /// <param name="pwsh">PowerShell.</param> - /// <returns>Collection of PSObjects representing output.</returns> - public static Collection<PSObject> InvokeAndStopOnError(this PowerShell pwsh) - { - var settings = new PSInvocationSettings() - { - ErrorActionPreference = ActionPreference.Stop, - }; - - return pwsh.Invoke(null, settings); - } - - /// <summary> - /// Calls Invoke with <see cref="ActionPreference.Stop"/> to stop execution of commands. - /// </summary> - /// <typeparam name="T">Type of output object(s) expected from the command invocation.</typeparam> - /// <param name="pwsh">PowerShell.</param> - /// <returns>Collection of the type output representing output.</returns> - public static Collection<T> InvokeAndStopOnError<T>(this PowerShell pwsh) - { - var settings = new PSInvocationSettings() - { - ErrorActionPreference = ActionPreference.Stop, - }; - - return pwsh.Invoke<T>(null, settings); - } - - /// <summary> - /// Gets the error stream message if any. - /// </summary> - /// <param name="pwsh">PowerShell.</param> - /// <returns>Error message. Null if none.</returns> - public static string? GetErrorMessage(this PowerShell pwsh) - { - if (pwsh.HadErrors) - { - var psStreamBuilder = new StringBuilder(); - foreach (var line in pwsh.Streams.Error) - { - psStreamBuilder.AppendLine(line.ToString()); - } - - return psStreamBuilder.ToString(); - } - - return null; - } - - /// <summary> - /// Determines if the given shell contains a property error, meaning that the source of this error is the - /// configuration values and not the configuration unit itself. - /// </summary> - /// <param name="pwsh">The shell to inspect.</param> - /// <returns>True if it only contains property errors; false otherwise.</returns> - public static bool ContainsPropertyError(this PowerShell pwsh) - { - if (!pwsh.HadErrors) - { - return false; - } - - bool result = true; - - foreach (ErrorRecord? error in pwsh.Streams.Error) - { - if (error?.FullyQualifiedErrorId == "PropertyAssignmentException") - { - result = result && true; - } - } - - return result; - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Factory/ConfigurationSetProcessorFactoryBase.cs b/src/Microsoft.Management.Configuration.Processor/Factory/ConfigurationSetProcessorFactoryBase.cs @@ -0,0 +1,175 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigurationSetProcessorFactoryBase.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Factory +{ + using System; + using System.Runtime.CompilerServices; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Set; + + /// <summary> + /// IConfigurationSetProcessorFactory base implementation. + /// </summary> + internal abstract partial class ConfigurationSetProcessorFactoryBase + { + private bool isCreateProcessorInvoked = false; + + // Backing variables for properties that are restricted in limit mode. + private ConfigurationSet? limitationSet; + + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationSetProcessorFactoryBase"/> class. + /// </summary> + public ConfigurationSetProcessorFactoryBase() + { + } + + /// <summary> + /// Diagnostics event; useful for logging and/or verbose output. + /// </summary> + public event EventHandler<IDiagnosticInformation>? Diagnostics; + + /// <summary> + /// Gets or sets the minimum diagnostic level to send. + /// </summary> + public DiagnosticLevel MinimumLevel { get; set; } = DiagnosticLevel.Informational; + + /// <summary> + /// Gets or sets the limitation set. Limitation set can only be set once. + /// </summary> + public ConfigurationSet? LimitationSet + { + get + { + return this.limitationSet; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting LimitationSet in limit mode is invalid."); + } + + this.limitationSet = value; + } + } + + /// <summary> + /// Gets the configuration unit processor details for the given unit. + /// </summary> + /// <param name="incomingSet">Configuration Set.</param> + /// <returns>Configuration set processor.</returns> + public IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet? incomingSet) + { + try + { + bool isLimitMode = this.IsLimitMode(); + this.OnDiagnostics(DiagnosticLevel.Informational, $"The set processor factory is running in limit mode: {isLimitMode}."); + + this.CheckLimitMode(); + + ConfigurationSet? set = isLimitMode ? this.limitationSet : incomingSet; + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating set processor for `{set?.Name ?? "<null>"}`..."); + + if (set != null && (set.Parameters.Count > 0 || set.Variables.Count > 0)) + { + this.OnDiagnostics(DiagnosticLevel.Error, $" Parameters/variables are not yet supported."); + throw new NotImplementedException(); + } + + IConfigurationSetProcessor result = this.CreateSetProcessorInternal(set, isLimitMode); + + this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating set processor."); + + return result; + } + catch (Exception ex) + { + this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); + throw; + } + } + + /// <summary> + /// Sends diagnostics if appropriate. + /// </summary> + /// <param name="level">The level of this diagnostic message.</param> + /// <param name="message">The diagnostic message.</param> + internal void OnDiagnostics(DiagnosticLevel level, string message) + { + EventHandler<IDiagnosticInformation>? diagnostics = this.Diagnostics; + if (diagnostics != null && level >= this.MinimumLevel) + { + this.InvokeDiagnostics(diagnostics, level, message); + } + } + + /// <summary> + /// Determines if diagnostics are enabled. + /// This allows optimizing out string construction for some cases. + /// </summary> + /// <returns>True if diagnostics are enabled; false if not.</returns> + protected bool AreDiagnosticsEnabled() + { + return this.Diagnostics != null; + } + + /// <summary> + /// Gets the configuration unit processor details for the given unit. + /// </summary> + /// <param name="set">Configuration Set.</param> + /// <param name="isLimitMode">Whether the processor should be in limit mode.</param> + /// <returns>Configuration set processor.</returns> + protected abstract IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode); + + /// <summary> + /// Gets a value indicating whether the factory is operation in limit mode. + /// </summary> + /// <returns>True if the factory is in limit mode, false otherwise.</returns> + protected bool IsLimitMode() + { + return this.limitationSet != null; + } + + /// <summary> + /// Sends diagnostics to the given handler. + /// </summary> + /// <param name="diagnostics">The handler to invoke.</param> + /// <param name="level">The level of this diagnostic message.</param> + /// <param name="message">The diagnostic message.</param> + private void InvokeDiagnostics(EventHandler<IDiagnosticInformation> diagnostics, DiagnosticLevel level, string message) + { + Helpers.DiagnosticInformation information = new () + { + Level = level, + Message = message, + }; + diagnostics.Invoke(this, information); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private void CheckLimitMode() + { + if (!this.IsLimitMode()) + { + return; + } + + if (this.isCreateProcessorInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "CreateSetProcessor is already invoked in limit mode."); + throw new InvalidOperationException("CreateSetProcessor is already invoked in limit mode."); + } + else + { + this.isCreateProcessorInvoked = true; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitAndResource.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitAndResource.cs @@ -1,102 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="ConfigurationUnitAndResource.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - - /// <summary> - /// Contains information about the unit and the DSC resource that applies to it. - /// </summary> - internal class ConfigurationUnitAndResource - { - private readonly DscResourceInfoInternal dscResourceInfoInternal; - - /// <summary> - /// Initializes a new instance of the <see cref="ConfigurationUnitAndResource"/> class. - /// </summary> - /// <param name="configurationUnitInternal">Configuration unit internal.</param> - /// <param name="dscResourceInfoInternal">DscResourceInfoInternal.</param> - public ConfigurationUnitAndResource( - ConfigurationUnitInternal configurationUnitInternal, - DscResourceInfoInternal dscResourceInfoInternal) - { - if (!configurationUnitInternal.ResourceName.Equals(dscResourceInfoInternal.Name, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException(); - } - - this.UnitInternal = configurationUnitInternal; - this.dscResourceInfoInternal = dscResourceInfoInternal; - } - - /// <summary> - /// Gets or initializes the internal unit. - /// </summary> - public ConfigurationUnitInternal UnitInternal { get; private init; } - - /// <summary> - /// Gets the configuration unit. - /// </summary> - public ConfigurationUnit Unit - { - get { return this.UnitInternal.Unit; } - } - - /// <summary> - /// Gets the DSC resource name. - /// </summary> - /// <returns>DSC resource name.</returns> - public string ResourceName - { - get { return this.dscResourceInfoInternal.Name; } - } - - /// <summary> - /// Gets the module specification. - /// </summary> - public ModuleSpecification? Module - { - get { return this.UnitInternal.Module; } - } - - /// <summary> - /// Gets a directive if exits. - /// </summary> - /// <param name="directiveName">Name of directive.</param> - /// <returns>The value of the directive. Null if doesn't exist.</returns> - /// <typeparam name="TType">Directive type value.</typeparam> - public TType? GetDirective<TType>(string directiveName) - where TType : class - { - return this.UnitInternal.GetDirective<TType>(directiveName); - } - - /// <summary> - /// Gets a directive bool value. - /// </summary> - /// <param name="directiveName">Name of directive.</param> - /// <returns>The value of the directive. False if not set.</returns> - public bool? GetDirective(string directiveName) - { - return this.UnitInternal.GetDirective(directiveName); - } - - /// <summary> - /// Gets the settings of the unit. - /// </summary> - /// <returns>ValueSet with settings.</returns> - public ValueSet GetSettings() - { - return this.UnitInternal.GetExpandedSettings(); - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitInternal.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitInternal.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationUnitInternal.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -12,7 +12,6 @@ namespace Microsoft.Management.Configuration.Processor.Helpers using System.IO; using Microsoft.Management.Configuration.Processor.Constants; using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.PowerShell.Commands; using Windows.Foundation.Collections; /// <summary> @@ -39,21 +38,6 @@ namespace Microsoft.Management.Configuration.Processor.Helpers this.InitializeDirectives(); this.InitializeNames(); - string? moduleName = this.GetDirective<string>(DirectiveConstants.Module); - if (string.IsNullOrEmpty(moduleName)) - { - this.Module = null; - } - else - { - this.Module = PowerShellHelpers.CreateModuleSpecification( - moduleName, - this.GetDirective<string>(DirectiveConstants.Version), - this.GetDirective<string>(DirectiveConstants.MinVersion), - this.GetDirective<string>(DirectiveConstants.MaxVersion), - this.GetDirective<string>(DirectiveConstants.ModuleGuid)); - } - if (!string.IsNullOrEmpty(configurationFilePath)) { if (!File.Exists(configurationFilePath)) @@ -76,11 +60,6 @@ namespace Microsoft.Management.Configuration.Processor.Helpers public bool UnitTypeIsResourceName { get; init; } = false; /// <summary> - /// Gets the module specification. - /// </summary> - public ModuleSpecification? Module { get; } - - /// <summary> /// Gets the resource name *only*. For example, "Resource". /// </summary> public string ResourceName { get; private set; } diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/DscResourcesMap.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/DscResourcesMap.cs @@ -1,223 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="DscResourcesMap.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; - - /// <summary> - /// DscResources in the system. Get-DscResource looks the entire PSModulePath to find resources every time, so - /// calling it multiples times is expensive. This class will store them in memory per configuration set. - /// The resources are stored in a dictionary in the following hierarchy. - /// - Module name - /// - Resource name - /// - Module Version - /// - DscResourceInfo obj. - /// There are some resources that don't have a module name. These are modules that live under - /// C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources. - /// This is not currently used. For now, we let PowerShell handle all the versioning. In order to avoid - /// calls to Get-DscResource every time a unit is set, this class needs to be updated to handle min and max - /// version. As it is, it just does RequiredVersion. - /// </summary> - internal sealed class DscResourcesMap - { - private readonly Dictionary<string, ModuleDscResources> resources = new Dictionary<string, ModuleDscResources>(); - - /// <summary> - /// Initializes a new instance of the <see cref="DscResourcesMap"/> class. - /// </summary> - /// <param name="resources">DSC Resources.</param> - public DscResourcesMap(IReadOnlyList<DscResourceInfoInternal> resources) - { - foreach (var resource in resources) - { - if (string.IsNullOrEmpty(resource.Name)) - { - throw new ArgumentException(nameof(resource.Name)); - } - - if (resource.Version is null) - { - throw new ArgumentException(nameof(resource.Version)); - } - - this.Insert(resource); - } - } - - /// <summary> - /// Looks for a DSC resource in the specified module. - /// </summary> - /// <param name="dscResourceName">DSC resource name.</param> - /// <param name="moduleName">Optional module name.</param> - /// <param name="version">Optional module version.</param> - /// <returns>DscResourceInfoInternal. Null if not found.</returns> - public DscResourceInfoInternal? GetResource(string dscResourceName, string? moduleName, Version? version) - { - string normalizedResourceName = StringHelpers.Normalize(dscResourceName); - string? normalizedModuleName = null; - if (moduleName is not null) - { - normalizedModuleName = StringHelpers.Normalize(moduleName); - } - - if (normalizedModuleName is null) - { - foreach (var module in this.resources.Values) - { - var resourceInfo = module.GetDscResourceInfo(normalizedResourceName, version); - if (resourceInfo is not null) - { - return resourceInfo; - } - } - } - else - { - if (this.resources.TryGetValue(normalizedModuleName, out ModuleDscResources? module)) - { - return module.GetDscResourceInfo(normalizedResourceName, version); - } - } - - return null; - } - - /// <summary> - /// Does a DSC resource exists. - /// </summary> - /// <param name="dscResourceName">DSC resource name.</param> - /// <param name="moduleName">Optional module name.</param> - /// <param name="version">Optional module version.</param> - /// <returns>True if resource exists.</returns> - public bool Exists(string dscResourceName, string? moduleName, Version? version) - { - return this.GetResource(dscResourceName, moduleName, version) != null; - } - - private void Insert(DscResourceInfoInternal dscResourceInfo) - { - string? normalizedModuleName = dscResourceInfo.NormalizedModuleName; - - // There are some resources that doesn't have module, use empty as key. - string moduleKey = string.Empty; - if (normalizedModuleName is not null) - { - moduleKey = normalizedModuleName; - } - - if (!this.resources.ContainsKey(moduleKey)) - { - this.resources.Add(moduleKey, new ModuleDscResources(moduleKey, dscResourceInfo)); - } - else - { - this.resources[moduleKey].AddResource(moduleKey, dscResourceInfo); - } - } - - /// <summary> - /// Represents a map versions for a DscResource. - /// </summary> - private class DscResourceVersions : Dictionary<Version, DscResourceInfoInternal> - { - private readonly string resourceName; - - public DscResourceVersions(DscResourceInfoInternal dscResourceInfo) - { - this.resourceName = dscResourceInfo.NormalizedName; - this.AddVersion(dscResourceInfo); - } - - /// <summary> - /// Adds a resource version. - /// </summary> - /// <param name="dscResourceInfo">DscResourceInfo.</param> - public void AddVersion(DscResourceInfoInternal dscResourceInfo) - { - if (this.resourceName != dscResourceInfo.NormalizedName) - { - throw new ArgumentException(dscResourceInfo.NormalizedName); - } - - // There are some system resources without version or module name. - if (dscResourceInfo.Version is null) - { - this.Add(new Version(), dscResourceInfo); - } - else - { - // Get-DscResource will fail if the same module with same - // resources and same version exists in the module path - // so this shouldn't happen. Either way, we should throw there or here. - this.Add(dscResourceInfo.Version, dscResourceInfo); - } - } - - public DscResourceInfoInternal? GetDscResourceInfo(Version? version) - { - if (version is null) - { - // Sort keys, get the latest. - var newestVersion = this.Keys.OrderByDescending(v => v).First(); - return this[newestVersion]; - } - - if (this.TryGetValue(version, out DscResourceInfoInternal? resourceInfo)) - { - return resourceInfo; - } - - return null; - } - } - - /// <summary> - /// Represent a map of resources for a module. - /// </summary> - private class ModuleDscResources : Dictionary<string, DscResourceVersions> - { - private readonly string normalizedModuleName; - - public ModuleDscResources(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) - { - this.normalizedModuleName = normalizedModuleName; - this.AddResource(this.normalizedModuleName, dscResourceInfo); - } - - public void AddResource(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) - { - if (this.normalizedModuleName != normalizedModuleName) - { - throw new ArgumentException(nameof(normalizedModuleName)); - } - - var normalizedResourceName = dscResourceInfo.NormalizedName; - if (!this.ContainsKey(normalizedResourceName)) - { - this.Add(normalizedResourceName, new DscResourceVersions(dscResourceInfo)); - } - else - { - this[normalizedResourceName].AddVersion(dscResourceInfo); - } - } - - public DscResourceInfoInternal? GetDscResourceInfo(string dscResourceName, Version? version) - { - if (this.TryGetValue(dscResourceName, out DscResourceVersions? versions)) - { - return versions.GetDscResourceInfo(version); - } - - return null; - } - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/IPowerShellGet.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/IPowerShellGet.cs @@ -1,93 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="IPowerShellGet.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System.Management.Automation; - using Microsoft.PowerShell.Commands; - - /// <summary> - /// Interface for PowerShellGet cmdlets. - /// </summary> - internal interface IPowerShellGet - { - /// <summary> - /// Calls Find-Module. - /// </summary> - /// <param name="pwsh">PowerShell instance.</param> - /// <param name="moduleName">Module name.</param> - /// <param name="semanticVersion">Optional version.</param> - /// <param name="semanticMinVersion">Optional min version.</param> - /// <param name="semanticMaxVersion">Optional max version.</param> - /// <param name="repository">Optional repository.</param> - /// <param name="allowPrerelease">Optional allow prerelease module.</param> - /// <returns>Module info, null if not found.</returns> - PSObject? FindModule( - PowerShell pwsh, - string moduleName, - SemanticVersion? semanticVersion, - SemanticVersion? semanticMinVersion, - SemanticVersion? semanticMaxVersion, - string? repository, - bool? allowPrerelease); - - /// <summary> - /// Calls Find-DscResource. - /// </summary> - /// <param name="pwsh">PowerShell instance.</param> - /// <param name="resourceName">resource name.</param> - /// <param name="moduleName">Optional module name.</param> - /// <param name="semanticVersion">Optional version.</param> - /// <param name="semanticMinVersion">Optional min version.</param> - /// <param name="semanticMaxVersion">Optional max version.</param> - /// <param name="repository">Optional repository.</param> - /// <param name="allowPrerelease">Optional allow prerelease module.</param> - /// <returns>Dsc Resource info, null if not found.</returns> - PSObject? FindDscResource( - PowerShell pwsh, - string resourceName, - string? moduleName, - SemanticVersion? semanticVersion, - SemanticVersion? semanticMinVersion, - SemanticVersion? semanticMaxVersion, - string? repository, - bool? allowPrerelease); - - /// <summary> - /// Calls Save-Module with module specification. - /// </summary> - /// <param name="pwsh">PowerShell instance.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <param name="location">Location to save module.</param> - void SaveModule(PowerShell pwsh, ModuleSpecification moduleSpecification, string location); - - /// <summary> - /// Calls Save-Module -InputObject object -Path location. - /// Input object must be the result of Find cmdlets of PowerShellGet. - /// </summary> - /// <param name="pwsh">PowerShell instance.</param> - /// <param name="inputObject">Input object.</param> - /// <param name="location">Location to save module.</param> - void SaveModule(PowerShell pwsh, PSObject inputObject, string location); - - /// <summary> - /// Calls Install-Module -InputObject object. - /// Input object must be the result of Find cmdlets of PowerShellGet. - /// </summary> - /// <param name="pwsh">PowerShell instance.</param> - /// <param name="inputObject">Input object.</param> - /// <param name="allUsers">If to install to all users.</param> - void InstallModule(PowerShell pwsh, PSObject inputObject, bool allUsers); - - /// <summary> - /// Calls Install-Module with a module specification. - /// </summary> - /// <param name="pwsh">PowerShell instance.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <param name="allUsers">If to install to all users.</param> - void InstallModule(PowerShell pwsh, ModuleSpecification moduleSpecification, bool allUsers); - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellGetV2.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellGetV2.cs @@ -1,249 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="PowerShellGetV2.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using Microsoft.PowerShell.Commands; - using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; - - /// <summary> - /// PowerShellGet implementation for 2.2.5 . - /// </summary> - internal class PowerShellGetV2 : IPowerShellGet - { - private const string AllUsers = "AllUsers"; - - /// <inheritdoc/> - public PSObject? FindModule( - PowerShell pwsh, - string moduleName, - SemanticVersion? semanticVersion, - SemanticVersion? semanticMinVersion, - SemanticVersion? semanticMaxVersion, - string? repository, - bool? allowPrerelease) - { - bool implicitAllowPrerelease = false; - - var parameters = new Dictionary<string, object>() - { - { Parameters.Name, moduleName }, - }; - - if (semanticVersion != null) - { - implicitAllowPrerelease |= semanticVersion.IsPrerelease; - parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); - } - - if (semanticMinVersion != null) - { - implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; - parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); - } - - if (semanticMaxVersion != null) - { - implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; - parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); - } - - if (!string.IsNullOrEmpty(repository)) - { - parameters.Add(Parameters.Repository, repository); - } - - if (allowPrerelease.HasValue || implicitAllowPrerelease) - { - // If explicit allowPrerelease = false don't use implicit. - bool allow = allowPrerelease ?? implicitAllowPrerelease; - parameters.Add(Parameters.AllowPrerelease, allow); - } - - pwsh.AddCommand(Commands.FindModule) - .AddParameters(parameters); - - return pwsh.Invoke().FirstOrDefault(); - } - - /// <inheritdoc/> - public PSObject? FindDscResource( - PowerShell pwsh, - string resourceName, - string? moduleName, - SemanticVersion? semanticVersion, - SemanticVersion? semanticMinVersion, - SemanticVersion? semanticMaxVersion, - string? repository, - bool? allowPrerelease) - { - var parameters = new Dictionary<string, object>() - { - { Parameters.Name, resourceName }, - }; - - bool implicitAllowPrerelease = false; - - if (!string.IsNullOrEmpty(moduleName)) - { - parameters.Add(Parameters.ModuleName, moduleName); - } - - if (semanticVersion != null) - { - implicitAllowPrerelease |= semanticVersion.IsPrerelease; - parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); - } - - if (semanticMinVersion != null) - { - implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; - parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); - } - - if (semanticMaxVersion != null) - { - implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; - parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); - } - - if (!string.IsNullOrEmpty(repository)) - { - parameters.Add(Parameters.Repository, repository); - } - - if (allowPrerelease.HasValue || implicitAllowPrerelease) - { - // If explicit allowPrerelease = false don't use implicit. - bool allow = allowPrerelease ?? implicitAllowPrerelease; - parameters.Add(Parameters.AllowPrerelease, allow); - } - - pwsh.AddCommand(Commands.FindDscResource) - .AddParameters(parameters); - - // The result is just a PSCustomObject with a type name of Microsoft.PowerShell.Commands.PSGetDscResourceInfo. - // When no module is passed and a resource is not found, this will return an empty list. If a module - // is specified and no resource is found then it will fail earlier because of a Write-Error. - return pwsh.Invoke().FirstOrDefault(); - } - - /// <inheritdoc/> - public void SaveModule( - PowerShell pwsh, - ModuleSpecification moduleSpecification, - string location) - { - var parameters = new Dictionary<string, object>() - { - { Parameters.Name, moduleSpecification.Name }, - { Parameters.Path, location }, - }; - - if (moduleSpecification.Version is not null) - { - parameters.Add(Parameters.MinimumVersion, moduleSpecification.Version); - } - - if (moduleSpecification.MaximumVersion is not null) - { - parameters.Add(Parameters.MaximumVersion, moduleSpecification.MaximumVersion); - } - - if (moduleSpecification.RequiredVersion is not null) - { - parameters.Add(Parameters.RequiredVersion, moduleSpecification.RequiredVersion); - } - - _ = pwsh.AddCommand(Commands.SaveModule) - .AddParameters(parameters) - .AddParameter(Parameters.Force) - .Invoke(); - } - - /// <inheritdoc/> - public void SaveModule( - PowerShell pwsh, - PSObject inputObject, - string location) - { - _ = pwsh.AddCommand(Commands.SaveModule) - .AddParameter(Parameters.Path, location) - .AddParameter(Parameters.InputObject, inputObject) - .AddParameter(Parameters.Force) - .Invoke(); - } - - /// <inheritdoc/> - public void InstallModule( - PowerShell pwsh, - PSObject inputObject, - bool allUsers) - { - var parameters = new Dictionary<string, object>() - { - { Parameters.InputObject, inputObject }, - }; - - if (allUsers) - { - parameters.Add(Parameters.Scope, AllUsers); - } - - // If the repository is untrusted, it will fail with: - // Microsoft.PowerShell.Commands.WriteErrorException : Exception calling "ShouldContinue" with "5" - // argument(s): "A command that prompts the user failed because the host program or the command type - // does not support user interaction. - // If its trusted, PowerShellGets adds the Force parameter to the call to PackageManager\Install-Package. - // TODO: Once we have policies, we should remove Force. For hosted environments and depending - // on the policy we will trust PSGallery when we create the Runspace or add Force here. - _ = pwsh.AddCommand(Commands.InstallModule) - .AddParameters(parameters) - .AddParameter(Parameters.Force) - .Invoke(); - } - - /// <inheritdoc/> - public void InstallModule( - PowerShell pwsh, - ModuleSpecification moduleSpecification, - bool allUsers) - { - var parameters = new Dictionary<string, object>() - { - { Parameters.Name, moduleSpecification.Name }, - }; - - if (moduleSpecification.Version is not null) - { - parameters.Add(Parameters.MinimumVersion, moduleSpecification.Version); - } - - if (moduleSpecification.MaximumVersion is not null) - { - parameters.Add(Parameters.MaximumVersion, moduleSpecification.MaximumVersion); - } - - if (moduleSpecification.RequiredVersion is not null) - { - parameters.Add(Parameters.RequiredVersion, moduleSpecification.RequiredVersion); - } - - if (allUsers) - { - parameters.Add(Parameters.Scope, AllUsers); - } - - _ = pwsh.AddCommand(Commands.InstallModule) - .AddParameters(parameters) - .AddParameter(Parameters.Force) - .Invoke(); - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellHelpers.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellHelpers.cs @@ -1,82 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="PowerShellHelpers.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System.Collections; - using Microsoft.PowerShell.Commands; - using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; - - /// <summary> - /// General PowerShell helpers. - /// </summary> - internal static class PowerShellHelpers - { - /// <summary> - /// Creates a module specification object. - /// </summary> - /// <param name="moduleName">Module name.</param> - /// <param name="version">Optional version.</param> - /// <param name="minVersion">Optional min version.</param> - /// <param name="maxVersion">Optional max version.</param> - /// <param name="guid">Optional guid.</param> - /// <returns>ModuleSpecification.</returns> - public static ModuleSpecification CreateModuleSpecification( - string moduleName, - string? version = null, - string? minVersion = null, - string? maxVersion = null, - string? guid = null) - { - if (version is null && - minVersion is null && - maxVersion is null) - { - // Otherwise will fail with MissingMemberException... - return new ModuleSpecification(moduleName); - } - - var moduleInfo = new Hashtable - { - { Parameters.ModuleName, moduleName }, - }; - - if (!string.IsNullOrEmpty(version)) - { - var semanticVersion = new SemanticVersion(version); - moduleInfo.Add(Parameters.RequiredVersion, semanticVersion.Version); - } - - if (!string.IsNullOrEmpty(minVersion)) - { - var semanticVersion = new SemanticVersion(minVersion); - moduleInfo.Add(Parameters.ModuleVersion, semanticVersion.Version); - } - - if (!string.IsNullOrEmpty(maxVersion)) - { - var semanticVersion = new SemanticVersion(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, semanticVersion.Version.ToString()); - } - - if (!string.IsNullOrEmpty(guid)) - { - moduleInfo.Add(Parameters.Guid, guid); - } - - // Using the Hashtable constructor will verify that RequiredVersion is used properly. - return new ModuleSpecification(moduleInfo); - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj b/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj @@ -68,6 +68,11 @@ <CopyToOutputDirectory>Always</CopyToOutputDirectory> </ProjectReference> </ItemGroup> + + <ItemGroup> + <Folder Include="PowerShell\Unit\" /> + <Folder Include="PowerShell\Set\" /> + </ItemGroup> <PropertyGroup Condition="'$(WinGetCsWinRTEmbedded)'=='true'"> <DefineConstants>$(DefineConstants);WinGetCsWinRTEmbedded</DefineConstants> diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Constants/PowerShellConstants.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Constants/PowerShellConstants.cs @@ -0,0 +1,81 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PowerShellConstants.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Constants +{ + /// <summary> + /// Constants related to PowerShell. + /// </summary> + internal static class PowerShellConstants + { +#pragma warning disable SA1600 // ElementsMustBeDocumented + public const string Core = "Core"; + + internal static class Variables + { + public const string PSEdition = "PSEdition"; + public const string Error = "Error"; + public const string PSModulePath = "env:PSModulePath"; + } + + internal static class Modules + { + public const string PSDesiredStateConfiguration = "PSDesiredStateConfiguration"; + public const string PSDesiredStateConfigurationMinVersion = "2.0.7"; + public const string PowerShellGet = "PowerShellGet"; + public const string PowerShellGetMinVersion = "2.2.5"; + public const string PSDesiredStateConfigurationMaxVersion = "2.*"; + } + + internal static class Commands + { + public const string FindDscResource = "Find-DscResource"; + public const string GetAuthenticodeSignature = "Get-AuthenticodeSignature"; + public const string GetChildItem = "Get-ChildItem"; + public const string GetCommand = "Get-Command"; + public const string GetDscResource = "Get-DscResource"; + public const string GetInstalledModule = "Get-InstalledModule"; + public const string GetModule = "Get-Module"; + public const string ImportModule = "Import-Module"; + public const string InstallModule = "Install-Module"; + public const string InvokeDscResource = "Invoke-DscResource"; + public const string SaveModule = "Save-Module"; + public const string FindModule = "Find-Module"; + public const string ImportCliXml = "Import-CliXml"; + } + + internal static class Parameters + { + public const string AllowPrerelease = "AllowPrerelease"; + public const string Force = "Force"; + public const string FullyQualifiedName = "FullyQualifiedName"; + public const string Guid = "GUID"; + public const string InputObject = "InputObject"; + public const string ListAvailable = "ListAvailable"; + public const string MaximumVersion = "MaximumVersion"; + public const string Method = "Method"; + public const string MinimumVersion = "MinimumVersion"; + public const string Module = "Module"; + public const string ModuleName = "ModuleName"; + public const string ModuleVersion = "ModuleVersion"; + public const string Name = "Name"; + public const string Path = "Path"; + public const string Property = "Property"; + public const string Recurse = "Recurse"; + public const string Repository = "Repository"; + public const string RequiredVersion = "RequiredVersion"; + public const string Scope = "Scope"; + } + + internal static class DscMethods + { + public const string Get = "Get"; + public const string Set = "Set"; + public const string Test = "Test"; + } +#pragma warning restore SA1600 // ElementsMustBeDocumented + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/DscModuleV2.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/DscModuleV2.cs @@ -0,0 +1,297 @@ +// ----------------------------------------------------------------------------- +// <copyright file="DscModuleV2.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscModules +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Extensions; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// <summary> + /// PSDesiredStateConfiguration v2. + /// </summary> + internal class DscModuleV2 : IDscModule + { + private const string InDesiredState = "InDesiredState"; + private const string RebootRequired = "RebootRequired"; + + private static readonly IEnumerable<string> ExclusionResourcesParentPath = new string[] + { + @"C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources", + @"C:\Program Files\WindowsPowerShell\Modules\PackageManagement\1.0.0.1", + }; + + /// <summary> + /// Initializes a new instance of the <see cref="DscModuleV2"/> class. + /// </summary> + public DscModuleV2() + { + this.ModuleSpecification = PowerShellHelpers.CreateModuleSpecification( + Modules.PSDesiredStateConfiguration, + minVersion: Modules.PSDesiredStateConfigurationMinVersion, + maxVersion: Modules.PSDesiredStateConfigurationMaxVersion); + } + + /// <inheritdoc/> + public ModuleSpecification ModuleSpecification { get; private init; } + + /// <inheritdoc/> + public string GetDscResourceCmd { get; } = Commands.GetDscResource; + + /// <inheritdoc/> + public string InvokeDscResourceCmd { get; } = Commands.InvokeDscResource; + + /// <inheritdoc/> + public IReadOnlyList<DscResourceInfoInternal> GetAllDscResources(PowerShell pwsh) + { + return this.GetDscResources(pwsh, null, null); + } + + /// <inheritdoc/> + public IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule( + PowerShell pwsh, + ModuleSpecification moduleSpecification) + { + return this.GetDscResources(pwsh, null, moduleSpecification); + } + + /// <inheritdoc/> + public DscResourceInfoInternal? GetDscResource( + PowerShell pwsh, + string name, + ModuleSpecification? moduleSpecification) + { + var dscResourceInfos = this.GetDscResources(pwsh, name, moduleSpecification); + + if (dscResourceInfos.Count == 0) + { + return null; + } + else if (dscResourceInfos.Count > 1) + { + throw new GetDscResourceMultipleMatches(name, moduleSpecification); + } + + return dscResourceInfos[0]; + } + + /// <inheritdoc/> + public ValueSet InvokeGetResource( + PowerShell pwsh, + ValueSet settings, + string name, + ModuleSpecification? moduleSpecification) + { + PSObject? getResult = null; + + try + { + getResult = pwsh.AddCommand(this.InvokeDscResourceCmd) + .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) + .AddParameter(Parameters.Method, DscMethods.Get) + .InvokeAndStopOnError() + .FirstOrDefault(); + } + catch (Exception ex) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, ex); + } + + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, errorMessage); + } + + if (getResult is null) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification); + } + + // Script based resource. + if (getResult.BaseObject is Hashtable) + { + Hashtable hashTable = (Hashtable)getResult.BaseObject; + var resultSettings = new ValueSet(); + foreach (DictionaryEntry entry in hashTable) + { + resultSettings.Add(entry.Key as string, entry.Value); + } + + return resultSettings; + } + + // Class-based resource. + return TypeHelpers.GetAllPropertiesValues(getResult.BaseObject); + } + + /// <inheritdoc/> + public bool InvokeTestResource( + PowerShell pwsh, + ValueSet settings, + string name, + ModuleSpecification? moduleSpecification) + { + // Returned type is InvokeDscResourceTestResult which is a PowerShell classed defined + // in PSDesiredStateConfiguration.psm1. + dynamic? testResult = null; + + try + { + testResult = pwsh.AddCommand(this.InvokeDscResourceCmd) + .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) + .AddParameter(Parameters.Method, DscMethods.Test) + .InvokeAndStopOnError() + .FirstOrDefault(); + } + catch (Exception ex) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, ex); + } + + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, errorMessage); + } + + if (testResult is null || + !TypeHelpers.PropertyWithTypeExists<bool>(testResult, InDesiredState)) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification); + } + + return testResult?.InDesiredState; + } + + /// <inheritdoc/> + public bool InvokeSetResource( + PowerShell pwsh, + ValueSet settings, + string name, + ModuleSpecification? moduleSpecification) + { + // Returned type is InvokeDscResourceSetResult which is a PowerShell classed defined + // in PSDesiredStateConfiguration.psm1. + dynamic? setResult = null; + + try + { + setResult = pwsh.AddCommand(this.InvokeDscResourceCmd) + .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) + .AddParameter(Parameters.Method, DscMethods.Set) + .InvokeAndStopOnError() + .FirstOrDefault(); + } + catch (Exception ex) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, ex); + } + + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, errorMessage, pwsh.ContainsPropertyError()); + } + + if (setResult is null || + !TypeHelpers.PropertyWithTypeExists<bool>(setResult, RebootRequired)) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification); + } + + return setResult?.RebootRequired; + } + + private static Dictionary<string, object> PrepareInvokeParameters( + string name, + ValueSet settings, + ModuleSpecification? moduleSpecification) + { + Hashtable properties = settings.ToHashtable(); + + var parameters = new Dictionary<string, object>() + { + { Parameters.Name, name }, + { Parameters.Property, properties }, + }; + + if (moduleSpecification is not null) + { + parameters.Add(Parameters.ModuleName, moduleSpecification); + } + + return parameters; + } + + private IReadOnlyList<DscResourceInfoInternal> GetDscResources( + PowerShell pwsh, + string? name, + ModuleSpecification? moduleSpecification) + { + pwsh.AddCommand(this.GetDscResourceCmd); + + if (name is not null) + { + pwsh.AddParameter(Parameters.Name, name); + } + + if (moduleSpecification is not null) + { + pwsh.AddParameter(Parameters.Module, moduleSpecification); + } + + try + { + var resources = pwsh.Invoke(); + return this.ConvertToDscResourceInfoInternal(resources); + } + catch (RuntimeException e) + { + // Detect easily this. + if (e.ErrorRecord.FullyQualifiedErrorId == "ExceptionWhenSetting,GetResourceFromKeyword") + { + throw new GetDscResourceModuleConflict(name, moduleSpecification, e); + } + + throw; + } + } + + private List<DscResourceInfoInternal> ConvertToDscResourceInfoInternal(Collection<PSObject> psObjects) + { + var result = new List<DscResourceInfoInternal>(); + foreach (dynamic psObject in psObjects) + { + var dscResourceInfo = new DscResourceInfoInternal(psObject); + + // Explicitly don't support old DSC resources from v1 PSDesiredStateConfiguration. + // Even if the Windows System32 Windows PowerShell module path is removed they + // will show up. + if (ExclusionResourcesParentPath.Any(e => dscResourceInfo.ParentPath!.StartsWith(e))) + { + continue; + } + + result.Add(dscResourceInfo); + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/IDscModule.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/IDscModule.cs @@ -0,0 +1,89 @@ +// ----------------------------------------------------------------------------- +// <copyright file="IDscModule.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscModules +{ + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + + /// <summary> + /// Interface for defining a PSDesiredStateConfiguration module. + /// </summary> + internal interface IDscModule + { + /// <summary> + /// Gets the module specification. + /// </summary> + ModuleSpecification ModuleSpecification { get; } + + /// <summary> + /// Gets the name of the Get-DscResource Cmdlet. + /// </summary> + string GetDscResourceCmd { get; } + + /// <summary> + /// Gets the name of the Invoke-DscResource Cmdlet. + /// </summary> + string InvokeDscResourceCmd { get; } + + /// <summary> + /// Gets all DSC resource. + /// </summary> + /// <param name="pwsh">PowerShell.</param> + /// <returns>A list with the DSC resource.</returns> + IReadOnlyList<DscResourceInfoInternal> GetAllDscResources(PowerShell pwsh); + + /// <summary> + /// Gets all resources in a module. + /// </summary> + /// <param name="pwsh">PowerShell.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>List of resources of that module and version.</returns> + IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule(PowerShell pwsh, ModuleSpecification moduleSpecification); + + /// <summary> + /// Gets a DSC Resource. + /// </summary> + /// <param name="pwsh">PowerShell.</param> + /// <param name="name">Name.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>DSC Resource from that module and version.</returns> + DscResourceInfoInternal? GetDscResource(PowerShell pwsh, string name, ModuleSpecification? moduleSpecification); + + /// <summary> + /// Calls Invoke-DscResource -Method Get from this module. + /// </summary> + /// <param name="pwsh">PowerShell.</param> + /// <param name="settings">Settings.</param> + /// <param name="name">Name.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>Properties of resource.</returns> + ValueSet InvokeGetResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// <summary> + /// Calls Invoke-DscResource -Method Test from this module. + /// </summary> + /// <param name="pwsh">PowerShell.</param> + /// <param name="settings">Settings.</param> + /// <param name="name">Name.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>Is in desired state.</returns> + bool InvokeTestResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// <summary> + /// Calls Invoke-DscResource -Method Set from this module. + /// </summary> + /// <param name="pwsh">PowerShell.</param> + /// <param name="settings">Settings.</param> + /// <param name="name">Name.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>If a reboot is required.</returns> + bool InvokeSetResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourceInfoInternal.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourceInfoInternal.cs @@ -0,0 +1,157 @@ +// ----------------------------------------------------------------------------- +// <copyright file="DscResourceInfoInternal.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Helpers; + + /// <summary> + /// Contains a DSC resource information. + /// This object should be initialized with a Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo. + /// That object is the result of Get-DscResource and is not the same as System.Management.Automation.DscResourceInfo. + /// The type is defined in a psd1 in the PSDesiredStateConfiguration. This file is based on that. + /// If Invoke-DscResource gets support for passing the DscResourceInfo, we could keep the original object as member + /// to then pass it in code. + /// </summary> + internal class DscResourceInfoInternal + { + private const string DscResourceInfoFullName = "Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo"; + + /// <summary> + /// Initializes a new instance of the <see cref="DscResourceInfoInternal"/> class. + /// </summary> + /// <param name="info">Dynamic object. Expected DscResourceInfo.</param> + public DscResourceInfoInternal(dynamic info) + { + if (info.GetType().FullName != DscResourceInfoFullName) + { + throw new ArgumentException(); + } + + this.ResourceType = info.ResourceType; + this.Name = info.Name; + this.FriendlyName = info.FriendlyName; + this.Module = info.Module; + this.Path = info.Path; + this.ParentPath = info.ParentPath; + this.ImplementedAs = Enum.Parse<ImplementedAsTypeInternal>(info.ImplementedAs.ToString()); + this.CompanyName = info.CompanyName; + + if (this.Module is not null) + { + this.ModuleName = this.Module.Name; + this.Version = this.Module.Version; + } + + foreach (object property in info.Properties) + { + this.Properties.Add(new DscResourcePropertyInfoInternal(property)); + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="DscResourceInfoInternal"/> class. + /// Used for unit tests. + /// </summary> + /// <param name="name">Resource name.</param> + /// <param name="moduleName">Module name.</param> + /// <param name="version">Version.</param> + internal DscResourceInfoInternal(string name, string? moduleName, Version? version) + { + this.Name = name; + this.ModuleName = moduleName; + this.Version = version; + } + + /// <summary> + /// Gets or sets resource type name. + /// </summary> + public string? ResourceType { get; set; } + + /// <summary> + /// Gets or sets Name of the resource. This name is used to access the resource. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets the normalized resource name. + /// </summary> + public string NormalizedName + { + get { return StringHelpers.Normalize(this.Name); } + } + + /// <summary> + /// Gets or sets friendly name defined for the resource. + /// </summary> + public string? FriendlyName { get; set; } + + /// <summary> + /// Gets or sets module which implements the resource. This could point to parent module, if the DSC resource is implemented. + /// by one of nested modules. + /// </summary> + public PSModuleInfo? Module { get; set; } + + /// <summary> + /// Gets or sets name of the module which implements the resource. + /// </summary> + public string? ModuleName { get; set; } + + /// <summary> + /// Gets the normalized module name. + /// </summary> + public string? NormalizedModuleName + { + get { return this.ModuleName is not null ? StringHelpers.Normalize(this.ModuleName) : null; } + } + + /// <summary> + /// Gets or sets version of the module which implements the resource. + /// </summary> + public Version? Version { get; set; } + + /// <summary> + /// Gets or sets of the file which implements the resource. For the resources which are defined using + /// MOF file, this will be path to a module which resides in the same folder where schema.mof file is present. + /// For composite resources, this will be the module which implements the resource. + /// </summary> + public string? Path { get; set; } + + /// <summary> + /// Gets or sets parent folder, where the resource is defined + /// It is the folder containing either the implementing module(=Path) or folder containing ".schema.mof". + /// For native providers, Path will be null and only ParentPath will be present. + /// </summary> + public string? ParentPath { get; set; } + + /// <summary> + /// Gets or sets a value which indicate how DSC resource is implemented. + /// </summary> + public ImplementedAsTypeInternal? ImplementedAs { get; set; } + + /// <summary> + /// Gets or sets company which owns this resource. + /// </summary> + public string? CompanyName { get; set; } + + /// <summary> + /// Gets properties of the resource. + /// </summary> + public List<DscResourcePropertyInfoInternal> Properties { get; private set; } = new List<DscResourcePropertyInfoInternal>(); + + /// <summary> + /// Updates properties of the resource. + /// </summary> + /// <param name="properties">Updated properties.</param> + public void UpdateProperties(List<DscResourcePropertyInfoInternal> properties) + { + this.Properties = properties; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourcePropertyInfoInternal.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourcePropertyInfoInternal.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------------- +// <copyright file="DscResourcePropertyInfoInternal.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Contains a DSC resource property information. + /// </summary> + internal sealed class DscResourcePropertyInfoInternal + { + private const string DscResourcePropertyInfoFullName = "Microsoft.PowerShell.DesiredStateConfiguration.DscResourcePropertyInfo"; + + /// <summary> + /// Initializes a new instance of the <see cref="DscResourcePropertyInfoInternal"/> class. + /// </summary> + /// <param name="dscPropertyInfo">Dynamic DSC property info.</param> + public DscResourcePropertyInfoInternal(dynamic dscPropertyInfo) + { + if (dscPropertyInfo.GetType().FullName != DscResourcePropertyInfoFullName) + { + throw new ArgumentException(); + } + + this.Name = dscPropertyInfo.Name; + this.PropertyType = dscPropertyInfo.PropertyType; + this.IsMandatory = dscPropertyInfo.IsMandatory; + this.Values = dscPropertyInfo.Values; + } + + /// <summary> + /// Gets or sets name of the property. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets type of the property. + /// </summary> + public string PropertyType { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the property is mandatory or not. + /// </summary> + public bool IsMandatory { get; set; } + + /// <summary> + /// Gets Values for a resource property. + /// </summary> + public List<string> Values { get; private set; } = new List<string>(); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/ImplementedAsTypeInternal.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/ImplementedAsTypeInternal.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ImplementedAsTypeInternal.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo +{ + /// <summary> + /// Enumerated values for DSC resource implementation type. + /// </summary> + internal enum ImplementedAsTypeInternal + { + /// <summary> + /// DSC resource implementation type not known. + /// </summary> + None = 0, + + /// <summary> + /// DSC resource is implemented using PowerShell module. + /// </summary> + PowerShell = 1, + + /// <summary> + /// DSC resource is implemented using a CIM provider. + /// </summary> + Binary = 2, + + /// <summary> + /// DSC resource is a composite and implemented using configuration keyword. + /// </summary> + Composite = 3, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Extensions/PowerShellExtensions.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Extensions/PowerShellExtensions.cs @@ -0,0 +1,96 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PowerShellExtensions.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Extensions +{ + using System.Collections.ObjectModel; + using System.Management.Automation; + using System.Text; + + /// <summary> + /// Extensions methods for <see cref="PowerShell"/> class. + /// </summary> + internal static class PowerShellExtensions + { + /// <summary> + /// Calls Invoke with <see cref="ActionPreference.Stop"/> to stop execution of commands. + /// </summary> + /// <param name="pwsh">PowerShell.</param> + /// <returns>Collection of PSObjects representing output.</returns> + public static Collection<PSObject> InvokeAndStopOnError(this PowerShell pwsh) + { + var settings = new PSInvocationSettings() + { + ErrorActionPreference = ActionPreference.Stop, + }; + + return pwsh.Invoke(null, settings); + } + + /// <summary> + /// Calls Invoke with <see cref="ActionPreference.Stop"/> to stop execution of commands. + /// </summary> + /// <typeparam name="T">Type of output object(s) expected from the command invocation.</typeparam> + /// <param name="pwsh">PowerShell.</param> + /// <returns>Collection of the type output representing output.</returns> + public static Collection<T> InvokeAndStopOnError<T>(this PowerShell pwsh) + { + var settings = new PSInvocationSettings() + { + ErrorActionPreference = ActionPreference.Stop, + }; + + return pwsh.Invoke<T>(null, settings); + } + + /// <summary> + /// Gets the error stream message if any. + /// </summary> + /// <param name="pwsh">PowerShell.</param> + /// <returns>Error message. Null if none.</returns> + public static string? GetErrorMessage(this PowerShell pwsh) + { + if (pwsh.HadErrors) + { + var psStreamBuilder = new StringBuilder(); + foreach (var line in pwsh.Streams.Error) + { + psStreamBuilder.AppendLine(line.ToString()); + } + + return psStreamBuilder.ToString(); + } + + return null; + } + + /// <summary> + /// Determines if the given shell contains a property error, meaning that the source of this error is the + /// configuration values and not the configuration unit itself. + /// </summary> + /// <param name="pwsh">The shell to inspect.</param> + /// <returns>True if it only contains property errors; false otherwise.</returns> + public static bool ContainsPropertyError(this PowerShell pwsh) + { + if (!pwsh.HadErrors) + { + return false; + } + + bool result = true; + + foreach (ErrorRecord? error in pwsh.Streams.Error) + { + if (error?.FullyQualifiedErrorId == "PropertyAssignmentException") + { + result = result && true; + } + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndModule.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndModule.cs @@ -0,0 +1,49 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigurationUnitAndModule.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Constants; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.PowerShell.Commands; + + /// <summary> + /// Contains information about the unit and the DSC resource that applies to it. + /// </summary> + internal class ConfigurationUnitAndModule : ConfigurationUnitInternal + { + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationUnitAndModule"/> class. + /// </summary> + /// <param name="unit">Configuration unit.</param> + /// <param name="configurationFilePath">The configuration file path.</param> + public ConfigurationUnitAndModule(ConfigurationUnit unit, string? configurationFilePath) + : base(unit, configurationFilePath) + { + string? moduleName = this.GetDirective<string>(DirectiveConstants.Module); + if (string.IsNullOrEmpty(moduleName)) + { + this.Module = null; + } + else + { + this.Module = PowerShellHelpers.CreateModuleSpecification( + moduleName, + this.GetDirective<string>(DirectiveConstants.Version), + this.GetDirective<string>(DirectiveConstants.MinVersion), + this.GetDirective<string>(DirectiveConstants.MaxVersion), + this.GetDirective<string>(DirectiveConstants.ModuleGuid)); + } + } + + /// <summary> + /// Gets the module specification. + /// </summary> + public ModuleSpecification? Module { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndResource.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndResource.cs @@ -0,0 +1,102 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigurationUnitAndResource.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + + /// <summary> + /// Contains information about the unit and the DSC resource that applies to it. + /// </summary> + internal class ConfigurationUnitAndResource + { + private readonly DscResourceInfoInternal dscResourceInfoInternal; + + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationUnitAndResource"/> class. + /// </summary> + /// <param name="configurationUnitInternal">Configuration unit internal.</param> + /// <param name="dscResourceInfoInternal">DscResourceInfoInternal.</param> + public ConfigurationUnitAndResource( + ConfigurationUnitAndModule configurationUnitInternal, + DscResourceInfoInternal dscResourceInfoInternal) + { + if (!configurationUnitInternal.ResourceName.Equals(dscResourceInfoInternal.Name, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException(); + } + + this.UnitInternal = configurationUnitInternal; + this.dscResourceInfoInternal = dscResourceInfoInternal; + } + + /// <summary> + /// Gets or initializes the internal unit. + /// </summary> + public ConfigurationUnitAndModule UnitInternal { get; private init; } + + /// <summary> + /// Gets the configuration unit. + /// </summary> + public ConfigurationUnit Unit + { + get { return this.UnitInternal.Unit; } + } + + /// <summary> + /// Gets the DSC resource name. + /// </summary> + /// <returns>DSC resource name.</returns> + public string ResourceName + { + get { return this.dscResourceInfoInternal.Name; } + } + + /// <summary> + /// Gets the module specification. + /// </summary> + public ModuleSpecification? Module + { + get { return this.UnitInternal.Module; } + } + + /// <summary> + /// Gets a directive if exits. + /// </summary> + /// <param name="directiveName">Name of directive.</param> + /// <returns>The value of the directive. Null if doesn't exist.</returns> + /// <typeparam name="TType">Directive type value.</typeparam> + public TType? GetDirective<TType>(string directiveName) + where TType : class + { + return this.UnitInternal.GetDirective<TType>(directiveName); + } + + /// <summary> + /// Gets a directive bool value. + /// </summary> + /// <param name="directiveName">Name of directive.</param> + /// <returns>The value of the directive. False if not set.</returns> + public bool? GetDirective(string directiveName) + { + return this.UnitInternal.GetDirective(directiveName); + } + + /// <summary> + /// Gets the settings of the unit. + /// </summary> + /// <returns>ValueSet with settings.</returns> + public ValueSet GetSettings() + { + return this.UnitInternal.GetExpandedSettings(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/DscResourcesMap.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/DscResourcesMap.cs @@ -0,0 +1,224 @@ +// ----------------------------------------------------------------------------- +// <copyright file="DscResourcesMap.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + + /// <summary> + /// DscResources in the system. Get-DscResource looks the entire PSModulePath to find resources every time, so + /// calling it multiples times is expensive. This class will store them in memory per configuration set. + /// The resources are stored in a dictionary in the following hierarchy. + /// - Module name + /// - Resource name + /// - Module Version + /// - DscResourceInfo obj. + /// There are some resources that don't have a module name. These are modules that live under + /// C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources. + /// This is not currently used. For now, we let PowerShell handle all the versioning. In order to avoid + /// calls to Get-DscResource every time a unit is set, this class needs to be updated to handle min and max + /// version. As it is, it just does RequiredVersion. + /// </summary> + internal sealed class DscResourcesMap + { + private readonly Dictionary<string, ModuleDscResources> resources = new Dictionary<string, ModuleDscResources>(); + + /// <summary> + /// Initializes a new instance of the <see cref="DscResourcesMap"/> class. + /// </summary> + /// <param name="resources">DSC Resources.</param> + public DscResourcesMap(IReadOnlyList<DscResourceInfoInternal> resources) + { + foreach (var resource in resources) + { + if (string.IsNullOrEmpty(resource.Name)) + { + throw new ArgumentException(nameof(resource.Name)); + } + + if (resource.Version is null) + { + throw new ArgumentException(nameof(resource.Version)); + } + + this.Insert(resource); + } + } + + /// <summary> + /// Looks for a DSC resource in the specified module. + /// </summary> + /// <param name="dscResourceName">DSC resource name.</param> + /// <param name="moduleName">Optional module name.</param> + /// <param name="version">Optional module version.</param> + /// <returns>DscResourceInfoInternal. Null if not found.</returns> + public DscResourceInfoInternal? GetResource(string dscResourceName, string? moduleName, Version? version) + { + string normalizedResourceName = StringHelpers.Normalize(dscResourceName); + string? normalizedModuleName = null; + if (moduleName is not null) + { + normalizedModuleName = StringHelpers.Normalize(moduleName); + } + + if (normalizedModuleName is null) + { + foreach (var module in this.resources.Values) + { + var resourceInfo = module.GetDscResourceInfo(normalizedResourceName, version); + if (resourceInfo is not null) + { + return resourceInfo; + } + } + } + else + { + if (this.resources.TryGetValue(normalizedModuleName, out ModuleDscResources? module)) + { + return module.GetDscResourceInfo(normalizedResourceName, version); + } + } + + return null; + } + + /// <summary> + /// Does a DSC resource exists. + /// </summary> + /// <param name="dscResourceName">DSC resource name.</param> + /// <param name="moduleName">Optional module name.</param> + /// <param name="version">Optional module version.</param> + /// <returns>True if resource exists.</returns> + public bool Exists(string dscResourceName, string? moduleName, Version? version) + { + return this.GetResource(dscResourceName, moduleName, version) != null; + } + + private void Insert(DscResourceInfoInternal dscResourceInfo) + { + string? normalizedModuleName = dscResourceInfo.NormalizedModuleName; + + // There are some resources that doesn't have module, use empty as key. + string moduleKey = string.Empty; + if (normalizedModuleName is not null) + { + moduleKey = normalizedModuleName; + } + + if (!this.resources.ContainsKey(moduleKey)) + { + this.resources.Add(moduleKey, new ModuleDscResources(moduleKey, dscResourceInfo)); + } + else + { + this.resources[moduleKey].AddResource(moduleKey, dscResourceInfo); + } + } + + /// <summary> + /// Represents a map versions for a DscResource. + /// </summary> + private class DscResourceVersions : Dictionary<Version, DscResourceInfoInternal> + { + private readonly string resourceName; + + public DscResourceVersions(DscResourceInfoInternal dscResourceInfo) + { + this.resourceName = dscResourceInfo.NormalizedName; + this.AddVersion(dscResourceInfo); + } + + /// <summary> + /// Adds a resource version. + /// </summary> + /// <param name="dscResourceInfo">DscResourceInfo.</param> + public void AddVersion(DscResourceInfoInternal dscResourceInfo) + { + if (this.resourceName != dscResourceInfo.NormalizedName) + { + throw new ArgumentException(dscResourceInfo.NormalizedName); + } + + // There are some system resources without version or module name. + if (dscResourceInfo.Version is null) + { + this.Add(new Version(), dscResourceInfo); + } + else + { + // Get-DscResource will fail if the same module with same + // resources and same version exists in the module path + // so this shouldn't happen. Either way, we should throw there or here. + this.Add(dscResourceInfo.Version, dscResourceInfo); + } + } + + public DscResourceInfoInternal? GetDscResourceInfo(Version? version) + { + if (version is null) + { + // Sort keys, get the latest. + var newestVersion = this.Keys.OrderByDescending(v => v).First(); + return this[newestVersion]; + } + + if (this.TryGetValue(version, out DscResourceInfoInternal? resourceInfo)) + { + return resourceInfo; + } + + return null; + } + } + + /// <summary> + /// Represent a map of resources for a module. + /// </summary> + private class ModuleDscResources : Dictionary<string, DscResourceVersions> + { + private readonly string normalizedModuleName; + + public ModuleDscResources(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) + { + this.normalizedModuleName = normalizedModuleName; + this.AddResource(this.normalizedModuleName, dscResourceInfo); + } + + public void AddResource(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) + { + if (this.normalizedModuleName != normalizedModuleName) + { + throw new ArgumentException(nameof(normalizedModuleName)); + } + + var normalizedResourceName = dscResourceInfo.NormalizedName; + if (!this.ContainsKey(normalizedResourceName)) + { + this.Add(normalizedResourceName, new DscResourceVersions(dscResourceInfo)); + } + else + { + this[normalizedResourceName].AddVersion(dscResourceInfo); + } + } + + public DscResourceInfoInternal? GetDscResourceInfo(string dscResourceName, Version? version) + { + if (this.TryGetValue(dscResourceName, out DscResourceVersions? versions)) + { + return versions.GetDscResourceInfo(version); + } + + return null; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/Factory.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/Factory.cs @@ -0,0 +1,223 @@ +// ----------------------------------------------------------------------------- +// <copyright file="Factory.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.Unit; + using Windows.Security.Cryptography.Certificates; + + /// <summary> + /// Enables creation of PowerShell specific instances of configuration interfaces. + /// </summary> + internal static class Factory + { + private static readonly IEnumerable<string> PublicRepositories = new string[] + { + "https://www.powershellgallery.com/api/v2", + }; + + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationUnitProcessorDetails"/> class. + /// </summary> + /// <param name="unitName">Unit name.</param> + /// <param name="dscResourceInfo">DSC Resource Info.</param> + /// <param name="psModuleInfo">PSModuleInfo.</param> + /// <param name="getModuleInfo">GetModuleInfo.</param> + /// <param name="certs">List of certificates.</param> + /// <returns>An initialized instance of <see cref="ConfigurationUnitProcessorDetails"/>.</returns> + public static ConfigurationUnitProcessorDetails CreateUnitProcessorDetails( + string unitName, + DscResourceInfoInternal? dscResourceInfo, + PSModuleInfo? psModuleInfo, + PSObject? getModuleInfo, + List<Certificate>? certs) + { + if (dscResourceInfo is null && + psModuleInfo is null && + getModuleInfo is null) + { + throw new ArgumentException(); + } + + ConfigurationUnitProcessorDetails result = new ConfigurationUnitProcessorDetails + { + UnitType = unitName, + }; + + if (dscResourceInfo is not null) + { + result.IsLocal = true; + + var settings = new List<IConfigurationUnitSettingDetails>(); + foreach (var properties in dscResourceInfo.Properties) + { + settings.Add(CreateUnitSettingDetails(properties)); + } + + result.Settings = settings; + + if (psModuleInfo is null) + { + result.ModuleName = dscResourceInfo.ModuleName; + result.Version = dscResourceInfo.Version is not null ? dscResourceInfo.Version.ToString() : null; + } + } + + if (psModuleInfo is not null) + { + result.ModuleDocumentationUri = psModuleInfo.HelpInfoUri is not null ? new Uri(psModuleInfo.HelpInfoUri) : null; + result.UnitIconUri = psModuleInfo.IconUri; + result.ModuleName = psModuleInfo.Name; + result.ModuleType = psModuleInfo.ModuleType.ToString(); + result.ModuleDescription = psModuleInfo.Description; + result.PublishedModuleUri = psModuleInfo.ProjectUri; + result.Version = psModuleInfo.Version.ToString(); + result.Author = psModuleInfo.Author; + result.Publisher = psModuleInfo.CompanyName; + } + + if (getModuleInfo is not null) + { + result.ModuleSource = TryGetPropertyAsString(getModuleInfo, "Repository"); + result.PublishedDate = TryGetPropertyFromDateTimeToDateTimeOffset(getModuleInfo, "PublishedDate"); + + var repoSourceLocation = getModuleInfo.Properties["RepositorySourceLocation"]; + if (repoSourceLocation is not null) + { + string? repoSourceLocationValue = repoSourceLocation.Value as string; + if (repoSourceLocationValue is not null) + { + result.IsPublic = PublicRepositories.Any(r => r == repoSourceLocationValue); + } + } + + if (psModuleInfo is null) + { + // Type is not the same as this PSModuleType. + result.UnitIconUri = TryGetPropertyAsUri(getModuleInfo, "IconUri"); + result.ModuleName = TryGetPropertyAsString(getModuleInfo, "Name"); + result.ModuleDescription = TryGetPropertyAsString(getModuleInfo, "Description"); + result.Version = TryGetPropertyAsString(getModuleInfo, "Version"); + result.Author = TryGetPropertyAsString(getModuleInfo, "Author"); + result.Publisher = TryGetPropertyAsString(getModuleInfo, "CompanyName"); + } + } + + result.SigningInformation = certs; + + return result; + } + + /// <summary> + /// Initializes a new instance of the <see cref="Factory"/> class. + /// </summary> + /// <param name="dscResourceInfo">DSC Resource info.</param> + /// <returns>An initialized instance of <see cref="ConfigurationUnitSettingDetails"/>.</returns> + public static ConfigurationUnitSettingDetails CreateUnitSettingDetails(DscResourcePropertyInfoInternal dscResourceInfo) + { + return new ConfigurationUnitSettingDetails + { + Identifier = dscResourceInfo.Name, + IsRequired = dscResourceInfo.IsMandatory, + Type = GetPropertyType(dscResourceInfo.PropertyType), + }; + } + + private static string? TryGetPropertyAsString(PSObject getModuleInfo, string getModuleInfoProperty) + { + var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; + if (moduleProperty is not null) + { + return moduleProperty.Value as string; + } + + return null; + } + + private static Uri? TryGetPropertyAsUri(PSObject getModuleInfo, string getModuleInfoProperty) + { + var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; + if (moduleProperty is not null) + { + string? modulePropertyString = moduleProperty.Value as string; + if (modulePropertyString is not null) + { + return new Uri(modulePropertyString); + } + } + + return null; + } + + private static DateTimeOffset TryGetPropertyFromDateTimeToDateTimeOffset(PSObject getModuleInfo, string getModuleInfoProperty) + { + var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; + if (moduleProperty is not null) + { + DateTime propertyAsDateTime; + + try + { + propertyAsDateTime = (DateTime)moduleProperty.Value; + return new DateTimeOffset(propertyAsDateTime); + } + catch + { + } + } + + return default; + } + + private static Windows.Foundation.PropertyType GetPropertyType(string propertyType) + { + switch (propertyType.ToLowerInvariant()) + { + case "[byte]": return Windows.Foundation.PropertyType.UInt8; + case "[int16]": return Windows.Foundation.PropertyType.Int16; + case "[uint16]": return Windows.Foundation.PropertyType.UInt16; + case "[int32]": return Windows.Foundation.PropertyType.Int32; + case "[uint32]": return Windows.Foundation.PropertyType.UInt32; + case "[int64]": return Windows.Foundation.PropertyType.Int64; + case "[uint64]": return Windows.Foundation.PropertyType.UInt64; + case "[single]": return Windows.Foundation.PropertyType.Single; + case "[double]": return Windows.Foundation.PropertyType.Double; + case "[char]": return Windows.Foundation.PropertyType.Char16; + case "[bool]": return Windows.Foundation.PropertyType.Boolean; + case "[string]": return Windows.Foundation.PropertyType.String; + case "[datetime]": return Windows.Foundation.PropertyType.DateTime; + case "[datetimeoffset]": return Windows.Foundation.PropertyType.DateTime; + case "[timespan]": return Windows.Foundation.PropertyType.TimeSpan; + case "[guid]": return Windows.Foundation.PropertyType.Guid; + case "[byte[]]": return Windows.Foundation.PropertyType.UInt8Array; + case "[int16[]]": return Windows.Foundation.PropertyType.Int16Array; + case "[uint16[]]": return Windows.Foundation.PropertyType.UInt16Array; + case "[int32[]]": return Windows.Foundation.PropertyType.Int32Array; + case "[uint32[]]": return Windows.Foundation.PropertyType.UInt32Array; + case "[int64[]]": return Windows.Foundation.PropertyType.Int64Array; + case "[uint64[]]": return Windows.Foundation.PropertyType.UInt64Array; + case "[single[]]": return Windows.Foundation.PropertyType.SingleArray; + case "[double[]]": return Windows.Foundation.PropertyType.DoubleArray; + case "[char[]]": return Windows.Foundation.PropertyType.Char16Array; + case "[bool[]]": return Windows.Foundation.PropertyType.BooleanArray; + case "[string[]]": return Windows.Foundation.PropertyType.StringArray; + case "[object[]]": return Windows.Foundation.PropertyType.InspectableArray; + case "[datetime[]]": return Windows.Foundation.PropertyType.DateTimeArray; + case "[datetimeoffset[]]": return Windows.Foundation.PropertyType.DateTimeArray; + case "[timespan[]]": return Windows.Foundation.PropertyType.TimeSpanArray; + case "[guid[]]": return Windows.Foundation.PropertyType.GuidArray; + + // Everything else will just be an object... + default: return Windows.Foundation.PropertyType.Inspectable; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/IPowerShellGet.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/IPowerShellGet.cs @@ -0,0 +1,94 @@ +// ----------------------------------------------------------------------------- +// <copyright file="IPowerShellGet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System.Management.Automation; + using Microsoft.PowerShell.Commands; + using SemanticVersion = Microsoft.Management.Configuration.Processor.Helpers.SemanticVersion; + + /// <summary> + /// Interface for PowerShellGet cmdlets. + /// </summary> + internal interface IPowerShellGet + { + /// <summary> + /// Calls Find-Module. + /// </summary> + /// <param name="pwsh">PowerShell instance.</param> + /// <param name="moduleName">Module name.</param> + /// <param name="semanticVersion">Optional version.</param> + /// <param name="semanticMinVersion">Optional min version.</param> + /// <param name="semanticMaxVersion">Optional max version.</param> + /// <param name="repository">Optional repository.</param> + /// <param name="allowPrerelease">Optional allow prerelease module.</param> + /// <returns>Module info, null if not found.</returns> + PSObject? FindModule( + PowerShell pwsh, + string moduleName, + SemanticVersion? semanticVersion, + SemanticVersion? semanticMinVersion, + SemanticVersion? semanticMaxVersion, + string? repository, + bool? allowPrerelease); + + /// <summary> + /// Calls Find-DscResource. + /// </summary> + /// <param name="pwsh">PowerShell instance.</param> + /// <param name="resourceName">resource name.</param> + /// <param name="moduleName">Optional module name.</param> + /// <param name="semanticVersion">Optional version.</param> + /// <param name="semanticMinVersion">Optional min version.</param> + /// <param name="semanticMaxVersion">Optional max version.</param> + /// <param name="repository">Optional repository.</param> + /// <param name="allowPrerelease">Optional allow prerelease module.</param> + /// <returns>Dsc Resource info, null if not found.</returns> + PSObject? FindDscResource( + PowerShell pwsh, + string resourceName, + string? moduleName, + SemanticVersion? semanticVersion, + SemanticVersion? semanticMinVersion, + SemanticVersion? semanticMaxVersion, + string? repository, + bool? allowPrerelease); + + /// <summary> + /// Calls Save-Module with module specification. + /// </summary> + /// <param name="pwsh">PowerShell instance.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <param name="location">Location to save module.</param> + void SaveModule(PowerShell pwsh, ModuleSpecification moduleSpecification, string location); + + /// <summary> + /// Calls Save-Module -InputObject object -Path location. + /// Input object must be the result of Find cmdlets of PowerShellGet. + /// </summary> + /// <param name="pwsh">PowerShell instance.</param> + /// <param name="inputObject">Input object.</param> + /// <param name="location">Location to save module.</param> + void SaveModule(PowerShell pwsh, PSObject inputObject, string location); + + /// <summary> + /// Calls Install-Module -InputObject object. + /// Input object must be the result of Find cmdlets of PowerShellGet. + /// </summary> + /// <param name="pwsh">PowerShell instance.</param> + /// <param name="inputObject">Input object.</param> + /// <param name="allUsers">If to install to all users.</param> + void InstallModule(PowerShell pwsh, PSObject inputObject, bool allUsers); + + /// <summary> + /// Calls Install-Module with a module specification. + /// </summary> + /// <param name="pwsh">PowerShell instance.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <param name="allUsers">If to install to all users.</param> + void InstallModule(PowerShell pwsh, ModuleSpecification moduleSpecification, bool allUsers); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellGetV2.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellGetV2.cs @@ -0,0 +1,250 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PowerShellGetV2.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using Microsoft.PowerShell.Commands; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + using SemanticVersion = Microsoft.Management.Configuration.Processor.Helpers.SemanticVersion; + + /// <summary> + /// PowerShellGet implementation for 2.2.5 . + /// </summary> + internal class PowerShellGetV2 : IPowerShellGet + { + private const string AllUsers = "AllUsers"; + + /// <inheritdoc/> + public PSObject? FindModule( + PowerShell pwsh, + string moduleName, + SemanticVersion? semanticVersion, + SemanticVersion? semanticMinVersion, + SemanticVersion? semanticMaxVersion, + string? repository, + bool? allowPrerelease) + { + bool implicitAllowPrerelease = false; + + var parameters = new Dictionary<string, object>() + { + { Parameters.Name, moduleName }, + }; + + if (semanticVersion != null) + { + implicitAllowPrerelease |= semanticVersion.IsPrerelease; + parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); + } + + if (semanticMinVersion != null) + { + implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; + parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); + } + + if (semanticMaxVersion != null) + { + implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; + parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); + } + + if (!string.IsNullOrEmpty(repository)) + { + parameters.Add(Parameters.Repository, repository); + } + + if (allowPrerelease.HasValue || implicitAllowPrerelease) + { + // If explicit allowPrerelease = false don't use implicit. + bool allow = allowPrerelease ?? implicitAllowPrerelease; + parameters.Add(Parameters.AllowPrerelease, allow); + } + + pwsh.AddCommand(Commands.FindModule) + .AddParameters(parameters); + + return pwsh.Invoke().FirstOrDefault(); + } + + /// <inheritdoc/> + public PSObject? FindDscResource( + PowerShell pwsh, + string resourceName, + string? moduleName, + SemanticVersion? semanticVersion, + SemanticVersion? semanticMinVersion, + SemanticVersion? semanticMaxVersion, + string? repository, + bool? allowPrerelease) + { + var parameters = new Dictionary<string, object>() + { + { Parameters.Name, resourceName }, + }; + + bool implicitAllowPrerelease = false; + + if (!string.IsNullOrEmpty(moduleName)) + { + parameters.Add(Parameters.ModuleName, moduleName); + } + + if (semanticVersion != null) + { + implicitAllowPrerelease |= semanticVersion.IsPrerelease; + parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); + } + + if (semanticMinVersion != null) + { + implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; + parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); + } + + if (semanticMaxVersion != null) + { + implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; + parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); + } + + if (!string.IsNullOrEmpty(repository)) + { + parameters.Add(Parameters.Repository, repository); + } + + if (allowPrerelease.HasValue || implicitAllowPrerelease) + { + // If explicit allowPrerelease = false don't use implicit. + bool allow = allowPrerelease ?? implicitAllowPrerelease; + parameters.Add(Parameters.AllowPrerelease, allow); + } + + pwsh.AddCommand(Commands.FindDscResource) + .AddParameters(parameters); + + // The result is just a PSCustomObject with a type name of Microsoft.PowerShell.Commands.PSGetDscResourceInfo. + // When no module is passed and a resource is not found, this will return an empty list. If a module + // is specified and no resource is found then it will fail earlier because of a Write-Error. + return pwsh.Invoke().FirstOrDefault(); + } + + /// <inheritdoc/> + public void SaveModule( + PowerShell pwsh, + ModuleSpecification moduleSpecification, + string location) + { + var parameters = new Dictionary<string, object>() + { + { Parameters.Name, moduleSpecification.Name }, + { Parameters.Path, location }, + }; + + if (moduleSpecification.Version is not null) + { + parameters.Add(Parameters.MinimumVersion, moduleSpecification.Version); + } + + if (moduleSpecification.MaximumVersion is not null) + { + parameters.Add(Parameters.MaximumVersion, moduleSpecification.MaximumVersion); + } + + if (moduleSpecification.RequiredVersion is not null) + { + parameters.Add(Parameters.RequiredVersion, moduleSpecification.RequiredVersion); + } + + _ = pwsh.AddCommand(Commands.SaveModule) + .AddParameters(parameters) + .AddParameter(Parameters.Force) + .Invoke(); + } + + /// <inheritdoc/> + public void SaveModule( + PowerShell pwsh, + PSObject inputObject, + string location) + { + _ = pwsh.AddCommand(Commands.SaveModule) + .AddParameter(Parameters.Path, location) + .AddParameter(Parameters.InputObject, inputObject) + .AddParameter(Parameters.Force) + .Invoke(); + } + + /// <inheritdoc/> + public void InstallModule( + PowerShell pwsh, + PSObject inputObject, + bool allUsers) + { + var parameters = new Dictionary<string, object>() + { + { Parameters.InputObject, inputObject }, + }; + + if (allUsers) + { + parameters.Add(Parameters.Scope, AllUsers); + } + + // If the repository is untrusted, it will fail with: + // Microsoft.PowerShell.Commands.WriteErrorException : Exception calling "ShouldContinue" with "5" + // argument(s): "A command that prompts the user failed because the host program or the command type + // does not support user interaction. + // If its trusted, PowerShellGets adds the Force parameter to the call to PackageManager\Install-Package. + // TODO: Once we have policies, we should remove Force. For hosted environments and depending + // on the policy we will trust PSGallery when we create the Runspace or add Force here. + _ = pwsh.AddCommand(Commands.InstallModule) + .AddParameters(parameters) + .AddParameter(Parameters.Force) + .Invoke(); + } + + /// <inheritdoc/> + public void InstallModule( + PowerShell pwsh, + ModuleSpecification moduleSpecification, + bool allUsers) + { + var parameters = new Dictionary<string, object>() + { + { Parameters.Name, moduleSpecification.Name }, + }; + + if (moduleSpecification.Version is not null) + { + parameters.Add(Parameters.MinimumVersion, moduleSpecification.Version); + } + + if (moduleSpecification.MaximumVersion is not null) + { + parameters.Add(Parameters.MaximumVersion, moduleSpecification.MaximumVersion); + } + + if (moduleSpecification.RequiredVersion is not null) + { + parameters.Add(Parameters.RequiredVersion, moduleSpecification.RequiredVersion); + } + + if (allUsers) + { + parameters.Add(Parameters.Scope, AllUsers); + } + + _ = pwsh.AddCommand(Commands.InstallModule) + .AddParameters(parameters) + .AddParameter(Parameters.Force) + .Invoke(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellHelpers.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellHelpers.cs @@ -0,0 +1,83 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PowerShellHelpers.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System.Collections; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.PowerShell.Commands; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// <summary> + /// General PowerShell helpers. + /// </summary> + internal static class PowerShellHelpers + { + /// <summary> + /// Creates a module specification object. + /// </summary> + /// <param name="moduleName">Module name.</param> + /// <param name="version">Optional version.</param> + /// <param name="minVersion">Optional min version.</param> + /// <param name="maxVersion">Optional max version.</param> + /// <param name="guid">Optional guid.</param> + /// <returns>ModuleSpecification.</returns> + public static ModuleSpecification CreateModuleSpecification( + string moduleName, + string? version = null, + string? minVersion = null, + string? maxVersion = null, + string? guid = null) + { + if (version is null && + minVersion is null && + maxVersion is null) + { + // Otherwise will fail with MissingMemberException... + return new ModuleSpecification(moduleName); + } + + var moduleInfo = new Hashtable + { + { Parameters.ModuleName, moduleName }, + }; + + if (!string.IsNullOrEmpty(version)) + { + var semanticVersion = new SemanticVersion(version); + moduleInfo.Add(Parameters.RequiredVersion, semanticVersion.Version); + } + + if (!string.IsNullOrEmpty(minVersion)) + { + var semanticVersion = new SemanticVersion(minVersion); + moduleInfo.Add(Parameters.ModuleVersion, semanticVersion.Version); + } + + if (!string.IsNullOrEmpty(maxVersion)) + { + var semanticVersion = new SemanticVersion(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, semanticVersion.Version.ToString()); + } + + if (!string.IsNullOrEmpty(guid)) + { + moduleInfo.Add(Parameters.Guid, guid); + } + + // Using the Hashtable constructor will verify that RequiredVersion is used properly. + return new ModuleSpecification(moduleInfo); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/HostedEnvironment.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/HostedEnvironment.cs @@ -0,0 +1,555 @@ +// ----------------------------------------------------------------------------- +// <copyright file="HostedEnvironment.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Runspaces +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Runtime.InteropServices.WindowsRuntime; + using Microsoft.Management.Configuration.Processor.Constants; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + using Windows.Security.Cryptography.Certificates; + using Windows.Storage.Streams; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// <summary> + /// Process environment. Provides interaction with PowerShell for a hosted environment. + /// </summary> + internal class HostedEnvironment : IProcessorEnvironment + { + private readonly PowerShellConfigurationProcessorType type; + private readonly IPowerShellGet powerShellGet; + + private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.CurrentUser; + private string? customLocation; + + /// <summary> + /// Initializes a new instance of the <see cref="HostedEnvironment"/> class. + /// </summary> + /// <param name="runspace">PowerShell Runspace.</param> + /// <param name="type">Configuration processor type.</param> + /// <param name="dscModule">IDscModule.</param> + public HostedEnvironment( + Runspace runspace, + PowerShellConfigurationProcessorType type, + IDscModule dscModule) + { + this.Runspace = runspace; + this.type = type; + this.DscModule = dscModule; + + // TODO: once v3 is release implement v3 version. + this.powerShellGet = new PowerShellGetV2(); + } + + /// <inheritdoc/> + public Runspace Runspace { get; } + + /// <summary> + /// Gets the DscModule. + /// </summary> + internal IDscModule DscModule { get; } + + /// <summary> + /// Gets or initializes the set processor factory. + /// </summary> + internal PowerShellConfigurationSetProcessorFactory? SetProcessorFactory { get; init; } + + /// <inheritdoc/> + public void ValidateRunspace() + { + // Only support PowerShell Core. + if (this.GetVariable<string>(Variables.PSEdition) != Core) + { + throw new NotSupportedException("Only PowerShell Core is supported."); + } + + // If opening a runspace has failures, like one of the modules in ImportPSModule is not found, it won't throw but + // write to the error output. This is not a fatal error, since we install PSDesiredStateConfiguration + // module if not found, so unless there's a real reason keep it in verbose. + var errors = this.GetVariable<ArrayList>(Variables.Error); + if (errors.Count > 0) + { + this.OnDiagnostics( + DiagnosticLevel.Verbose, + $"Error creating runspace '{string.Join("\n", errors.Cast<string>().ToArray())}'"); + } + + var powerShellGet = PowerShellHelpers.CreateModuleSpecification( + Modules.PowerShellGet, + minVersion: Modules.PowerShellGetMinVersion); + if (!this.ValidateModule(powerShellGet)) + { + var previousVersion = this.GetAvailableModule( + PowerShellHelpers.CreateModuleSpecification( + Modules.PowerShellGet)); + string message = $"Required '{powerShellGet}'"; + if (previousVersion is not null) + { + message += $" Found '{previousVersion.Name} {previousVersion.Version}'"; + } + + throw new NotSupportedException(message); + } + + // Make sure PSDesiredConfiguration is present. + this.InstallModule(this.DscModule.ModuleSpecification); + } + + /// <inheritdoc/> + public IReadOnlyList<DscResourceInfoInternal> GetAllDscResources() + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var results = this.DscModule.GetAllDscResources(pwsh); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return results; + } + + /// <inheritdoc/> + public IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule(ModuleSpecification moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var results = this.DscModule.GetDscResourcesInModule(pwsh, moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return results; + } + + /// <inheritdoc/> + public DscResourceInfoInternal? GetDscResource(ConfigurationUnitAndModule unitInternal) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var result = this.DscModule.GetDscResource(pwsh, unitInternal.ResourceName, unitInternal.Module); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// <inheritdoc/> + public ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var result = this.DscModule.InvokeGetResource(pwsh, settings, name, moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// <inheritdoc/> + public bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var result = this.DscModule.InvokeTestResource(pwsh, settings, name, moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// <inheritdoc/> + public bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var result = this.DscModule.InvokeSetResource(pwsh, settings, name, moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// <inheritdoc/> + public PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var moduleInfo = pwsh.AddCommand(Commands.GetModule) + .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) + .Invoke<PSModuleInfo>() + .FirstOrDefault(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return moduleInfo; + } + + /// <inheritdoc/> + public PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var moduleInfo = pwsh.AddCommand(Commands.GetModule) + .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) + .AddParameter(Parameters.ListAvailable) + .Invoke<PSModuleInfo>() + .FirstOrDefault(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return moduleInfo; + } + + /// <inheritdoc/> + public PSModuleInfo? GetAvailableModule(string path) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var moduleInfo = pwsh.AddCommand(Commands.GetModule) + .AddParameter(Parameters.Name, path) + .AddParameter(Parameters.ListAvailable) + .Invoke<PSModuleInfo>() + .FirstOrDefault(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return moduleInfo; + } + + /// <inheritdoc/> + public void ImportModule(ModuleSpecification moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + _ = pwsh.AddCommand(Commands.ImportModule) + .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) + .Invoke(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + /// <inheritdoc/> + public void ImportModule(string path) + { + if (!File.Exists(path)) + { + throw new FileNotFoundException(path); + } + + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + _ = pwsh.AddCommand(Commands.ImportModule) + .AddParameter(Parameters.Name, path) + .Invoke(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + /// <inheritdoc/> + public PSObject? GetInstalledModule(ModuleSpecification moduleSpecification) + { + // Instead of Get-InstalledModule, we look for PSGetModuleInfo.xml and serialize it + // if found. This allow us to get the information from Install-Module and Save-Module. + var module = this.GetAvailableModule(moduleSpecification); + if (module is null) + { + return null; + } + + var getModuleInfoFile = Path.Combine(module.ModuleBase, "PSGetModuleInfo.xml"); + if (!File.Exists(getModuleInfoFile)) + { + // Keep Get-InstalledModule behaviour. + return null; + } + + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var installedModule = pwsh.AddCommand(Commands.ImportCliXml) + .AddParameter(Parameters.Path, getModuleInfoFile) + .Invoke() + .FirstOrDefault(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return installedModule; + } + + /// <inheritdoc/> + public PSObject? FindModule(ConfigurationUnitInternal unitInternal) + { + // Don't use ModuleSpecification here. Each parameter is independent and + // we need version even if a module was not specified. + string? moduleName = unitInternal.GetDirective<string>(DirectiveConstants.Module); + + if (string.IsNullOrEmpty(moduleName)) + { + return null; + } + + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var result = this.powerShellGet.FindModule( + pwsh, + moduleName, + unitInternal.GetSemanticVersion(), + unitInternal.GetSemanticMinVersion(), + unitInternal.GetSemanticMaxVersion(), + unitInternal.GetDirective<string>(DirectiveConstants.Repository), + unitInternal.GetDirective(DirectiveConstants.AllowPrerelease)); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// <inheritdoc/> + public PSObject? FindDscResource(ConfigurationUnitInternal unitInternal) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var result = this.powerShellGet.FindDscResource( + pwsh, + unitInternal.ResourceName, + unitInternal.GetDirective<string>(DirectiveConstants.Module), + unitInternal.GetSemanticVersion(), + unitInternal.GetSemanticMinVersion(), + unitInternal.GetSemanticMaxVersion(), + unitInternal.GetDirective<string>(DirectiveConstants.Repository), + unitInternal.GetDirective(DirectiveConstants.AllowPrerelease)); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// <inheritdoc/> + public void SaveModule(PSObject inputObject, string location) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + this.powerShellGet.SaveModule(pwsh, inputObject, location); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + /// <inheritdoc/> + public void SaveModule(ModuleSpecification moduleSpecification, string location) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + this.powerShellGet.SaveModule(pwsh, moduleSpecification, location); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + /// <inheritdoc/> + public void InstallModule(PSObject inputObject) + { + if (this.location == PowerShellConfigurationProcessorLocation.Custom) + { + if (string.IsNullOrEmpty(this.customLocation)) + { + throw new ArgumentNullException(nameof(this.customLocation)); + } + + this.SaveModule(inputObject, this.customLocation); + } + else + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + this.powerShellGet.InstallModule(pwsh, inputObject, this.location == PowerShellConfigurationProcessorLocation.AllUsers); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + } + + /// <inheritdoc/> + public void InstallModule(ModuleSpecification moduleSpecification) + { + // Maybe is already there. + if (!this.ValidateModule(moduleSpecification)) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Installing module: {moduleSpecification.Name} ..."); + + // Ok, we have to get it. + if (this.location == PowerShellConfigurationProcessorLocation.Custom) + { + if (string.IsNullOrEmpty(this.customLocation)) + { + throw new ArgumentNullException(nameof(this.customLocation)); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling save module ..."); + this.SaveModule(moduleSpecification, this.customLocation); + } + else + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling install module ..."); + using PowerShell pwsh = PowerShell.Create(this.Runspace); + this.powerShellGet.InstallModule(pwsh, moduleSpecification, this.location == PowerShellConfigurationProcessorLocation.AllUsers); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module installed."); + } + } + + /// <inheritdoc/> + public List<Certificate> GetCertsOfValidSignedFiles(string[] paths) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var signatures = pwsh.AddCommand(Commands.GetChildItem) + .AddParameter(Parameters.Path, paths) + .AddCommand(Commands.GetAuthenticodeSignature) + .Invoke<Signature>(); + + var thumbprint = new HashSet<string>(); + var certificates = new List<Certificate>(); + foreach (var signature in signatures) + { + if (signature.Status == SignatureStatus.Valid) + { + if (thumbprint.Add(signature.SignerCertificate.Thumbprint)) + { + IBuffer buffer = signature.SignerCertificate.GetRawCertData().AsBuffer(); + certificates.Add(new Certificate(buffer)); + } + } + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return certificates; + } + + /// <inheritdoc/> + public TType GetVariable<TType>(string name) + { + return (TType)this.Runspace.SessionStateProxy.PSVariable.GetValue(name); + } + + /// <inheritdoc/> + public void SetVariable(string name, object value) + { + this.Runspace.SessionStateProxy.PSVariable.Set(name, value); + } + + /// <inheritdoc/> + public void SetPSModulePath(string path) + { + this.SetVariable(Variables.PSModulePath, path); + } + + /// <inheritdoc/> + public void SetPSModulePaths(IReadOnlyList<string> paths) + { + this.SetVariable(Variables.PSModulePath, string.Join(";", paths)); + } + + /// <inheritdoc/> + public void PrependPSModulePath(string path) + { + var oldModulePath = this.GetModulePaths(); + if (!oldModulePath.Contains(path)) + { + this.SetPSModulePath($"{path};{string.Join(";", oldModulePath)}"); + } + } + + /// <inheritdoc/> + public void PrependPSModulePaths(IReadOnlyList<string> paths) + { + var newPaths = paths.ToList(); + var oldModulePath = this.GetModulePaths(); + foreach (var newPath in paths) + { + if (oldModulePath.Contains(newPath)) + { + newPaths.Remove(newPath); + } + } + + if (newPaths.Any()) + { + this.SetPSModulePath($"{string.Join(";", newPaths)};{string.Join(";", oldModulePath)}"); + } + } + + /// <inheritdoc/> + public void AppendPSModulePath(string path) + { + var oldModulePath = this.GetModulePaths(); + if (!oldModulePath.Contains(path)) + { + this.SetPSModulePath($"{string.Join(";", oldModulePath)};{path}"); + } + } + + /// <inheritdoc/> + public void AppendPSModulePaths(IReadOnlyList<string> paths) + { + var newPaths = paths.ToList(); + var oldModulePath = this.GetModulePaths(); + foreach (var newPath in paths) + { + if (oldModulePath.Contains(newPath)) + { + newPaths.Remove(newPath); + } + } + + if (newPaths.Any()) + { + this.SetPSModulePath($"{string.Join(";", oldModulePath)};{string.Join(";", newPaths)}"); + } + } + + /// <inheritdoc/> + public void CleanupPSModulePath(string path) + { + string newModulePath = this.GetVariable<string>(Variables.PSModulePath) + .Replace($"{path};", null) + .Replace($";{path}", null); + + this.SetPSModulePath(newModulePath); + } + + /// <inheritdoc/> + public void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation) + { + this.location = location; + if (this.location == PowerShellConfigurationProcessorLocation.Custom) + { + if (string.IsNullOrEmpty(customLocation)) + { + throw new ArgumentNullException(nameof(customLocation)); + } + + this.customLocation = customLocation; + } + } + + private bool ValidateModule(ModuleSpecification moduleSpecification) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Validating module: {moduleSpecification.Name} ..."); + + var loadedModule = this.GetImportedModule(moduleSpecification); + if (loadedModule is not null) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is already imported."); + return true; + } + + var availableModule = this.GetAvailableModule(moduleSpecification); + if (availableModule is not null) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is available, importing ..."); + this.ImportModule(moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module imported."); + return true; + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module not found."); + return false; + } + + private void OnDiagnostics(DiagnosticLevel level, PowerShell pwsh) + { + this.SetProcessorFactory?.OnDiagnostics(level, pwsh); + } + + private void OnDiagnostics(DiagnosticLevel level, string message) + { + this.SetProcessorFactory?.OnDiagnostics(level, message); + } + + private HashSet<string> GetModulePaths() + { + return this.GetVariable<string>(Variables.PSModulePath).Split(";").ToHashSet<string>(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/IProcessorEnvironment.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/IProcessorEnvironment.cs @@ -0,0 +1,233 @@ +// ----------------------------------------------------------------------------- +// <copyright file="IProcessorEnvironment.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments +{ + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + using Windows.Security.Cryptography.Certificates; + + /// <summary> + /// IProcessorEnvironment. Provides interaction with PowerShell. + /// </summary> + internal interface IProcessorEnvironment + { + /// <summary> + /// Gets the runspace. + /// </summary> + System.Management.Automation.Runspaces.Runspace Runspace { get; } + + /// <summary> + /// Validates the runspace. + /// </summary> + void ValidateRunspace(); + + /// <summary> + /// Gets all DSC resource. + /// </summary> + /// <returns>A list with the DSC resource.</returns> + IReadOnlyList<DscResourceInfoInternal> GetAllDscResources(); + + /// <summary> + /// Gets all resources in a module. + /// </summary> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>List of resources.</returns> + IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule(ModuleSpecification moduleSpecification); + + /// <summary> + /// Gets a DSC Resource. + /// </summary> + /// <param name="unitInternal">Configuration unit internal.</param> + /// <returns>DSC Resource.</returns> + DscResourceInfoInternal? GetDscResource(ConfigurationUnitAndModule unitInternal); + + /// <summary> + /// Calls Invoke-DscResource -Method Get from this module. + /// </summary> + /// <param name="settings">Settings.</param> + /// <param name="name">Name.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>Properties of resource.</returns> + ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// <summary> + /// Calls Invoke-DscResource -Method Test from this module. + /// </summary> + /// <param name="settings">Settings.</param> + /// <param name="name">Name.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>Is in desired state.</returns> + bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// <summary> + /// Calls Invoke-DscResource -Method Set from this module. + /// </summary> + /// <param name="settings">Settings.</param> + /// <param name="name">Name.</param> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>If a reboot is required.</returns> + bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// <summary> + /// Calls Get-Module with fully qualified name. + /// </summary> + /// <param name="moduleSpecification">Module name.</param> + /// <returns>PSModuleInfo, null if not imported.</returns> + PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification); + + /// <summary> + /// Calls Get-Module with the fully qualified name and using ListAvailable. + /// </summary> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>PSModuleInfo, null if not found.</returns> + PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification); + + /// <summary> + /// Calls Get-Module from a path using ListAvailable. + /// </summary> + /// <param name="path">Path.</param> + /// <returns>The first module returned, null if none.</returns> + PSModuleInfo? GetAvailableModule(string path); + + /// <summary> + /// Calls Import-Module with the fully qualified name. + /// </summary> + /// <param name="moduleSpecification">Module specification.</param> + void ImportModule(ModuleSpecification moduleSpecification); + + /// <summary> + /// Calls Import-Module with a file path. + /// </summary> + /// <param name="path">Module file path.</param> + void ImportModule(string path); + + /// <summary> + /// Calls Get-InstalledModule. + /// </summary> + /// <param name="moduleSpecification">Module specification.</param> + /// <returns>Module info, null if not installed.</returns> + PSObject? GetInstalledModule(ModuleSpecification moduleSpecification); + + /// <summary> + /// Calls Find-Module. + /// </summary> + /// <param name="unitInternal">Configuration unit internal.</param> + /// <returns>Module info, null if not found.</returns> + PSObject? FindModule(ConfigurationUnitInternal unitInternal); + + /// <summary> + /// Calls Find-DscResource. + /// </summary> + /// <param name="unitInternal">Configuration unit internal.</param> + /// <returns>Dsc Resource info, null if not found.</returns> + PSObject? FindDscResource(ConfigurationUnitInternal unitInternal); + + /// <summary> + /// Calls Save-Module -InputObject object -Path location. + /// Input object must be the result of Find cmdlets of PowerShellGet. + /// </summary> + /// <param name="inputObject">Input object.</param> + /// <param name="location">Location to save module.</param> + void SaveModule(PSObject inputObject, string location); + + /// <summary> + /// Calls Save-Module. + /// </summary> + /// <param name="moduleSpecification">Module specification.</param> + /// <param name="location">Location to save module.</param> + void SaveModule(ModuleSpecification moduleSpecification, string location); + + /// <summary> + /// Calls Install-Module -InputObject object. + /// Input object must be the result of Find cmdlets of PowerShellGet. + /// </summary> + /// <param name="inputObject">Input object.</param> + void InstallModule(PSObject inputObject); + + /// <summary> + /// Calls Install-Module with a module specification. + /// </summary> + /// <param name="moduleSpecification">Module specification.</param> + void InstallModule(ModuleSpecification moduleSpecification); + + /// <summary> + /// Get unique certificates of valid signed files from the specified paths. + /// </summary> + /// <param name="paths">Path.</param> + /// <returns>List with valid signatures.</returns> + List<Certificate> GetCertsOfValidSignedFiles(string[] paths); + + /// <summary> + /// Gets the value of a variable. + /// </summary> + /// <typeparam name="TType">Type of the variable.</typeparam> + /// <param name="name">Name of variable.</param> + /// <returns>The value of a variable, null if doesn't exist.</returns> + TType GetVariable<TType>(string name); + + /// <summary> + /// Sets a variable with its value. + /// </summary> + /// <param name="name">Name of variable.</param> + /// <param name="value">Value of variable.</param> + void SetVariable(string name, object value); + + /// <summary> + /// Overwrites PSModulePath with the specified path. + /// </summary> + /// <param name="path">Path.</param> + void SetPSModulePath(string path); + + /// <summary> + /// Overwrites PSModulePath with the specified paths. + /// </summary> + /// <param name="paths">Paths.</param> + void SetPSModulePaths(IReadOnlyList<string> paths); + + /// <summary> + /// Prepends path to the PSModulePath. + /// </summary> + /// <param name="path">Path.</param> + void PrependPSModulePath(string path); + + /// <summary> + /// Prepends paths to the PSModulePath. + /// </summary> + /// <param name="paths">Paths.</param> + void PrependPSModulePaths(IReadOnlyList<string> paths); + + /// <summary> + /// Append path to the PSModulePath. + /// </summary> + /// <param name="path">Path.</param> + void AppendPSModulePath(string path); + + /// <summary> + /// Append paths to the PSModulePath. + /// </summary> + /// <param name="paths">Path.</param> + void AppendPSModulePaths(IReadOnlyList<string> paths); + + /// <summary> + /// Removes a path from the module path. + /// </summary> + /// <param name="path">Path.</param> + void CleanupPSModulePath(string path); + + /// <summary> + /// Sets the location for installing modules. + /// </summary> + /// <param name="location">Location.</param> + /// <param name="customLocation">Path for custom location.</param> + void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/ProcessorEnvironmentFactory.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/ProcessorEnvironmentFactory.cs @@ -0,0 +1,109 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ProcessorEnvironmentFactory.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments +{ + using System; + using System.Collections.Generic; + using System.Management.Automation.Runspaces; + using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; + using Microsoft.Management.Configuration.Processor.PowerShell.Runspaces; + using Microsoft.PowerShell; + using Microsoft.PowerShell.Commands; + + /// <summary> + /// Factory class to create a processor environment. + /// </summary> + internal class ProcessorEnvironmentFactory + { + private readonly PowerShellConfigurationProcessorType type; + + /// <summary> + /// Initializes a new instance of the <see cref="ProcessorEnvironmentFactory"/> class. + /// </summary> + /// <param name="type">Configuration processor type.</param> + public ProcessorEnvironmentFactory(PowerShellConfigurationProcessorType type) + { + this.type = type; + } + + /// <summary> + /// Create process environment. + /// </summary> + /// <param name="setProcessorFactory">Optional processor factory.</param> + /// <param name="policy">Configuration processor policy.</param> + /// <returns>IProcessorEnvironment.</returns> + public IProcessorEnvironment CreateEnvironment( + PowerShellConfigurationSetProcessorFactory? setProcessorFactory, + PowerShellConfigurationProcessorPolicy policy) + { + IDscModule dscModule = new DscModuleV2(); + ExecutionPolicy executionPolicy = this.GetExecutionPolicy(policy); + + // The for ConfigurationProcessorType.Default the idea was that since is already running in PowerShell we will + // have access to the variables in the current runspace, but we can't use that runspace and AFAIK + // there's not a simple way to simply clone a runspace. If we want to do it, we will need to get the + // variables from the current runspace and add them here, but maybe some of them are objects that can't + // handle being used in different runspace. It will also be time consuming and we can't block for creating + // the create set processor. Even if we could clone it, at this point we are running in a different thread, + // so there's no default runspace to clone here (aka. PowerShell.Create(RunspaceMode.CurrentRunspace) throws) + // + // If we want to somehow support, it might be easier to explicitly ask for the variables that need to be + // ported. We can add a new property to IConfigurationProcessorFactoryProperties with the variable names + // and set them here, but if they change they won't get reflected in our runspace (which might be a good thing). + // The problem with that is that they will need to be defined when the configuration set is opened and it really + // just makes sense before the ConfigurationSetProcessor gets created. We could add a new IConfigurationSetProcessorProperties + // Then in PowerShell it can be something like + // Get-WinGetConfiguration | Add-WinGetConfigurationVariable -Name foo | Start-WinGetConfiguration + if (this.type == PowerShellConfigurationProcessorType.Hosted || + this.type == PowerShellConfigurationProcessorType.Default) + { + var initialSessionState = this.CreateInitialSessionState( + executionPolicy, + new List<ModuleSpecification> + { + dscModule.ModuleSpecification, + }); + + var runspace = RunspaceFactory.CreateRunspace(initialSessionState); + runspace.Open(); + + return new HostedEnvironment(runspace, this.type, dscModule) + { + SetProcessorFactory = setProcessorFactory, + }; + } + + throw new ArgumentException(this.type.ToString()); + } + + private InitialSessionState CreateInitialSessionState(ExecutionPolicy policy, IReadOnlyList<ModuleSpecification> modules) + { + InitialSessionState initialSessionState = InitialSessionState.CreateDefault(); + + // If this call fails importing the module, it won't throw but write to the error output. DSCModule is + // in charge of verifying that it got loaded correctly and if not, to install it. + initialSessionState.ImportPSModule(modules); + + initialSessionState.ExecutionPolicy = policy; + + return initialSessionState; + } + + private ExecutionPolicy GetExecutionPolicy(PowerShellConfigurationProcessorPolicy policy) + { + return policy switch + { + PowerShellConfigurationProcessorPolicy.Unrestricted => ExecutionPolicy.Unrestricted, + PowerShellConfigurationProcessorPolicy.RemoteSigned => ExecutionPolicy.RemoteSigned, + PowerShellConfigurationProcessorPolicy.AllSigned => ExecutionPolicy.AllSigned, + PowerShellConfigurationProcessorPolicy.Restricted => ExecutionPolicy.Restricted, + PowerShellConfigurationProcessorPolicy.Bypass => ExecutionPolicy.Bypass, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Set/PowerShellConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Set/PowerShellConfigurationSetProcessor.cs @@ -0,0 +1,309 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PowerShellConfigurationSetProcessor.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Set +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.PowerShell.Unit; + using Microsoft.Management.Configuration.Processor.Set; + using Microsoft.Management.Configuration.Processor.Unit; + using Windows.Security.Cryptography.Certificates; + + /// <summary> + /// Configuration set processor. + /// </summary> + internal sealed partial class PowerShellConfigurationSetProcessor : ConfigurationSetProcessorBase, IConfigurationSetProcessor + { + /// <summary> + /// Initializes a new instance of the <see cref="PowerShellConfigurationSetProcessor"/> class. + /// </summary> + /// <param name="processorEnvironment">The processor environment.</param> + /// <param name="configurationSet">Configuration set.</param> + /// <param name="isLimitMode">Whether the set processor should work in limitation mode.</param> + public PowerShellConfigurationSetProcessor(IProcessorEnvironment processorEnvironment, ConfigurationSet? configurationSet, bool isLimitMode = false) + : base(configurationSet, isLimitMode) + { + this.ProcessorEnvironment = processorEnvironment; + } + + /// <summary> + /// Gets the processor environment. + /// </summary> + internal IProcessorEnvironment ProcessorEnvironment { get; } + + /// <inheritdoc /> + protected override IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit) + { + var configurationUnitInternal = new ConfigurationUnitAndModule(unit, this.ConfigurationSet?.Path) { UnitTypeIsResourceName = IsUnitTypeResourceName(this.ConfigurationSet?.SchemaVersion) }; + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating unit processor for: {configurationUnitInternal.QualifiedName}..."); + + var dscResourceInfo = this.PrepareUnitForProcessing(configurationUnitInternal); + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Using unit from location: {dscResourceInfo.Path}"); + return new PowerShellConfigurationUnitProcessor( + this.ProcessorEnvironment, + new ConfigurationUnitAndResource(configurationUnitInternal, dscResourceInfo), + this.IsLimitMode) + { SetProcessorFactory = this.SetProcessorFactory }; + } + + /// <inheritdoc /> + protected override IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) + { + var unitInternal = new ConfigurationUnitAndModule(unit, this.ConfigurationSet?.Path); + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting unit details [{detailFlags}] for: {unitInternal.QualifiedName}"); + + // (Local | Download | Load) will all work off of local files, so if any one is an option just use the local module info if found. + DscResourceInfoInternal? dscResourceInfo = null; + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Local) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) + { + dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + } + + if (dscResourceInfo is not null) + { + return this.GetUnitProcessorDetailsLocal( + dscResourceInfo.Name, + dscResourceInfo, + detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)); + } + + if (!(detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load))) + { + // Not found locally. + return null; + } + + var unitModuleInfo = this.FindUnitModule(unitInternal); + if (unitModuleInfo is null) + { + // Not found in catalog. + return null; + } + + PSObject foundModule = unitModuleInfo.Value.Module; + string resourceName = unitModuleInfo.Value.ResourceName; + + dynamic foundModuleInfo = foundModule; + + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog)) + { + return Factory.CreateUnitProcessorDetails( + resourceName, + null, + null, + foundModule, + null); + } + + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download)) + { + var tempSavePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempSavePath); + this.ProcessorEnvironment.SaveModule(foundModule, tempSavePath); + + var moduleInfo = this.ProcessorEnvironment.GetAvailableModule( + Path.Combine(tempSavePath, foundModuleInfo.Name)); + + return Factory.CreateUnitProcessorDetails( + resourceName, + null, + moduleInfo, + foundModule, + this.GetCertificates(moduleInfo)); + } + + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) + { + this.ProcessorEnvironment.InstallModule(foundModule); + + dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + + if (dscResourceInfo is null) + { + // Well, this is awkward. + throw new InstallDscResourceException( + unitInternal.ResourceName, + PowerShellHelpers.CreateModuleSpecification(foundModuleInfo.Name, foundModuleInfo.Version)); + } + + return this.GetUnitProcessorDetailsLocal(dscResourceInfo.Name, dscResourceInfo, true); + } + + return null; + } + + private static bool IsUnitTypeResourceName(string? schemaVersion) + { + return schemaVersion != null && schemaVersion == "0.1"; + } + + /// <summary> + /// Finds the module and preferred resource name for processing the configuration unit. + /// </summary> + /// <param name="unitInternal">The internal configuration unit.</param> + /// <returns>A tuple containing the module info and preferred resource name, or null if not found.</returns> + private (PSObject Module, string ResourceName)? FindUnitModule(ConfigurationUnitAndModule unitInternal) + { + PSObject? foundModule = null; + string resourceName = string.Empty; + + // If module has been specified, find it and assume that the resource will be within it. + // Do this first as we do not currently gain much from FindDscResource; if that changes then it can be the primary. + if (unitInternal.Module != null) + { + foundModule = this.ProcessorEnvironment.FindModule(unitInternal); + if (foundModule != null) + { + resourceName = unitInternal.ResourceName; + } + } + else + { + dynamic? foundResource = this.ProcessorEnvironment.FindDscResource(unitInternal); + if (foundResource != null) + { + foundModule = foundResource.PSGetModuleInfo; + + // Hopefully they will never change the properties name. If someone can explain to me + // why assign it Name to $_ in Find-DscResource turns into a string in PowerShell but + // into a PSObject here that would be nice... + resourceName = foundResource.Name.ToString(); + } + } + + if (foundModule != null) + { + return (foundModule, resourceName); + } + + return null; + } + + private DscResourceInfoInternal PrepareUnitForProcessing(ConfigurationUnitAndModule unitInternal) + { + // Invoke-DscResource makes a call to Get-DscResource which looks at the entire PSModulePath + // to see if a resource exists. DscResourcesMap is an attempt to try to optimize Get-DscResource + // by making just one call and get all of them, but it doesn't support minVersion and maxVersion. + // For now, lets make PowerShell fully figure out which module to use and try to optimize it later. + // This class will have a private member Lazy<DscResourcesMap> which will be initialized by calling + // this.ProcessorEnvironment.GetAllDscResources() + // To improve the performance even more, we will still need Invoke-DscResource to be update to + // get a DSC resource info object instead of calling Get-DscResource every time. + var dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + + if (dscResourceInfo is null) + { + var findUnitModuleResult = this.FindUnitModule(unitInternal); + + if (findUnitModuleResult is null) + { + throw new FindDscResourceNotFoundException(unitInternal.ResourceName, unitInternal.Module); + } + + this.ProcessorEnvironment.InstallModule(findUnitModuleResult.Value.Module); + + // Now we should find it. + dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + if (dscResourceInfo is null) + { + throw new InstallDscResourceException(unitInternal.ResourceName, unitInternal.Module); + } + } + + // PowerShell will prompt the user when a module that is downloaded from the internet is imported. + // For a hosted environment, this will throw an exception because it doesn't support user interaction. + // In the case we don't import the module here, eventually Invoke-DscResource will fail for class + // resources because they will call a method on a null obj. It is easier to just fail here. + // The exception being thrown will have the correct details (user needs to call Unblock-File) + // instead of the cryptic Invoke with 0 arguments. + if (!string.IsNullOrEmpty(dscResourceInfo.Path)) + { + try + { + this.ProcessorEnvironment.ImportModule(dscResourceInfo.Path); + } + catch (Exception e) + { + throw new ImportModuleException(dscResourceInfo.ModuleName, e); + } + } + + return dscResourceInfo; + } + + private ConfigurationUnitProcessorDetails GetUnitProcessorDetailsLocal( + string unitName, + DscResourceInfoInternal dscResourceInfo, + bool importModule) + { + // I'm looking at you resources under C:\WINDOWS\system32\WindowsPowershell + if (dscResourceInfo.ModuleName is null || + dscResourceInfo.Version is null) + { + return Factory.CreateUnitProcessorDetails( + dscResourceInfo.Name, + dscResourceInfo, + null, + null, + null); + } + + var module = PowerShellHelpers.CreateModuleSpecification( + dscResourceInfo.ModuleName, + dscResourceInfo.Version.ToString()); + + // Get-InstalledModule only works for modules installed via PowerShell-Get. + // There are some properties that can only be obtain by that it so is better to take both. + var moduleInfo = this.ProcessorEnvironment.GetAvailableModule(module); + var installedModule = this.ProcessorEnvironment.GetInstalledModule(module); + + if (importModule) + { + this.ProcessorEnvironment.ImportModule(module); + } + + return Factory.CreateUnitProcessorDetails( + dscResourceInfo.Name, + dscResourceInfo, + moduleInfo, + installedModule, + this.GetCertificates(moduleInfo)); + } + + private List<Certificate>? GetCertificates(PSModuleInfo? moduleInfo) + { + if (moduleInfo is null) + { + return null; + } + + // TODO: we still need to investigate more here, but lets start with something. + var paths = new List<string>(); + + var psdPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psd1"); + if (File.Exists(psdPath)) + { + paths.Add(psdPath); + } + + var psmPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psm1"); + if (File.Exists(psmPath)) + { + paths.Add(psmPath); + } + + return this.ProcessorEnvironment.GetCertsOfValidSignedFiles(paths.ToArray()); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Unit/PowerShellConfigurationUnitProcessor.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Unit/PowerShellConfigurationUnitProcessor.cs @@ -0,0 +1,63 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PowerShellConfigurationUnitProcessor.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Unit +{ + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.Unit; + using Windows.Foundation.Collections; + + /// <summary> + /// Provides access to a specific configuration unit within the runtime. + /// </summary> + internal sealed partial class PowerShellConfigurationUnitProcessor : ConfigurationUnitProcessorBase, IConfigurationUnitProcessor + { + private readonly IProcessorEnvironment processorEnvironment; + private readonly ConfigurationUnitAndResource unitResource; + + /// <summary> + /// Initializes a new instance of the <see cref="PowerShellConfigurationUnitProcessor"/> class. + /// </summary> + /// <param name="processorEnvironment">Processor environment.</param> + /// <param name="unitResource">UnitResource.</param> + /// <param name="isLimitMode">Whether it is under limit mode.</param> + internal PowerShellConfigurationUnitProcessor(IProcessorEnvironment processorEnvironment, ConfigurationUnitAndResource unitResource, bool isLimitMode = false) + : base(unitResource.UnitInternal, isLimitMode) + { + this.processorEnvironment = processorEnvironment; + this.unitResource = unitResource; + } + + /// <inheritdoc /> + protected override ValueSet GetSettingsInternal() + { + return this.processorEnvironment.InvokeGetResource( + this.unitResource.GetSettings(), + this.unitResource.ResourceName, + this.unitResource.Module); + } + + /// <inheritdoc /> + protected override bool TestSettingsInternal() + { + return this.processorEnvironment.InvokeTestResource( + this.unitResource.GetSettings(), + this.unitResource.ResourceName, + this.unitResource.Module); + } + + /// <inheritdoc /> + protected override bool ApplySettingsInternal() + { + return this.processorEnvironment.InvokeSetResource( + this.unitResource.GetSettings(), + this.unitResource.ResourceName, + this.unitResource.Module); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/ProcessorEnvironments/HostedEnvironment.cs b/src/Microsoft.Management.Configuration.Processor/ProcessorEnvironments/HostedEnvironment.cs @@ -1,554 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="HostedEnvironment.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Runspaces -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Management.Automation; - using System.Management.Automation.Runspaces; - using System.Runtime.InteropServices.WindowsRuntime; - using Microsoft.Management.Configuration.Processor.Constants; - using Microsoft.Management.Configuration.Processor.DscModule; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - using Windows.Security.Cryptography.Certificates; - using Windows.Storage.Streams; - using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; - - /// <summary> - /// Process environment. Provides interaction with PowerShell for a hosted environment. - /// </summary> - internal class HostedEnvironment : IProcessorEnvironment - { - private readonly PowerShellConfigurationProcessorType type; - private readonly IPowerShellGet powerShellGet; - - private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.CurrentUser; - private string? customLocation; - - /// <summary> - /// Initializes a new instance of the <see cref="HostedEnvironment"/> class. - /// </summary> - /// <param name="runspace">PowerShell Runspace.</param> - /// <param name="type">Configuration processor type.</param> - /// <param name="dscModule">IDscModule.</param> - public HostedEnvironment( - Runspace runspace, - PowerShellConfigurationProcessorType type, - IDscModule dscModule) - { - this.Runspace = runspace; - this.type = type; - this.DscModule = dscModule; - - // TODO: once v3 is release implement v3 version. - this.powerShellGet = new PowerShellGetV2(); - } - - /// <inheritdoc/> - public Runspace Runspace { get; } - - /// <summary> - /// Gets the DscModule. - /// </summary> - internal IDscModule DscModule { get; } - - /// <summary> - /// Gets or initializes the set processor factory. - /// </summary> - internal PowerShellConfigurationSetProcessorFactory? SetProcessorFactory { get; init; } - - /// <inheritdoc/> - public void ValidateRunspace() - { - // Only support PowerShell Core. - if (this.GetVariable<string>(Variables.PSEdition) != Core) - { - throw new NotSupportedException("Only PowerShell Core is supported."); - } - - // If opening a runspace has failures, like one of the modules in ImportPSModule is not found, it won't throw but - // write to the error output. This is not a fatal error, since we install PSDesiredStateConfiguration - // module if not found, so unless there's a real reason keep it in verbose. - var errors = this.GetVariable<ArrayList>(Variables.Error); - if (errors.Count > 0) - { - this.OnDiagnostics( - DiagnosticLevel.Verbose, - $"Error creating runspace '{string.Join("\n", errors.Cast<string>().ToArray())}'"); - } - - var powerShellGet = PowerShellHelpers.CreateModuleSpecification( - Modules.PowerShellGet, - minVersion: Modules.PowerShellGetMinVersion); - if (!this.ValidateModule(powerShellGet)) - { - var previousVersion = this.GetAvailableModule( - PowerShellHelpers.CreateModuleSpecification( - Modules.PowerShellGet)); - string message = $"Required '{powerShellGet}'"; - if (previousVersion is not null) - { - message += $" Found '{previousVersion.Name} {previousVersion.Version}'"; - } - - throw new NotSupportedException(message); - } - - // Make sure PSDesiredConfiguration is present. - this.InstallModule(this.DscModule.ModuleSpecification); - } - - /// <inheritdoc/> - public IReadOnlyList<DscResourceInfoInternal> GetAllDscResources() - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var results = this.DscModule.GetAllDscResources(pwsh); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return results; - } - - /// <inheritdoc/> - public IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule(ModuleSpecification moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var results = this.DscModule.GetDscResourcesInModule(pwsh, moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return results; - } - - /// <inheritdoc/> - public DscResourceInfoInternal? GetDscResource(ConfigurationUnitInternal unitInternal) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var result = this.DscModule.GetDscResource(pwsh, unitInternal.ResourceName, unitInternal.Module); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// <inheritdoc/> - public ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var result = this.DscModule.InvokeGetResource(pwsh, settings, name, moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// <inheritdoc/> - public bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var result = this.DscModule.InvokeTestResource(pwsh, settings, name, moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// <inheritdoc/> - public bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var result = this.DscModule.InvokeSetResource(pwsh, settings, name, moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// <inheritdoc/> - public PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var moduleInfo = pwsh.AddCommand(Commands.GetModule) - .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) - .Invoke<PSModuleInfo>() - .FirstOrDefault(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return moduleInfo; - } - - /// <inheritdoc/> - public PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var moduleInfo = pwsh.AddCommand(Commands.GetModule) - .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) - .AddParameter(Parameters.ListAvailable) - .Invoke<PSModuleInfo>() - .FirstOrDefault(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return moduleInfo; - } - - /// <inheritdoc/> - public PSModuleInfo? GetAvailableModule(string path) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var moduleInfo = pwsh.AddCommand(Commands.GetModule) - .AddParameter(Parameters.Name, path) - .AddParameter(Parameters.ListAvailable) - .Invoke<PSModuleInfo>() - .FirstOrDefault(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return moduleInfo; - } - - /// <inheritdoc/> - public void ImportModule(ModuleSpecification moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - _ = pwsh.AddCommand(Commands.ImportModule) - .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) - .Invoke(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - /// <inheritdoc/> - public void ImportModule(string path) - { - if (!File.Exists(path)) - { - throw new FileNotFoundException(path); - } - - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - _ = pwsh.AddCommand(Commands.ImportModule) - .AddParameter(Parameters.Name, path) - .Invoke(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - /// <inheritdoc/> - public PSObject? GetInstalledModule(ModuleSpecification moduleSpecification) - { - // Instead of Get-InstalledModule, we look for PSGetModuleInfo.xml and serialize it - // if found. This allow us to get the information from Install-Module and Save-Module. - var module = this.GetAvailableModule(moduleSpecification); - if (module is null) - { - return null; - } - - var getModuleInfoFile = Path.Combine(module.ModuleBase, "PSGetModuleInfo.xml"); - if (!File.Exists(getModuleInfoFile)) - { - // Keep Get-InstalledModule behaviour. - return null; - } - - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var installedModule = pwsh.AddCommand(Commands.ImportCliXml) - .AddParameter(Parameters.Path, getModuleInfoFile) - .Invoke() - .FirstOrDefault(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return installedModule; - } - - /// <inheritdoc/> - public PSObject? FindModule(ConfigurationUnitInternal unitInternal) - { - // Don't use ModuleSpecification here. Each parameter is independent and - // we need version even if a module was not specified. - string? moduleName = unitInternal.GetDirective<string>(DirectiveConstants.Module); - - if (string.IsNullOrEmpty(moduleName)) - { - return null; - } - - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var result = this.powerShellGet.FindModule( - pwsh, - moduleName, - unitInternal.GetSemanticVersion(), - unitInternal.GetSemanticMinVersion(), - unitInternal.GetSemanticMaxVersion(), - unitInternal.GetDirective<string>(DirectiveConstants.Repository), - unitInternal.GetDirective(DirectiveConstants.AllowPrerelease)); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// <inheritdoc/> - public PSObject? FindDscResource(ConfigurationUnitInternal unitInternal) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var result = this.powerShellGet.FindDscResource( - pwsh, - unitInternal.ResourceName, - unitInternal.GetDirective<string>(DirectiveConstants.Module), - unitInternal.GetSemanticVersion(), - unitInternal.GetSemanticMinVersion(), - unitInternal.GetSemanticMaxVersion(), - unitInternal.GetDirective<string>(DirectiveConstants.Repository), - unitInternal.GetDirective(DirectiveConstants.AllowPrerelease)); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// <inheritdoc/> - public void SaveModule(PSObject inputObject, string location) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - this.powerShellGet.SaveModule(pwsh, inputObject, location); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - /// <inheritdoc/> - public void SaveModule(ModuleSpecification moduleSpecification, string location) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - this.powerShellGet.SaveModule(pwsh, moduleSpecification, location); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - /// <inheritdoc/> - public void InstallModule(PSObject inputObject) - { - if (this.location == PowerShellConfigurationProcessorLocation.Custom) - { - if (string.IsNullOrEmpty(this.customLocation)) - { - throw new ArgumentNullException(nameof(this.customLocation)); - } - - this.SaveModule(inputObject, this.customLocation); - } - else - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - this.powerShellGet.InstallModule(pwsh, inputObject, this.location == PowerShellConfigurationProcessorLocation.AllUsers); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - } - - /// <inheritdoc/> - public void InstallModule(ModuleSpecification moduleSpecification) - { - // Maybe is already there. - if (!this.ValidateModule(moduleSpecification)) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Installing module: {moduleSpecification.Name} ..."); - - // Ok, we have to get it. - if (this.location == PowerShellConfigurationProcessorLocation.Custom) - { - if (string.IsNullOrEmpty(this.customLocation)) - { - throw new ArgumentNullException(nameof(this.customLocation)); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling save module ..."); - this.SaveModule(moduleSpecification, this.customLocation); - } - else - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling install module ..."); - using PowerShell pwsh = PowerShell.Create(this.Runspace); - this.powerShellGet.InstallModule(pwsh, moduleSpecification, this.location == PowerShellConfigurationProcessorLocation.AllUsers); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module installed."); - } - } - - /// <inheritdoc/> - public List<Certificate> GetCertsOfValidSignedFiles(string[] paths) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var signatures = pwsh.AddCommand(Commands.GetChildItem) - .AddParameter(Parameters.Path, paths) - .AddCommand(Commands.GetAuthenticodeSignature) - .Invoke<Signature>(); - - var thumbprint = new HashSet<string>(); - var certificates = new List<Certificate>(); - foreach (var signature in signatures) - { - if (signature.Status == SignatureStatus.Valid) - { - if (thumbprint.Add(signature.SignerCertificate.Thumbprint)) - { - IBuffer buffer = signature.SignerCertificate.GetRawCertData().AsBuffer(); - certificates.Add(new Certificate(buffer)); - } - } - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return certificates; - } - - /// <inheritdoc/> - public TType GetVariable<TType>(string name) - { - return (TType)this.Runspace.SessionStateProxy.PSVariable.GetValue(name); - } - - /// <inheritdoc/> - public void SetVariable(string name, object value) - { - this.Runspace.SessionStateProxy.PSVariable.Set(name, value); - } - - /// <inheritdoc/> - public void SetPSModulePath(string path) - { - this.SetVariable(Variables.PSModulePath, path); - } - - /// <inheritdoc/> - public void SetPSModulePaths(IReadOnlyList<string> paths) - { - this.SetVariable(Variables.PSModulePath, string.Join(";", paths)); - } - - /// <inheritdoc/> - public void PrependPSModulePath(string path) - { - var oldModulePath = this.GetModulePaths(); - if (!oldModulePath.Contains(path)) - { - this.SetPSModulePath($"{path};{string.Join(";", oldModulePath)}"); - } - } - - /// <inheritdoc/> - public void PrependPSModulePaths(IReadOnlyList<string> paths) - { - var newPaths = paths.ToList(); - var oldModulePath = this.GetModulePaths(); - foreach (var newPath in paths) - { - if (oldModulePath.Contains(newPath)) - { - newPaths.Remove(newPath); - } - } - - if (newPaths.Any()) - { - this.SetPSModulePath($"{string.Join(";", newPaths)};{string.Join(";", oldModulePath)}"); - } - } - - /// <inheritdoc/> - public void AppendPSModulePath(string path) - { - var oldModulePath = this.GetModulePaths(); - if (!oldModulePath.Contains(path)) - { - this.SetPSModulePath($"{string.Join(";", oldModulePath)};{path}"); - } - } - - /// <inheritdoc/> - public void AppendPSModulePaths(IReadOnlyList<string> paths) - { - var newPaths = paths.ToList(); - var oldModulePath = this.GetModulePaths(); - foreach (var newPath in paths) - { - if (oldModulePath.Contains(newPath)) - { - newPaths.Remove(newPath); - } - } - - if (newPaths.Any()) - { - this.SetPSModulePath($"{string.Join(";", oldModulePath)};{string.Join(";", newPaths)}"); - } - } - - /// <inheritdoc/> - public void CleanupPSModulePath(string path) - { - string newModulePath = this.GetVariable<string>(Variables.PSModulePath) - .Replace($"{path};", null) - .Replace($";{path}", null); - - this.SetPSModulePath(newModulePath); - } - - /// <inheritdoc/> - public void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation) - { - this.location = location; - if (this.location == PowerShellConfigurationProcessorLocation.Custom) - { - if (string.IsNullOrEmpty(customLocation)) - { - throw new ArgumentNullException(nameof(customLocation)); - } - - this.customLocation = customLocation; - } - } - - private bool ValidateModule(ModuleSpecification moduleSpecification) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Validating module: {moduleSpecification.Name} ..."); - - var loadedModule = this.GetImportedModule(moduleSpecification); - if (loadedModule is not null) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is already imported."); - return true; - } - - var availableModule = this.GetAvailableModule(moduleSpecification); - if (availableModule is not null) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is available, importing ..."); - this.ImportModule(moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module imported."); - return true; - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module not found."); - return false; - } - - private void OnDiagnostics(DiagnosticLevel level, PowerShell pwsh) - { - this.SetProcessorFactory?.OnDiagnostics(level, pwsh); - } - - private void OnDiagnostics(DiagnosticLevel level, string message) - { - this.SetProcessorFactory?.OnDiagnostics(level, message); - } - - private HashSet<string> GetModulePaths() - { - return this.GetVariable<string>(Variables.PSModulePath).Split(";").ToHashSet<string>(); - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/ProcessorEnvironments/IProcessorEnvironment.cs b/src/Microsoft.Management.Configuration.Processor/ProcessorEnvironments/IProcessorEnvironment.cs @@ -1,232 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="IProcessorEnvironment.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.ProcessorEnvironments -{ - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - using Windows.Security.Cryptography.Certificates; - - /// <summary> - /// IProcessorEnvironment. Provides interaction with PowerShell. - /// </summary> - internal interface IProcessorEnvironment - { - /// <summary> - /// Gets the runspace. - /// </summary> - System.Management.Automation.Runspaces.Runspace Runspace { get; } - - /// <summary> - /// Validates the runspace. - /// </summary> - void ValidateRunspace(); - - /// <summary> - /// Gets all DSC resource. - /// </summary> - /// <returns>A list with the DSC resource.</returns> - IReadOnlyList<DscResourceInfoInternal> GetAllDscResources(); - - /// <summary> - /// Gets all resources in a module. - /// </summary> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>List of resources.</returns> - IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule(ModuleSpecification moduleSpecification); - - /// <summary> - /// Gets a DSC Resource. - /// </summary> - /// <param name="unitInternal">Configuration unit internal.</param> - /// <returns>DSC Resource.</returns> - DscResourceInfoInternal? GetDscResource(ConfigurationUnitInternal unitInternal); - - /// <summary> - /// Calls Invoke-DscResource -Method Get from this module. - /// </summary> - /// <param name="settings">Settings.</param> - /// <param name="name">Name.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>Properties of resource.</returns> - ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// <summary> - /// Calls Invoke-DscResource -Method Test from this module. - /// </summary> - /// <param name="settings">Settings.</param> - /// <param name="name">Name.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>Is in desired state.</returns> - bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// <summary> - /// Calls Invoke-DscResource -Method Set from this module. - /// </summary> - /// <param name="settings">Settings.</param> - /// <param name="name">Name.</param> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>If a reboot is required.</returns> - bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// <summary> - /// Calls Get-Module with fully qualified name. - /// </summary> - /// <param name="moduleSpecification">Module name.</param> - /// <returns>PSModuleInfo, null if not imported.</returns> - PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification); - - /// <summary> - /// Calls Get-Module with the fully qualified name and using ListAvailable. - /// </summary> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>PSModuleInfo, null if not found.</returns> - PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification); - - /// <summary> - /// Calls Get-Module from a path using ListAvailable. - /// </summary> - /// <param name="path">Path.</param> - /// <returns>The first module returned, null if none.</returns> - PSModuleInfo? GetAvailableModule(string path); - - /// <summary> - /// Calls Import-Module with the fully qualified name. - /// </summary> - /// <param name="moduleSpecification">Module specification.</param> - void ImportModule(ModuleSpecification moduleSpecification); - - /// <summary> - /// Calls Import-Module with a file path. - /// </summary> - /// <param name="path">Module file path.</param> - void ImportModule(string path); - - /// <summary> - /// Calls Get-InstalledModule. - /// </summary> - /// <param name="moduleSpecification">Module specification.</param> - /// <returns>Module info, null if not installed.</returns> - PSObject? GetInstalledModule(ModuleSpecification moduleSpecification); - - /// <summary> - /// Calls Find-Module. - /// </summary> - /// <param name="unitInternal">Configuration unit internal.</param> - /// <returns>Module info, null if not found.</returns> - PSObject? FindModule(ConfigurationUnitInternal unitInternal); - - /// <summary> - /// Calls Find-DscResource. - /// </summary> - /// <param name="unitInternal">Configuration unit internal.</param> - /// <returns>Dsc Resource info, null if not found.</returns> - PSObject? FindDscResource(ConfigurationUnitInternal unitInternal); - - /// <summary> - /// Calls Save-Module -InputObject object -Path location. - /// Input object must be the result of Find cmdlets of PowerShellGet. - /// </summary> - /// <param name="inputObject">Input object.</param> - /// <param name="location">Location to save module.</param> - void SaveModule(PSObject inputObject, string location); - - /// <summary> - /// Calls Save-Module. - /// </summary> - /// <param name="moduleSpecification">Module specification.</param> - /// <param name="location">Location to save module.</param> - void SaveModule(ModuleSpecification moduleSpecification, string location); - - /// <summary> - /// Calls Install-Module -InputObject object. - /// Input object must be the result of Find cmdlets of PowerShellGet. - /// </summary> - /// <param name="inputObject">Input object.</param> - void InstallModule(PSObject inputObject); - - /// <summary> - /// Calls Install-Module with a module specification. - /// </summary> - /// <param name="moduleSpecification">Module specification.</param> - void InstallModule(ModuleSpecification moduleSpecification); - - /// <summary> - /// Get unique certificates of valid signed files from the specified paths. - /// </summary> - /// <param name="paths">Path.</param> - /// <returns>List with valid signatures.</returns> - List<Certificate> GetCertsOfValidSignedFiles(string[] paths); - - /// <summary> - /// Gets the value of a variable. - /// </summary> - /// <typeparam name="TType">Type of the variable.</typeparam> - /// <param name="name">Name of variable.</param> - /// <returns>The value of a variable, null if doesn't exist.</returns> - TType GetVariable<TType>(string name); - - /// <summary> - /// Sets a variable with its value. - /// </summary> - /// <param name="name">Name of variable.</param> - /// <param name="value">Value of variable.</param> - void SetVariable(string name, object value); - - /// <summary> - /// Overwrites PSModulePath with the specified path. - /// </summary> - /// <param name="path">Path.</param> - void SetPSModulePath(string path); - - /// <summary> - /// Overwrites PSModulePath with the specified paths. - /// </summary> - /// <param name="paths">Paths.</param> - void SetPSModulePaths(IReadOnlyList<string> paths); - - /// <summary> - /// Prepends path to the PSModulePath. - /// </summary> - /// <param name="path">Path.</param> - void PrependPSModulePath(string path); - - /// <summary> - /// Prepends paths to the PSModulePath. - /// </summary> - /// <param name="paths">Paths.</param> - void PrependPSModulePaths(IReadOnlyList<string> paths); - - /// <summary> - /// Append path to the PSModulePath. - /// </summary> - /// <param name="path">Path.</param> - void AppendPSModulePath(string path); - - /// <summary> - /// Append paths to the PSModulePath. - /// </summary> - /// <param name="paths">Path.</param> - void AppendPSModulePaths(IReadOnlyList<string> paths); - - /// <summary> - /// Removes a path from the module path. - /// </summary> - /// <param name="path">Path.</param> - void CleanupPSModulePath(string path); - - /// <summary> - /// Sets the location for installing modules. - /// </summary> - /// <param name="location">Location.</param> - /// <param name="customLocation">Path for custom location.</param> - void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation); - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/ProcessorEnvironments/ProcessorEnvironmentFactory.cs b/src/Microsoft.Management.Configuration.Processor/ProcessorEnvironments/ProcessorEnvironmentFactory.cs @@ -1,109 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="ProcessorEnvironmentFactory.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.ProcessorEnvironments -{ - using System; - using System.Collections.Generic; - using System.Management.Automation.Runspaces; - using Microsoft.Management.Configuration.Processor.DscModule; - using Microsoft.Management.Configuration.Processor.Runspaces; - using Microsoft.PowerShell; - using Microsoft.PowerShell.Commands; - - /// <summary> - /// Factory class to create a processor environment. - /// </summary> - internal class ProcessorEnvironmentFactory - { - private readonly PowerShellConfigurationProcessorType type; - - /// <summary> - /// Initializes a new instance of the <see cref="ProcessorEnvironmentFactory"/> class. - /// </summary> - /// <param name="type">Configuration processor type.</param> - public ProcessorEnvironmentFactory(PowerShellConfigurationProcessorType type) - { - this.type = type; - } - - /// <summary> - /// Create process environment. - /// </summary> - /// <param name="setProcessorFactory">Optional processor factory.</param> - /// <param name="policy">Configuration processor policy.</param> - /// <returns>IProcessorEnvironment.</returns> - public IProcessorEnvironment CreateEnvironment( - PowerShellConfigurationSetProcessorFactory? setProcessorFactory, - PowerShellConfigurationProcessorPolicy policy) - { - IDscModule dscModule = new DscModuleV2(); - ExecutionPolicy executionPolicy = this.GetExecutionPolicy(policy); - - // The for ConfigurationProcessorType.Default the idea was that since is already running in PowerShell we will - // have access to the variables in the current runspace, but we can't use that runspace and AFAIK - // there's not a simple way to simply clone a runspace. If we want to do it, we will need to get the - // variables from the current runspace and add them here, but maybe some of them are objects that can't - // handle being used in different runspace. It will also be time consuming and we can't block for creating - // the create set processor. Even if we could clone it, at this point we are running in a different thread, - // so there's no default runspace to clone here (aka. PowerShell.Create(RunspaceMode.CurrentRunspace) throws) - // - // If we want to somehow support, it might be easier to explicitly ask for the variables that need to be - // ported. We can add a new property to IConfigurationProcessorFactoryProperties with the variable names - // and set them here, but if they change they won't get reflected in our runspace (which might be a good thing). - // The problem with that is that they will need to be defined when the configuration set is opened and it really - // just makes sense before the ConfigurationSetProcessor gets created. We could add a new IConfigurationSetProcessorProperties - // Then in PowerShell it can be something like - // Get-WinGetConfiguration | Add-WinGetConfigurationVariable -Name foo | Start-WinGetConfiguration - if (this.type == PowerShellConfigurationProcessorType.Hosted || - this.type == PowerShellConfigurationProcessorType.Default) - { - var initialSessionState = this.CreateInitialSessionState( - executionPolicy, - new List<ModuleSpecification> - { - dscModule.ModuleSpecification, - }); - - var runspace = RunspaceFactory.CreateRunspace(initialSessionState); - runspace.Open(); - - return new HostedEnvironment(runspace, this.type, dscModule) - { - SetProcessorFactory = setProcessorFactory, - }; - } - - throw new ArgumentException(this.type.ToString()); - } - - private InitialSessionState CreateInitialSessionState(ExecutionPolicy policy, IReadOnlyList<ModuleSpecification> modules) - { - InitialSessionState initialSessionState = InitialSessionState.CreateDefault(); - - // If this call fails importing the module, it won't throw but write to the error output. DSCModule is - // in charge of verifying that it got loaded correctly and if not, to install it. - initialSessionState.ImportPSModule(modules); - - initialSessionState.ExecutionPolicy = policy; - - return initialSessionState; - } - - private ExecutionPolicy GetExecutionPolicy(PowerShellConfigurationProcessorPolicy policy) - { - return policy switch - { - PowerShellConfigurationProcessorPolicy.Unrestricted => ExecutionPolicy.Unrestricted, - PowerShellConfigurationProcessorPolicy.RemoteSigned => ExecutionPolicy.RemoteSigned, - PowerShellConfigurationProcessorPolicy.AllSigned => ExecutionPolicy.AllSigned, - PowerShellConfigurationProcessorPolicy.Restricted => ExecutionPolicy.Restricted, - PowerShellConfigurationProcessorPolicy.Bypass => ExecutionPolicy.Bypass, - _ => throw new InvalidOperationException(), - }; - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs @@ -9,14 +9,14 @@ namespace Microsoft.Management.Configuration.Processor using System; using System.Collections.Generic; using System.IO; - using System.Management.Automation; using System.Runtime.CompilerServices; using System.Text; using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.Set; + using Microsoft.Management.Configuration.Processor.Factory; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.PowerShell.Set; using Microsoft.Management.Configuration.SetProcessorFactory; - using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; /// <summary> /// ConfigurationSetProcessorFactory implementation. @@ -26,12 +26,9 @@ namespace Microsoft.Management.Configuration.Processor #else public #endif - sealed class PowerShellConfigurationSetProcessorFactory : IConfigurationSetProcessorFactory, IPowerShellConfigurationProcessorFactoryProperties, IPwshConfigurationSetProcessorFactoryProperties + sealed partial class PowerShellConfigurationSetProcessorFactory : ConfigurationSetProcessorFactoryBase, IConfigurationSetProcessorFactory, IPowerShellConfigurationProcessorFactoryProperties, IPwshConfigurationSetProcessorFactoryProperties { - private bool isCreateProcessorInvoked = false; - // Backing variables for properties that are restricted in limit mode. - private ConfigurationSet? limitationSet; private PowerShellConfigurationProcessorType processorType = PowerShellConfigurationProcessorType.Default; private IReadOnlyList<string>? additionalModulePaths; private IReadOnlyList<string>? implicitModulePaths; @@ -47,37 +44,6 @@ namespace Microsoft.Management.Configuration.Processor } /// <summary> - /// Diagnostics event; useful for logging and/or verbose output. - /// </summary> - public event EventHandler<IDiagnosticInformation>? Diagnostics; - - /// <summary> - /// Gets or sets the minimum diagnostic level to send. - /// </summary> - public DiagnosticLevel MinimumLevel { get; set; } = DiagnosticLevel.Informational; - - /// <summary> - /// Gets or sets the limitation set. Limitation set can only be set once. - /// </summary> - public ConfigurationSet? LimitationSet - { - get - { - return this.limitationSet; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting LimitationSet in limit mode is invalid."); - } - - this.limitationSet = value; - } - } - - /// <summary> /// Gets or sets the processor type. /// </summary> public PowerShellConfigurationProcessorType ProcessorType @@ -267,77 +233,6 @@ namespace Microsoft.Management.Configuration.Processor } /// <summary> - /// Gets the configuration unit processor details for the given unit. - /// </summary> - /// <param name="incomingSet">Configuration Set.</param> - /// <returns>Configuration set processor.</returns> - public IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet? incomingSet) - { - try - { - this.OnDiagnostics(DiagnosticLevel.Informational, $"The set processor factory is running in limit mode: {this.IsLimitMode()}."); - - this.CheckLimitMode(); - - ConfigurationSet? set = this.IsLimitMode() ? this.limitationSet : incomingSet; - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating set processor for `{set?.Name ?? "<null>"}`..."); - - if (set != null && (set.Parameters.Count > 0 || set.Variables.Count > 0)) - { - this.OnDiagnostics(DiagnosticLevel.Error, $" Parameters/variables are not yet supported."); - throw new NotImplementedException(); - } - - var envFactory = new ProcessorEnvironmentFactory(this.ProcessorType); - var processorEnvironment = envFactory.CreateEnvironment( - this, - this.Policy); - - if (this.AdditionalModulePaths is not null) - { - processorEnvironment.PrependPSModulePaths(this.AdditionalModulePaths); - } - - // Always add the winget path. - var wingetModulePath = GetWinGetModulePath(); - processorEnvironment.PrependPSModulePath(wingetModulePath); - if (this.Location == PowerShellConfigurationProcessorLocation.WinGetModulePath) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, "Using winget module path"); - processorEnvironment.SetLocation(PowerShellConfigurationProcessorLocation.Custom, wingetModulePath); - } - else if (this.Location == PowerShellConfigurationProcessorLocation.Custom) - { - if (string.IsNullOrEmpty(this.CustomLocation)) - { - throw new ArgumentNullException(nameof(this.CustomLocation)); - } - - processorEnvironment.SetLocation(this.Location, this.CustomLocation); - processorEnvironment.PrependPSModulePath(this.CustomLocation); - } - else - { - processorEnvironment.SetLocation(this.Location, null); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $" Effective module path:\n{processorEnvironment.GetVariable<string>(Variables.PSModulePath)}"); - - processorEnvironment.ValidateRunspace(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating set processor."); - - return new ConfigurationSetProcessor(processorEnvironment, set, this.IsLimitMode()) { SetProcessorFactory = this }; - } - catch (Exception ex) - { - this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); - throw; - } - } - - /// <summary> /// Gets the winget module path. /// </summary> /// <returns>The winget module path.</returns> @@ -349,28 +244,13 @@ namespace Microsoft.Management.Configuration.Processor } /// <summary> - /// Sends diagnostics if appropriate. - /// </summary> - /// <param name="level">The level of this diagnostic message.</param> - /// <param name="message">The diagnostic message.</param> - internal void OnDiagnostics(DiagnosticLevel level, string message) - { - EventHandler<IDiagnosticInformation>? diagnostics = this.Diagnostics; - if (diagnostics != null && level >= this.MinimumLevel) - { - this.InvokeDiagnostics(diagnostics, level, message); - } - } - - /// <summary> /// Sends diagnostic if appropriate for PowerShell streams. /// </summary> /// <param name="level">The level of this diagnostic message.</param> /// <param name="pwsh">The PowerShell object.</param> - internal void OnDiagnostics(DiagnosticLevel level, PowerShell pwsh) + internal void OnDiagnostics(DiagnosticLevel level, System.Management.Automation.PowerShell pwsh) { - EventHandler<IDiagnosticInformation>? diagnostics = this.Diagnostics; - if (diagnostics != null && level >= this.MinimumLevel && pwsh.HadErrors) + if (this.AreDiagnosticsEnabled() && level >= this.MinimumLevel && pwsh.HadErrors) { var builder = new StringBuilder(); @@ -398,42 +278,51 @@ namespace Microsoft.Management.Configuration.Processor builder.AppendLine($"[WriteError] {error}"); } - this.InvokeDiagnostics(diagnostics, level, builder.ToString()); + this.OnDiagnostics(level, builder.ToString()); } } - private void InvokeDiagnostics(EventHandler<IDiagnosticInformation> diagnostics, DiagnosticLevel level, string message) + /// <inheritdoc /> + protected override IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode) { - Helpers.DiagnosticInformation information = new () - { - Level = level, - Message = message, - }; - diagnostics.Invoke(this, information); - } + var envFactory = new ProcessorEnvironmentFactory(this.ProcessorType); + var processorEnvironment = envFactory.CreateEnvironment( + this, + this.Policy); - [MethodImpl(MethodImplOptions.Synchronized)] - private void CheckLimitMode() - { - if (!this.IsLimitMode()) + if (this.AdditionalModulePaths is not null) { - return; + processorEnvironment.PrependPSModulePaths(this.AdditionalModulePaths); } - if (this.isCreateProcessorInvoked) + // Always add the winget path. + var wingetModulePath = GetWinGetModulePath(); + processorEnvironment.PrependPSModulePath(wingetModulePath); + if (this.Location == PowerShellConfigurationProcessorLocation.WinGetModulePath) { - this.OnDiagnostics(DiagnosticLevel.Error, "CreateSetProcessor is already invoked in limit mode."); - throw new InvalidOperationException("CreateSetProcessor is already invoked in limit mode."); + this.OnDiagnostics(DiagnosticLevel.Verbose, "Using winget module path"); + processorEnvironment.SetLocation(PowerShellConfigurationProcessorLocation.Custom, wingetModulePath); + } + else if (this.Location == PowerShellConfigurationProcessorLocation.Custom) + { + if (string.IsNullOrEmpty(this.CustomLocation)) + { + throw new ArgumentNullException(nameof(this.CustomLocation)); + } + + processorEnvironment.SetLocation(this.Location, this.CustomLocation); + processorEnvironment.PrependPSModulePath(this.CustomLocation); } else { - this.isCreateProcessorInvoked = true; + processorEnvironment.SetLocation(this.Location, null); } - } - private bool IsLimitMode() - { - return this.limitationSet != null; + this.OnDiagnostics(DiagnosticLevel.Verbose, $" Effective module path:\n{processorEnvironment.GetVariable<string>(Variables.PSModulePath)}"); + + processorEnvironment.ValidateRunspace(); + + return new PowerShellConfigurationSetProcessor(processorEnvironment, set, isLimitMode) { SetProcessorFactory = this }; } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs @@ -1,457 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="ConfigurationSetProcessor.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Set -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Management.Automation; - using System.Runtime.CompilerServices; - 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.Management.Configuration.Processor.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.Unit; - using Windows.Security.Cryptography.Certificates; - - /// <summary> - /// Configuration set processor. - /// </summary> - internal sealed partial class ConfigurationSetProcessor : IConfigurationSetProcessor - { - private readonly ConfigurationSet? configurationSet; - private List<ConfigurationUnit> limitUnitList = new List<ConfigurationUnit>(); - - /// <summary> - /// Initializes a new instance of the <see cref="ConfigurationSetProcessor"/> class. - /// </summary> - /// <param name="processorEnvironment">The processor environment.</param> - /// <param name="configurationSet">Configuration set.</param> - /// <param name="isLimitMode">Whether the set processor should work in limitation mode.</param> - public ConfigurationSetProcessor(IProcessorEnvironment processorEnvironment, ConfigurationSet? configurationSet, bool isLimitMode = false) - { - this.ProcessorEnvironment = processorEnvironment; - this.configurationSet = configurationSet; - this.IsLimitMode = isLimitMode; - - // In limit mode, configurationSet is the limitation set to be used. It cannot be null. - if (this.IsLimitMode) - { - if (this.configurationSet == null) - { - throw new ArgumentNullException(nameof(configurationSet), "configurationSet is required in limit mode."); - } - - foreach (var unit in this.configurationSet.Units) - { - this.limitUnitList.Add(unit); - } - } - } - - /// <summary> - /// Gets or initializes the set processor factory. - /// </summary> - internal PowerShellConfigurationSetProcessorFactory? SetProcessorFactory { get; init; } - - /// <summary> - /// Gets the processor environment. - /// </summary> - internal IProcessorEnvironment ProcessorEnvironment { get; } - - /// <summary> - /// Gets a value indicating whether the set processor is running in limit mode. - /// </summary> - internal bool IsLimitMode { get; private set; } - - /// <summary> - /// Creates a configuration unit processor for the given unit. - /// </summary> - /// <param name="incomingUnit">Configuration unit.</param> - /// <returns>A configuration unit processor.</returns> - public IConfigurationUnitProcessor CreateUnitProcessor( - ConfigurationUnit incomingUnit) - { - try - { - this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); - - // CreateUnitProcessor can only be called once on each configuration unit in limit mode. - var unit = this.GetConfigurationUnit(incomingUnit, true); - - var configurationUnitInternal = new ConfigurationUnitInternal(unit, this.configurationSet?.Path) { UnitTypeIsResourceName = IsUnitTypeResourceName(this.configurationSet?.SchemaVersion) }; - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating unit processor for: {configurationUnitInternal.QualifiedName}..."); - - var dscResourceInfo = this.PrepareUnitForProcessing(configurationUnitInternal); - - this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating unit processor."); - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Using unit from location: {dscResourceInfo.Path}"); - return new ConfigurationUnitProcessor( - this.ProcessorEnvironment, - new ConfigurationUnitAndResource(configurationUnitInternal, dscResourceInfo), - this.IsLimitMode) - { SetProcessorFactory = this.SetProcessorFactory }; - } - catch (Exception ex) - { - this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); - throw; - } - } - - /// <summary> - /// Gets the configuration unit processor details for the given unit. - /// </summary> - /// <param name="incomingUnit">Configuration unit.</param> - /// <param name="detailFlags">Detail flags.</param> - /// <returns>Configuration unit processor details.</returns> - public IConfigurationUnitProcessorDetails? GetUnitProcessorDetails( - ConfigurationUnit incomingUnit, - ConfigurationUnitDetailFlags detailFlags) - { - try - { - this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); - - // GetUnitProcessorDetails can be invoked multiple times on each configuration unit in limit mode. - var unit = this.GetConfigurationUnit(incomingUnit); - - var unitInternal = new ConfigurationUnitInternal(unit, this.configurationSet?.Path); - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting unit details [{detailFlags}] for: {unitInternal.QualifiedName}"); - - // (Local | Download | Load) will all work off of local files, so if any one is an option just use the local module info if found. - DscResourceInfoInternal? dscResourceInfo = null; - if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Local) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) - { - dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); - } - - if (dscResourceInfo is not null) - { - return this.GetUnitProcessorDetailsLocal( - dscResourceInfo.Name, - dscResourceInfo, - detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)); - } - - if (!(detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load))) - { - // Not found locally. - return null; - } - - var unitModuleInfo = this.FindUnitModule(unitInternal); - if (unitModuleInfo is null) - { - // Not found in catalog. - return null; - } - - PSObject foundModule = unitModuleInfo.Value.Module; - string resourceName = unitModuleInfo.Value.ResourceName; - - dynamic foundModuleInfo = foundModule; - - if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog)) - { - return new ConfigurationUnitProcessorDetails( - resourceName, - null, - null, - foundModule, - null); - } - - if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download)) - { - var tempSavePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempSavePath); - this.ProcessorEnvironment.SaveModule(foundModule, tempSavePath); - - var moduleInfo = this.ProcessorEnvironment.GetAvailableModule( - Path.Combine(tempSavePath, foundModuleInfo.Name)); - - return new ConfigurationUnitProcessorDetails( - resourceName, - null, - moduleInfo, - foundModule, - this.GetCertificates(moduleInfo)); - } - - if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) - { - this.ProcessorEnvironment.InstallModule(foundModule); - - dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); - - if (dscResourceInfo is null) - { - // Well, this is awkward. - throw new InstallDscResourceException( - unitInternal.ResourceName, - PowerShellHelpers.CreateModuleSpecification(foundModuleInfo.Name, foundModuleInfo.Version)); - } - - return this.GetUnitProcessorDetailsLocal(dscResourceInfo.Name, dscResourceInfo, true); - } - - return null; - } - catch (Exception ex) - { - this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); - throw; - } - } - - private static bool IsUnitTypeResourceName(string? schemaVersion) - { - return schemaVersion != null && schemaVersion == "0.1"; - } - - private static bool ConfigurationUnitEquals(ConfigurationUnit first, ConfigurationUnit second) - { - var firstIdentifier = first.Identifier; - var firstIntent = first.Intent; - var firstType = first.Type; - var secondIdentifier = second.Identifier; - var secondType = second.Type; - var secondIntent = second.Intent; - - if (firstIdentifier != secondIdentifier || - firstType != secondType || - firstIntent != secondIntent) - { - return false; - } - - var firstEnvironment = first.Environment; - var secondEnvironment = second.Environment; - if (firstEnvironment.Context != secondEnvironment.Context || - firstEnvironment.ProcessorIdentifier != secondEnvironment.ProcessorIdentifier || - !firstEnvironment.ProcessorProperties.ContentEquals(secondEnvironment.ProcessorProperties)) - { - return false; - } - - if (!first.Settings.ContentEquals(second.Settings)) - { - return false; - } - - if (!first.Metadata.ContentEquals(second.Metadata)) - { - return false; - } - - // Note: Consider group units logic when group units are supported. - return true; - } - - /// <summary> - /// Finds the module and preferred resource name for processing the configuration unit. - /// </summary> - /// <param name="unitInternal">The internal configuration unit.</param> - /// <returns>A tuple containing the module info and preferred resource name, or null if not found.</returns> - private (PSObject Module, string ResourceName)? FindUnitModule(ConfigurationUnitInternal unitInternal) - { - PSObject? foundModule = null; - string resourceName = string.Empty; - - // If module has been specified, find it and assume that the resource will be within it. - // Do this first as we do not currently gain much from FindDscResource; if that changes then it can be the primary. - if (unitInternal.Module != null) - { - foundModule = this.ProcessorEnvironment.FindModule(unitInternal); - if (foundModule != null) - { - resourceName = unitInternal.ResourceName; - } - } - else - { - dynamic? foundResource = this.ProcessorEnvironment.FindDscResource(unitInternal); - if (foundResource != null) - { - foundModule = foundResource.PSGetModuleInfo; - - // Hopefully they will never change the properties name. If someone can explain to me - // why assign it Name to $_ in Find-DscResource turns into a string in PowerShell but - // into a PSObject here that would be nice... - resourceName = foundResource.Name.ToString(); - } - } - - if (foundModule != null) - { - return (foundModule, resourceName); - } - - return null; - } - - private DscResourceInfoInternal PrepareUnitForProcessing(ConfigurationUnitInternal unitInternal) - { - // Invoke-DscResource makes a call to Get-DscResource which looks at the entire PSModulePath - // to see if a resource exists. DscResourcesMap is an attempt to try to optimize Get-DscResource - // by making just one call and get all of them, but it doesn't support minVersion and maxVersion. - // For now, lets make PowerShell fully figure out which module to use and try to optimize it later. - // This class will have a private member Lazy<DscResourcesMap> which will be initialized by calling - // this.ProcessorEnvironment.GetAllDscResources() - // To improve the performance even more, we will still need Invoke-DscResource to be update to - // get a DSC resource info object instead of calling Get-DscResource every time. - var dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); - - if (dscResourceInfo is null) - { - var findUnitModuleResult = this.FindUnitModule(unitInternal); - - if (findUnitModuleResult is null) - { - throw new FindDscResourceNotFoundException(unitInternal.ResourceName, unitInternal.Module); - } - - this.ProcessorEnvironment.InstallModule(findUnitModuleResult.Value.Module); - - // Now we should find it. - dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); - if (dscResourceInfo is null) - { - throw new InstallDscResourceException(unitInternal.ResourceName, unitInternal.Module); - } - } - - // PowerShell will prompt the user when a module that is downloaded from the internet is imported. - // For a hosted environment, this will throw an exception because it doesn't support user interaction. - // In the case we don't import the module here, eventually Invoke-DscResource will fail for class - // resources because they will call a method on a null obj. It is easier to just fail here. - // The exception being thrown will have the correct details (user needs to call Unblock-File) - // instead of the cryptic Invoke with 0 arguments. - if (!string.IsNullOrEmpty(dscResourceInfo.Path)) - { - try - { - this.ProcessorEnvironment.ImportModule(dscResourceInfo.Path); - } - catch (Exception e) - { - throw new ImportModuleException(dscResourceInfo.ModuleName, e); - } - } - - return dscResourceInfo; - } - - private ConfigurationUnitProcessorDetails GetUnitProcessorDetailsLocal( - string unitName, - DscResourceInfoInternal dscResourceInfo, - bool importModule) - { - // I'm looking at you resources under C:\WINDOWS\system32\WindowsPowershell - if (dscResourceInfo.ModuleName is null || - dscResourceInfo.Version is null) - { - return new ConfigurationUnitProcessorDetails( - dscResourceInfo.Name, - dscResourceInfo, - null, - null, - null); - } - - var module = PowerShellHelpers.CreateModuleSpecification( - dscResourceInfo.ModuleName, - dscResourceInfo.Version.ToString()); - - // Get-InstalledModule only works for modules installed via PowerShell-Get. - // There are some properties that can only be obtain by that it so is better to take both. - var moduleInfo = this.ProcessorEnvironment.GetAvailableModule(module); - var installedModule = this.ProcessorEnvironment.GetInstalledModule(module); - - if (importModule) - { - this.ProcessorEnvironment.ImportModule(module); - } - - return new ConfigurationUnitProcessorDetails( - dscResourceInfo.Name, - dscResourceInfo, - moduleInfo, - installedModule, - this.GetCertificates(moduleInfo)); - } - - private List<Certificate>? GetCertificates(PSModuleInfo? moduleInfo) - { - if (moduleInfo is null) - { - return null; - } - - // TODO: we still need to investigate more here, but lets start with something. - var paths = new List<string>(); - - var psdPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psd1"); - if (File.Exists(psdPath)) - { - paths.Add(psdPath); - } - - var psmPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psm1"); - if (File.Exists(psmPath)) - { - paths.Add(psmPath); - } - - return this.ProcessorEnvironment.GetCertsOfValidSignedFiles(paths.ToArray()); - } - - private void OnDiagnostics(DiagnosticLevel level, string message) - { - this.SetProcessorFactory?.OnDiagnostics(level, message); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - private ConfigurationUnit GetConfigurationUnit(ConfigurationUnit incomingUnit, bool useLimitList = false) - { - if (this.IsLimitMode) - { - if (this.configurationSet == null) - { - throw new InvalidOperationException("Configuration set should not be null in limit mode."); - } - - var unitList = useLimitList ? this.limitUnitList : this.configurationSet.Units; - - for (int i = 0; i < unitList.Count; i++) - { - var unit = unitList[i]; - if (ConfigurationUnitEquals(incomingUnit, unit)) - { - if (useLimitList) - { - this.limitUnitList.RemoveAt(i); - } - - return unit; - } - - // Note: Consider group units logic when group units are supported. - } - - this.OnDiagnostics(DiagnosticLevel.Error, "Configuration unit not found in limit mode."); - throw new InvalidOperationException("Configuration unit not found in limit mode."); - } - else - { - return incomingUnit; - } - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs @@ -0,0 +1,219 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigurationSetProcessorBase.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Set +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.Processor.Factory; + using Microsoft.Management.Configuration.Processor.Unit; + + /// <summary> + /// IConfigurationSetProcessor base implementation. + /// </summary> + internal abstract partial class ConfigurationSetProcessorBase + { + private readonly ConfigurationSet? configurationSet; + private List<ConfigurationUnit> limitUnitList = new List<ConfigurationUnit>(); + + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationSetProcessorBase"/> class. + /// </summary> + /// <param name="configurationSet">Configuration set.</param> + /// <param name="isLimitMode">Whether the set processor should work in limitation mode.</param> + public ConfigurationSetProcessorBase(ConfigurationSet? configurationSet, bool isLimitMode) + { + this.configurationSet = configurationSet; + this.IsLimitMode = isLimitMode; + + // In limit mode, configurationSet is the limitation set to be used. It cannot be null. + if (this.IsLimitMode) + { + if (this.configurationSet == null) + { + throw new ArgumentNullException(nameof(configurationSet), "configurationSet is required in limit mode."); + } + + foreach (var unit in this.configurationSet.Units) + { + this.limitUnitList.Add(unit); + } + } + } + + /// <summary> + /// Gets or initializes the set processor factory. + /// </summary> + internal ConfigurationSetProcessorFactoryBase? SetProcessorFactory { get; init; } + + /// <summary> + /// Gets a value indicating whether the set processor is running in limit mode. + /// </summary> + internal bool IsLimitMode { get; private set; } + + /// <summary> + /// Gets the configuration set for this processor. + /// </summary> + protected ConfigurationSet? ConfigurationSet + { + get { return this.configurationSet; } + } + + /// <summary> + /// Creates a configuration unit processor for the given unit. + /// </summary> + /// <param name="incomingUnit">Configuration unit.</param> + /// <returns>A configuration unit processor.</returns> + public IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit incomingUnit) + { + try + { + this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); + + // CreateUnitProcessor can only be called once on each configuration unit in limit mode. + var unit = this.GetConfigurationUnit(incomingUnit, true); + + IConfigurationUnitProcessor result = this.CreateUnitProcessorInternal(unit); + + this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating unit processor."); + + return result; + } + catch (Exception ex) + { + this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); + throw; + } + } + + /// <summary> + /// Gets the configuration unit processor details for the given unit. + /// </summary> + /// <param name="incomingUnit">Configuration unit.</param> + /// <param name="detailFlags">Detail flags.</param> + /// <returns>Configuration unit processor details.</returns> + public IConfigurationUnitProcessorDetails? GetUnitProcessorDetails(ConfigurationUnit incomingUnit, ConfigurationUnitDetailFlags detailFlags) + { + try + { + this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); + + // GetUnitProcessorDetails can be invoked multiple times on each configuration unit in limit mode. + var unit = this.GetConfigurationUnit(incomingUnit); + + return this.GetUnitProcessorDetailsInternal(unit, detailFlags); + } + catch (Exception ex) + { + this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); + throw; + } + } + + /// <summary> + /// Creates a configuration unit processor for the given unit. + /// </summary> + /// <param name="unit">Configuration unit.</param> + /// <returns>A configuration unit processor.</returns> + protected abstract IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit); + + /// <summary> + /// Gets the configuration unit processor details for the given unit. + /// </summary> + /// <param name="unit">Configuration unit.</param> + /// <param name="detailFlags">Detail flags.</param> + /// <returns>Configuration unit processor details.</returns> + protected abstract IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); + + /// <summary> + /// Sends diagnostics to factory. + /// </summary> + /// <param name="level">The level of this diagnostic message.</param> + /// <param name="message">The diagnostic message.</param> + protected void OnDiagnostics(DiagnosticLevel level, string message) + { + this.SetProcessorFactory?.OnDiagnostics(level, message); + } + + private static bool ConfigurationUnitEquals(ConfigurationUnit first, ConfigurationUnit second) + { + var firstIdentifier = first.Identifier; + var firstIntent = first.Intent; + var firstType = first.Type; + var secondIdentifier = second.Identifier; + var secondType = second.Type; + var secondIntent = second.Intent; + + if (firstIdentifier != secondIdentifier || + firstType != secondType || + firstIntent != secondIntent) + { + return false; + } + + var firstEnvironment = first.Environment; + var secondEnvironment = second.Environment; + if (firstEnvironment.Context != secondEnvironment.Context || + firstEnvironment.ProcessorIdentifier != secondEnvironment.ProcessorIdentifier || + !firstEnvironment.ProcessorProperties.ContentEquals(secondEnvironment.ProcessorProperties)) + { + return false; + } + + if (!first.Settings.ContentEquals(second.Settings)) + { + return false; + } + + if (!first.Metadata.ContentEquals(second.Metadata)) + { + return false; + } + + // Note: Consider group units logic when group units are supported. + return true; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private ConfigurationUnit GetConfigurationUnit(ConfigurationUnit incomingUnit, bool useLimitList = false) + { + if (this.IsLimitMode) + { + if (this.configurationSet == null) + { + throw new InvalidOperationException("Configuration set should not be null in limit mode."); + } + + var unitList = useLimitList ? this.limitUnitList : this.configurationSet.Units; + + for (int i = 0; i < unitList.Count; i++) + { + var unit = unitList[i]; + if (ConfigurationUnitEquals(incomingUnit, unit)) + { + if (useLimitList) + { + this.limitUnitList.RemoveAt(i); + } + + return unit; + } + + // Note: Consider group units logic when group units are supported. + } + + this.OnDiagnostics(DiagnosticLevel.Error, "Configuration unit not found in limit mode."); + throw new InvalidOperationException("Configuration unit not found in limit mode."); + } + else + { + return incomingUnit; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessor.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessor.cs @@ -1,223 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="ConfigurationUnitProcessor.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using System; - using System.ComponentModel; - using System.Runtime.CompilerServices; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; - - /// <summary> - /// Provides access to a specific configuration unit within the runtime. - /// </summary> - internal sealed partial class ConfigurationUnitProcessor : IConfigurationUnitProcessor - { - private readonly IProcessorEnvironment processorEnvironment; - private readonly ConfigurationUnitAndResource unitResource; - private readonly bool isLimitMode; - - private bool isTestInvoked = false; - private bool isApplyInvoked = false; - - /// <summary> - /// Initializes a new instance of the <see cref="ConfigurationUnitProcessor"/> class. - /// </summary> - /// <param name="processorEnvironment">Processor environment.</param> - /// <param name="unitResource">UnitResource.</param> - /// <param name="isLimitMode">Whether it is under limit mode.</param> - internal ConfigurationUnitProcessor( - IProcessorEnvironment processorEnvironment, - ConfigurationUnitAndResource unitResource, - bool isLimitMode = false) - { - this.processorEnvironment = processorEnvironment; - this.unitResource = unitResource; - this.isLimitMode = isLimitMode; - } - - /// <summary> - /// Gets the configuration unit that the processor was created for. - /// </summary> - public ConfigurationUnit Unit => this.unitResource.Unit; - - /// <summary> - /// Gets or initializes the set processor factory. - /// </summary> - internal PowerShellConfigurationSetProcessorFactory? SetProcessorFactory { get; init; } - - /// <summary> - /// Gets the current system state for the configuration unit. - /// Calls Get on the DSC resource. - /// </summary> - /// <returns>A <see cref="IGetSettingsResult"/>.</returns> - public IGetSettingsResult GetSettings() - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Get` for resource: {this.unitResource.UnitInternal.QualifiedName}..."); - - this.CheckLimitMode(ConfigurationUnitIntent.Inform); - var result = new GetSettingsResult(this.Unit); - - try - { - result.Settings = this.processorEnvironment.InvokeGetResource( - this.unitResource.GetSettings(), - this.unitResource.ResourceName, - this.unitResource.Module); - } - catch (Exception e) - { - this.ExtractExceptionInformation(e, result.InternalResult); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Get`."); - return result; - } - - /// <summary> - /// Determines if the system is already in the state described by the configuration unit. - /// Calls Test on the DSC resource. - /// </summary> - /// <returns>A <see cref="ITestSettingsResult"/>.</returns> - public ITestSettingsResult TestSettings() - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Test` for resource: {this.unitResource.UnitInternal.QualifiedName}..."); - - if (this.Unit.Intent == ConfigurationUnitIntent.Inform) - { - this.OnDiagnostics(DiagnosticLevel.Error, "`Test` should not be called on a unit with intent of `Inform`"); - throw new NotSupportedException(); - } - - this.CheckLimitMode(ConfigurationUnitIntent.Assert); - var result = new TestSettingsResult(this.Unit); - result.TestResult = ConfigurationTestResult.Failed; - try - { - bool testResult = this.processorEnvironment.InvokeTestResource( - this.unitResource.GetSettings(), - this.unitResource.ResourceName, - this.unitResource.Module); - - result.TestResult = testResult ? ConfigurationTestResult.Positive : ConfigurationTestResult.Negative; - } - catch (Exception e) - { - this.ExtractExceptionInformation(e, result.InternalResult); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Test`."); - return result; - } - - /// <summary> - /// Applies the state described in the configuration unit. - /// Calls Set in the DSC resource. - /// </summary> - /// <returns>A <see cref="IApplySettingsResult"/>.</returns> - public IApplySettingsResult ApplySettings() - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Apply` for resource: {this.unitResource.UnitInternal.QualifiedName}..."); - - if (this.Unit.Intent == ConfigurationUnitIntent.Inform || - this.Unit.Intent == ConfigurationUnitIntent.Assert) - { - this.OnDiagnostics(DiagnosticLevel.Error, $"`Apply` should not be called on a unit with intent of `{this.Unit.Intent}`"); - throw new NotSupportedException(); - } - - this.CheckLimitMode(ConfigurationUnitIntent.Apply); - var result = new ApplySettingsResult(this.Unit); - try - { - result.RebootRequired = this.processorEnvironment.InvokeSetResource( - this.unitResource.GetSettings(), - this.unitResource.ResourceName, - this.unitResource.Module); - } - catch (Exception e) - { - this.ExtractExceptionInformation(e, result.InternalResult); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Apply`."); - return result; - } - - private void ExtractExceptionInformation(Exception e, ConfigurationUnitResultInformation resultInformation) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, e.ToString()); - - IConfigurationUnitResultException? configurationUnitResultException = e as IConfigurationUnitResultException; - if (configurationUnitResultException != null) - { - resultInformation.ResultCode = e; - resultInformation.Description = configurationUnitResultException.Description; - resultInformation.Details = configurationUnitResultException.Details; - resultInformation.ResultSource = configurationUnitResultException.ResultSource; - } - else - { - var inner = e.GetMostInnerException(); - resultInformation.ResultCode = inner; - resultInformation.Description = e.Message; - resultInformation.Details = e.ToString(); - resultInformation.ResultSource = ConfigurationUnitResultSource.Internal; - } - } - - [MethodImpl(MethodImplOptions.Synchronized)] - private void CheckLimitMode(ConfigurationUnitIntent intent) - { - if (!this.isLimitMode) - { - return; - } - - if (intent == ConfigurationUnitIntent.Unknown) - { - throw new InvalidEnumArgumentException(nameof(ConfigurationUnitIntent.Unknown)); - } - - if (intent == ConfigurationUnitIntent.Assert) - { - if (this.isTestInvoked) - { - this.OnDiagnostics(DiagnosticLevel.Error, "TestSettings is already invoked in limit mode."); - throw new InvalidOperationException("TestSettings is already invoked in limit mode."); - } - else - { - this.isTestInvoked = true; - } - } - - if (intent == ConfigurationUnitIntent.Apply) - { - if (this.isApplyInvoked) - { - this.OnDiagnostics(DiagnosticLevel.Error, "ApplySettings is already invoked in limit mode."); - throw new InvalidOperationException("ApplySettings is already invoked in limit mode."); - } - else - { - this.isApplyInvoked = true; - } - } - - // Get is always allowed now. - } - - private void OnDiagnostics(DiagnosticLevel level, string message) - { - this.SetProcessorFactory?.OnDiagnostics(level, message); - } - } -} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorBase.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorBase.cs @@ -0,0 +1,227 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigurationUnitProcessorBase.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using System; + using System.ComponentModel; + using System.Runtime.CompilerServices; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.Processor.Factory; + using Microsoft.Management.Configuration.Processor.Helpers; + using Windows.Foundation.Collections; + + /// <summary> + /// IConfigurationUnitProcessor base implementation. + /// </summary> + internal abstract partial class ConfigurationUnitProcessorBase + { + private readonly ConfigurationUnitInternal unitInternal; + private readonly bool isLimitMode; + + private bool isTestInvoked = false; + private bool isApplyInvoked = false; + + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationUnitProcessorBase"/> class. + /// </summary> + /// <param name="unitInternal">UnitInternal.</param> + /// <param name="isLimitMode">Whether it is under limit mode.</param> + internal ConfigurationUnitProcessorBase(ConfigurationUnitInternal unitInternal, bool isLimitMode) + { + this.unitInternal = unitInternal; + this.isLimitMode = isLimitMode; + } + + /// <summary> + /// Gets the configuration unit that the processor was created for. + /// </summary> + public ConfigurationUnit Unit => this.unitInternal.Unit; + + /// <summary> + /// Gets or initializes the set processor factory. + /// </summary> + internal ConfigurationSetProcessorFactoryBase? SetProcessorFactory { get; init; } + + /// <summary> + /// Gets the current system state for the configuration unit. + /// Calls Get on the DSC resource. + /// </summary> + /// <returns>A <see cref="IGetSettingsResult"/>.</returns> + public IGetSettingsResult GetSettings() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Get` for resource: {this.unitInternal.QualifiedName}..."); + + this.CheckLimitMode(ConfigurationUnitIntent.Inform); + var result = new GetSettingsResult(this.Unit); + + try + { + result.Settings = this.GetSettingsInternal(); + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Get`."); + return result; + } + + /// <summary> + /// Determines if the system is already in the state described by the configuration unit. + /// Calls Test on the DSC resource. + /// </summary> + /// <returns>A <see cref="ITestSettingsResult"/>.</returns> + public ITestSettingsResult TestSettings() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Test` for resource: {this.unitInternal.QualifiedName}..."); + + if (this.Unit.Intent == ConfigurationUnitIntent.Inform) + { + this.OnDiagnostics(DiagnosticLevel.Error, "`Test` should not be called on a unit with intent of `Inform`"); + throw new NotSupportedException(); + } + + this.CheckLimitMode(ConfigurationUnitIntent.Assert); + var result = new TestSettingsResult(this.Unit); + result.TestResult = ConfigurationTestResult.Failed; + try + { + bool testResult = this.TestSettingsInternal(); + + result.TestResult = testResult ? ConfigurationTestResult.Positive : ConfigurationTestResult.Negative; + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Test`."); + return result; + } + + /// <summary> + /// Applies the state described in the configuration unit. + /// Calls Set in the DSC resource. + /// </summary> + /// <returns>A <see cref="IApplySettingsResult"/>.</returns> + public IApplySettingsResult ApplySettings() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Apply` for resource: {this.unitInternal.QualifiedName}..."); + + if (this.Unit.Intent == ConfigurationUnitIntent.Inform || + this.Unit.Intent == ConfigurationUnitIntent.Assert) + { + this.OnDiagnostics(DiagnosticLevel.Error, $"`Apply` should not be called on a unit with intent of `{this.Unit.Intent}`"); + throw new NotSupportedException(); + } + + this.CheckLimitMode(ConfigurationUnitIntent.Apply); + var result = new ApplySettingsResult(this.Unit); + try + { + result.RebootRequired = this.ApplySettingsInternal(); + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Apply`."); + return result; + } + + /// <summary> + /// Gets the current settings. + /// </summary> + /// <returns>The current settings.</returns> + protected abstract ValueSet GetSettingsInternal(); + + /// <summary> + /// Tests the current settings. + /// </summary> + /// <returns>A boolean indicating whether the settings are in the desired state.</returns> + protected abstract bool TestSettingsInternal(); + + /// <summary> + /// Applies the desired settings. + /// </summary> + /// <returns>A boolean indicating whether a reboot is required.</returns> + protected abstract bool ApplySettingsInternal(); + + private void ExtractExceptionInformation(Exception e, ConfigurationUnitResultInformation resultInformation) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, e.ToString()); + + IConfigurationUnitResultException? configurationUnitResultException = e as IConfigurationUnitResultException; + if (configurationUnitResultException != null) + { + resultInformation.ResultCode = e; + resultInformation.Description = configurationUnitResultException.Description; + resultInformation.Details = configurationUnitResultException.Details; + resultInformation.ResultSource = configurationUnitResultException.ResultSource; + } + else + { + var inner = e.GetMostInnerException(); + resultInformation.ResultCode = inner; + resultInformation.Description = e.Message; + resultInformation.Details = e.ToString(); + resultInformation.ResultSource = ConfigurationUnitResultSource.Internal; + } + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private void CheckLimitMode(ConfigurationUnitIntent intent) + { + if (!this.isLimitMode) + { + return; + } + + if (intent == ConfigurationUnitIntent.Unknown) + { + throw new InvalidEnumArgumentException(nameof(ConfigurationUnitIntent.Unknown)); + } + + if (intent == ConfigurationUnitIntent.Assert) + { + if (this.isTestInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "TestSettings is already invoked in limit mode."); + throw new InvalidOperationException("TestSettings is already invoked in limit mode."); + } + else + { + this.isTestInvoked = true; + } + } + + if (intent == ConfigurationUnitIntent.Apply) + { + if (this.isApplyInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "ApplySettings is already invoked in limit mode."); + throw new InvalidOperationException("ApplySettings is already invoked in limit mode."); + } + else + { + this.isApplyInvoked = true; + } + } + + // Get is always allowed now. + } + + private void OnDiagnostics(DiagnosticLevel level, string message) + { + this.SetProcessorFactory?.OnDiagnostics(level, message); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorDetails.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorDetails.cs @@ -8,258 +8,108 @@ namespace Microsoft.Management.Configuration.Processor.Unit { using System; using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using System.Reflection; using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; - using Windows.Security.Cryptography.Certificates; /// <summary> /// Provides information for a specific configuration unit within the runtime. /// </summary> internal sealed partial class ConfigurationUnitProcessorDetails : IConfigurationUnitProcessorDetails { - private static readonly IEnumerable<string> PublicRepositories = new string[] - { - "https://www.powershellgallery.com/api/v2", - }; - /// <summary> /// Initializes a new instance of the <see cref="ConfigurationUnitProcessorDetails"/> class. /// </summary> - /// <param name="unitName">Unit name.</param> - /// <param name="dscResourceInfo">DSC Resource Info.</param> - /// <param name="psModuleInfo">PSModuleInfo.</param> - /// <param name="getModuleInfo">GetModuleInfo.</param> - /// <param name="certs">List of certificates..</param> - public ConfigurationUnitProcessorDetails( - string unitName, - DscResourceInfoInternal? dscResourceInfo, - PSModuleInfo? psModuleInfo, - PSObject? getModuleInfo, - List<Certificate>? certs) + public ConfigurationUnitProcessorDetails() { - if (dscResourceInfo is null && - psModuleInfo is null && - getModuleInfo is null) - { - throw new ArgumentException(); - } - - this.UnitType = unitName; - - if (dscResourceInfo is not null) - { - this.IsLocal = true; - - var settings = new List<IConfigurationUnitSettingDetails>(); - foreach (var properties in dscResourceInfo.Properties) - { - settings.Add(new ConfigurationUnitSettingDetails(properties)); - } - - this.Settings = settings; - - if (psModuleInfo is null) - { - this.ModuleName = dscResourceInfo.ModuleName; - this.Version = dscResourceInfo.Version is not null ? dscResourceInfo.Version.ToString() : null; - } - } - - if (psModuleInfo is not null) - { - this.ModuleDocumentationUri = psModuleInfo.HelpInfoUri is not null ? new Uri(psModuleInfo.HelpInfoUri) : null; - this.UnitIconUri = psModuleInfo.IconUri; - this.ModuleName = psModuleInfo.Name; - this.ModuleType = psModuleInfo.ModuleType.ToString(); - this.ModuleDescription = psModuleInfo.Description; - this.PublishedModuleUri = psModuleInfo.ProjectUri; - this.Version = psModuleInfo.Version.ToString(); - this.Author = psModuleInfo.Author; - this.Publisher = psModuleInfo.CompanyName; - } - - if (getModuleInfo is not null) - { - this.TrySetPropertyAsString(getModuleInfo, "Repository", nameof(this.ModuleSource)); - this.TrySetPropertyFromDateTimeToDateTimeOffset(getModuleInfo, "PublishedDate", nameof(this.PublishedDate)); - - var repoSourceLocation = getModuleInfo.Properties["RepositorySourceLocation"]; - if (repoSourceLocation is not null) - { - string? repoSourceLocationValue = repoSourceLocation.Value as string; - if (repoSourceLocationValue is not null) - { - this.IsPublic = PublicRepositories.Any(r => r == repoSourceLocationValue); - } - } - - if (psModuleInfo is null) - { - // Type is not the same as this PSModuleType. - this.TrySetPropertyAsUri(getModuleInfo, "IconUri", nameof(this.UnitIconUri)); - this.TrySetPropertyAsString(getModuleInfo, "Name", nameof(this.ModuleName)); - this.TrySetPropertyAsString(getModuleInfo, "Description", nameof(this.ModuleDescription)); - this.TrySetPropertyAsString(getModuleInfo, "Version", nameof(this.Version)); - this.TrySetPropertyAsString(getModuleInfo, "Author", nameof(this.Author)); - this.TrySetPropertyAsString(getModuleInfo, "CompanyName", nameof(this.Publisher)); - } - } - - this.SigningInformation = certs; } /// <summary> /// Gets the name of the unit of configuration. /// </summary> - public string UnitType { get; private set; } + required public string UnitType { get; init; } /// <summary> - /// Gets the description of the unit of configuration. + /// Gets or sets the description of the unit of configuration. /// </summary> - public string? UnitDescription { get; private set; } + public string? UnitDescription { get; internal set; } /// <summary> - /// Gets the URI of the documentation for the unit of configuration. + /// Gets or sets the URI of the documentation for the unit of configuration. /// </summary> - public Uri? UnitDocumentationUri { get; private set; } + public Uri? UnitDocumentationUri { get; internal set; } /// <summary> - /// Gets the URI of the icon for the unit of configuration. + /// Gets or sets the URI of the icon for the unit of configuration. /// </summary> - public Uri? UnitIconUri { get; private set; } + public Uri? UnitIconUri { get; internal set; } /// <summary> - /// Gets the name of the module containing the unit of configuration. + /// Gets or sets the name of the module containing the unit of configuration. /// </summary> - public string? ModuleName { get; private set; } + public string? ModuleName { get; internal set; } /// <summary> - /// Gets the type of the module containing the unit of configuration. + /// Gets or sets the type of the module containing the unit of configuration. /// </summary> - public string? ModuleType { get; private set; } + public string? ModuleType { get; internal set; } /// <summary> - /// Gets the source of the module containing the unit of configuration. + /// Gets or sets the source of the module containing the unit of configuration. /// </summary> - public string? ModuleSource { get; private set; } + public string? ModuleSource { get; internal set; } /// <summary> - /// Gets the description of the module containing the unit of configuration. + /// Gets or sets the description of the module containing the unit of configuration. /// </summary> - public string? ModuleDescription { get; private set; } + public string? ModuleDescription { get; internal set; } /// <summary> - /// Gets the URI of the documentation for the module containing the unit of configuration. + /// Gets or sets the URI of the documentation for the module containing the unit of configuration. /// </summary> - public Uri? ModuleDocumentationUri { get; private set; } + public Uri? ModuleDocumentationUri { get; internal set; } /// <summary> - /// Gets the URI for the published module containing the unit of configuration. + /// Gets or sets the URI for the published module containing the unit of configuration. /// </summary> - public Uri? PublishedModuleUri { get; private set; } + public Uri? PublishedModuleUri { get; internal set; } /// <summary> - /// Gets the version of the module containing the unit of configuration. + /// Gets or sets the version of the module containing the unit of configuration. /// </summary> - public string? Version { get; private set; } + public string? Version { get; internal set; } /// <summary> - /// Gets the publishing date of the module containing the unit of configuration. + /// Gets or sets the publishing date of the module containing the unit of configuration. /// </summary> - public DateTimeOffset PublishedDate { get; private set; } + public DateTimeOffset PublishedDate { get; internal set; } /// <summary> - /// Gets a value indicating whether the module is already present on the system. + /// Gets or sets a value indicating whether the module is already present on the system. /// </summary> - public bool IsLocal { get; private set; } + public bool IsLocal { get; internal set; } /// <summary> - /// Gets the author of the module containing the unit of configuration. + /// Gets or sets the author of the module containing the unit of configuration. /// </summary> - public string? Author { get; private set; } + public string? Author { get; internal set; } /// <summary> - /// Gets the publisher of the module containing the unit of configuration. + /// Gets or sets the publisher of the module containing the unit of configuration. /// </summary> - public string? Publisher { get; private set; } + public string? Publisher { get; internal set; } /// <summary> - /// Gets the signing certificate of the module files containing the unit of configuration. + /// Gets or sets the signing certificate of the module files containing the unit of configuration. /// </summary> - public IReadOnlyList<object>? SigningInformation { get; private set; } + public IReadOnlyList<object>? SigningInformation { get; internal set; } /// <summary> - /// Gets the settings information for the unit of configuration. + /// Gets or sets the settings information for the unit of configuration. /// </summary> - public IReadOnlyList<IConfigurationUnitSettingDetails>? Settings { get; } + public IReadOnlyList<IConfigurationUnitSettingDetails>? Settings { get; internal set; } /// <summary> - /// Gets a value indicating whether the module comes from a public repository. + /// Gets or sets a value indicating whether the module comes from a public repository. /// </summary> - public bool IsPublic { get; private set; } - - private void TrySetPropertyAsString(PSObject getModuleInfo, string getModuleInfoProperty, string propertyName) - { - Type meType = typeof(ConfigurationUnitProcessorDetails); - PropertyInfo? propertyInfo = meType.GetProperty(propertyName); - if (propertyInfo is not null) - { - var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; - if (moduleProperty is not null) - { - propertyInfo.SetValue(this, moduleProperty.Value as string); - } - else - { - propertyInfo.SetValue(this, null); - } - } - } - - private void TrySetPropertyAsUri(PSObject getModuleInfo, string getModuleInfoProperty, string propertyName) - { - Type meType = typeof(ConfigurationUnitProcessorDetails); - PropertyInfo? propertyInfo = meType.GetProperty(propertyName); - if (propertyInfo is not null) - { - var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; - if (moduleProperty is not null) - { - string? modulePropertyString = moduleProperty.Value as string; - if (modulePropertyString is not null) - { - propertyInfo.SetValue(this, new Uri(modulePropertyString)); - } - } - } - } - - private void TrySetPropertyFromDateTimeToDateTimeOffset(PSObject getModuleInfo, string getModuleInfoProperty, string propertyName) - { - Type meType = typeof(ConfigurationUnitProcessorDetails); - PropertyInfo? propertyInfo = meType.GetProperty(propertyName); - if (propertyInfo is not null) - { - var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; - if (moduleProperty is not null) - { - DateTime propertyAsDateTime; - - try - { - propertyAsDateTime = (DateTime)moduleProperty.Value; - } - catch - { - return; - } - - propertyInfo.SetValue(this, new DateTimeOffset(propertyAsDateTime)); - } - } - } + public bool IsPublic { get; internal set; } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitSettingDetails.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitSettingDetails.cs @@ -7,8 +7,6 @@ namespace Microsoft.Management.Configuration.Processor.Unit { using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; - using Windows.Foundation.Metadata; /// <summary> /// Provides information for a specific configuration unit setting. @@ -18,96 +16,49 @@ namespace Microsoft.Management.Configuration.Processor.Unit /// <summary> /// Initializes a new instance of the <see cref="ConfigurationUnitSettingDetails"/> class. /// </summary> - /// <param name="dscResourceInfo">DSC Resource info.</param> - public ConfigurationUnitSettingDetails(DscResourcePropertyInfoInternal dscResourceInfo) + public ConfigurationUnitSettingDetails() { - this.Identifier = dscResourceInfo.Name; - this.IsRequired = dscResourceInfo.IsMandatory; - this.Type = GetPropertyType(dscResourceInfo.PropertyType); } /// <summary> /// Gets the name of the setting. /// </summary> - public string Identifier { get; } + required public string Identifier { get; init; } /// <summary> /// Gets the title of the setting. /// </summary> - public string Title { get; } = string.Empty; + public string Title { get; init; } = string.Empty; /// <summary> /// Gets the description of the setting. /// </summary> - public string Description { get; } = string.Empty; + public string Description { get; init; } = string.Empty; /// <summary> /// Gets a value indicating whether the setting is a key. This is used to determine if different settings are in conflict. /// </summary> - public bool IsKey { get; } = false; + public bool IsKey { get; init; } = false; /// <summary> /// Gets a value indicating whether a non-empty value for the setting is required. /// </summary> - public bool IsRequired { get; } + public bool IsRequired { get; init; } = false; /// <summary> /// Gets a value indicating whether the setting should be serialized in order to be applied on another system. /// </summary> - public bool IsInformational { get; } = false; + public bool IsInformational { get; init; } = false; /// <summary> /// Gets the data type for the value of this setting. /// </summary> - public Windows.Foundation.PropertyType Type { get; } + public Windows.Foundation.PropertyType Type { get; init; } = Windows.Foundation.PropertyType.Inspectable; /// <summary> /// Gets the semantics to be used for this setting. The goal is to enable richer conflict detection and authoring /// scenarios by having a deeper understanding of this value than "String". /// </summary> - public string Schema { get; } = string.Empty; - - private static Windows.Foundation.PropertyType GetPropertyType(string propertyType) - { - switch (propertyType.ToLowerInvariant()) - { - case "[byte]": return Windows.Foundation.PropertyType.UInt8; - case "[int16]": return Windows.Foundation.PropertyType.Int16; - case "[uint16]": return Windows.Foundation.PropertyType.UInt16; - case "[int32]": return Windows.Foundation.PropertyType.Int32; - case "[uint32]": return Windows.Foundation.PropertyType.UInt32; - case "[int64]": return Windows.Foundation.PropertyType.Int64; - case "[uint64]": return Windows.Foundation.PropertyType.UInt64; - case "[single]": return Windows.Foundation.PropertyType.Single; - case "[double]": return Windows.Foundation.PropertyType.Double; - case "[char]": return Windows.Foundation.PropertyType.Char16; - case "[bool]": return Windows.Foundation.PropertyType.Boolean; - case "[string]": return Windows.Foundation.PropertyType.String; - case "[datetime]": return Windows.Foundation.PropertyType.DateTime; - case "[datetimeoffset]": return Windows.Foundation.PropertyType.DateTime; - case "[timespan]": return Windows.Foundation.PropertyType.TimeSpan; - case "[guid]": return Windows.Foundation.PropertyType.Guid; - case "[byte[]]": return Windows.Foundation.PropertyType.UInt8Array; - case "[int16[]]": return Windows.Foundation.PropertyType.Int16Array; - case "[uint16[]]": return Windows.Foundation.PropertyType.UInt16Array; - case "[int32[]]": return Windows.Foundation.PropertyType.Int32Array; - case "[uint32[]]": return Windows.Foundation.PropertyType.UInt32Array; - case "[int64[]]": return Windows.Foundation.PropertyType.Int64Array; - case "[uint64[]]": return Windows.Foundation.PropertyType.UInt64Array; - case "[single[]]": return Windows.Foundation.PropertyType.SingleArray; - case "[double[]]": return Windows.Foundation.PropertyType.DoubleArray; - case "[char[]]": return Windows.Foundation.PropertyType.Char16Array; - case "[bool[]]": return Windows.Foundation.PropertyType.BooleanArray; - case "[string[]]": return Windows.Foundation.PropertyType.StringArray; - case "[object[]]": return Windows.Foundation.PropertyType.InspectableArray; - case "[datetime[]]": return Windows.Foundation.PropertyType.DateTimeArray; - case "[datetimeoffset[]]": return Windows.Foundation.PropertyType.DateTimeArray; - case "[timespan[]]": return Windows.Foundation.PropertyType.TimeSpanArray; - case "[guid[]]": return Windows.Foundation.PropertyType.GuidArray; - - // Everything else will just be an object... - default: return Windows.Foundation.PropertyType.Inspectable; - } - } + public string Schema { get; init; } = string.Empty; } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestFixture.cs b/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestFixture.cs @@ -8,16 +8,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Fixtures { using System; using System.IO; - using System.Management.Automation.Runspaces; using System.Reflection; using Microsoft.Management.Configuration.Processor; - using Microsoft.Management.Configuration.Processor.DscModule; - using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.Runspaces; - using Moq; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; using WinRT; using Xunit.Abstractions; - using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; /// <summary> /// Unit test fixture. diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs @@ -17,7 +17,9 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers /// <summary> /// Initializes a new instance of the <see cref="ConfigurationEnvironmentData"/> class. /// </summary> - internal ConfigurationEnvironmentData() { } + internal ConfigurationEnvironmentData() + { + } /// <summary> /// Gets or sets the security context. diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationDetailsTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationDetailsTests.cs @@ -10,8 +10,9 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using System.Collections.Generic; using System.Linq; using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; using Microsoft.Management.Configuration.Processor.Unit; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; @@ -75,7 +76,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests if (!hasDscInfo && !hasPSModuleInfo && !hasGetModuleInfo) { Assert.Throws<ArgumentException>( - () => new ConfigurationUnitProcessorDetails("unitName", null, null, null, certsInput)); + () => Factory.CreateUnitProcessorDetails("unitName", null, null, null, certsInput)); } else { @@ -100,7 +101,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests getModuleInfo = this.CreateGetModuleInfo(); } - var details = new ConfigurationUnitProcessorDetails(unit.Type, dscResourceInfoInput, psModuleInfoInput, getModuleInfo, certsInput); + var details = Factory.CreateUnitProcessorDetails(unit.Type, dscResourceInfoInput, psModuleInfoInput, getModuleInfo, certsInput); Assert.Equal(unit.Type, details.UnitType); @@ -191,7 +192,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests // This is easier than trying to mock sealed class from external code... var testEnv = this.fixture.PrepareTestProcessorEnvironment(true); - var dscResourceInfo = testEnv.GetDscResource(new ConfigurationUnitInternal(unit, string.Empty)); + var dscResourceInfo = testEnv.GetDscResource(new ConfigurationUnitAndModule(unit, string.Empty)); var psModuleInfo = testEnv.GetAvailableModule(PowerShellHelpers.CreateModuleSpecification("xSimpleTestResource", "0.0.0.1")); if (dscResourceInfo is null || psModuleInfo is null) diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorFactoryTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorFactoryTests.cs @@ -8,13 +8,13 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { using System.Collections.Generic; using Microsoft.Management.Configuration.Processor; - using Microsoft.Management.Configuration.Processor.Set; + using Microsoft.Management.Configuration.Processor.PowerShell.Set; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using WinRT; using Xunit; using Xunit.Abstractions; - using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; /// <summary> /// Tests ConfigurationProcessorFactory. @@ -53,8 +53,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationProcessorSet = configurationProcessorFactory.CreateSetProcessor(configurationSet); Assert.NotNull(configurationProcessorSet); - Assert.IsType<ConfigurationSetProcessor>(configurationProcessorSet); - var processorSet = configurationProcessorSet as ConfigurationSetProcessor; + Assert.IsType<PowerShellConfigurationSetProcessor>(configurationProcessorSet); + var processorSet = configurationProcessorSet as PowerShellConfigurationSetProcessor; Assert.NotNull(processorSet); } @@ -78,8 +78,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationProcessorSet = configurationProcessorFactory.CreateSetProcessor(configurationSet); - Assert.IsType<ConfigurationSetProcessor>(configurationProcessorSet); - var processorSet = configurationProcessorSet as ConfigurationSetProcessor; + Assert.IsType<PowerShellConfigurationSetProcessor>(configurationProcessorSet); + var processorSet = configurationProcessorSet as PowerShellConfigurationSetProcessor; Assert.NotNull(processorSet); var modulePath = processorSet.ProcessorEnvironment.GetVariable<string>(Variables.PSModulePath); @@ -109,7 +109,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationSet = new ConfigurationSet(); - var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as ConfigurationSetProcessor; + var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; Assert.NotNull(setProcessor); var modulePath = setProcessor.ProcessorEnvironment.GetVariable<string>(Variables.PSModulePath); @@ -130,7 +130,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationSet = new ConfigurationSet(); - var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as ConfigurationSetProcessor; + var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; Assert.NotNull(setProcessor); var modulePath = setProcessor.ProcessorEnvironment.GetVariable<string>(Variables.PSModulePath); @@ -155,7 +155,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.Location = PowerShellConfigurationProcessorLocation.Custom); Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.CustomLocation = @"c:\this\is\a\module\path"); - var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as ConfigurationSetProcessor; + var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; Assert.NotNull(setProcessor); Assert.True(setProcessor.IsLimitMode); diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs @@ -10,11 +10,12 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using System.Collections.Generic; using System.Management.Automation; using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; using Microsoft.Management.Configuration.Processor.Exceptions; using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.Set; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.PowerShell.Set; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Microsoft.PowerShell.Commands; @@ -57,11 +58,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -91,11 +92,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type.Equals("Name", StringComparison.OrdinalIgnoreCase)))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type.Equals("Name", StringComparison.OrdinalIgnoreCase)))) .Returns(new DscResourceInfoInternal("Name", moduleName, version)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -125,11 +126,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(new DscResourceInfoInternal("OtherName", moduleName, version)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -157,11 +158,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -190,11 +191,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -224,13 +225,13 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.SetupSequence( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(nullResource) .Returns(dscResourceInfo); PSObject findDscResourceResult = new PSObject(processorEnvMock); processorEnvMock.Setup( - m => m.FindModule(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.FindModule(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(findDscResourceResult) .Verifiable(); @@ -238,7 +239,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests m => m.InstallModule(findDscResourceResult)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -269,13 +270,13 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, null, version); var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.SetupSequence( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(nullResource) .Returns(dscResourceInfo); PSObject findDscResourceResult = this.CreateFindResourceInfo(); processorEnvMock.Setup( - m => m.FindDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.FindDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(findDscResourceResult) .Verifiable(); @@ -284,7 +285,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests m => m.InstallModule(moduleInfo)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -315,12 +316,12 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(nullResource); PSObject findDscResourceResult = new PSObject(processorEnvMock); processorEnvMock.Setup( - m => m.FindModule(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.FindModule(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(findDscResourceResult) .Verifiable(); @@ -328,7 +329,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests m => m.InstallModule(findDscResourceResult)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -359,16 +360,16 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(nullResource); PSObject? findDscResourceResult = null; processorEnvMock.Setup( - m => m.FindModule(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.FindModule(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(findDscResourceResult) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -396,11 +397,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == resourceName))) .Returns(nullDscInfoInternal) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -430,7 +431,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == dscResourceInfo.Name))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == dscResourceInfo.Name))) .Returns(dscResourceInfo) .Verifiable(); processorEnvMock.Setup( @@ -446,7 +447,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests .Returns(new List<Certificate>()) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -476,7 +477,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == dscResourceInfo.Name))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == dscResourceInfo.Name))) .Returns(dscResourceInfo) .Verifiable(); processorEnvMock.Setup( @@ -492,7 +493,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests .Returns(new List<Certificate>()) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -504,7 +505,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); processorEnvMock.Verify(); - processorEnvMock.Verify(m => m.FindDscResource(It.IsAny<ConfigurationUnitInternal>()), Times.Never()); + processorEnvMock.Verify(m => m.FindDscResource(It.IsAny<ConfigurationUnitAndModule>()), Times.Never()); processorEnvMock.Verify(m => m.ImportModule(It.IsAny<ModuleSpecification>()), Times.Never()); } @@ -520,7 +521,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == dscResourceInfo.Name))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == dscResourceInfo.Name))) .Returns(dscResourceInfo) .Verifiable(); processorEnvMock.Setup( @@ -539,7 +540,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests .Returns(new List<Certificate>()) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -551,7 +552,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); processorEnvMock.Verify(); - processorEnvMock.Verify(m => m.FindDscResource(It.IsAny<ConfigurationUnitInternal>()), Times.Never()); + processorEnvMock.Verify(m => m.FindDscResource(It.IsAny<ConfigurationUnitAndModule>()), Times.Never()); } /// <summary> @@ -566,15 +567,15 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == unit.Type))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == unit.Type))) .Returns(nullDscResourceInfo) .Verifiable(); processorEnvMock.Setup( - m => m.FindModule(It.Is<ConfigurationUnitInternal>(c => unit.Type == unit.Type))) + m => m.FindModule(It.Is<ConfigurationUnitAndModule>(c => unit.Type == unit.Type))) .Returns(nullPsModuleInfo) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -599,15 +600,15 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == unit.Type))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == unit.Type))) .Returns(nullDscResourceInfo) .Verifiable(); processorEnvMock.Setup( - m => m.FindModule(It.Is<ConfigurationUnitInternal>(c => unit.Type == unit.Type))) + m => m.FindModule(It.Is<ConfigurationUnitAndModule>(c => unit.Type == unit.Type))) .Returns(getFindResourceInfo) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -634,11 +635,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == unit.Type))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == unit.Type))) .Returns(nullDscResourceInfo) .Verifiable(); processorEnvMock.Setup( - m => m.FindModule(It.Is<ConfigurationUnitInternal>(c => unit.Type == unit.Type))) + m => m.FindModule(It.Is<ConfigurationUnitAndModule>(c => unit.Type == unit.Type))) .Returns(getFindModuleInfo) .Verifiable(); processorEnvMock.Setup( @@ -653,7 +654,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests .Returns(new List<Certificate>()) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -682,18 +683,18 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == unit.Type))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == unit.Type))) .Returns(nullDscResourceInfo) .Verifiable(); processorEnvMock.Setup( - m => m.FindModule(It.Is<ConfigurationUnitInternal>(c => unit.Type == unit.Type))) + m => m.FindModule(It.Is<ConfigurationUnitAndModule>(c => unit.Type == unit.Type))) .Returns(getFindResourceInfo) .Verifiable(); processorEnvMock.Setup( m => m.InstallModule(getFindResourceInfo)) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -703,7 +704,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests processorEnvMock.Verify(); processorEnvMock.Verify( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == unit.Type)), + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == unit.Type)), Times.Exactly(2)); } @@ -720,11 +721,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.SetupSequence( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == unit.Type))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == unit.Type))) .Returns(nullDscResourceInfo) .Returns(dscResourceInfo); processorEnvMock.Setup( - m => m.FindModule(It.Is<ConfigurationUnitInternal>(c => unit.Type == unit.Type))) + m => m.FindModule(It.Is<ConfigurationUnitAndModule>(c => unit.Type == unit.Type))) .Returns(getFindResourceInfo) .Verifiable(); processorEnvMock.Setup( @@ -738,7 +739,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests m => m.ImportModule(It.Is<ModuleSpecification>(s => s.Name == dscResourceInfo.ModuleName))) .Verifiable(); - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, new ConfigurationSet()); @@ -751,7 +752,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests processorEnvMock.Verify(); processorEnvMock.Verify( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(u => u.Unit.Type == unit.Type)), + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(u => u.Unit.Type == unit.Type)), Times.Exactly(2)); } @@ -764,7 +765,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - var setProcessor = new ConfigurationSetProcessor(processorEnv, new ConfigurationSet()); + var setProcessor = new PowerShellConfigurationSetProcessor(processorEnv, new ConfigurationSet()); var unit = new ConfigurationUnit { @@ -800,7 +801,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - var setProcessor = new ConfigurationSetProcessor(processorEnv, new ConfigurationSet()); + var setProcessor = new PowerShellConfigurationSetProcessor(processorEnv, new ConfigurationSet()); var unit = new ConfigurationUnit { @@ -828,7 +829,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) .Verifiable(); @@ -842,7 +843,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests limitUnit.Metadata.Add("version", version.ToString()); limitSet.Units = new List<ConfigurationUnit> { limitUnit }; - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, limitSet, true); @@ -884,7 +885,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); processorEnvMock.Setup( - m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + m => m.GetDscResource(It.Is<ConfigurationUnitAndModule>(c => c.Unit.Type == resourceName))) .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) .Verifiable(); @@ -898,7 +899,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests limitUnit.Metadata.Add("version", version.ToString()); limitSet.Units = new List<ConfigurationUnit> { limitUnit, limitUnit }; - var configurationSetProcessor = new ConfigurationSetProcessor( + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( processorEnvMock.Object, limitSet, true); @@ -940,7 +941,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { // This is easier than trying to mock sealed class from external code... var testEnv = this.fixture.PrepareTestProcessorEnvironment(true); - var dscResourceInfo = testEnv.GetDscResource(new ConfigurationUnitInternal(unit, string.Empty)); + var dscResourceInfo = testEnv.GetDscResource(new ConfigurationUnitAndModule(unit, string.Empty)); var psModuleInfo = testEnv.GetAvailableModule(PowerShellHelpers.CreateModuleSpecification("xSimpleTestResource", "0.0.0.1")); if (dscResourceInfo is null || psModuleInfo is null) diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitInternalTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitInternalTests.cs @@ -13,6 +13,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using Microsoft.Management.Configuration; using Microsoft.Management.Configuration.Processor.Exceptions; using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Xunit; @@ -67,7 +68,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests unit.Metadata.Add(boolDirective, boolDirectiveValue); unit.Metadata.Add(boolDirective2, boolDirective2Value); - var unitInternal = new ConfigurationUnitInternal(unit, string.Empty); + var unitInternal = new ConfigurationUnitAndModule(unit, string.Empty); var description = unitInternal.GetDirective<string>(descriptionDirective); Assert.Equal(description, unitDescription); diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs @@ -7,14 +7,13 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { using System; - using System.Collections.Generic; - using System.Linq; using System.Management.Automation; using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.Unit; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.PowerShell.Unit; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Microsoft.PowerShell.Commands; @@ -71,7 +70,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(Assert.IsType<ConfigurationUnitIntent>(intent)); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var result = unitProcessor.GetSettings(); @@ -100,7 +99,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var result = unitProcessor.GetSettings(); @@ -129,7 +128,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var result = unitProcessor.GetSettings(); @@ -150,7 +149,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); Assert.Throws<NotSupportedException>(() => unitProcessor.TestSettings()); } @@ -177,7 +176,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(Assert.IsType<ConfigurationUnitIntent>(intent)); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var testResult = unitProcessor.TestSettings(); @@ -209,7 +208,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Assert); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var result = unitProcessor.TestSettings(); @@ -240,7 +239,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Assert); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var result = unitProcessor.TestSettings(); @@ -266,7 +265,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var processorEnvMock = new Mock<IProcessorEnvironment>(); var unitResource = this.CreateUnitResource(Assert.IsType<ConfigurationUnitIntent>(intent)); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); Assert.Throws<NotSupportedException>(() => unitProcessor.ApplySettings()); } @@ -290,7 +289,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var result = unitProcessor.ApplySettings(); @@ -314,7 +313,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var result = unitProcessor.ApplySettings(); @@ -343,7 +342,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); var result = unitProcessor.ApplySettings(); @@ -378,7 +377,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); - var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource, true); + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource, true); // GetSettings can be called multiple times. var getResult = unitProcessor.GetSettings(); @@ -397,7 +396,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { string resourceName = "xResourceName"; return new ConfigurationUnitAndResource( - new ConfigurationUnitInternal( + new ConfigurationUnitAndModule( new ConfigurationUnit { Type = resourceName, diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2SimpleFileResourceTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2SimpleFileResourceTests.cs @@ -8,8 +8,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { using System.IO; using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.DscModule; - using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Windows.Foundation.Collections; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs @@ -9,9 +9,9 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using System; using System.IO; using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.DscModule; using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Microsoft.PowerShell.Commands; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscResourceMapTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscResourceMapTests.cs @@ -8,8 +8,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { using System; using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Xunit; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/PowerShellHelperTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/PowerShellHelperTests.cs @@ -7,7 +7,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { using System; - using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Xunit; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using Moq; using Xunit; using Xunit.Abstractions; - using static Microsoft.Management.Configuration.Processor.Constants.PowerShellConstants; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; /// <summary> /// HostedEnvironment tests, that is more ProcessorEnvironmentBase tests for non forwarding functions.