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 }