PowerShellConfigurationSetProcessorFactory.cs (12189B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="PowerShellConfigurationSetProcessorFactory.cs" company="Microsoft Corporation"> 3 // Copyright (c) Microsoft Corporation. Licensed under the MIT License. 4 // </copyright> 5 // ----------------------------------------------------------------------------- 6 7 namespace Microsoft.Management.Configuration.Processor 8 { 9 using System; 10 using System.Collections.Generic; 11 using System.IO; 12 using System.Text; 13 using Microsoft.Management.Configuration; 14 using Microsoft.Management.Configuration.Processor.Factory; 15 using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; 16 using Microsoft.Management.Configuration.Processor.PowerShell.Set; 17 using Microsoft.Management.Configuration.SetProcessorFactory; 18 using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; 19 20 /// <summary> 21 /// IConfigurationSetProcessorFactory implementation using PowerShell DSC v2. 22 /// </summary> 23 #if WinGetCsWinRTEmbedded 24 internal 25 #else 26 public 27 #endif 28 sealed partial class PowerShellConfigurationSetProcessorFactory : ConfigurationSetProcessorFactoryBase, IConfigurationSetProcessorFactory, IPowerShellConfigurationProcessorFactoryProperties, IPwshConfigurationSetProcessorFactoryProperties 29 { 30 // Backing variables for properties that are restricted in limit mode. 31 private PowerShellConfigurationProcessorType processorType = PowerShellConfigurationProcessorType.Default; 32 private IReadOnlyList<string>? additionalModulePaths; 33 private IReadOnlyList<string>? implicitModulePaths; 34 private PowerShellConfigurationProcessorPolicy policy = PowerShellConfigurationProcessorPolicy.Default; 35 private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.Default; 36 private string? customLocation; 37 38 /// <summary> 39 /// Initializes a new instance of the <see cref="PowerShellConfigurationSetProcessorFactory"/> class. 40 /// </summary> 41 public PowerShellConfigurationSetProcessorFactory() 42 { 43 } 44 45 /// <summary> 46 /// Gets or sets the processor type. 47 /// </summary> 48 public PowerShellConfigurationProcessorType ProcessorType 49 { 50 get 51 { 52 return this.processorType; 53 } 54 55 set 56 { 57 if (this.IsLimitMode()) 58 { 59 throw new InvalidOperationException("Setting ProcessorType in limit mode is invalid."); 60 } 61 62 this.processorType = value; 63 } 64 } 65 66 /// <summary> 67 /// Gets or sets the additional module paths. 68 /// </summary> 69 public IReadOnlyList<string>? AdditionalModulePaths 70 { 71 get 72 { 73 return this.additionalModulePaths; 74 } 75 76 set 77 { 78 if (this.IsLimitMode()) 79 { 80 throw new InvalidOperationException("Setting AdditionalModulePaths in limit mode is invalid."); 81 } 82 83 // Create a copy of incoming value 84 List<string> newModulePaths = new List<string>(); 85 if (value != null) 86 { 87 foreach (string path in value) 88 { 89 newModulePaths.Add(path); 90 } 91 } 92 93 // Add implicit module paths if applicable 94 if (this.implicitModulePaths != null) 95 { 96 foreach (string path in this.implicitModulePaths) 97 { 98 if (!newModulePaths.Contains(path)) 99 { 100 newModulePaths.Add(path); 101 } 102 } 103 } 104 105 this.additionalModulePaths = newModulePaths; 106 } 107 } 108 109 /// <summary> 110 /// Gets or sets the implicit module paths. These paths are always included in AdditionalModulePaths. 111 /// </summary> 112 public IReadOnlyList<string>? ImplicitModulePaths 113 { 114 get 115 { 116 return this.implicitModulePaths; 117 } 118 119 set 120 { 121 if (this.IsLimitMode()) 122 { 123 throw new InvalidOperationException("Setting ImplicitModulePaths in limit mode is invalid."); 124 } 125 126 this.implicitModulePaths = value; 127 128 // Apply to additional module paths if applicable. 129 if (this.implicitModulePaths != null) 130 { 131 List<string> newModulePaths = new List<string>(); 132 if (this.additionalModulePaths != null) 133 { 134 foreach (string path in this.additionalModulePaths) 135 { 136 newModulePaths.Add(path); 137 } 138 } 139 140 foreach (string path in this.implicitModulePaths) 141 { 142 if (!newModulePaths.Contains(path)) 143 { 144 newModulePaths.Add(path); 145 } 146 } 147 148 this.additionalModulePaths = newModulePaths; 149 } 150 } 151 } 152 153 /// <summary> 154 /// Gets or sets the configuration policy. 155 /// </summary> 156 public PowerShellConfigurationProcessorPolicy Policy 157 { 158 get 159 { 160 return this.policy; 161 } 162 163 set 164 { 165 if (this.IsLimitMode()) 166 { 167 throw new InvalidOperationException("Setting Policy in limit mode is invalid."); 168 } 169 170 this.policy = value; 171 } 172 } 173 174 /// <summary> 175 /// Gets or sets the configuration policy. 176 /// </summary> 177 PwshConfigurationProcessorPolicy IPwshConfigurationSetProcessorFactoryProperties.Policy 178 { 179 get { return Helpers.TypeHelpers.ToPwshConfigurationProcessorPolicy(this.Policy); } 180 set { this.Policy = Helpers.TypeHelpers.ToPowerShellConfigurationProcessorPolicy(value); } 181 } 182 183 /// <summary> 184 /// Gets or sets the module location. 185 /// </summary> 186 public PowerShellConfigurationProcessorLocation Location 187 { 188 get 189 { 190 return this.location; 191 } 192 193 set 194 { 195 if (this.IsLimitMode()) 196 { 197 throw new InvalidOperationException("Setting Location in limit mode is invalid."); 198 } 199 200 this.location = value; 201 } 202 } 203 204 /// <summary> 205 /// Gets or sets the module location. 206 /// </summary> 207 PwshConfigurationProcessorLocation IPwshConfigurationSetProcessorFactoryProperties.Location 208 { 209 get { return Helpers.TypeHelpers.ToPwshConfigurationProcessorLocation(this.Location); } 210 set { this.Location = Helpers.TypeHelpers.ToPowerShellConfigurationProcessorLocation(value); } 211 } 212 213 /// <summary> 214 /// Gets or sets the install module path. Only used for Scope = Custom. 215 /// </summary> 216 public string? CustomLocation 217 { 218 get 219 { 220 return this.customLocation; 221 } 222 223 set 224 { 225 if (this.IsLimitMode()) 226 { 227 throw new InvalidOperationException("Setting CustomLocation in limit mode is invalid."); 228 } 229 230 this.customLocation = value; 231 } 232 } 233 234 /// <summary> 235 /// Gets the winget module path. 236 /// </summary> 237 /// <returns>The winget module path.</returns> 238 internal static string GetWinGetModulePath() 239 { 240 return Path.Combine( 241 Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), 242 @"Microsoft\WinGet\Configuration\Modules"); 243 } 244 245 /// <summary> 246 /// Sends diagnostic if appropriate for PowerShell streams. 247 /// </summary> 248 /// <param name="level">The level of this diagnostic message.</param> 249 /// <param name="pwsh">The PowerShell object.</param> 250 internal void OnDiagnostics(DiagnosticLevel level, System.Management.Automation.PowerShell pwsh) 251 { 252 if (this.AreDiagnosticsEnabled() && level >= this.MinimumLevel && pwsh.HadErrors) 253 { 254 var builder = new StringBuilder(); 255 256 // There are the last commands ran by that PowerShell obj, not all in our session. 257 builder.Append("PowerShellCommands: "); 258 foreach (var c in pwsh.Commands.Commands) 259 { 260 builder.Append($"['{c.CommandText}'"); 261 if (c.Parameters.Count > 0) 262 { 263 builder.Append(" Parameters: "); 264 foreach (var p in c.Parameters) 265 { 266 builder.Append($"{p.Name} = '{p.Value}' "); 267 } 268 269 builder.Append("]"); 270 } 271 272 builder.AppendLine(); 273 } 274 275 foreach (var error in pwsh.Streams.Error) 276 { 277 builder.AppendLine($"[WriteError] {error}"); 278 } 279 280 this.OnDiagnostics(level, builder.ToString()); 281 } 282 } 283 284 /// <inheritdoc /> 285 protected override IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode) 286 { 287 var envFactory = new ProcessorEnvironmentFactory(this.ProcessorType); 288 var processorEnvironment = envFactory.CreateEnvironment( 289 this, 290 this.Policy); 291 292 if (this.AdditionalModulePaths is not null) 293 { 294 processorEnvironment.PrependPSModulePaths(this.AdditionalModulePaths); 295 } 296 297 // Always add the winget path. 298 var wingetModulePath = GetWinGetModulePath(); 299 processorEnvironment.PrependPSModulePath(wingetModulePath); 300 if (this.Location == PowerShellConfigurationProcessorLocation.WinGetModulePath) 301 { 302 this.OnDiagnostics(DiagnosticLevel.Verbose, "Using winget module path"); 303 processorEnvironment.SetLocation(PowerShellConfigurationProcessorLocation.Custom, wingetModulePath); 304 } 305 else if (this.Location == PowerShellConfigurationProcessorLocation.Custom) 306 { 307 if (string.IsNullOrEmpty(this.CustomLocation)) 308 { 309 throw new ArgumentNullException(nameof(this.CustomLocation)); 310 } 311 312 processorEnvironment.SetLocation(this.Location, this.CustomLocation); 313 processorEnvironment.PrependPSModulePath(this.CustomLocation); 314 } 315 else 316 { 317 processorEnvironment.SetLocation(this.Location, null); 318 } 319 320 this.OnDiagnostics(DiagnosticLevel.Verbose, $" Effective module path:\n{processorEnvironment.GetVariable<string>(Variables.PSModulePath)}"); 321 322 processorEnvironment.ValidateRunspace(); 323 324 return new PowerShellConfigurationSetProcessor(processorEnvironment, set, isLimitMode) { SetProcessorFactory = this }; 325 } 326 } 327 }