winget-cli

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

PowerShellConfigurationSetProcessor.cs (14438B)


      1 // -----------------------------------------------------------------------------
      2 // <copyright file="PowerShellConfigurationSetProcessor.cs" company="Microsoft Corporation">
      3 //     Copyright (c) Microsoft Corporation. Licensed under the MIT License.
      4 // </copyright>
      5 // -----------------------------------------------------------------------------
      6 
      7 namespace Microsoft.Management.Configuration.Processor.PowerShell.Set
      8 {
      9     using System;
     10     using System.Collections.Generic;
     11     using System.IO;
     12     using System.Management.Automation;
     13     using Microsoft.Management.Configuration.Processor.Exceptions;
     14     using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo;
     15     using Microsoft.Management.Configuration.Processor.PowerShell.Helpers;
     16     using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments;
     17     using Microsoft.Management.Configuration.Processor.PowerShell.Unit;
     18     using Microsoft.Management.Configuration.Processor.Set;
     19     using Microsoft.Management.Configuration.Processor.Unit;
     20     using Windows.Security.Cryptography.Certificates;
     21 
     22     /// <summary>
     23     /// IConfigurationSetProcessor implementation using PowerShell DSC v2.
     24     /// </summary>
     25     internal sealed partial class PowerShellConfigurationSetProcessor : ConfigurationSetProcessorBase, IConfigurationSetProcessor
     26     {
     27         /// <summary>
     28         /// Initializes a new instance of the <see cref="PowerShellConfigurationSetProcessor"/> class.
     29         /// </summary>
     30         /// <param name="processorEnvironment">The processor environment.</param>
     31         /// <param name="configurationSet">Configuration set.</param>
     32         /// <param name="isLimitMode">Whether the set processor should work in limitation mode.</param>
     33         public PowerShellConfigurationSetProcessor(IProcessorEnvironment processorEnvironment, ConfigurationSet? configurationSet, bool isLimitMode = false)
     34             : base(configurationSet, isLimitMode)
     35         {
     36             this.ProcessorEnvironment = processorEnvironment;
     37         }
     38 
     39         /// <summary>
     40         /// Gets the processor environment.
     41         /// </summary>
     42         internal IProcessorEnvironment ProcessorEnvironment { get; }
     43 
     44         /// <inheritdoc />
     45         protected override IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit)
     46         {
     47             var configurationUnitInternal = new ConfigurationUnitAndModule(unit, this.ConfigurationSet?.Path) { UnitTypeIsResourceName = IsUnitTypeResourceName(this.ConfigurationSet?.SchemaVersion) };
     48             this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating unit processor for: {configurationUnitInternal.QualifiedName}...");
     49 
     50             var dscResourceInfo = this.PrepareUnitForProcessing(configurationUnitInternal);
     51 
     52             this.OnDiagnostics(DiagnosticLevel.Verbose, $"Using unit from location: {dscResourceInfo.Path}");
     53             return new PowerShellConfigurationUnitProcessor(
     54                 this.ProcessorEnvironment,
     55                 new ConfigurationUnitAndResource(configurationUnitInternal, dscResourceInfo),
     56                 this.IsLimitMode)
     57             { SetProcessorFactory = this.SetProcessorFactory };
     58         }
     59 
     60         /// <inheritdoc />
     61         protected override IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags)
     62         {
     63             var unitInternal = new ConfigurationUnitAndModule(unit, this.ConfigurationSet?.Path);
     64             this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting unit details [{detailFlags}] for: {unitInternal.QualifiedName}");
     65 
     66             // (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.
     67             DscResourceInfoInternal? dscResourceInfo = null;
     68             if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Local) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load))
     69             {
     70                 dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal);
     71             }
     72 
     73             if (dscResourceInfo is not null)
     74             {
     75                 return this.GetUnitProcessorDetailsLocal(
     76                     dscResourceInfo.Name,
     77                     dscResourceInfo,
     78                     detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load));
     79             }
     80 
     81             if (!(detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)))
     82             {
     83                 // Not found locally.
     84                 return null;
     85             }
     86 
     87             var unitModuleInfo = this.FindUnitModule(unitInternal);
     88             if (unitModuleInfo is null)
     89             {
     90                 // Not found in catalog.
     91                 return null;
     92             }
     93 
     94             PSObject foundModule = unitModuleInfo.Value.Module;
     95             string resourceName = unitModuleInfo.Value.ResourceName;
     96 
     97             dynamic foundModuleInfo = foundModule;
     98 
     99             if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog))
    100             {
    101                 return Factory.CreateUnitProcessorDetails(
    102                     resourceName,
    103                     null,
    104                     null,
    105                     foundModule,
    106                     null);
    107             }
    108 
    109             if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download))
    110             {
    111                 var tempSavePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    112                 Directory.CreateDirectory(tempSavePath);
    113                 this.ProcessorEnvironment.SaveModule(foundModule, tempSavePath);
    114 
    115                 var moduleInfo = this.ProcessorEnvironment.GetAvailableModule(
    116                     Path.Combine(tempSavePath, foundModuleInfo.Name));
    117 
    118                 return Factory.CreateUnitProcessorDetails(
    119                     resourceName,
    120                     null,
    121                     moduleInfo,
    122                     foundModule,
    123                     this.GetCertificates(moduleInfo));
    124             }
    125 
    126             if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load))
    127             {
    128                 this.ProcessorEnvironment.InstallModule(foundModule);
    129 
    130                 dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal);
    131 
    132                 if (dscResourceInfo is null)
    133                 {
    134                     // Well, this is awkward.
    135                     throw new InstallDscResourceException(
    136                         unitInternal.ResourceName,
    137                         PowerShellHelpers.CreateModuleSpecification(foundModuleInfo.Name, foundModuleInfo.Version));
    138                 }
    139 
    140                 return this.GetUnitProcessorDetailsLocal(dscResourceInfo.Name, dscResourceInfo, true);
    141             }
    142 
    143             return null;
    144         }
    145 
    146         private static bool IsUnitTypeResourceName(string? schemaVersion)
    147         {
    148             return schemaVersion != null && schemaVersion == "0.1";
    149         }
    150 
    151         /// <summary>
    152         /// Finds the module and preferred resource name for processing the configuration unit.
    153         /// </summary>
    154         /// <param name="unitInternal">The internal configuration unit.</param>
    155         /// <returns>A tuple containing the module info and preferred resource name, or null if not found.</returns>
    156         private (PSObject Module, string ResourceName)? FindUnitModule(ConfigurationUnitAndModule unitInternal)
    157         {
    158             PSObject? foundModule = null;
    159             string resourceName = string.Empty;
    160 
    161             // If module has been specified, find it and assume that the resource will be within it.
    162             // Do this first as we do not currently gain much from FindDscResource; if that changes then it can be the primary.
    163             if (unitInternal.Module != null)
    164             {
    165                 foundModule = this.ProcessorEnvironment.FindModule(unitInternal);
    166                 if (foundModule != null)
    167                 {
    168                     resourceName = unitInternal.ResourceName;
    169                 }
    170             }
    171             else
    172             {
    173                 dynamic? foundResource = this.ProcessorEnvironment.FindDscResource(unitInternal);
    174                 if (foundResource != null)
    175                 {
    176                     foundModule = foundResource.PSGetModuleInfo;
    177 
    178                     // Hopefully they will never change the properties name. If someone can explain to me
    179                     // why assign it Name to $_ in Find-DscResource turns into a string in PowerShell but
    180                     // into a PSObject here that would be nice...
    181                     resourceName = foundResource.Name.ToString();
    182                 }
    183             }
    184 
    185             if (foundModule != null)
    186             {
    187                 return (foundModule, resourceName);
    188             }
    189 
    190             return null;
    191         }
    192 
    193         private DscResourceInfoInternal PrepareUnitForProcessing(ConfigurationUnitAndModule unitInternal)
    194         {
    195             // Invoke-DscResource makes a call to Get-DscResource which looks at the entire PSModulePath
    196             // to see if a resource exists. DscResourcesMap is an attempt to try to optimize Get-DscResource
    197             // by making just one call and get all of them, but it doesn't support minVersion and maxVersion.
    198             // For now, lets make PowerShell fully figure out which module to use and try to optimize it later.
    199             // This class will have a private member Lazy<DscResourcesMap> which will be initialized by calling
    200             // this.ProcessorEnvironment.GetAllDscResources()
    201             // To improve the performance even more, we will still need Invoke-DscResource to be update to
    202             // get a DSC resource info object instead of calling Get-DscResource every time.
    203             var dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal);
    204 
    205             if (dscResourceInfo is null)
    206             {
    207                 var findUnitModuleResult = this.FindUnitModule(unitInternal);
    208 
    209                 if (findUnitModuleResult is null)
    210                 {
    211                     throw new FindDscResourceNotFoundException(unitInternal.ResourceName, unitInternal.Module);
    212                 }
    213 
    214                 this.ProcessorEnvironment.InstallModule(findUnitModuleResult.Value.Module);
    215 
    216                 // Now we should find it.
    217                 dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal);
    218                 if (dscResourceInfo is null)
    219                 {
    220                     throw new InstallDscResourceException(unitInternal.ResourceName, unitInternal.Module);
    221                 }
    222             }
    223 
    224             // PowerShell will prompt the user when a module that is downloaded from the internet is imported.
    225             // For a hosted environment, this will throw an exception because it doesn't support user interaction.
    226             // In the case we don't import the module here, eventually Invoke-DscResource will fail for class
    227             // resources because they will call a method on a null obj. It is easier to just fail here.
    228             // The exception being thrown will have the correct details (user needs to call Unblock-File)
    229             // instead of the cryptic Invoke with 0 arguments.
    230             if (!string.IsNullOrEmpty(dscResourceInfo.Path))
    231             {
    232                 try
    233                 {
    234                     this.ProcessorEnvironment.ImportModule(dscResourceInfo.Path);
    235                 }
    236                 catch (Exception e)
    237                 {
    238                     throw new ImportModuleException(dscResourceInfo.ModuleName, e);
    239                 }
    240             }
    241 
    242             return dscResourceInfo;
    243         }
    244 
    245         private ConfigurationUnitProcessorDetails GetUnitProcessorDetailsLocal(
    246             string unitName,
    247             DscResourceInfoInternal dscResourceInfo,
    248             bool importModule)
    249         {
    250             // I'm looking at you resources under C:\WINDOWS\system32\WindowsPowershell
    251             if (dscResourceInfo.ModuleName is null ||
    252                 dscResourceInfo.Version is null)
    253             {
    254                 return Factory.CreateUnitProcessorDetails(
    255                     dscResourceInfo.Name,
    256                     dscResourceInfo,
    257                     null,
    258                     null,
    259                     null);
    260             }
    261 
    262             var module = PowerShellHelpers.CreateModuleSpecification(
    263                             dscResourceInfo.ModuleName,
    264                             dscResourceInfo.Version.ToString());
    265 
    266             // Get-InstalledModule only works for modules installed via PowerShell-Get.
    267             // There are some properties that can only be obtain by that it so is better to take both.
    268             var moduleInfo = this.ProcessorEnvironment.GetAvailableModule(module);
    269             var installedModule = this.ProcessorEnvironment.GetInstalledModule(module);
    270 
    271             if (importModule)
    272             {
    273                 this.ProcessorEnvironment.ImportModule(module);
    274             }
    275 
    276             return Factory.CreateUnitProcessorDetails(
    277                 dscResourceInfo.Name,
    278                 dscResourceInfo,
    279                 moduleInfo,
    280                 installedModule,
    281                 this.GetCertificates(moduleInfo));
    282         }
    283 
    284         private List<Certificate>? GetCertificates(PSModuleInfo? moduleInfo)
    285         {
    286             if (moduleInfo is null)
    287             {
    288                 return null;
    289             }
    290 
    291             // TODO: we still need to investigate more here, but lets start with something.
    292             var paths = new List<string>();
    293 
    294             var psdPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psd1");
    295             if (File.Exists(psdPath))
    296             {
    297                 paths.Add(psdPath);
    298             }
    299 
    300             var psmPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psm1");
    301             if (File.Exists(psmPath))
    302             {
    303                 paths.Add(psmPath);
    304             }
    305 
    306             return this.ProcessorEnvironment.GetCertsOfValidSignedFiles(paths.ToArray());
    307         }
    308     }
    309 }