winget-cli

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

DSCv3.cs (15797B)


      1 // -----------------------------------------------------------------------------
      2 // <copyright file="DSCv3.cs" company="Microsoft Corporation">
      3 //     Copyright (c) Microsoft Corporation. Licensed under the MIT License.
      4 // </copyright>
      5 // -----------------------------------------------------------------------------
      6 
      7 namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04
      8 {
      9     using System;
     10     using System.Collections.Generic;
     11     using System.Linq;
     12     using System.Text.Json;
     13     using System.Text.Json.Serialization;
     14     using Microsoft.Management.Configuration.Processor.DSCv3.Helpers;
     15     using Microsoft.Management.Configuration.Processor.DSCv3.Model;
     16     using Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs;
     17     using Microsoft.Management.Configuration.Processor.Extensions;
     18     using Microsoft.Management.Configuration.Processor.Helpers;
     19     using Windows.Foundation.Collections;
     20 
     21     /// <summary>
     22     /// An instance of IDSCv3 for interacting with 1.0.
     23     /// </summary>
     24     internal class DSCv3 : IDSCv3
     25     {
     26         private const string PlainTextTraces = "-t plaintext";
     27         private const string DiagnosticTraceLevelArguments = "-l trace";
     28         private const string ResourceCommand = "resource";
     29         private const string ListCommand = "list";
     30         private const string TestCommand = "test";
     31         private const string GetCommand = "get";
     32         private const string SetCommand = "set";
     33         private const string ExportCommand = "export";
     34         private const string ResourceParameter = "-r";
     35         private const string FileParameter = "-f";
     36         private const string StdInputIdentifier = "-";
     37 
     38         private const string PathEnvironmentVariable = "PATH";
     39         private const string DSCResourcePathEnvironmentVariable = "DSC_RESOURCE_PATH";
     40 
     41         private readonly ProcessorSettings processorSettings;
     42 
     43         /// <summary>
     44         /// Initializes a new instance of the <see cref="DSCv3"/> class.
     45         /// </summary>
     46         /// <param name="processorSettings">The processor settings.</param>
     47         public DSCv3(ProcessorSettings processorSettings)
     48         {
     49             this.processorSettings = processorSettings;
     50         }
     51 
     52         private string DiagnosticTraceLevel
     53         {
     54             get
     55             {
     56                 return this.processorSettings.DiagnosticTraceEnabled ? DiagnosticTraceLevelArguments : string.Empty;
     57             }
     58         }
     59 
     60         /// <inheritdoc />
     61         public IResourceListItem? GetResourceByType(string resourceType, ProcessorRunSettings? runSettings)
     62         {
     63             ResourceListItem? result = this.GetResourceByTypeInternal(resourceType, null, runSettings);
     64             if (result != null)
     65             {
     66                 return result;
     67             }
     68 
     69             // Check for this resource within adapters
     70             List<ResourceListItem> results = new List<ResourceListItem>();
     71 
     72             foreach (ResourceListItem resource in this.GetAllResources(runSettings))
     73             {
     74                 if (resource.Kind == Definitions.ResourceKind.Adapter)
     75                 {
     76                     result = this.GetResourceByTypeInternal(resourceType, resource.Type, runSettings);
     77                     if (result != null)
     78                     {
     79                         results.Add(result);
     80                     }
     81                 }
     82             }
     83 
     84             return this.GetResourceByLatestVersion(results);
     85         }
     86 
     87         /// <inheritdoc />
     88         public IResourceTestItem TestResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings)
     89         {
     90             ProcessExecution processExecution = new ProcessExecution()
     91             {
     92                 ExecutablePath = this.processorSettings.EffectiveDscExecutablePath,
     93                 Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, TestCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier },
     94                 Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()),
     95                 EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings),
     96             };
     97 
     98             if (this.RunSynchronously(processExecution))
     99             {
    100                 throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Test, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines());
    101             }
    102 
    103             return TestFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Test, unitInternal.QualifiedName), GetDefaultJsonOptions());
    104         }
    105 
    106         /// <inheritdoc />
    107         public IResourceGetItem GetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings)
    108         {
    109             ProcessExecution processExecution = new ProcessExecution()
    110             {
    111                 ExecutablePath = this.processorSettings.EffectiveDscExecutablePath,
    112                 Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, GetCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier },
    113                 Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()),
    114                 EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings),
    115             };
    116 
    117             if (this.RunSynchronously(processExecution))
    118             {
    119                 throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Get, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines());
    120             }
    121 
    122             return GetFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Get, unitInternal.QualifiedName), GetDefaultJsonOptions());
    123         }
    124 
    125         /// <inheritdoc />
    126         public IResourceSetItem SetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings)
    127         {
    128             ProcessExecution processExecution = new ProcessExecution()
    129             {
    130                 ExecutablePath = this.processorSettings.EffectiveDscExecutablePath,
    131                 Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, SetCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier },
    132                 Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()),
    133                 EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings),
    134             };
    135 
    136             if (this.RunSynchronously(processExecution))
    137             {
    138                 throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines());
    139             }
    140 
    141             return SetFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName), GetDefaultJsonOptions());
    142         }
    143 
    144         /// <inheritdoc />
    145         public IList<IResourceExportItem> ExportResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings)
    146         {
    147             // 3.0 can't handle input to export; 3.1 will fix that.
    148             ValueSet expandedSettings = unitInternal.GetExpandedSettings();
    149             if (expandedSettings.Count != 0)
    150             {
    151                 throw new NotImplementedException("Must use DSC v3.1.* to provide input to export.");
    152             }
    153 
    154             ProcessExecution processExecution = new ProcessExecution()
    155             {
    156                 ExecutablePath = this.processorSettings.EffectiveDscExecutablePath,
    157                 Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ExportCommand, ResourceParameter, unitInternal.QualifiedName },
    158                 EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings),
    159             };
    160 
    161             if (this.RunSynchronously(processExecution))
    162             {
    163                 throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Export, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines());
    164             }
    165 
    166             return ConfigurationDocument.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName), GetDefaultJsonOptions()).InterfaceResources;
    167         }
    168 
    169         /// <inheritdoc />
    170         public List<IResourceListItem> GetAllResources(ProcessorRunSettings? runSettings)
    171         {
    172             ProcessExecution processExecution = new ProcessExecution()
    173             {
    174                 ExecutablePath = this.processorSettings.EffectiveDscExecutablePath,
    175                 Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ListCommand },
    176                 EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings),
    177             };
    178 
    179             this.RunSynchronously(processExecution);
    180 
    181             return GetOutputLinesAs<ResourceListItem>(processExecution).ToList<IResourceListItem>();
    182         }
    183 
    184         private static void ThrowOnMultipleOutputLines(ProcessExecution processExecution, string method, string resourceName)
    185         {
    186             if (processExecution.Output.Count > 1)
    187             {
    188                 throw new Exceptions.InvokeDscResourceException(method, resourceName, processExecution.GetAllOutputLines());
    189             }
    190         }
    191 
    192         private static void ThrowOnZeroOutputLines(ProcessExecution processExecution, string method, string resourceName)
    193         {
    194             if (processExecution.Output.Count == 0)
    195             {
    196                 throw new Exceptions.InvokeDscResourceException(method, resourceName);
    197             }
    198         }
    199 
    200         private static T? GetOptionalSingleOutputLineAs<T>(ProcessExecution processExecution)
    201         {
    202             if (processExecution.Output.Count == 0)
    203             {
    204                 return default;
    205             }
    206 
    207             return JsonSerializer.Deserialize<T>(processExecution.Output.First(), GetDefaultJsonOptions());
    208         }
    209 
    210         private static List<T> GetOutputLinesAs<T>(ProcessExecution processExecution)
    211         {
    212             List<T> result = new List<T>();
    213             var options = GetDefaultJsonOptions();
    214 
    215             foreach (string line in processExecution.Output)
    216             {
    217                 T? lineObject = JsonSerializer.Deserialize<T>(line, options);
    218                 if (lineObject != null)
    219                 {
    220                     result.Add(lineObject);
    221                 }
    222             }
    223 
    224             return result;
    225         }
    226 
    227         private static JsonDocument GetRequiredSingleOutputLineAsJSON(ProcessExecution processExecution, string method, string resourceName)
    228         {
    229             ThrowOnMultipleOutputLines(processExecution, method, resourceName);
    230             ThrowOnZeroOutputLines(processExecution, method, resourceName);
    231 
    232             return JsonDocument.Parse(processExecution.Output.First());
    233         }
    234 
    235         private static JsonSerializerOptions GetDefaultJsonOptions()
    236         {
    237             return new JsonSerializerOptions()
    238             {
    239                 PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    240                 Converters =
    241                 {
    242                     new JsonStringEnumConverter(),
    243                 },
    244             };
    245         }
    246 
    247         private static string ConvertValueSetToJSON(ValueSet valueSet)
    248         {
    249             return JsonSerializer.Serialize(valueSet.ToHashtable());
    250         }
    251 
    252         private static List<ProcessExecutionEnvironmentVariable> CreateEnvironmentVariablesFromProcessorRunSettings(ProcessorRunSettings? runSettings)
    253         {
    254             List<ProcessExecutionEnvironmentVariable> result = new List<ProcessExecutionEnvironmentVariable>();
    255             if (runSettings is not null && !string.IsNullOrEmpty(runSettings.ResourceSearchPaths))
    256             {
    257                 // For exclusive search paths, adding to PATH is still needed as anything referenced in the manifest is still searched from PATH.
    258                 result.Add(new ProcessExecutionEnvironmentVariable { Name = PathEnvironmentVariable, Value = runSettings.ResourceSearchPaths, ValueType = ProcessExecutionEnvironmentVariableValueType.Prepend });
    259 
    260                 if (runSettings.ResourceSearchPathsExclusive)
    261                 {
    262                     result.Add(new ProcessExecutionEnvironmentVariable { Name = DSCResourcePathEnvironmentVariable, Value = runSettings.ResourceSearchPaths, ValueType = ProcessExecutionEnvironmentVariableValueType.Override });
    263                 }
    264             }
    265 
    266             return result;
    267         }
    268 
    269         /// <summary>
    270         /// Runs the process, waiting until it completes.
    271         /// </summary>
    272         /// <param name="processExecution">The process to run.</param>
    273         /// <returns>True if the exit code was not 0.</returns>
    274         private bool RunSynchronously(ProcessExecution processExecution)
    275         {
    276             this.processorSettings.DiagnosticsSink?.OnDiagnostics(DiagnosticLevel.Verbose, $"Starting process: {processExecution.CommandLine}");
    277 
    278             processExecution.Start().WaitForExit();
    279 
    280             this.processorSettings.DiagnosticsSink?.OnDiagnostics(DiagnosticLevel.Verbose, $"Process exited with code: {processExecution.ExitCode}\n--- Output Stream ---\n{processExecution.GetAllOutputLines()}\n--- Error Stream ---\n{processExecution.GetAllErrorLines()}");
    281 
    282             return processExecution.ExitCode != 0;
    283         }
    284 
    285         private ResourceListItem? GetResourceByTypeInternal(string resourceType, string? adapter, ProcessorRunSettings? runSettings)
    286         {
    287             ProcessExecution processExecution = new ProcessExecution()
    288             {
    289                 ExecutablePath = this.processorSettings.EffectiveDscExecutablePath,
    290                 Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ListCommand, adapter != null ? $"-a {adapter}" : string.Empty, resourceType },
    291                 EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings),
    292             };
    293 
    294             this.RunSynchronously(processExecution);
    295 
    296             List<ResourceListItem> results = GetOutputLinesAs<ResourceListItem>(processExecution);
    297 
    298             return this.GetResourceByLatestVersion(results);
    299         }
    300 
    301         private ResourceListItem? GetResourceByLatestVersion(List<ResourceListItem> resources)
    302         {
    303             // There may be different versions of same resource on the system. We check if all
    304             // resource types match, we return the first one.
    305             // TODO: May want to pick the latest one from the list. But since we are not using
    306             // the version in our commands, picking any one is good for now.
    307             ResourceListItem? candidate = null;
    308             string candidateType = string.Empty;
    309 
    310             foreach (ResourceListItem resource in resources)
    311             {
    312                 if (candidate == null)
    313                 {
    314                     candidate = resource;
    315                     candidateType = resource.Type;
    316                 }
    317                 else if (!candidateType.Equals(resource.Type, StringComparison.OrdinalIgnoreCase))
    318                 {
    319                     throw new Exceptions.GetDscResourceMultipleMatches(candidateType, null);
    320                 }
    321             }
    322 
    323             return candidate;
    324         }
    325     }
    326 }