winget-cli

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

HostedEnvironment.cs (22631B)


      1 // -----------------------------------------------------------------------------
      2 // <copyright file="HostedEnvironment.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.Runspaces
      8 {
      9     using System;
     10     using System.Collections;
     11     using System.Collections.Generic;
     12     using System.IO;
     13     using System.Linq;
     14     using System.Management.Automation;
     15     using System.Management.Automation.Runspaces;
     16     using System.Runtime.InteropServices.WindowsRuntime;
     17     using Microsoft.Management.Configuration.Processor.Constants;
     18     using Microsoft.Management.Configuration.Processor.Helpers;
     19     using Microsoft.Management.Configuration.Processor.PowerShell.DscModules;
     20     using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo;
     21     using Microsoft.Management.Configuration.Processor.PowerShell.Helpers;
     22     using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments;
     23     using Microsoft.PowerShell.Commands;
     24     using Windows.Foundation.Collections;
     25     using Windows.Security.Cryptography.Certificates;
     26     using Windows.Storage.Streams;
     27     using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants;
     28 
     29     /// <summary>
     30     /// Process environment. Provides interaction with PowerShell for a hosted environment.
     31     /// </summary>
     32     internal class HostedEnvironment : IProcessorEnvironment
     33     {
     34         private readonly PowerShellConfigurationProcessorType type;
     35         private readonly IPowerShellGet powerShellGet;
     36 
     37         private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.CurrentUser;
     38         private string? customLocation;
     39 
     40         /// <summary>
     41         /// Initializes a new instance of the <see cref="HostedEnvironment"/> class.
     42         /// </summary>
     43         /// <param name="runspace">PowerShell Runspace.</param>
     44         /// <param name="type">Configuration processor type.</param>
     45         /// <param name="dscModule">IDscModule.</param>
     46         public HostedEnvironment(
     47             Runspace runspace,
     48             PowerShellConfigurationProcessorType type,
     49             IDscModule dscModule)
     50         {
     51             this.Runspace = runspace;
     52             this.type = type;
     53             this.DscModule = dscModule;
     54 
     55             // TODO: once v3 is release implement v3 version.
     56             this.powerShellGet = new PowerShellGetV2();
     57         }
     58 
     59         /// <inheritdoc/>
     60         public Runspace Runspace { get; }
     61 
     62         /// <summary>
     63         /// Gets the DscModule.
     64         /// </summary>
     65         internal IDscModule DscModule { get; }
     66 
     67         /// <summary>
     68         /// Gets or initializes the set processor factory.
     69         /// </summary>
     70         internal PowerShellConfigurationSetProcessorFactory? SetProcessorFactory { get; init; }
     71 
     72         /// <inheritdoc/>
     73         public void ValidateRunspace()
     74         {
     75             // Only support PowerShell Core.
     76             if (this.GetVariable<string>(Variables.PSEdition) != Core)
     77             {
     78                 throw new NotSupportedException("Only PowerShell Core is supported.");
     79             }
     80 
     81             // If opening a runspace has failures, like one of the modules in ImportPSModule is not found, it won't throw but
     82             // write to the error output. This is not a fatal error, since we install PSDesiredStateConfiguration
     83             // module if not found, so unless there's a real reason keep it in verbose.
     84             var errors = this.GetVariable<ArrayList>(Variables.Error);
     85             if (errors.Count > 0)
     86             {
     87                 this.OnDiagnostics(
     88                     DiagnosticLevel.Verbose,
     89                     $"Error creating runspace '{string.Join("\n", errors.Cast<string>().ToArray())}'");
     90             }
     91 
     92             var powerShellGet = PowerShellHelpers.CreateModuleSpecification(
     93                     Modules.PowerShellGet,
     94                     minVersion: Modules.PowerShellGetMinVersion);
     95             if (!this.ValidateModule(powerShellGet))
     96             {
     97                 var previousVersion = this.GetAvailableModule(
     98                     PowerShellHelpers.CreateModuleSpecification(
     99                         Modules.PowerShellGet));
    100                 string message = $"Required '{powerShellGet}'";
    101                 if (previousVersion is not null)
    102                 {
    103                     message += $" Found '{previousVersion.Name} {previousVersion.Version}'";
    104                 }
    105 
    106                 throw new NotSupportedException(message);
    107             }
    108 
    109             // Make sure PSDesiredConfiguration is present.
    110             this.InstallModule(this.DscModule.ModuleSpecification);
    111         }
    112 
    113         /// <inheritdoc/>
    114         public IReadOnlyList<DscResourceInfoInternal> GetAllDscResources()
    115         {
    116             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    117             var results = this.DscModule.GetAllDscResources(pwsh);
    118             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    119             return results;
    120         }
    121 
    122         /// <inheritdoc/>
    123         public IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule(ModuleSpecification moduleSpecification)
    124         {
    125             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    126             var results = this.DscModule.GetDscResourcesInModule(pwsh, moduleSpecification);
    127             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    128             return results;
    129         }
    130 
    131         /// <inheritdoc/>
    132         public DscResourceInfoInternal? GetDscResource(ConfigurationUnitAndModule unitInternal)
    133         {
    134             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    135             var result = this.DscModule.GetDscResource(pwsh, unitInternal.ResourceName, unitInternal.Module);
    136             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    137             return result;
    138         }
    139 
    140         /// <inheritdoc/>
    141         public ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification)
    142         {
    143             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    144             var result = this.DscModule.InvokeGetResource(pwsh, settings, name, moduleSpecification);
    145             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    146             return result;
    147         }
    148 
    149         /// <inheritdoc/>
    150         public bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification)
    151         {
    152             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    153             var result = this.DscModule.InvokeTestResource(pwsh, settings, name, moduleSpecification);
    154             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    155             return result;
    156         }
    157 
    158         /// <inheritdoc/>
    159         public bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification)
    160         {
    161             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    162             var result = this.DscModule.InvokeSetResource(pwsh, settings, name, moduleSpecification);
    163             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    164             return result;
    165         }
    166 
    167         /// <inheritdoc/>
    168         public PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification)
    169         {
    170             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    171 
    172             var moduleInfo = pwsh.AddCommand(Commands.GetModule)
    173                                  .AddParameter(Parameters.FullyQualifiedName, moduleSpecification)
    174                                  .Invoke<PSModuleInfo>()
    175                                  .FirstOrDefault();
    176 
    177             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    178             return moduleInfo;
    179         }
    180 
    181         /// <inheritdoc/>
    182         public PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification)
    183         {
    184             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    185 
    186             var moduleInfo = pwsh.AddCommand(Commands.GetModule)
    187                                  .AddParameter(Parameters.FullyQualifiedName, moduleSpecification)
    188                                  .AddParameter(Parameters.ListAvailable)
    189                                  .Invoke<PSModuleInfo>()
    190                                  .FirstOrDefault();
    191 
    192             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    193             return moduleInfo;
    194         }
    195 
    196         /// <inheritdoc/>
    197         public PSModuleInfo? GetAvailableModule(string path)
    198         {
    199             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    200 
    201             var moduleInfo = pwsh.AddCommand(Commands.GetModule)
    202                                  .AddParameter(Parameters.Name, path)
    203                                  .AddParameter(Parameters.ListAvailable)
    204                                  .Invoke<PSModuleInfo>()
    205                                  .FirstOrDefault();
    206 
    207             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    208             return moduleInfo;
    209         }
    210 
    211         /// <inheritdoc/>
    212         public void ImportModule(ModuleSpecification moduleSpecification)
    213         {
    214             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    215 
    216             _ = pwsh.AddCommand(Commands.ImportModule)
    217                     .AddParameter(Parameters.FullyQualifiedName, moduleSpecification)
    218                     .Invoke();
    219 
    220             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    221         }
    222 
    223         /// <inheritdoc/>
    224         public void ImportModule(string path)
    225         {
    226             if (!File.Exists(path))
    227             {
    228                 throw new FileNotFoundException(path);
    229             }
    230 
    231             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    232 
    233             _ = pwsh.AddCommand(Commands.ImportModule)
    234                     .AddParameter(Parameters.Name, path)
    235                     .Invoke();
    236 
    237             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    238         }
    239 
    240         /// <inheritdoc/>
    241         public PSObject? GetInstalledModule(ModuleSpecification moduleSpecification)
    242         {
    243             // Instead of Get-InstalledModule, we look for PSGetModuleInfo.xml and serialize it
    244             // if found. This allow us to get the information from Install-Module and Save-Module.
    245             var module = this.GetAvailableModule(moduleSpecification);
    246             if (module is null)
    247             {
    248                 return null;
    249             }
    250 
    251             var getModuleInfoFile = Path.Combine(module.ModuleBase, "PSGetModuleInfo.xml");
    252             if (!File.Exists(getModuleInfoFile))
    253             {
    254                 // Keep Get-InstalledModule behaviour.
    255                 return null;
    256             }
    257 
    258             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    259             var installedModule = pwsh.AddCommand(Commands.ImportCliXml)
    260                                       .AddParameter(Parameters.Path, getModuleInfoFile)
    261                                       .Invoke()
    262                                       .FirstOrDefault();
    263 
    264             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    265             return installedModule;
    266         }
    267 
    268         /// <inheritdoc/>
    269         public PSObject? FindModule(ConfigurationUnitInternal unitInternal)
    270         {
    271             // Don't use ModuleSpecification here. Each parameter is independent and
    272             // we need version even if a module was not specified.
    273             string? moduleName = unitInternal.GetDirective<string>(DirectiveConstants.Module);
    274 
    275             if (string.IsNullOrEmpty(moduleName))
    276             {
    277                 return null;
    278             }
    279 
    280             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    281 
    282             var result = this.powerShellGet.FindModule(
    283                 pwsh,
    284                 moduleName,
    285                 unitInternal.GetSemanticVersion(),
    286                 unitInternal.GetSemanticMinVersion(),
    287                 unitInternal.GetSemanticMaxVersion(),
    288                 unitInternal.GetDirective<string>(DirectiveConstants.Repository),
    289                 unitInternal.GetDirective(DirectiveConstants.AllowPrerelease));
    290 
    291             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    292             return result;
    293         }
    294 
    295         /// <inheritdoc/>
    296         public PSObject? FindDscResource(ConfigurationUnitInternal unitInternal)
    297         {
    298             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    299 
    300             var result = this.powerShellGet.FindDscResource(
    301                 pwsh,
    302                 unitInternal.ResourceName,
    303                 unitInternal.GetDirective<string>(DirectiveConstants.Module),
    304                 unitInternal.GetSemanticVersion(),
    305                 unitInternal.GetSemanticMinVersion(),
    306                 unitInternal.GetSemanticMaxVersion(),
    307                 unitInternal.GetDirective<string>(DirectiveConstants.Repository),
    308                 unitInternal.GetDirective(DirectiveConstants.AllowPrerelease));
    309 
    310             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    311             return result;
    312         }
    313 
    314         /// <inheritdoc/>
    315         public void SaveModule(PSObject inputObject, string location)
    316         {
    317             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    318             this.powerShellGet.SaveModule(pwsh, inputObject, location);
    319             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    320         }
    321 
    322         /// <inheritdoc/>
    323         public void SaveModule(ModuleSpecification moduleSpecification, string location)
    324         {
    325             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    326             this.powerShellGet.SaveModule(pwsh, moduleSpecification, location);
    327             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    328         }
    329 
    330         /// <inheritdoc/>
    331         public void InstallModule(PSObject inputObject)
    332         {
    333             if (this.location == PowerShellConfigurationProcessorLocation.Custom)
    334             {
    335                 if (string.IsNullOrEmpty(this.customLocation))
    336                 {
    337                     throw new ArgumentNullException(nameof(this.customLocation));
    338                 }
    339 
    340                 this.SaveModule(inputObject, this.customLocation);
    341             }
    342             else
    343             {
    344                 using PowerShell pwsh = PowerShell.Create(this.Runspace);
    345                 this.powerShellGet.InstallModule(pwsh, inputObject, this.location == PowerShellConfigurationProcessorLocation.AllUsers);
    346                 this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    347             }
    348         }
    349 
    350         /// <inheritdoc/>
    351         public void InstallModule(ModuleSpecification moduleSpecification)
    352         {
    353             // Maybe is already there.
    354             if (!this.ValidateModule(moduleSpecification))
    355             {
    356                 this.OnDiagnostics(DiagnosticLevel.Verbose, $"Installing module: {moduleSpecification.Name} ...");
    357 
    358                 // Ok, we have to get it.
    359                 if (this.location == PowerShellConfigurationProcessorLocation.Custom)
    360                 {
    361                     if (string.IsNullOrEmpty(this.customLocation))
    362                     {
    363                         throw new ArgumentNullException(nameof(this.customLocation));
    364                     }
    365 
    366                     this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling save module ...");
    367                     this.SaveModule(moduleSpecification, this.customLocation);
    368                 }
    369                 else
    370                 {
    371                     this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling install module ...");
    372                     using PowerShell pwsh = PowerShell.Create(this.Runspace);
    373                     this.powerShellGet.InstallModule(pwsh, moduleSpecification, this.location == PowerShellConfigurationProcessorLocation.AllUsers);
    374                     this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    375                 }
    376 
    377                 this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module installed.");
    378             }
    379         }
    380 
    381         /// <inheritdoc/>
    382         public List<Certificate> GetCertsOfValidSignedFiles(string[] paths)
    383         {
    384             using PowerShell pwsh = PowerShell.Create(this.Runspace);
    385 
    386             var signatures = pwsh.AddCommand(Commands.GetChildItem)
    387                                  .AddParameter(Parameters.Path, paths)
    388                                  .AddCommand(Commands.GetAuthenticodeSignature)
    389                                  .Invoke<Signature>();
    390 
    391             var thumbprint = new HashSet<string>();
    392             var certificates = new List<Certificate>();
    393             foreach (var signature in signatures)
    394             {
    395                 if (signature.Status == SignatureStatus.Valid)
    396                 {
    397                     if (thumbprint.Add(signature.SignerCertificate.Thumbprint))
    398                     {
    399                         IBuffer buffer = signature.SignerCertificate.GetRawCertData().AsBuffer();
    400                         certificates.Add(new Certificate(buffer));
    401                     }
    402                 }
    403             }
    404 
    405             this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh);
    406             return certificates;
    407         }
    408 
    409         /// <inheritdoc/>
    410         public TType GetVariable<TType>(string name)
    411         {
    412             return (TType)this.Runspace.SessionStateProxy.PSVariable.GetValue(name);
    413         }
    414 
    415         /// <inheritdoc/>
    416         public void SetVariable(string name, object value)
    417         {
    418             this.Runspace.SessionStateProxy.PSVariable.Set(name, value);
    419         }
    420 
    421         /// <inheritdoc/>
    422         public void SetPSModulePath(string path)
    423         {
    424             this.SetVariable(Variables.PSModulePath, path);
    425         }
    426 
    427         /// <inheritdoc/>
    428         public void SetPSModulePaths(IReadOnlyList<string> paths)
    429         {
    430             this.SetVariable(Variables.PSModulePath, string.Join(";", paths));
    431         }
    432 
    433         /// <inheritdoc/>
    434         public void PrependPSModulePath(string path)
    435         {
    436             var oldModulePath = this.GetModulePaths();
    437             if (!oldModulePath.Contains(path))
    438             {
    439                 this.SetPSModulePath($"{path};{string.Join(";", oldModulePath)}");
    440             }
    441         }
    442 
    443         /// <inheritdoc/>
    444         public void PrependPSModulePaths(IReadOnlyList<string> paths)
    445         {
    446             var newPaths = paths.ToList();
    447             var oldModulePath = this.GetModulePaths();
    448             foreach (var newPath in paths)
    449             {
    450                 if (oldModulePath.Contains(newPath))
    451                 {
    452                     newPaths.Remove(newPath);
    453                 }
    454             }
    455 
    456             if (newPaths.Any())
    457             {
    458                 this.SetPSModulePath($"{string.Join(";", newPaths)};{string.Join(";", oldModulePath)}");
    459             }
    460         }
    461 
    462         /// <inheritdoc/>
    463         public void AppendPSModulePath(string path)
    464         {
    465             var oldModulePath = this.GetModulePaths();
    466             if (!oldModulePath.Contains(path))
    467             {
    468                 this.SetPSModulePath($"{string.Join(";", oldModulePath)};{path}");
    469             }
    470         }
    471 
    472         /// <inheritdoc/>
    473         public void AppendPSModulePaths(IReadOnlyList<string> paths)
    474         {
    475             var newPaths = paths.ToList();
    476             var oldModulePath = this.GetModulePaths();
    477             foreach (var newPath in paths)
    478             {
    479                 if (oldModulePath.Contains(newPath))
    480                 {
    481                     newPaths.Remove(newPath);
    482                 }
    483             }
    484 
    485             if (newPaths.Any())
    486             {
    487                 this.SetPSModulePath($"{string.Join(";", oldModulePath)};{string.Join(";", newPaths)}");
    488             }
    489         }
    490 
    491         /// <inheritdoc/>
    492         public void CleanupPSModulePath(string path)
    493         {
    494             string newModulePath = this.GetVariable<string>(Variables.PSModulePath)
    495                                        .Replace($"{path};", null)
    496                                        .Replace($";{path}", null);
    497 
    498             this.SetPSModulePath(newModulePath);
    499         }
    500 
    501         /// <inheritdoc/>
    502         public void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation)
    503         {
    504             this.location = location;
    505             if (this.location == PowerShellConfigurationProcessorLocation.Custom)
    506             {
    507                 if (string.IsNullOrEmpty(customLocation))
    508                 {
    509                     throw new ArgumentNullException(nameof(customLocation));
    510                 }
    511 
    512                 this.customLocation = customLocation;
    513             }
    514         }
    515 
    516         private bool ValidateModule(ModuleSpecification moduleSpecification)
    517         {
    518             this.OnDiagnostics(DiagnosticLevel.Verbose, $"Validating module: {moduleSpecification.Name} ...");
    519 
    520             var loadedModule = this.GetImportedModule(moduleSpecification);
    521             if (loadedModule is not null)
    522             {
    523                 this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is already imported.");
    524                 return true;
    525             }
    526 
    527             var availableModule = this.GetAvailableModule(moduleSpecification);
    528             if (availableModule is not null)
    529             {
    530                 this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is available, importing ...");
    531                 this.ImportModule(moduleSpecification);
    532                 this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module imported.");
    533                 return true;
    534             }
    535 
    536             this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module not found.");
    537             return false;
    538         }
    539 
    540         private void OnDiagnostics(DiagnosticLevel level, PowerShell pwsh)
    541         {
    542             this.SetProcessorFactory?.OnDiagnostics(level, pwsh);
    543         }
    544 
    545         private void OnDiagnostics(DiagnosticLevel level, string message)
    546         {
    547             this.SetProcessorFactory?.OnDiagnostics(level, message);
    548         }
    549 
    550         private HashSet<string> GetModulePaths()
    551         {
    552             return this.GetVariable<string>(Variables.PSModulePath).Split(";").ToHashSet<string>();
    553         }
    554     }
    555 }