DscModuleV2.cs (11520B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="DscModuleV2.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.DscModules 8 { 9 using System; 10 using System.Collections; 11 using System.Collections.Generic; 12 using System.Collections.ObjectModel; 13 using System.Linq; 14 using System.Management.Automation; 15 using Microsoft.Management.Configuration.Processor.Exceptions; 16 using Microsoft.Management.Configuration.Processor.Extensions; 17 using Microsoft.Management.Configuration.Processor.Helpers; 18 using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; 19 using Microsoft.Management.Configuration.Processor.PowerShell.Extensions; 20 using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; 21 using Microsoft.PowerShell.Commands; 22 using Windows.Foundation.Collections; 23 using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; 24 25 /// <summary> 26 /// PSDesiredStateConfiguration v2. 27 /// </summary> 28 internal class DscModuleV2 : IDscModule 29 { 30 private const string InDesiredState = "InDesiredState"; 31 private const string RebootRequired = "RebootRequired"; 32 33 private static readonly IEnumerable<string> ExclusionResourcesParentPath = new string[] 34 { 35 @"C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources", 36 @"C:\Program Files\WindowsPowerShell\Modules\PackageManagement\1.0.0.1", 37 }; 38 39 /// <summary> 40 /// Initializes a new instance of the <see cref="DscModuleV2"/> class. 41 /// </summary> 42 public DscModuleV2() 43 { 44 this.ModuleSpecification = PowerShellHelpers.CreateModuleSpecification( 45 Modules.PSDesiredStateConfiguration, 46 minVersion: Modules.PSDesiredStateConfigurationMinVersion, 47 maxVersion: Modules.PSDesiredStateConfigurationMaxVersion); 48 } 49 50 /// <inheritdoc/> 51 public ModuleSpecification ModuleSpecification { get; private init; } 52 53 /// <inheritdoc/> 54 public string GetDscResourceCmd { get; } = Commands.GetDscResource; 55 56 /// <inheritdoc/> 57 public string InvokeDscResourceCmd { get; } = Commands.InvokeDscResource; 58 59 /// <inheritdoc/> 60 public IReadOnlyList<DscResourceInfoInternal> GetAllDscResources(PowerShell pwsh) 61 { 62 return this.GetDscResources(pwsh, null, null); 63 } 64 65 /// <inheritdoc/> 66 public IReadOnlyList<DscResourceInfoInternal> GetDscResourcesInModule( 67 PowerShell pwsh, 68 ModuleSpecification moduleSpecification) 69 { 70 return this.GetDscResources(pwsh, null, moduleSpecification); 71 } 72 73 /// <inheritdoc/> 74 public DscResourceInfoInternal? GetDscResource( 75 PowerShell pwsh, 76 string name, 77 ModuleSpecification? moduleSpecification) 78 { 79 var dscResourceInfos = this.GetDscResources(pwsh, name, moduleSpecification); 80 81 if (dscResourceInfos.Count == 0) 82 { 83 return null; 84 } 85 else if (dscResourceInfos.Count > 1) 86 { 87 throw new GetDscResourceMultipleMatches(name, moduleSpecification); 88 } 89 90 return dscResourceInfos[0]; 91 } 92 93 /// <inheritdoc/> 94 public ValueSet InvokeGetResource( 95 PowerShell pwsh, 96 ValueSet settings, 97 string name, 98 ModuleSpecification? moduleSpecification) 99 { 100 PSObject? getResult = null; 101 102 try 103 { 104 getResult = pwsh.AddCommand(this.InvokeDscResourceCmd) 105 .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) 106 .AddParameter(Parameters.Method, DscMethods.Get) 107 .InvokeAndStopOnError() 108 .FirstOrDefault(); 109 } 110 catch (Exception ex) 111 { 112 throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, ex); 113 } 114 115 string? errorMessage = pwsh.GetErrorMessage(); 116 if (errorMessage is not null) 117 { 118 throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, errorMessage); 119 } 120 121 if (getResult is null) 122 { 123 throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification); 124 } 125 126 // Script based resource. 127 if (getResult.BaseObject is Hashtable) 128 { 129 Hashtable hashTable = (Hashtable)getResult.BaseObject; 130 var resultSettings = new ValueSet(); 131 foreach (DictionaryEntry entry in hashTable) 132 { 133 resultSettings.Add(entry.Key as string, entry.Value); 134 } 135 136 return resultSettings; 137 } 138 139 // Class-based resource. 140 return TypeHelpers.GetAllPropertiesValues(getResult.BaseObject); 141 } 142 143 /// <inheritdoc/> 144 public bool InvokeTestResource( 145 PowerShell pwsh, 146 ValueSet settings, 147 string name, 148 ModuleSpecification? moduleSpecification) 149 { 150 // Returned type is InvokeDscResourceTestResult which is a PowerShell classed defined 151 // in PSDesiredStateConfiguration.psm1. 152 dynamic? testResult = null; 153 154 try 155 { 156 testResult = pwsh.AddCommand(this.InvokeDscResourceCmd) 157 .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) 158 .AddParameter(Parameters.Method, DscMethods.Test) 159 .InvokeAndStopOnError() 160 .FirstOrDefault(); 161 } 162 catch (Exception ex) 163 { 164 throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, ex); 165 } 166 167 string? errorMessage = pwsh.GetErrorMessage(); 168 if (errorMessage is not null) 169 { 170 throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, errorMessage); 171 } 172 173 if (testResult is null || 174 !TypeHelpers.PropertyWithTypeExists<bool>(testResult, InDesiredState)) 175 { 176 throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification); 177 } 178 179 return testResult?.InDesiredState; 180 } 181 182 /// <inheritdoc/> 183 public bool InvokeSetResource( 184 PowerShell pwsh, 185 ValueSet settings, 186 string name, 187 ModuleSpecification? moduleSpecification) 188 { 189 // Returned type is InvokeDscResourceSetResult which is a PowerShell classed defined 190 // in PSDesiredStateConfiguration.psm1. 191 dynamic? setResult = null; 192 193 try 194 { 195 setResult = pwsh.AddCommand(this.InvokeDscResourceCmd) 196 .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) 197 .AddParameter(Parameters.Method, DscMethods.Set) 198 .InvokeAndStopOnError() 199 .FirstOrDefault(); 200 } 201 catch (Exception ex) 202 { 203 throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, ex); 204 } 205 206 string? errorMessage = pwsh.GetErrorMessage(); 207 if (errorMessage is not null) 208 { 209 throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, errorMessage, pwsh.ContainsPropertyError()); 210 } 211 212 if (setResult is null || 213 !TypeHelpers.PropertyWithTypeExists<bool>(setResult, RebootRequired)) 214 { 215 throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification); 216 } 217 218 return setResult?.RebootRequired; 219 } 220 221 private static Dictionary<string, object> PrepareInvokeParameters( 222 string name, 223 ValueSet settings, 224 ModuleSpecification? moduleSpecification) 225 { 226 Hashtable properties = settings.ToHashtable(); 227 228 var parameters = new Dictionary<string, object>() 229 { 230 { Parameters.Name, name }, 231 { Parameters.Property, properties }, 232 }; 233 234 if (moduleSpecification is not null) 235 { 236 parameters.Add(Parameters.ModuleName, moduleSpecification); 237 } 238 239 return parameters; 240 } 241 242 private IReadOnlyList<DscResourceInfoInternal> GetDscResources( 243 PowerShell pwsh, 244 string? name, 245 ModuleSpecification? moduleSpecification) 246 { 247 pwsh.AddCommand(this.GetDscResourceCmd); 248 249 if (name is not null) 250 { 251 pwsh.AddParameter(Parameters.Name, name); 252 } 253 254 if (moduleSpecification is not null) 255 { 256 pwsh.AddParameter(Parameters.Module, moduleSpecification); 257 } 258 259 try 260 { 261 var resources = pwsh.Invoke(); 262 return this.ConvertToDscResourceInfoInternal(resources); 263 } 264 catch (RuntimeException e) 265 { 266 // Detect easily this. 267 if (e.ErrorRecord.FullyQualifiedErrorId == "ExceptionWhenSetting,GetResourceFromKeyword") 268 { 269 throw new GetDscResourceModuleConflict(name, moduleSpecification, e); 270 } 271 272 throw; 273 } 274 } 275 276 private List<DscResourceInfoInternal> ConvertToDscResourceInfoInternal(Collection<PSObject> psObjects) 277 { 278 var result = new List<DscResourceInfoInternal>(); 279 foreach (dynamic psObject in psObjects) 280 { 281 var dscResourceInfo = new DscResourceInfoInternal(psObject); 282 283 // Explicitly don't support old DSC resources from v1 PSDesiredStateConfiguration. 284 // Even if the Windows System32 Windows PowerShell module path is removed they 285 // will show up. 286 if (ExclusionResourcesParentPath.Any(e => dscResourceInfo.ParentPath!.StartsWith(e))) 287 { 288 continue; 289 } 290 291 result.Add(dscResourceInfo); 292 } 293 294 return result; 295 } 296 } 297 }