winget-cli

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

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 }