Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
ClrFacade.cs372 linesDownload Raw Back to utils
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