Program.cs (12959B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 using System.Reflection; 4 using System.Runtime.InteropServices; 5 using System.Runtime.Loader; 6 using System.Text; 7 using System.Text.Json; 8 using System.Text.Json.Serialization; 9 using Microsoft.Management.Configuration; 10 using Microsoft.Management.Configuration.Processor; 11 using Microsoft.Management.Configuration.Processor.Helpers; 12 using WinRT; 13 using IConfigurationSetProcessorFactory = global::Microsoft.Management.Configuration.IConfigurationSetProcessorFactory; 14 15 namespace ConfigurationRemotingServer 16 { 17 /// <summary> 18 /// Custom assembly load context. 19 /// </summary> 20 internal class NativeAssemblyLoadContext : AssemblyLoadContext 21 { 22 private static readonly string PackageRootPath; 23 24 private static readonly NativeAssemblyLoadContext NativeALC = new(); 25 26 static NativeAssemblyLoadContext() 27 { 28 var self = typeof(NativeAssemblyLoadContext).Assembly; 29 PackageRootPath = Path.Combine( 30 Path.GetDirectoryName(self.Location)!, 31 ".."); 32 } 33 34 private NativeAssemblyLoadContext() 35 : base("NativeAssemblyLoadContext", isCollectible: false) 36 { 37 } 38 39 /// <summary> 40 /// Handler to resolve unmanaged assemblies. 41 /// </summary> 42 /// <param name="context">Assembly load context.</param> 43 /// <param name="name">Assembly name.</param> 44 /// <returns>The assembly, null if not in our assembly location.</returns> 45 internal static IntPtr ResolvingUnmanagedHandler(Assembly context, string name) 46 { 47 if (name.Equals("WindowsPackageManager.dll", StringComparison.OrdinalIgnoreCase)) 48 { 49 return NativeALC.LoadUnmanagedDll(name); 50 } 51 52 return IntPtr.Zero; 53 } 54 55 /// <inheritdoc/> 56 protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) 57 { 58 string path = Path.Combine(PackageRootPath, unmanagedDllName); 59 if (File.Exists(path)) 60 { 61 return this.LoadUnmanagedDllFromPath(path); 62 } 63 64 return IntPtr.Zero; 65 } 66 } 67 68 internal class Program 69 { 70 private const string CommandLineSectionSeparator = "~~~~~~"; 71 private const string ExternalModulesName = "ExternalModules"; 72 73 static int Main(string[] args) 74 { 75 // Remove any attached console to prevent modules (or their actions) from writing to our console. 76 FreeConsole(); 77 78 // Help find WindowsPackageManager.dll 79 AssemblyLoadContext.Default.ResolvingUnmanagedDll += NativeAssemblyLoadContext.ResolvingUnmanagedHandler; 80 81 string staticsCallback = args[1]; 82 83 // Listen for setting change message and update PATH if needed. 84 EnvironmentChangeListener.EnvironmentChanged += OnEnvironmentChanged; 85 EnvironmentChangeListener environmentChangeListener = new EnvironmentChangeListener(); 86 87 try 88 { 89 string completionEventName = args[2]; 90 uint parentProcessId = uint.Parse(args[3]); 91 string processorEngine = args[4]; 92 93 ConfigurationSet? limitationSet = null; 94 LimitationSetMetadata? limitationSetMetadata = null; 95 96 // Parse limitation set if applicable. 97 // The format will be: 98 // <Common args for initialization> ~~~~~~ <Metadata json> ~~~~~~ <Limitation Set in yaml> 99 // Metadata json format: 100 // { 101 // "path": "C:\full\file\path.yaml" 102 // } 103 // If a limitation set is provided, the processor will be limited 104 // to only work on units defined inside the limitation set. 105 var commandPtr = GetCommandLineW(); 106 var commandStr = Marshal.PtrToStringUni(commandPtr) ?? string.Empty; 107 108 // In case the limitation set content contains the separator, we'll not use Split method. 109 var firstSeparatorIndex = commandStr.IndexOf(CommandLineSectionSeparator); 110 if (firstSeparatorIndex > 0) 111 { 112 var secondSeparatorIndex = commandStr.IndexOf(CommandLineSectionSeparator, firstSeparatorIndex + CommandLineSectionSeparator.Length); 113 if (secondSeparatorIndex <= 0) 114 { 115 throw new ArgumentException("The input command contains only one separator string."); 116 } 117 118 // Parse limitation set. 119 byte[] limitationSetBytes = Encoding.UTF8.GetBytes(commandStr.Substring(secondSeparatorIndex + CommandLineSectionSeparator.Length)); 120 MemoryStream memoryStream = new MemoryStream(); 121 memoryStream.Write(limitationSetBytes); 122 memoryStream.Flush(); 123 memoryStream.Seek(0, SeekOrigin.Begin); 124 ConfigurationProcessor processor = new ConfigurationProcessor((IConfigurationSetProcessorFactory?)null); 125 var limitationSetResult = processor.OpenConfigurationSet(memoryStream.AsInputStream()); 126 memoryStream.Close(); 127 128 if (limitationSetResult.ResultCode != null) 129 { 130 throw limitationSetResult.ResultCode; 131 } 132 133 limitationSet = limitationSetResult.Set; 134 if (limitationSet == null) 135 { 136 throw new ArgumentException("The limitation set cannot be parsed."); 137 } 138 139 // Now parse metadata json and update the limitation set 140 limitationSetMetadata = JsonSerializer.Deserialize<LimitationSetMetadata>(commandStr.Substring( 141 firstSeparatorIndex + CommandLineSectionSeparator.Length, 142 secondSeparatorIndex - firstSeparatorIndex - CommandLineSectionSeparator.Length)); 143 144 if (limitationSetMetadata != null) 145 { 146 limitationSet.Path = limitationSetMetadata.Path; 147 } 148 } 149 150 IConfigurationSetProcessorFactory factory = CreateFactory(processorEngine, limitationSet, limitationSetMetadata); 151 IObjectReference factoryInterface = MarshalInterface<IConfigurationSetProcessorFactory>.CreateMarshaler(factory); 152 153 return WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(0, factoryInterface.ThisPtr, staticsCallback, completionEventName, parentProcessId); 154 } 155 catch (Exception ex) 156 { 157 WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(ex.HResult, IntPtr.Zero, staticsCallback, null, 0); 158 return ex.HResult; 159 } 160 finally 161 { 162 environmentChangeListener.Stop(); 163 } 164 } 165 166 private static void OnEnvironmentChanged() 167 { 168 PathEnvironmentVariableHandler.UpdatePath(); 169 } 170 171 private class LimitationSetMetadata 172 { 173 [JsonPropertyName("path")] 174 public string Path { get; set; } = string.Empty; 175 176 [JsonPropertyName("modulePath")] 177 public string? ModulePath { get; set; } = null; 178 179 [JsonPropertyName("processorPath")] 180 public string? ProcessorPath { get; set; } = null; 181 } 182 183 private static IConfigurationSetProcessorFactory CreateFactory(string processorEngine, ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) 184 { 185 switch (processorEngine) 186 { 187 case "pwsh": 188 return CreatePowerShellFactory(limitationSet, limitationSetMetadata); 189 case "dscv3": 190 return CreateDSCv3Factory(limitationSet, limitationSetMetadata); 191 } 192 193 throw new NotImplementedException($"Processor engine unknown: {processorEngine}"); 194 } 195 196 private static IConfigurationSetProcessorFactory CreatePowerShellFactory(ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) 197 { 198 PowerShellConfigurationSetProcessorFactory factory = new PowerShellConfigurationSetProcessorFactory(); 199 200 // Set default properties. 201 var externalModulesPath = GetExternalModulesPath(); 202 if (string.IsNullOrWhiteSpace(externalModulesPath)) 203 { 204 throw new DirectoryNotFoundException("Failed to get ExternalModules."); 205 } 206 207 // Set as implicit module paths so it will be always included in AdditionalModulePaths 208 factory.ImplicitModulePaths = new List<string>() { externalModulesPath }; 209 factory.ProcessorType = PowerShellConfigurationProcessorType.Hosted; 210 211 if (limitationSetMetadata != null) 212 { 213 if (limitationSetMetadata.ModulePath != null) 214 { 215 PowerShellConfigurationProcessorLocation parsedLocation = PowerShellConfigurationProcessorLocation.Default; 216 if (Enum.TryParse(limitationSetMetadata.ModulePath, out parsedLocation)) 217 { 218 factory.Location = parsedLocation; 219 } 220 else 221 { 222 factory.Location = PowerShellConfigurationProcessorLocation.Custom; 223 factory.CustomLocation = limitationSetMetadata.ModulePath; 224 } 225 } 226 } 227 228 // Apply limitation set and thereby disable changing properties. 229 if (limitationSet != null) 230 { 231 factory.LimitationSet = limitationSet; 232 } 233 234 return factory; 235 } 236 237 private static IConfigurationSetProcessorFactory CreateDSCv3Factory(ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) 238 { 239 DSCv3ConfigurationSetProcessorFactory factory = new DSCv3ConfigurationSetProcessorFactory(); 240 241 if (limitationSetMetadata != null) 242 { 243 if (limitationSetMetadata.ProcessorPath != null) 244 { 245 factory.DscExecutablePath = limitationSetMetadata.ProcessorPath; 246 } 247 else 248 { 249 // Require that the path to the DSC executable be presented to the user in limitation mode. 250 // This helps prevent path attacks against an elevated process (as long as the user checks the value). 251 throw new ArgumentNullException("The path to the DSC executable must be supplied in limitation mode."); 252 } 253 } 254 255 // Apply limitation set and thereby disable changing properties. 256 if (limitationSet != null) 257 { 258 factory.LimitationSet = limitationSet; 259 } 260 261 return factory; 262 } 263 264 private static string GetExternalModulesPath() 265 { 266 var currentAssemblyDirectoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 267 if (currentAssemblyDirectoryPath != null) 268 { 269 var packageRootPath = Directory.GetParent(currentAssemblyDirectoryPath)?.FullName; 270 if (packageRootPath != null) 271 { 272 var externalModulesPath = Path.Combine(packageRootPath, ExternalModulesName); 273 if (Directory.Exists(externalModulesPath)) 274 { 275 return externalModulesPath; 276 } 277 } 278 } 279 280 return string.Empty; 281 } 282 283 [DllImport("WindowsPackageManager.dll")] 284 private static extern int WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization( 285 int result, 286 IntPtr factory, 287 [MarshalAs(UnmanagedType.LPWStr)]string staticsCallback, 288 [MarshalAs(UnmanagedType.LPWStr)]string? completionEventName, 289 uint parentProcessId); 290 291 [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 292 private static extern IntPtr GetCommandLineW(); 293 294 [DllImport("kernel32.dll")] 295 [return: MarshalAs(UnmanagedType.Bool)] 296 private static extern bool FreeConsole(); 297 } 298 }