MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Globalization;6using System.IO;7using System.Management.Automation.Internal;8using System.Management.Automation.Language;9using System.Reflection;10using System.Runtime.Loader;11using System.Security;12using System.Text;13using System.Text.RegularExpressions;14 15namespace System.Management.Automation16{17 /// <summary>18 /// ClrFacade contains all diverging code (different implementation for FullCLR and CoreCLR using if/def).19 /// It exposes common APIs that can be used by the rest of the code base.20 /// </summary>21 internal static class ClrFacade22 {23 /// <summary>24 /// Initialize powershell AssemblyLoadContext and register the 'Resolving' event, if it's not done already.25 /// If powershell is hosted by a native host such as DSC, then PS ALC may be initialized via 'SetPowerShellAssemblyLoadContext' before loading S.M.A.26 /// </summary>27 /// <remarks>28 /// We do this both here and during the initialization of the 'RunspaceBase' type.29 /// This is because we want to make sure the assembly/library resolvers are:30 /// 1. registered before any script/cmdlet can run.31 /// 2. registered before 'ClrFacade' gets used for assembly related operations.32 ///33 /// The 'ClrFacade' type may be used without a Runspace created, for example, by calling type conversion methods in the 'LanguagePrimitive' type.34 /// And at the mean time, script or cmdlet may run without the 'ClrFacade' type initialized.35 /// That's why we attempt to create the singleton of 'PowerShellAssemblyLoadContext' at both places.36 /// </remarks>37 static ClrFacade()38 {39 if (PowerShellAssemblyLoadContext.Instance is null)40 {41 PowerShellAssemblyLoadContext.InitializeSingleton(string.Empty, throwOnReentry: false);42 }43 }44 45 #region Assembly46 47 internal static IEnumerable<Assembly> GetAssemblies(TypeResolutionState typeResolutionState, TypeName typeName)48 {49 string typeNameToSearch = typeResolutionState.GetAlternateTypeName(typeName.Name) ?? typeName.Name;50 return GetAssemblies(typeNameToSearch);51 }52 53 /// <summary>54 /// Facade for AppDomain.GetAssemblies.55 /// </summary>56 /// <param name="namespaceQualifiedTypeName">57 /// In CoreCLR context, if it's for string-to-type conversion and the namespace qualified type name is known, pass it in so that58 /// powershell can load the necessary TPA if the target type is from an unloaded TPA.59 /// </param>60 internal static IEnumerable<Assembly> GetAssemblies(string namespaceQualifiedTypeName = null)61 {62 return PSAssemblyLoadContext.GetAssembly(namespaceQualifiedTypeName) ?? GetPSVisibleAssemblies();63 }64 65 /// <summary>66 /// Return assemblies from the default load context and the 'individual' load contexts.67 /// The 'individual' load contexts are the ones holding assemblies loaded via 'Assembly.Load(byte[])' and 'Assembly.LoadFile'.68 /// Assemblies loaded in any custom load contexts are not consider visible to PowerShell to avoid type identity issues.69 /// </summary>70 private static IEnumerable<Assembly> GetPSVisibleAssemblies()71 {72 const string IndividualAssemblyLoadContext = "System.Runtime.Loader.IndividualAssemblyLoadContext";73 74 foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies)75 {76 if (!assembly.FullName.StartsWith(TypeDefiner.DynamicClassAssemblyFullNamePrefix, StringComparison.Ordinal))77 {78 yield return assembly;79 }80 }81 82 foreach (AssemblyLoadContext context in AssemblyLoadContext.All)83 {84 if (IndividualAssemblyLoadContext.Equals(context.GetType().FullName, StringComparison.Ordinal))85 {86 foreach (Assembly assembly in context.Assemblies)87 {88 yield return assembly;89 }90 }91 }92 }93 94 /// <summary>95 /// Get the namespace-qualified type names of all available .NET Core types shipped with PowerShell.96 /// This is used for type name auto-completion in PS engine.97 /// </summary>98 internal static IEnumerable<string> AvailableDotNetTypeNames => PSAssemblyLoadContext.AvailableDotNetTypeNames;99 100 /// <summary>101 /// Get the assembly names of all available .NET Core assemblies shipped with PowerShell.102 /// This is used for type name auto-completion in PS engine.103 /// </summary>104 internal static HashSet<string> AvailableDotNetAssemblyNames => PSAssemblyLoadContext.AvailableDotNetAssemblyNames;105 106 private static PowerShellAssemblyLoadContext PSAssemblyLoadContext => PowerShellAssemblyLoadContext.Instance;107 108 #endregion Assembly109 110 #region Encoding111 112 /// <summary>113 /// Facade for getting OEM encoding114 /// OEM encodings work on all platforms, or rather codepage 437 is available on both Windows and Non-Windows.115 /// </summary>116 internal static Encoding GetOEMEncoding()117 {118 if (s_oemEncoding == null)119 {120#if UNIX121 s_oemEncoding = Encoding.Default;122#else123 uint oemCp = Interop.Windows.GetOEMCP();124 s_oemEncoding = Encoding.GetEncoding((int)oemCp);125#endif126 }127 128 return s_oemEncoding;129 }130 131 private static volatile Encoding s_oemEncoding;132 133 #endregion Encoding134 135#if !UNIX136 #region Security137 138 /// <summary>139 /// Facade to get the SecurityZone information of a file.140 /// </summary>141 internal static SecurityZone GetFileSecurityZone(string filePath)142 {143 Diagnostics.Assert(Path.IsPathRooted(filePath), "Caller makes sure the path is rooted.");144 Diagnostics.Assert(File.Exists(filePath), "Caller makes sure the file exists.");145 return MapSecurityZone(filePath);146 }147 148 /// <summary>149 /// Map the file to SecurityZone.150 /// </summary>151 /// <remarks>152 /// The algorithm is as follows:153 ///154 /// 1. Alternate data stream "Zone.Identifier" is checked first. If this alternate data stream has content, then the content is parsed to determine the SecurityZone.155 /// 2. If the alternate data stream "Zone.Identifier" doesn't exist, or its content is not expected, then the file path will be analyzed to determine the SecurityZone.156 ///157 /// For #1, the parsing rules are observed as follows:158 /// A. Read content of the data stream line by line. Each line is trimmed.159 /// B. Try to match the current line with '^\[ZoneTransfer\]'.160 /// - if matching, then do step (#C) starting from the next line161 /// - if not matching, then continue to do step (#B) with the next line.162 /// C. Try to match the current line with '^ZoneId\s*=\s*(.*)'163 /// - if matching, check if the ZoneId is valid. Then return the corresponding SecurityZone if the 'ZoneId' is valid, or 'NoZone' if invalid.164 /// - if not matching, then continue to do step (#C) with the next line.165 /// D. Reach EOF, then return 'NoZone'.166 /// After #1, if the returned SecurityZone is 'NoZone', then proceed with #2. Otherwise, return it as the mapping result.167 ///168 /// For #2, the analysis rules are observed as follows:169 /// A. If the path is a UNC path, then170 /// - if the host name of the UNC path is IP address, then mapping it to "Internet" zone.171 /// - if the host name of the UNC path has dot (.) in it, then mapping it to "internet" zone.172 /// - otherwise, mapping it to "intranet" zone.173 /// B. If the path is not UNC path, then get the root drive,174 /// - if the drive is CDRom, mapping it to "Untrusted" zone175 /// - if the drive is Network, mapping it to "Intranet" zone176 /// - otherwise, mapping it to "MyComputer" zone.177 ///178 /// The above algorithm has two changes comparing to the behavior of "Zone.CreateFromUrl" I observed:179 /// (1) If a file downloaded from internet (ZoneId=3) is not on the local machine, "Zone.CreateFromUrl" won't respect the MOTW.180 /// I think it makes more sense for powershell to always check the MOTW first, even for files not on local box.181 /// (2) When it's a UNC path and is actually a loopback (\\127.0.0.1\c$\test.txt), "Zone.CreateFromUrl" returns "Internet", but182 /// the above algorithm changes it to be "MyComputer" because it's actually the same computer.183 /// </remarks>184 private static SecurityZone MapSecurityZone(string filePath)185 {186 // WSL introduces a new filesystem path to access the Linux filesystem from Windows, like '\\wsl$\ubuntu'.187 // If the given file path is such a special case, we consider it's in 'MyComputer' zone.188 if (filePath.StartsWith(Utils.WslRootPath, StringComparison.OrdinalIgnoreCase))189 {190 return SecurityZone.MyComputer;191 }192 193 SecurityZone reval = ReadFromZoneIdentifierDataStream(filePath);194 if (reval != SecurityZone.NoZone)195 {196 return reval;197 }198 199 // If it reaches here, then we either couldn't get the ZoneId information, or the ZoneId is invalid.200 // In this case, we try to determine the SecurityZone by analyzing the file path.201 Uri uri = new Uri(filePath);202 if (uri.IsUnc)203 {204 if (uri.IsLoopback)205 {206 return SecurityZone.MyComputer;207 }208 209 if (uri.HostNameType == UriHostNameType.IPv4 ||210 uri.HostNameType == UriHostNameType.IPv6)211 {212 return SecurityZone.Internet;213 }214 215 // This is also an observation of Zone.CreateFromUrl/Zone.SecurityZone. If the host name216 // has 'dot' in it, the file will be treated as in Internet security zone. Otherwise, it's217 // in Intranet security zone.218 string hostName = uri.Host;219 return hostName.Contains('.') ? SecurityZone.Internet : SecurityZone.Intranet;220 }221 222 string root = Path.GetPathRoot(filePath);223 DriveInfo drive = new DriveInfo(root);224 switch (drive.DriveType)225 {226 case DriveType.NoRootDirectory:227 case DriveType.Unknown:228 case DriveType.CDRom:229 return SecurityZone.Untrusted;230 case DriveType.Network:231 return SecurityZone.Intranet;232 default:233 return SecurityZone.MyComputer;234 }235 }236 237 /// <summary>238 /// Read the 'Zone.Identifier' alternate data stream to determin SecurityZone of the file.239 /// </summary>240 private static SecurityZone ReadFromZoneIdentifierDataStream(string filePath)241 {242 if (!AlternateDataStreamUtilities.TryCreateFileStream(filePath, "Zone.Identifier", FileMode.Open, FileAccess.Read, FileShare.Read, out var zoneDataStream))243 {244 return SecurityZone.NoZone;245 }246 247 // If we successfully get the zone data stream, try to read the ZoneId information248 using (StreamReader zoneDataReader = new StreamReader(zoneDataStream, Encoding.Default))249 {250 string line = null;251 bool zoneTransferMatched = false;252 253 // After a lot experiments with Zone.CreateFromUrl/Zone.SecurityZone, the way it handles the alternate254 // data stream 'Zone.Identifier' is observed as follows:255 // 1. Read content of the data stream line by line. Each line is trimmed.256 // 2. Try to match the current line with '^\[ZoneTransfer\]'.257 // - if matching, then do step #3 starting from the next line258 // - if not matching, then continue to do step #2 with the next line.259 // 3. Try to match the current line with '^ZoneId\s*=\s*(.*)'260 // - if matching, check if the ZoneId is valid. Then return the corresponding SecurityZone if valid, or 'NoZone' if invalid.261 // - if not matching, then continue to do step #3 with the next line.262 // 4. Reach EOF, then return 'NoZone'.263 while ((line = zoneDataReader.ReadLine()) != null)264 {265 line = line.Trim();266 if (!zoneTransferMatched)267 {268 zoneTransferMatched = Regex.IsMatch(line, @"^\[ZoneTransfer\]", RegexOptions.IgnoreCase);269 }270 else271 {272 Match match = Regex.Match(line, @"^ZoneId\s*=\s*(.*)", RegexOptions.IgnoreCase);273 if (!match.Success)274 {275 continue;276 }277 278 // Match found. Validate ZoneId value.279 string zoneIdRawValue = match.Groups[1].Value;280 match = Regex.Match(zoneIdRawValue, @"^[+-]?\d+", RegexOptions.IgnoreCase);281 if (!match.Success)282 {283 return SecurityZone.NoZone;284 }285 286 string zoneId = match.Groups[0].Value;287 SecurityZone result;288 return LanguagePrimitives.TryConvertTo(zoneId, out result) ? result : SecurityZone.NoZone;289 }290 }291 }292 293 return SecurityZone.NoZone;294 }295 296 #endregion Security297#endif298 299 #region Misc300 301 /// <summary>302 /// Facade for ManagementDateTimeConverter.ToDmtfDateTime(DateTime)303 /// </summary>304 internal static string ToDmtfDateTime(DateTime date)305 {306#if CORECLR307 // This implementation is copied from ManagementDateTimeConverter.ToDmtfDateTime(DateTime date) with a minor adjustment:308 // Use TimeZoneInfo.Local instead of TimeZone.CurrentTimeZone. System.TimeZone is not in CoreCLR.309 // According to MSDN, CurrentTimeZone property corresponds to the TimeZoneInfo.Local property, and310 // it's recommended to use TimeZoneInfo.Local whenever possible.311 312 const int maxsizeUtcDmtf = 999;313 string UtcString = string.Empty;314 // Fill up the UTC field in the DMTF date with the current zones UTC value315 TimeZoneInfo curZone = TimeZoneInfo.Local;316 TimeSpan tickOffset = curZone.GetUtcOffset(date);317 long OffsetMins = (tickOffset.Ticks / TimeSpan.TicksPerMinute);318 IFormatProvider frmInt32 = (IFormatProvider)CultureInfo.InvariantCulture.GetFormat(typeof(int));319 320 // If the offset is more than that what can be specified in DMTF format, then321 // convert the date to UniversalTime322 if (Math.Abs(OffsetMins) > maxsizeUtcDmtf)323 {324 date = date.ToUniversalTime();325 UtcString = "+000";326 }327 else328 if ((tickOffset.Ticks >= 0))329 {330 UtcString = "+" + ((tickOffset.Ticks / TimeSpan.TicksPerMinute)).ToString(frmInt32).PadLeft(3, '0');331 }332 else333 {334 string strTemp = OffsetMins.ToString(frmInt32);335 UtcString = "-" + strTemp.Substring(1, strTemp.Length - 1).PadLeft(3, '0');336 }337 338 string dmtfDateTime = date.Year.ToString(frmInt32).PadLeft(4, '0');339 340 dmtfDateTime += date.Month.ToString(frmInt32).PadLeft(2, '0');341 dmtfDateTime += date.Day.ToString(frmInt32).PadLeft(2, '0');342 dmtfDateTime += date.Hour.ToString(frmInt32).PadLeft(2, '0');343 dmtfDateTime += date.Minute.ToString(frmInt32).PadLeft(2, '0');344 dmtfDateTime += date.Second.ToString(frmInt32).PadLeft(2, '0');345 dmtfDateTime += ".";346 347 // Construct a DateTime with the precision to Second as same as the passed DateTime and so get348 // the ticks difference so that the microseconds can be calculated349 DateTime dtTemp = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, 0);350 Int64 microsec = ((date.Ticks - dtTemp.Ticks) * 1000) / TimeSpan.TicksPerMillisecond;351 352 // fill the microseconds field353 string strMicrosec = microsec.ToString((IFormatProvider)CultureInfo.InvariantCulture.GetFormat(typeof(Int64)));354 if (strMicrosec.Length > 6)355 {356 strMicrosec = strMicrosec.Substring(0, 6);357 }358 359 dmtfDateTime += strMicrosec.PadLeft(6, '0');360 // adding the UTC offset361 dmtfDateTime += UtcString;362 363 return dmtfDateTime;364#else365 return ManagementDateTimeConverter.ToDmtfDateTime(date);366#endif367 }368 369 #endregion Misc370 }371}372 