MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Concurrent;6using System.Collections.Generic;7using System.Collections.ObjectModel;8using System.Diagnostics.CodeAnalysis;9using System.Globalization;10using System.IO;11using System.Linq;12using System.Management.Automation.Configuration;13using System.Management.Automation.Internal;14using System.Management.Automation.Remoting;15using System.Management.Automation.Security;16using System.Numerics;17using System.Reflection;18using System.Runtime.InteropServices;19using System.Security;20#if !UNIX21using System.Security.Principal;22#endif23using System.Text;24using System.Threading;25using Microsoft.PowerShell.Commands;26using Microsoft.Win32;27 28using TypeTable = System.Management.Automation.Runspaces.TypeTable;29 30namespace System.Management.Automation31{32 /// <summary>33 /// Helper fns.34 /// </summary>35 internal static class Utils36 {37 /// <summary>38 /// Converts a given double value to BigInteger via Math.Round().39 /// </summary>40 /// <param name="d">The value to convert.</param>41 /// <returns>Returns a BigInteger value equivalent to the input value rounded to nearest integer.</returns>42 internal static BigInteger AsBigInt(this double d) => new BigInteger(Math.Round(d));43 44 internal static bool TryCast(BigInteger value, out byte b)45 {46 if (value < byte.MinValue || value > byte.MaxValue)47 {48 b = 0;49 return false;50 }51 52 b = (byte)value;53 return true;54 }55 56 internal static bool TryCast(BigInteger value, out sbyte sb)57 {58 if (value < sbyte.MinValue || value > sbyte.MaxValue)59 {60 sb = 0;61 return false;62 }63 64 sb = (sbyte)value;65 return true;66 }67 68 internal static bool TryCast(BigInteger value, out short s)69 {70 if (value < short.MinValue || value > short.MaxValue)71 {72 s = 0;73 return false;74 }75 76 s = (short)value;77 return true;78 }79 80 internal static bool TryCast(BigInteger value, out ushort us)81 {82 if (value < ushort.MinValue || value > ushort.MaxValue)83 {84 us = 0;85 return false;86 }87 88 us = (ushort)value;89 return true;90 }91 92 internal static bool TryCast(BigInteger value, out int i)93 {94 if (value < int.MinValue || value > int.MaxValue)95 {96 i = 0;97 return false;98 }99 100 i = (int)value;101 return true;102 }103 104 internal static bool TryCast(BigInteger value, out uint u)105 {106 if (value < uint.MinValue || value > uint.MaxValue)107 {108 u = 0;109 return false;110 }111 112 u = (uint)value;113 return true;114 }115 116 internal static bool TryCast(BigInteger value, out long l)117 {118 if (value < long.MinValue || value > long.MaxValue)119 {120 l = 0;121 return false;122 }123 124 l = (long)value;125 return true;126 }127 128 internal static bool TryCast(BigInteger value, out ulong ul)129 {130 if (value < ulong.MinValue || value > ulong.MaxValue)131 {132 ul = 0;133 return false;134 }135 136 ul = (ulong)value;137 return true;138 }139 140 internal static bool TryCast(BigInteger value, out decimal dm)141 {142 if (value < (BigInteger)decimal.MinValue || (BigInteger)decimal.MaxValue < value)143 {144 dm = 0;145 return false;146 }147 148 dm = (decimal)value;149 return true;150 }151 152 internal static bool TryCast(BigInteger value, out double db)153 {154 if (value < (BigInteger)double.MinValue || (BigInteger)double.MaxValue < value)155 {156 db = 0;157 return false;158 }159 160 db = (double)value;161 return true;162 }163 164 /// <summary>165 /// Parses a given string or ReadOnlySpan<char> to calculate its value as a binary number.166 /// Assumes input has already been sanitized and only contains zeroes (0) or ones (1).167 /// </summary>168 /// <param name="digits">Span or string of binary digits. Assumes all digits are either 1 or 0.</param>169 /// <param name="unsigned">170 /// Whether to treat the number as unsigned. When false, respects established conventions171 /// with sign bits for certain input string lengths.172 /// </param>173 /// <returns>Returns the value of the binary string as a BigInteger.</returns>174 internal static BigInteger ParseBinary(ReadOnlySpan<char> digits, bool unsigned)175 {176 if (!unsigned)177 {178 if (digits[0] == '0')179 {180 unsigned = true;181 }182 else183 {184 switch (digits.Length)185 {186 // Only accept sign bits at these lengths:187 case 8: // byte188 case 16: // short189 case 32: // int190 case 64: // long191 case 96: // decimal192 case int n when n >= 128: // BigInteger193 break;194 default:195 // If we do not flag these as unsigned, bigint assumes a sign bit for any (8 * n) string length196 unsigned = true;197 break;198 }199 }200 }201 202 // Only use heap allocation for very large numbers203 const int MaxStackAllocation = 512;204 205 // Calculate number of 8-bit bytes needed to hold the input, rounded up to next whole number.206 int outputByteCount = (digits.Length + 7) / 8;207 Span<byte> outputBytes = outputByteCount <= MaxStackAllocation ? stackalloc byte[outputByteCount] : new byte[outputByteCount];208 int outputByteIndex = outputBytes.Length - 1;209 210 // We need to be prepared for any partial leading bytes, (e.g., 010|00000011|00101100), or cases211 // where we only have less than 8 bits to work with from the beginning.212 //213 // Walk bytes right to left, stepping one whole byte at a time (if there are any whole bytes).214 int byteWalker;215 for (byteWalker = digits.Length - 1; byteWalker >= 7; byteWalker -= 8)216 {217 // Use bit shifts and binary-or to sum the values in each byte. These calculations will218 // create values higher than a single byte, but the higher bits will be stripped out when cast219 // to byte.220 //221 // The low bits are added in separately to allow us to strip the higher 'noise' bits before we222 // sum the values using binary-or.223 //224 // Simplified representation of logic: (byte)( (7)|(6)|(5)|(4) ) | ( ( (3)|(2)|(1)|(0) ) & 0b1111 )225 //226 // N.B.: This code has been tested against a straight for loop iterating through the byte, and in no227 // circumstance was it faster or more effective than this unrolled version.228 outputBytes[outputByteIndex--] =229 (byte)(230 ((digits[byteWalker - 7] << 7)231 | (digits[byteWalker - 6] << 6)232 | (digits[byteWalker - 5] << 5)233 | (digits[byteWalker - 4] << 4)234 )235 | (236 ((digits[byteWalker - 3] << 3)237 | (digits[byteWalker - 2] << 2)238 | (digits[byteWalker - 1] << 1)239 | (digits[byteWalker])240 ) & 0b1111241 )242 );243 }244 245 // With complete bytes parsed, byteWalker is either at the partial byte start index, or at -1246 if (byteWalker >= 0)247 {248 int currentByteValue = 0;249 for (int i = 0; i <= byteWalker; i++)250 {251 currentByteValue = (currentByteValue << 1) | (digits[i] - '0');252 }253 254 outputBytes[outputByteIndex] = (byte)currentByteValue;255 }256 257 return new BigInteger(outputBytes, isUnsigned: unsigned, isBigEndian: true);258 }259 260 // From System.Web.Util.HashCodeCombiner261 internal static int CombineHashCodes(int h1, int h2)262 {263 return unchecked(((h1 << 5) + h1) ^ h2);264 }265 266 internal static int CombineHashCodes(int h1, int h2, int h3)267 {268 return CombineHashCodes(CombineHashCodes(h1, h2), h3);269 }270 271 internal static int CombineHashCodes(int h1, int h2, int h3, int h4)272 {273 return CombineHashCodes(CombineHashCodes(h1, h2), CombineHashCodes(h3, h4));274 }275 276 internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5)277 {278 return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), h5);279 }280 281 internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6)282 {283 return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6));284 }285 286 internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7)287 {288 return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7));289 }290 291 internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8)292 {293 return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7, h8));294 }295 296 /// <summary>297 /// Allowed PowerShell Editions.298 /// </summary>299 internal static readonly string[] AllowedEditionValues = { "Desktop", "Core" };300 301 /// <summary>302 /// Helper fn to check byte[] arg for null.303 /// </summary>304 /// <param name="arg"> arg to check </param>305 /// <param name="argName"> name of the arg </param>306 /// <returns> Does not return a value.</returns>307 internal static void CheckKeyArg(byte[] arg, string argName)308 {309 if (arg == null)310 {311 throw PSTraceSource.NewArgumentNullException(argName);312 }313 //314 // we use AES algorithm which supports key315 // lengths of 128, 192 and 256 bits.316 // We throw ArgumentException if the key is317 // of any other length318 //319 else if (!((arg.Length == 16) ||320 (arg.Length == 24) ||321 (arg.Length == 32)))322 {323 throw PSTraceSource.NewArgumentException(argName, Serialization.InvalidKeyLength, argName);324 }325 }326 327 /// <summary>328 /// Helper fn to check arg for empty or null.329 /// Throws ArgumentNullException on either condition.330 /// </summary>331 /// <param name="arg"> arg to check </param>332 /// <param name="argName"> name of the arg </param>333 /// <returns> Does not return a value.</returns>334 internal static void CheckArgForNullOrEmpty(string arg, string argName)335 {336 if (arg == null)337 {338 throw PSTraceSource.NewArgumentNullException(argName);339 }340 else if (arg.Length == 0)341 {342 throw PSTraceSource.NewArgumentException(argName);343 }344 }345 346 /// <summary>347 /// Helper fn to check arg for null.348 /// Throws ArgumentNullException on either condition.349 /// </summary>350 /// <param name="arg"> arg to check </param>351 /// <param name="argName"> name of the arg </param>352 /// <returns> Does not return a value.</returns>353 internal static void CheckArgForNull(object arg, string argName)354 {355 if (arg == null)356 {357 throw PSTraceSource.NewArgumentNullException(argName);358 }359 }360 361 /// <summary>362 /// Helper fn to check arg for null.363 /// </summary>364 /// <param name="arg"> arg to check </param>365 /// <param name="argName"> name of the arg </param>366 /// <returns> Does not return a value.</returns>367 internal static void CheckSecureStringArg(SecureString arg, string argName)368 {369 if (arg == null)370 {371 throw PSTraceSource.NewArgumentNullException(argName);372 }373 }374 375 internal static string GetStringFromSecureString(SecureString ss)376 {377 IntPtr p = IntPtr.Zero;378 string s = null;379 380 try381 {382 p = Marshal.SecureStringToCoTaskMemUnicode(ss);383 s = Marshal.PtrToStringUni(p);384 }385 finally386 {387 if (p != IntPtr.Zero)388 {389 Marshal.ZeroFreeCoTaskMemUnicode(p);390 }391 }392 393 return s;394 }395 396 /// <summary>397 /// Gets TypeTable by querying the ExecutionContext stored in398 /// Thread-Local-Storage. This will return null if ExecutionContext399 /// is not available.400 /// </summary>401 /// <returns></returns>402 internal static TypeTable GetTypeTableFromExecutionContextTLS()403 {404 ExecutionContext ecFromTLS = Runspaces.LocalPipeline.GetExecutionContextFromTLS();405 if (ecFromTLS == null)406 {407 return null;408 }409 410 return ecFromTLS.TypeTable;411 }412 413#if !UNIX414 private static string s_pshome = null;415 416 /// <summary>417 /// Get the application base path of the shell from registry.418 /// </summary>419 internal static string GetApplicationBaseFromRegistry(string shellId)420 {421 bool wantPsHome = (object)shellId == (object)DefaultPowerShellShellID;422 if (wantPsHome && s_pshome != null)423 return s_pshome;424 425 string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" +426 PSVersionInfo.RegistryVersionKey + "\\" + RegistryStrings.MonadEngineKey;427 428 using (RegistryKey engineKey = Registry.LocalMachine.OpenSubKey(engineKeyPath))429 {430 if (engineKey != null)431 {432 var result = engineKey.GetValue(RegistryStrings.MonadEngine_ApplicationBase) as string;433 result = Environment.ExpandEnvironmentVariables(result);434 if (wantPsHome)435 Interlocked.CompareExchange(ref s_pshome, null, result);436 437 return result;438 }439 }440 441 return null;442 }443 444 private static string s_windowsPowerShellVersion = null;445 446 /// <summary>447 /// Get the Windows PowerShell version from registry.448 /// </summary>449 /// <returns>450 /// String of Windows PowerShell version from registry.451 /// </returns>452 internal static string GetWindowsPowerShellVersionFromRegistry()453 {454 if (!string.IsNullOrEmpty(InternalTestHooks.TestWindowsPowerShellVersionString))455 {456 return InternalTestHooks.TestWindowsPowerShellVersionString;457 }458 459 if (s_windowsPowerShellVersion != null)460 {461 return s_windowsPowerShellVersion;462 }463 464 string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" +465 PSVersionInfo.RegistryVersionKey + "\\" + RegistryStrings.MonadEngineKey;466 467 using (RegistryKey engineKey = Registry.LocalMachine.OpenSubKey(engineKeyPath))468 {469 if (engineKey != null)470 {471 s_windowsPowerShellVersion = engineKey.GetValue(RegistryStrings.MonadEngine_MonadVersion) as string;472 return s_windowsPowerShellVersion;473 }474 }475 476 return string.Empty;477 }478#endif479 480 internal static string DefaultPowerShellAppBase => GetApplicationBase(DefaultPowerShellShellID);481 482 internal static string GetApplicationBase(string shellId)483 {484 // Use the location of SMA.dll as the application base if it exists,485 // otherwise, use the base directory from `AppContext`.486 var baseDirectory = Path.GetDirectoryName(typeof(PSObject).Assembly.Location);487 if (string.IsNullOrEmpty(baseDirectory))488 {489 // Need to remove any trailing directory separator characters490 baseDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar);491 }492 493 return baseDirectory;494 }495 496 private static string[] s_productFolderDirectories;497 498 private static string[] GetProductFolderDirectories()499 {500 if (s_productFolderDirectories == null)501 {502 List<string> baseDirectories = new List<string>();503 504 // Retrieve the application base from the registry505 string appBase = Utils.DefaultPowerShellAppBase;506 if (!string.IsNullOrEmpty(appBase))507 {508 baseDirectories.Add(appBase);509 }510#if !UNIX511 // Now add the two variations of System32512 baseDirectories.Add(Environment.GetFolderPath(Environment.SpecialFolder.System));513 string systemX86 = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86);514 if (!string.IsNullOrEmpty(systemX86))515 {516 baseDirectories.Add(systemX86);517 }518#endif519 Interlocked.CompareExchange(ref s_productFolderDirectories, baseDirectories.ToArray(), null);520 }521 522 return s_productFolderDirectories;523 }524 525 /// <summary>526 /// Checks if the filePath represents a file under product folder527 /// ie., PowerShell ApplicationBase or $env:windir\system32 or528 /// $env:windir\syswow64.529 /// </summary>530 /// <returns>531 /// true: if the filePath is under product folder532 /// false: otherwise533 /// </returns>534 internal static bool IsUnderProductFolder(string filePath)535 {536 FileInfo fileInfo = new FileInfo(filePath);537 string filename = fileInfo.FullName;538 539 var productFolderDirectories = GetProductFolderDirectories();540 for (int i = 0; i < productFolderDirectories.Length; i++)541 {542 string applicationBase = productFolderDirectories[i];543 if (filename.StartsWith(applicationBase, StringComparison.OrdinalIgnoreCase))544 return true;545 }546 547 return false;548 }549 550 /// <summary>551 /// Checks if the current process is using WOW.552 /// </summary>553 internal static bool IsRunningFromSysWOW64()554 {555 return DefaultPowerShellAppBase.Contains("SysWOW64");556 }557 558 /// <summary>559 /// Checks if host machine is WinPE.560 /// </summary>561 internal static bool IsWinPEHost()562 {563#if !UNIX564 RegistryKey winPEKey = null;565 566 try567 {568 // The existence of the following registry confirms that the host machine is a WinPE569 // HKLM\System\CurrentControlSet\Control\MiniNT570 winPEKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\MiniNT");571 572 return winPEKey != null;573 }574 catch (ArgumentException) { }575 catch (SecurityException) { }576 catch (ObjectDisposedException) { }577 finally578 {579 winPEKey?.Dispose();580 }581#endif582 return false;583 }584 585 #region Versioning related methods586 587 /// <summary>588 /// Returns current major version of monad ( that is running ) in a string589 /// format.590 /// </summary>591 /// <returns>String.</returns>592 /// <remarks>593 /// Cannot return a Version object as minor number is a requirement for594 /// version object.595 /// </remarks>596 internal static string GetCurrentMajorVersion()597 {598 return PSVersionInfo.PSVersion.Major.ToString(CultureInfo.InvariantCulture);599 }600 601 /// <summary>602 /// Coverts a string to version format.603 /// If the string is of the format x (ie., no dots), then ".0" is appended604 /// to the string.605 /// Version.TryParse will be used to convert the string to a Version606 /// object.607 /// </summary>608 /// <param name="versionString">String representing version.</param>609 /// <returns>A Version Object.</returns>610 internal static Version StringToVersion(string versionString)611 {612 // max of 1 dot is allowed in version613 if (string.IsNullOrEmpty(versionString))614 {615 return null;616 }617 618 int dotCount = 0;619 foreach (char c in versionString)620 {621 if (c == '.')622 {623 dotCount++;624 if (dotCount > 1)625 {626 break;627 }628 }629 }630 // Version.TryParse expects the string to be in format: major.minor[.build[.revision]]631 if (dotCount == 0)632 {633 versionString += ".0";634 }635 636 Version result = null;637 if (Version.TryParse(versionString, out result))638 {639 return result;640 }641 642 return null;643 }644 645 /// <summary>646 /// Checks whether current PowerShell session supports edition specified647 /// by checkEdition.648 /// </summary>649 /// <param name="checkEdition">Edition to check.</param>650 /// <returns>True if supported, false otherwise.</returns>651 internal static bool IsPSEditionSupported(string checkEdition)652 {653 return PSVersionInfo.PSEditionValue.Equals(checkEdition, StringComparison.OrdinalIgnoreCase);654 }655 656 /// <summary>657 /// Check whether the current PowerShell session supports any of the specified editions.658 /// </summary>659 /// <param name="editions">The PowerShell editions to check compatibility with.</param>660 /// <returns>True if the edition is supported by this runtime, false otherwise.</returns>661 internal static bool IsPSEditionSupported(IEnumerable<string> editions)662 {663 string currentPSEdition = PSVersionInfo.PSEditionValue;664 foreach (string edition in editions)665 {666 if (currentPSEdition.Equals(edition, StringComparison.OrdinalIgnoreCase))667 {668 return true;669 }670 }671 672 return false;673 }674 675 /// <summary>676 /// Checks whether the specified edition value is allowed.677 /// </summary>678 /// <param name="editionValue">Edition value to check.</param>679 /// <returns>True if allowed, false otherwise.</returns>680 internal static bool IsValidPSEditionValue(string editionValue)681 {682 return AllowedEditionValues.Contains(editionValue, StringComparer.OrdinalIgnoreCase);683 }684 685 #endregion686 687 /// <summary>688 /// String representing the Default shellID.689 /// </summary>690 internal const string DefaultPowerShellShellID = "Microsoft.PowerShell";691 692 /// <summary>693 /// This is used to construct the profile path.694 /// </summary>695 internal const string ProductNameForDirectory = "PowerShell";696 697 /// <summary>698 /// WSL introduces a new filesystem path to access the Linux filesystem from Windows, like '\\wsl$\ubuntu'.699 /// </summary>700 internal const string WslRootPath = @"\\wsl$";701 702 /// <summary>703 /// The subdirectory of module paths704 /// e.g. ~\Documents\WindowsPowerShell\Modules and %ProgramFiles%\WindowsPowerShell\Modules.705 /// </summary>706 internal static readonly string ModuleDirectory = Path.Combine(ProductNameForDirectory, "Modules");707 708 internal static readonly ConfigScope[] SystemWideOnlyConfig = new[] { ConfigScope.AllUsers };709 internal static readonly ConfigScope[] CurrentUserOnlyConfig = new[] { ConfigScope.CurrentUser };710 internal static readonly ConfigScope[] SystemWideThenCurrentUserConfig = new[] { ConfigScope.AllUsers, ConfigScope.CurrentUser };711 internal static readonly ConfigScope[] CurrentUserThenSystemWideConfig = new[] { ConfigScope.CurrentUser, ConfigScope.AllUsers };712 713 internal static T GetPolicySetting<T>(ConfigScope[] preferenceOrder) where T : PolicyBase, new()714 {715 T policy = null;716#if !UNIX717 // On Windows, group policy settings from registry take precedence.718 // If the requested policy is not defined in registry, we query the configuration file.719 policy = GetPolicySettingFromGPO<T>(preferenceOrder);720 if (policy != null) { return policy; }721#endif722 policy = GetPolicySettingFromConfigFile<T>(preferenceOrder);723 return policy;724 }725 726 private static readonly ConcurrentDictionary<ConfigScope, PowerShellPolicies> s_cachedPoliciesFromConfigFile =727 new ConcurrentDictionary<ConfigScope, PowerShellPolicies>();728 729 /// <summary>730 /// Get a specific kind of policy setting from the configuration file.731 /// </summary>732 private static T GetPolicySettingFromConfigFile<T>(ConfigScope[] preferenceOrder) where T : PolicyBase, new()733 {734 foreach (ConfigScope scope in preferenceOrder)735 {736 PowerShellPolicies policies;737 if (InternalTestHooks.BypassGroupPolicyCaching)738 {739 policies = PowerShellConfig.Instance.GetPowerShellPolicies(scope);740 }741 else if (!s_cachedPoliciesFromConfigFile.TryGetValue(scope, out policies))742 {743 // Use lock here to reduce the contention on accessing the configuration file744 lock (s_cachedPoliciesFromConfigFile)745 {746 policies = s_cachedPoliciesFromConfigFile.GetOrAdd(scope, PowerShellConfig.Instance.GetPowerShellPolicies);747 }748 }749 750 if (policies != null)751 {752 PolicyBase result = null;753 switch (typeof(T).Name)754 {755 case nameof(ScriptExecution):756 result = policies.ScriptExecution;757 break;758 case nameof(ScriptBlockLogging):759 result = policies.ScriptBlockLogging;760 break;761 case nameof(ModuleLogging):762 result = policies.ModuleLogging;763 break;764 case nameof(ProtectedEventLogging):765 result = policies.ProtectedEventLogging;766 break;767 case nameof(Transcription):768 result = policies.Transcription;769 break;770 case nameof(UpdatableHelp):771 result = policies.UpdatableHelp;772 break;773 case nameof(ConsoleSessionConfiguration):774 result = policies.ConsoleSessionConfiguration;775 break;776 default:777 Diagnostics.Assert(false, "Should be unreachable code. Update this switch block when new PowerShell policy types are added.");778 break;779 }780 781 if (result != null) { return (T)result; }782 }783 }784 785 return null;786 }787 788#if !UNIX789 private static readonly Dictionary<string, string> GroupPolicyKeys = new Dictionary<string, string>790 {791 {nameof(ScriptExecution), @"Software\Policies\Microsoft\PowerShellCore"},792 {nameof(ScriptBlockLogging), @"Software\Policies\Microsoft\PowerShellCore\ScriptBlockLogging"},793 {nameof(ModuleLogging), @"Software\Policies\Microsoft\PowerShellCore\ModuleLogging"},794 {nameof(ProtectedEventLogging), @"Software\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging"},795 {nameof(Transcription), @"Software\Policies\Microsoft\PowerShellCore\Transcription"},796 {nameof(UpdatableHelp), @"Software\Policies\Microsoft\PowerShellCore\UpdatableHelp"},797 {nameof(ConsoleSessionConfiguration), @"Software\Policies\Microsoft\PowerShellCore\ConsoleSessionConfiguration"}798 };799 800 private static readonly Dictionary<string, string> WindowsPowershellGroupPolicyKeys = new Dictionary<string, string>801 {802 { nameof(ScriptExecution), @"Software\Policies\Microsoft\Windows\PowerShell" },803 { nameof(ScriptBlockLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" },804 { nameof(ModuleLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging" },805 { nameof(Transcription), @"Software\Policies\Microsoft\Windows\PowerShell\Transcription" },806 { nameof(UpdatableHelp), @"Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp" },807 };808 809 private const string PolicySettingFallbackKey = "UseWindowsPowerShellPolicySetting";810 811 private static readonly ConcurrentDictionary<ConfigScope, ConcurrentDictionary<string, PolicyBase>> s_cachedPoliciesFromRegistry =812 new ConcurrentDictionary<ConfigScope, ConcurrentDictionary<string, PolicyBase>>();813 814 private static readonly Func<ConfigScope, ConcurrentDictionary<string, PolicyBase>> s_subCacheCreationDelegate =815 key => new ConcurrentDictionary<string, PolicyBase>(StringComparer.Ordinal);816 817 /// <summary>818 /// Read policy settings from a registry key into a policy object.819 /// </summary>820 /// <param name="instance">Policy object that will be filled with values from registry.</param>821 /// <param name="instanceType">Type of policy object used.</param>822 /// <param name="gpoKey">Registry key that has policy settings.</param>823 /// <returns>True if any property was successfully set on the policy object.</returns>824 private static bool TrySetPolicySettingsFromRegistryKey(object instance, Type instanceType, RegistryKey gpoKey)825 {826 var properties = instanceType.GetProperties(BindingFlags.Instance | BindingFlags.Public);827 bool isAnyPropertySet = false;828 829 string[] valueNames = gpoKey.GetValueNames();830 string[] subKeyNames = gpoKey.GetSubKeyNames();831 var valueNameSet = valueNames.Length > 0 ? new HashSet<string>(valueNames, StringComparer.OrdinalIgnoreCase) : null;832 var subKeyNameSet = subKeyNames.Length > 0 ? new HashSet<string>(subKeyNames, StringComparer.OrdinalIgnoreCase) : null;833 834 // If there are any values or subkeys in the registry key - read them into the policy instance object835 if ((valueNameSet != null) || (subKeyNameSet != null))836 {837 foreach (var property in properties)838 {839 string settingName = property.Name;840 object rawRegistryValue = null;841 842 // Get the raw value from registry.843 if (valueNameSet != null && valueNameSet.Contains(settingName))844 {845 rawRegistryValue = gpoKey.GetValue(settingName);846 }847 else if (subKeyNameSet != null && subKeyNameSet.Contains(settingName))848 {849 using (RegistryKey subKey = gpoKey.OpenSubKey(settingName))850 {851 if (subKey != null)852 {853 rawRegistryValue = subKey.GetValueNames();854 }855 }856 }857 858 // Get the actual property value based on the property type.859 // If the final property value is not null, then set the property.860 if (rawRegistryValue != null)861 {862 Type propertyType = property.PropertyType;863 object propertyValue = null;864 865 switch (propertyType)866 {867 case var _ when propertyType == typeof(bool?):868 if (rawRegistryValue is int rawIntValue)869 {870 if (rawIntValue == 1)871 {872 propertyValue = true;873 }874 else if (rawIntValue == 0)875 {876 propertyValue = false;877 }878 }879 880 break;881 case var _ when propertyType == typeof(string):882 if (rawRegistryValue is string rawStringValue)883 {884 propertyValue = rawStringValue;885 }886 887 break;888 case var _ when propertyType == typeof(string[]):889 if (rawRegistryValue is string[] rawStringArrayValue)890 {891 propertyValue = rawStringArrayValue;892 }893 else if (rawRegistryValue is string stringValue)894 {895 propertyValue = new string[] { stringValue };896 }897 898 break;899 default:900 throw System.Management.Automation.Interpreter.Assert.Unreachable;901 }902 903 // Set the property if the value is not null904 if (propertyValue != null)905 {906 property.SetValue(instance, propertyValue);907 isAnyPropertySet = true;908 }909 }910 }911 }912 913 return isAnyPropertySet;914 }915 916 /// <summary>917 /// The implementation of fetching a specific kind of policy setting from the given configuration scope.918 /// </summary>919 private static T GetPolicySettingFromGPOImpl<T>(ConfigScope scope) where T : PolicyBase, new()920 {921 Type tType = typeof(T);922 // SystemWide scope means 'LocalMachine' root key when query from registry923 RegistryKey rootKey = (scope == ConfigScope.AllUsers) ? Registry.LocalMachine : Registry.CurrentUser;924 925 GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath);926 Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name));927 928 using (RegistryKey gpoKey = rootKey.OpenSubKey(gpoKeyPath))929 {930 // If the corresponding GPO key doesn't exist, return null931 if (gpoKey == null) { return null; }932 933 // The corresponding GPO key exists, then create an instance of T934 // and populate its properties with the settings935 object tInstance = Activator.CreateInstance(tType, nonPublic: true);936 bool isAnyPropertySet = false;937 938 // if PolicySettingFallbackKey is Not set - use PowerShell Core policy reg key939 if ((int)gpoKey.GetValue(PolicySettingFallbackKey, 0) == 0)940 {941 isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, gpoKey);942 }943 else944 {945 // when PolicySettingFallbackKey flag is set (REG_DWORD "1") use Windows PS policy reg key946 WindowsPowershellGroupPolicyKeys.TryGetValue(tType.Name, out string winPowershellGpoKeyPath);947 Diagnostics.Assert(winPowershellGpoKeyPath != null, StringUtil.Format("The Windows PS GPO registry key path should be pre-defined for {0}", tType.Name));948 using (RegistryKey winPowershellGpoKey = rootKey.OpenSubKey(winPowershellGpoKeyPath))949 {950 // If the corresponding Windows PS GPO key doesn't exist, return null951 if (winPowershellGpoKey == null) { return null; }952 isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, winPowershellGpoKey);953 }954 }955 956 // If no property is set, then we consider this policy as undefined957 return isAnyPropertySet ? (T)tInstance : null;958 }959 }960 961 /// <summary>962 /// Get a specific kind of policy setting from the group policy registry key.963 /// </summary>964 private static T GetPolicySettingFromGPO<T>(ConfigScope[] preferenceOrder) where T : PolicyBase, new()965 {966 PolicyBase policy = null;967 string policyName = typeof(T).Name;968 969 foreach (ConfigScope scope in preferenceOrder)970 {971 if (InternalTestHooks.BypassGroupPolicyCaching)972 {973 policy = GetPolicySettingFromGPOImpl<T>(scope);974 }975 else976 {977 var subordinateCache = s_cachedPoliciesFromRegistry.GetOrAdd(scope, s_subCacheCreationDelegate);978 if (!subordinateCache.TryGetValue(policyName, out policy))979 {980 policy = subordinateCache.GetOrAdd(policyName, key => GetPolicySettingFromGPOImpl<T>(scope));981 }982 }983 984 if (policy != null) { return (T)policy; }985 }986 987 return null;988 }989#endif990 991 /// <summary>992 /// Scheduled job module name.993 /// </summary>994 internal const string ScheduledJobModuleName = "PSScheduledJob";995 996 internal static void EnsureModuleLoaded(string module, ExecutionContext context)997 {998 if (context != null && !context.AutoLoadingModuleInProgress.Contains(module))999 {1000 List<PSModuleInfo> loadedModules = context.Modules.GetModules(new string[] { module }, false);1001 1002 if ((loadedModules == null) || (loadedModules.Count == 0))1003 {1004 CommandInfo commandInfo = new CmdletInfo("Import-Module", typeof(Microsoft.PowerShell.Commands.ImportModuleCommand),1005 null, null, context);1006 var importModuleCommand = new System.Management.Automation.Runspaces.Command(commandInfo);1007 1008 context.AutoLoadingModuleInProgress.Add(module);1009 1010 PowerShell ps = null;1011 1012 try1013 {1014 ps = PowerShell.Create(RunspaceMode.CurrentRunspace)1015 .AddCommand(importModuleCommand)1016 .AddParameter("Name", module)1017 .AddParameter("Scope", StringLiterals.Global)1018 .AddParameter("ErrorAction", ActionPreference.Ignore)1019 .AddParameter("WarningAction", ActionPreference.Ignore)1020 .AddParameter("InformationAction", ActionPreference.Ignore)1021 .AddParameter("Verbose", false)1022 .AddParameter("Debug", false)1023 .AddParameter("PassThru");1024 1025 ps.Invoke<PSModuleInfo>();1026 }1027 catch (Exception)1028 {1029 // Call-out to user code, catch-all OK1030 }1031 finally1032 {1033 context.AutoLoadingModuleInProgress.Remove(module);1034 ps?.Dispose();1035 }1036 }1037 }1038 }1039 1040 /// <summary>1041 /// Returns modules (either loaded or in available) that match pattern <paramref name="module"/>.1042 /// Uses Get-Module -ListAvailable cmdlet.1043 /// </summary>1044 /// <param name="module"></param>1045 /// <param name="context"></param>1046 /// <returns>1047 /// List of PSModuleInfo's or Null.1048 /// </returns>1049 internal static List<PSModuleInfo> GetModules(string module, ExecutionContext context)1050 {1051 // first look in the loaded modules and then append the modules from gmo -Listavailable1052 // Reason: gmo -li looks only the PSModulepath. There may be cases where a module1053 // is imported directly from a path (that is not in PSModulePath).1054 List<PSModuleInfo> result = context.Modules.GetModules(new string[] { module }, false);1055 1056 CommandInfo commandInfo = new CmdletInfo("Get-Module", typeof(Microsoft.PowerShell.Commands.GetModuleCommand),1057 null, null, context);1058 var getModuleCommand = new System.Management.Automation.Runspaces.Command(commandInfo);1059 1060 PowerShell ps = null;1061 try1062 {1063 ps = PowerShell.Create(RunspaceMode.CurrentRunspace)1064 .AddCommand(getModuleCommand)1065 .AddParameter("Name", module)1066 .AddParameter("ErrorAction", ActionPreference.Ignore)1067 .AddParameter("WarningAction", ActionPreference.Ignore)1068 .AddParameter("Verbose", false)1069 .AddParameter("Debug", false)1070 .AddParameter("ListAvailable");1071 1072 Collection<PSModuleInfo> gmoOutPut = ps.Invoke<PSModuleInfo>();1073 if (gmoOutPut != null)1074 {1075 if (result == null)1076 {1077 result = gmoOutPut.ToList<PSModuleInfo>();1078 }1079 else1080 {1081 result.AddRange(gmoOutPut);1082 }1083 }1084 }1085 catch (Exception)1086 {1087 // Call-out to user code, catch-all OK1088 }1089 finally1090 {1091 ps?.Dispose();1092 }1093 1094 return result;1095 }1096 1097 /// <summary>1098 /// Returns modules (either loaded or in available) that match FullyQualifiedName <paramref name="fullyQualifiedName"/>.1099 /// Uses Get-Module -ListAvailable cmdlet.1100 /// </summary>1101 /// <param name="fullyQualifiedName"></param>1102 /// <param name="context"></param>1103 /// <returns>1104 /// List of PSModuleInfo's or Null.1105 /// </returns>1106 internal static List<PSModuleInfo> GetModules(ModuleSpecification fullyQualifiedName, ExecutionContext context)1107 {1108 // first look in the loaded modules and then append the modules from gmo -Listavailable1109 // Reason: gmo -li looks only the PSModulepath. There may be cases where a module1110 // is imported directly from a path (that is not in PSModulePath).1111 List<PSModuleInfo> result = context.Modules.GetModules(new[] { fullyQualifiedName }, false);1112 CommandInfo commandInfo = new CmdletInfo("Get-Module", typeof(GetModuleCommand),1113 null, null, context);1114 var getModuleCommand = new Runspaces.Command(commandInfo);1115 1116 PowerShell ps = null;1117 try1118 {1119 ps = PowerShell.Create(RunspaceMode.CurrentRunspace)1120 .AddCommand(getModuleCommand)1121 .AddParameter("FullyQualifiedName", fullyQualifiedName)1122 .AddParameter("ErrorAction", ActionPreference.Ignore)1123 .AddParameter("WarningAction", ActionPreference.Ignore)1124 .AddParameter("InformationAction", ActionPreference.Ignore)1125 .AddParameter("Verbose", false)1126 .AddParameter("Debug", false)1127 .AddParameter("ListAvailable");1128 1129 Collection<PSModuleInfo> gmoOutput = ps.Invoke<PSModuleInfo>();1130 if (gmoOutput != null)1131 {1132 if (result == null)1133 {1134 result = gmoOutput.ToList();1135 }1136 else1137 {1138 // append to result1139 result.AddRange(gmoOutput);1140 }1141 }1142 }1143 catch (Exception)1144 {1145 // Call-out to user code, catch-all OK1146 }1147 finally1148 {1149 ps?.Dispose();1150 }1151 1152 return result;1153 }1154 1155#if !UNIX1156 private static bool TryGetWindowsCurrentIdentity(out WindowsIdentity currentIdentity)1157 {1158 try1159 {1160 currentIdentity = WindowsIdentity.GetCurrent();1161 }1162 catch (SecurityException)1163 {1164 currentIdentity = null;1165 }1166 1167 return (currentIdentity != null);1168 }1169 1170 /// <summary>1171 /// Gets the current impersonating Windows identity, if any.1172 /// </summary>1173 /// <param name="impersonatedIdentity">Current impersonated Windows identity or null.</param>1174 /// <returns>True if current identity is impersonated.</returns>1175 internal static bool TryGetWindowsImpersonatedIdentity(out WindowsIdentity impersonatedIdentity)1176 {1177 WindowsIdentity currentIdentity;1178 if (TryGetWindowsCurrentIdentity(out currentIdentity) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation))1179 {1180 impersonatedIdentity = currentIdentity;1181 return true;1182 }1183 1184 impersonatedIdentity = null;1185 return false;1186 }1187#endif1188 1189 internal static bool IsAdministrator()1190 {1191 // Porting note: only Windows supports the SecurityPrincipal API of .NET. Due to1192 // advanced privilege models, the correct approach on Unix is to assume the user has1193 // permissions, attempt the task, and error gracefully if the task fails due to1194 // permissions. To fit into PowerShell's existing model of preemptively checking1195 // permissions (which cannot be assumed on Unix), we "assume" the user is an1196 // administrator by returning true, thus nullifying this check on Unix.1197#if UNIX1198 return true;1199#else1200 WindowsIdentity currentIdentity;