Files
PowerShell-PowerShell/src/System.Management.Automation/engine/Utils.cs
T

1828 lines
72 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Management.Automation.Configuration;
using System.Management.Automation.Internal;
using System.Management.Automation.Remoting;
using System.Management.Automation.Security;
using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
#if !UNIX
using System.Security.Principal;
#endif
using System.Text;
using System.Threading;
using Microsoft.PowerShell.Commands;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using TypeTable = System.Management.Automation.Runspaces.TypeTable;
namespace System.Management.Automation
{
/// <summary>
/// Helper fns.
/// </summary>
internal static class Utils
{
/// <summary>
/// Converts a given double value to BigInteger via Math.Round().
/// </summary>
/// <param name="d">The value to convert.</param>
/// <returns>Returns a BigInteger value equivalent to the input value rounded to nearest integer.</returns>
internal static BigInteger AsBigInt(this double d) => new BigInteger(Math.Round(d));
internal static bool TryCast(BigInteger value, out byte b)
{
if (value < byte.MinValue || value > byte.MaxValue)
{
b = 0;
return false;
}
b = (byte)value;
return true;
}
internal static bool TryCast(BigInteger value, out sbyte sb)
{
if (value < sbyte.MinValue || value > sbyte.MaxValue)
{
sb = 0;
return false;
}
sb = (sbyte)value;
return true;
}
internal static bool TryCast(BigInteger value, out short s)
{
if (value < short.MinValue || value > short.MaxValue)
{
s = 0;
return false;
}
s = (short)value;
return true;
}
internal static bool TryCast(BigInteger value, out ushort us)
{
if (value < ushort.MinValue || value > ushort.MaxValue)
{
us = 0;
return false;
}
us = (ushort)value;
return true;
}
internal static bool TryCast(BigInteger value, out int i)
{
if (value < int.MinValue || value > int.MaxValue)
{
i = 0;
return false;
}
i = (int)value;
return true;
}
internal static bool TryCast(BigInteger value, out uint u)
{
if (value < uint.MinValue || value > uint.MaxValue)
{
u = 0;
return false;
}
u = (uint)value;
return true;
}
internal static bool TryCast(BigInteger value, out long l)
{
if (value < long.MinValue || value > long.MaxValue)
{
l = 0;
return false;
}
l = (long)value;
return true;
}
internal static bool TryCast(BigInteger value, out ulong ul)
{
if (value < ulong.MinValue || value > ulong.MaxValue)
{
ul = 0;
return false;
}
ul = (ulong)value;
return true;
}
internal static bool TryCast(BigInteger value, out decimal dm)
{
if (value < (BigInteger)decimal.MinValue || (BigInteger)decimal.MaxValue < value)
{
dm = 0;
return false;
}
dm = (decimal)value;
return true;
}
internal static bool TryCast(BigInteger value, out double db)
{
if (value < (BigInteger)double.MinValue || (BigInteger)double.MaxValue < value)
{
db = 0;
return false;
}
db = (double)value;
return true;
}
/// <summary>
/// Parses a given string or ReadOnlySpan&lt;char&gt; to calculate its value as a binary number.
/// Assumes input has already been sanitized and only contains zeroes (0) or ones (1).
/// </summary>
/// <param name="digits">Span or string of binary digits. Assumes all digits are either 1 or 0.</param>
/// <param name="unsigned">
/// Whether to treat the number as unsigned. When false, respects established conventions
/// with sign bits for certain input string lengths.
/// </param>
/// <returns>Returns the value of the binary string as a BigInteger.</returns>
internal static BigInteger ParseBinary(ReadOnlySpan<char> digits, bool unsigned)
{
if (!unsigned)
{
if (digits[0] == '0')
{
unsigned = true;
}
else
{
switch (digits.Length)
{
// Only accept sign bits at these lengths:
case 8: // byte
case 16: // short
case 32: // int
case 64: // long
case 96: // decimal
case int n when n >= 128: // BigInteger
break;
default:
// If we do not flag these as unsigned, bigint assumes a sign bit for any (8 * n) string length
unsigned = true;
break;
}
}
}
// Only use heap allocation for very large numbers
const int MaxStackAllocation = 512;
// Calculate number of 8-bit bytes needed to hold the input, rounded up to next whole number.
int outputByteCount = (digits.Length + 7) / 8;
Span<byte> outputBytes = outputByteCount <= MaxStackAllocation ? stackalloc byte[outputByteCount] : new byte[outputByteCount];
int outputByteIndex = outputBytes.Length - 1;
// We need to be prepared for any partial leading bytes, (e.g., 010|00000011|00101100), or cases
// where we only have less than 8 bits to work with from the beginning.
//
// Walk bytes right to left, stepping one whole byte at a time (if there are any whole bytes).
int byteWalker;
for (byteWalker = digits.Length - 1; byteWalker >= 7; byteWalker -= 8)
{
// Use bit shifts and binary-or to sum the values in each byte. These calculations will
// create values higher than a single byte, but the higher bits will be stripped out when cast
// to byte.
//
// The low bits are added in separately to allow us to strip the higher 'noise' bits before we
// sum the values using binary-or.
//
// Simplified representation of logic: (byte)( (7)|(6)|(5)|(4) ) | ( ( (3)|(2)|(1)|(0) ) & 0b1111 )
//
// N.B.: This code has been tested against a straight for loop iterating through the byte, and in no
// circumstance was it faster or more effective than this unrolled version.
outputBytes[outputByteIndex--] =
(byte)(
((digits[byteWalker - 7] << 7)
| (digits[byteWalker - 6] << 6)
| (digits[byteWalker - 5] << 5)
| (digits[byteWalker - 4] << 4)
)
| (
((digits[byteWalker - 3] << 3)
| (digits[byteWalker - 2] << 2)
| (digits[byteWalker - 1] << 1)
| (digits[byteWalker])
) & 0b1111
)
);
}
// With complete bytes parsed, byteWalker is either at the partial byte start index, or at -1
if (byteWalker >= 0)
{
int currentByteValue = 0;
for (int i = 0; i <= byteWalker; i++)
{
currentByteValue = (currentByteValue << 1) | (digits[i] - '0');
}
outputBytes[outputByteIndex] = (byte)currentByteValue;
}
return new BigInteger(outputBytes, isUnsigned: unsigned, isBigEndian: true);
}
// From System.Web.Util.HashCodeCombiner
internal static int CombineHashCodes(int h1, int h2)
{
return unchecked(((h1 << 5) + h1) ^ h2);
}
internal static int CombineHashCodes(int h1, int h2, int h3)
{
return CombineHashCodes(CombineHashCodes(h1, h2), h3);
}
internal static int CombineHashCodes(int h1, int h2, int h3, int h4)
{
return CombineHashCodes(CombineHashCodes(h1, h2), CombineHashCodes(h3, h4));
}
internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5)
{
return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), h5);
}
internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6)
{
return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6));
}
internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7)
{
return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7));
}
internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8)
{
return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7, h8));
}
/// <summary>
/// Allowed PowerShell Editions.
/// </summary>
internal static readonly string[] AllowedEditionValues = { "Desktop", "Core" };
/// <summary>
/// Helper fn to check byte[] arg for null.
/// </summary>
///<param name="arg"> arg to check </param>
///<param name="argName"> name of the arg </param>
///<returns> Does not return a value.</returns>
internal static void CheckKeyArg(byte[] arg, string argName)
{
if (arg == null)
{
throw PSTraceSource.NewArgumentNullException(argName);
}
//
// we use AES algorithm which supports key
// lengths of 128, 192 and 256 bits.
// We throw ArgumentException if the key is
// of any other length
//
else if (!((arg.Length == 16) ||
(arg.Length == 24) ||
(arg.Length == 32)))
{
throw PSTraceSource.NewArgumentException(argName, Serialization.InvalidKeyLength, argName);
}
}
/// <summary>
/// Helper fn to check arg for empty or null.
/// Throws ArgumentNullException on either condition.
/// </summary>
///<param name="arg"> arg to check </param>
///<param name="argName"> name of the arg </param>
///<returns> Does not return a value.</returns>
internal static void CheckArgForNullOrEmpty(string arg, string argName)
{
if (arg == null)
{
throw PSTraceSource.NewArgumentNullException(argName);
}
else if (arg.Length == 0)
{
throw PSTraceSource.NewArgumentException(argName);
}
}
/// <summary>
/// Helper fn to check arg for null.
/// Throws ArgumentNullException on either condition.
/// </summary>
///<param name="arg"> arg to check </param>
///<param name="argName"> name of the arg </param>
///<returns> Does not return a value.</returns>
internal static void CheckArgForNull(object arg, string argName)
{
if (arg == null)
{
throw PSTraceSource.NewArgumentNullException(argName);
}
}
/// <summary>
/// Helper fn to check arg for null.
/// </summary>
///<param name="arg"> arg to check </param>
///<param name="argName"> name of the arg </param>
///<returns> Does not return a value.</returns>
internal static void CheckSecureStringArg(SecureString arg, string argName)
{
if (arg == null)
{
throw PSTraceSource.NewArgumentNullException(argName);
}
}
[ArchitectureSensitive]
internal static string GetStringFromSecureString(SecureString ss)
{
IntPtr p = IntPtr.Zero;
string s = null;
try
{
p = Marshal.SecureStringToCoTaskMemUnicode(ss);
s = Marshal.PtrToStringUni(p);
}
finally
{
if (p != IntPtr.Zero)
{
Marshal.ZeroFreeCoTaskMemUnicode(p);
}
}
return s;
}
/// <summary>
/// Gets TypeTable by querying the ExecutionContext stored in
/// Thread-Local-Storage. This will return null if ExecutionContext
/// is not available.
/// </summary>
/// <returns></returns>
internal static TypeTable GetTypeTableFromExecutionContextTLS()
{
ExecutionContext ecFromTLS = Runspaces.LocalPipeline.GetExecutionContextFromTLS();
if (ecFromTLS == null)
{
return null;
}
return ecFromTLS.TypeTable;
}
#if !UNIX
private static string s_pshome = null;
/// <summary>
/// Get the application base path of the shell from registry.
/// </summary>
internal static string GetApplicationBaseFromRegistry(string shellId)
{
bool wantPsHome = (object)shellId == (object)DefaultPowerShellShellID;
if (wantPsHome && s_pshome != null)
return s_pshome;
string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" +
PSVersionInfo.RegistryVersionKey + "\\" + RegistryStrings.MonadEngineKey;
using (RegistryKey engineKey = Registry.LocalMachine.OpenSubKey(engineKeyPath))
{
if (engineKey != null)
{
var result = engineKey.GetValue(RegistryStrings.MonadEngine_ApplicationBase) as string;
result = Environment.ExpandEnvironmentVariables(result);
if (wantPsHome)
Interlocked.CompareExchange(ref s_pshome, null, result);
return result;
}
}
return null;
}
private static string s_windowsPowerShellVersion = null;
/// <summary>
/// Get the Windows PowerShell version from registry.
/// </summary>
/// <returns>
/// String of Windows PowerShell version from registry.
/// </returns>
internal static string GetWindowsPowerShellVersionFromRegistry()
{
if (!string.IsNullOrEmpty(InternalTestHooks.TestWindowsPowerShellVersionString))
{
return InternalTestHooks.TestWindowsPowerShellVersionString;
}
if (s_windowsPowerShellVersion != null)
{
return s_windowsPowerShellVersion;
}
string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" +
PSVersionInfo.RegistryVersionKey + "\\" + RegistryStrings.MonadEngineKey;
using (RegistryKey engineKey = Registry.LocalMachine.OpenSubKey(engineKeyPath))
{
if (engineKey != null)
{
s_windowsPowerShellVersion = engineKey.GetValue(RegistryStrings.MonadEngine_MonadVersion) as string;
return s_windowsPowerShellVersion;
}
}
return string.Empty;
}
#endif
internal static string DefaultPowerShellAppBase => GetApplicationBase(DefaultPowerShellShellID);
internal static string GetApplicationBase(string shellId)
{
// Use the location of SMA.dll as the application base.
Assembly assembly = typeof(PSObject).Assembly;
return Path.GetDirectoryName(assembly.Location);
}
private static string[] s_productFolderDirectories;
private static string[] GetProductFolderDirectories()
{
if (s_productFolderDirectories == null)
{
List<string> baseDirectories = new List<string>();
// Retrieve the application base from the registry
string appBase = Utils.DefaultPowerShellAppBase;
if (!string.IsNullOrEmpty(appBase))
{
baseDirectories.Add(appBase);
}
#if !UNIX
// Now add the two variations of System32
baseDirectories.Add(Environment.GetFolderPath(Environment.SpecialFolder.System));
string systemX86 = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86);
if (!string.IsNullOrEmpty(systemX86))
{
baseDirectories.Add(systemX86);
}
#endif
Interlocked.CompareExchange(ref s_productFolderDirectories, baseDirectories.ToArray(), null);
}
return s_productFolderDirectories;
}
/// <summary>
/// Checks if the filePath represents a file under product folder
/// ie., PowerShell ApplicationBase or $env:windir\system32 or
/// $env:windir\syswow64.
/// </summary>
/// <returns>
/// true: if the filePath is under product folder
/// false: otherwise
/// </returns>
internal static bool IsUnderProductFolder(string filePath)
{
FileInfo fileInfo = new FileInfo(filePath);
string filename = fileInfo.FullName;
var productFolderDirectories = GetProductFolderDirectories();
for (int i = 0; i < productFolderDirectories.Length; i++)
{
string applicationBase = productFolderDirectories[i];
if (filename.StartsWith(applicationBase, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
/// <summary>
/// Checks if the current process is using WOW.
/// </summary>
internal static bool IsRunningFromSysWOW64()
{
return DefaultPowerShellAppBase.Contains("SysWOW64");
}
/// <summary>
/// Checks if host machine is WinPE.
/// </summary>
internal static bool IsWinPEHost()
{
#if !UNIX
RegistryKey winPEKey = null;
try
{
// The existence of the following registry confirms that the host machine is a WinPE
// HKLM\System\CurrentControlSet\Control\MiniNT
winPEKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\MiniNT");
return winPEKey != null;
}
catch (ArgumentException) { }
catch (SecurityException) { }
catch (ObjectDisposedException) { }
finally
{
winPEKey?.Dispose();
}
#endif
return false;
}
#region Versioning related methods
/// <summary>
/// Returns current major version of monad ( that is running ) in a string
/// format.
/// </summary>
/// <returns>String.</returns>
/// <remarks>
/// Cannot return a Version object as minor number is a requirement for
/// version object.
/// </remarks>
internal static string GetCurrentMajorVersion()
{
return PSVersionInfo.PSVersion.Major.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Coverts a string to version format.
/// If the string is of the format x (ie., no dots), then ".0" is appended
/// to the string.
/// Version.TryParse will be used to convert the string to a Version
/// object.
/// </summary>
/// <param name="versionString">String representing version.</param>
/// <returns>A Version Object.</returns>
internal static Version StringToVersion(string versionString)
{
// max of 1 dot is allowed in version
if (string.IsNullOrEmpty(versionString))
{
return null;
}
int dotCount = 0;
foreach (char c in versionString)
{
if (c == '.')
{
dotCount++;
if (dotCount > 1)
{
break;
}
}
}
// Version.TryParse expects the string to be in format: major.minor[.build[.revision]]
if (dotCount == 0)
{
versionString += ".0";
}
Version result = null;
if (Version.TryParse(versionString, out result))
{
return result;
}
return null;
}
/// <summary>
/// Checks whether current monad session supports version specified
/// by ver.
/// </summary>
/// <param name="ver">Version to check.</param>
/// <returns>True if supported, false otherwise.</returns>
internal static bool IsPSVersionSupported(string ver)
{
// Convert version to supported format ie., x.x
Version inputVersion = StringToVersion(ver);
return IsPSVersionSupported(inputVersion);
}
/// <summary>
/// Checks whether current monad session supports version specified
/// by checkVersion.
/// </summary>
/// <param name="checkVersion">Version to check.</param>
/// <returns>True if supported, false otherwise.</returns>
internal static bool IsPSVersionSupported(Version checkVersion)
{
if (checkVersion == null)
{
return false;
}
foreach (Version compatibleVersion in PSVersionInfo.PSCompatibleVersions)
{
if (checkVersion.Major == compatibleVersion.Major && checkVersion.Minor <= compatibleVersion.Minor)
return true;
}
return false;
}
/// <summary>
/// Checks whether current PowerShell session supports edition specified
/// by checkEdition.
/// </summary>
/// <param name="checkEdition">Edition to check.</param>
/// <returns>True if supported, false otherwise.</returns>
internal static bool IsPSEditionSupported(string checkEdition)
{
return PSVersionInfo.PSEdition.Equals(checkEdition, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Check whether the current PowerShell session supports any of the specified editions.
/// </summary>
/// <param name="editions">The PowerShell editions to check compatibility with.</param>
/// <returns>True if the edition is supported by this runtime, false otherwise.</returns>
internal static bool IsPSEditionSupported(IEnumerable<string> editions)
{
string currentPSEdition = PSVersionInfo.PSEdition;
foreach (string edition in editions)
{
if (currentPSEdition.Equals(edition, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
/// <summary>
/// Checks whether the specified edition value is allowed.
/// </summary>
/// <param name="editionValue">Edition value to check.</param>
/// <returns>True if allowed, false otherwise.</returns>
internal static bool IsValidPSEditionValue(string editionValue)
{
return AllowedEditionValues.Contains(editionValue, StringComparer.OrdinalIgnoreCase);
}
#endregion
/// <summary>
/// String representing the Default shellID.
/// </summary>
internal const string DefaultPowerShellShellID = "Microsoft.PowerShell";
/// <summary>
/// This is used to construct the profile path.
/// </summary>
internal const string ProductNameForDirectory = "PowerShell";
/// <summary>
/// WSL introduces a new filesystem path to access the Linux filesystem from Windows, like '\\wsl$\ubuntu'.
/// </summary>
internal const string WslRootPath = @"\\wsl$";
/// <summary>
/// The subdirectory of module paths
/// e.g. ~\Documents\WindowsPowerShell\Modules and %ProgramFiles%\WindowsPowerShell\Modules.
/// </summary>
internal static readonly string ModuleDirectory = Path.Combine(ProductNameForDirectory, "Modules");
internal static readonly ConfigScope[] SystemWideOnlyConfig = new[] { ConfigScope.AllUsers };
internal static readonly ConfigScope[] CurrentUserOnlyConfig = new[] { ConfigScope.CurrentUser };
internal static readonly ConfigScope[] SystemWideThenCurrentUserConfig = new[] { ConfigScope.AllUsers, ConfigScope.CurrentUser };
internal static readonly ConfigScope[] CurrentUserThenSystemWideConfig = new[] { ConfigScope.CurrentUser, ConfigScope.AllUsers };
internal static T GetPolicySetting<T>(ConfigScope[] preferenceOrder) where T : PolicyBase, new()
{
T policy = null;
#if !UNIX
// On Windows, group policy settings from registry take precedence.
// If the requested policy is not defined in registry, we query the configuration file.
policy = GetPolicySettingFromGPO<T>(preferenceOrder);
if (policy != null) { return policy; }
#endif
policy = GetPolicySettingFromConfigFile<T>(preferenceOrder);
return policy;
}
private static readonly ConcurrentDictionary<ConfigScope, PowerShellPolicies> s_cachedPoliciesFromConfigFile =
new ConcurrentDictionary<ConfigScope, PowerShellPolicies>();
/// <summary>
/// Get a specific kind of policy setting from the configuration file.
/// </summary>
private static T GetPolicySettingFromConfigFile<T>(ConfigScope[] preferenceOrder) where T : PolicyBase, new()
{
foreach (ConfigScope scope in preferenceOrder)
{
PowerShellPolicies policies;
if (InternalTestHooks.BypassGroupPolicyCaching)
{
policies = PowerShellConfig.Instance.GetPowerShellPolicies(scope);
}
else if (!s_cachedPoliciesFromConfigFile.TryGetValue(scope, out policies))
{
// Use lock here to reduce the contention on accessing the configuration file
lock (s_cachedPoliciesFromConfigFile)
{
policies = s_cachedPoliciesFromConfigFile.GetOrAdd(scope, PowerShellConfig.Instance.GetPowerShellPolicies);
}
}
if (policies != null)
{
PolicyBase result = null;
switch (typeof(T).Name)
{
case nameof(ScriptExecution):
result = policies.ScriptExecution;
break;
case nameof(ScriptBlockLogging):
result = policies.ScriptBlockLogging;
break;
case nameof(ModuleLogging):
result = policies.ModuleLogging;
break;
case nameof(ProtectedEventLogging):
result = policies.ProtectedEventLogging;
break;
case nameof(Transcription):
result = policies.Transcription;
break;
case nameof(UpdatableHelp):
result = policies.UpdatableHelp;
break;
case nameof(ConsoleSessionConfiguration):
result = policies.ConsoleSessionConfiguration;
break;
default:
Diagnostics.Assert(false, "Should be unreachable code. Update this switch block when new PowerShell policy types are added.");
break;
}
if (result != null) { return (T)result; }
}
}
return null;
}
#if !UNIX
private static readonly Dictionary<string, string> GroupPolicyKeys = new Dictionary<string, string>
{
{nameof(ScriptExecution), @"Software\Policies\Microsoft\PowerShellCore"},
{nameof(ScriptBlockLogging), @"Software\Policies\Microsoft\PowerShellCore\ScriptBlockLogging"},
{nameof(ModuleLogging), @"Software\Policies\Microsoft\PowerShellCore\ModuleLogging"},
{nameof(ProtectedEventLogging), @"Software\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging"},
{nameof(Transcription), @"Software\Policies\Microsoft\PowerShellCore\Transcription"},
{nameof(UpdatableHelp), @"Software\Policies\Microsoft\PowerShellCore\UpdatableHelp"},
{nameof(ConsoleSessionConfiguration), @"Software\Policies\Microsoft\PowerShellCore\ConsoleSessionConfiguration"}
};
private static readonly Dictionary<string, string> WindowsPowershellGroupPolicyKeys = new Dictionary<string, string>
{
{ nameof(ScriptExecution), @"Software\Policies\Microsoft\Windows\PowerShell" },
{ nameof(ScriptBlockLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" },
{ nameof(ModuleLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging" },
{ nameof(Transcription), @"Software\Policies\Microsoft\Windows\PowerShell\Transcription" },
{ nameof(UpdatableHelp), @"Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp" },
};
private const string PolicySettingFallbackKey = "UseWindowsPowerShellPolicySetting";
private static readonly ConcurrentDictionary<ConfigScope, ConcurrentDictionary<string, PolicyBase>> s_cachedPoliciesFromRegistry =
new ConcurrentDictionary<ConfigScope, ConcurrentDictionary<string, PolicyBase>>();
private static readonly Func<ConfigScope, ConcurrentDictionary<string, PolicyBase>> s_subCacheCreationDelegate =
key => new ConcurrentDictionary<string, PolicyBase>(StringComparer.Ordinal);
/// <summary>
/// Read policy settings from a registry key into a policy object.
/// </summary>
/// <param name="instance">Policy object that will be filled with values from registry.</param>
/// <param name="instanceType">Type of policy object used.</param>
/// <param name="gpoKey">Registry key that has policy settings.</param>
/// <returns>True if any property was successfully set on the policy object.</returns>
private static bool TrySetPolicySettingsFromRegistryKey(object instance, Type instanceType, RegistryKey gpoKey)
{
var properties = instanceType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
bool isAnyPropertySet = false;
string[] valueNames = gpoKey.GetValueNames();
string[] subKeyNames = gpoKey.GetSubKeyNames();
var valueNameSet = valueNames.Length > 0 ? new HashSet<string>(valueNames, StringComparer.OrdinalIgnoreCase) : null;
var subKeyNameSet = subKeyNames.Length > 0 ? new HashSet<string>(subKeyNames, StringComparer.OrdinalIgnoreCase) : null;
// If there are any values or subkeys in the registry key - read them into the policy instance object
if ((valueNameSet != null) || (subKeyNameSet != null))
{
foreach (var property in properties)
{
string settingName = property.Name;
object rawRegistryValue = null;
// Get the raw value from registry.
if (valueNameSet != null && valueNameSet.Contains(settingName))
{
rawRegistryValue = gpoKey.GetValue(settingName);
}
else if (subKeyNameSet != null && subKeyNameSet.Contains(settingName))
{
using (RegistryKey subKey = gpoKey.OpenSubKey(settingName))
{
if (subKey != null)
{
rawRegistryValue = subKey.GetValueNames();
}
}
}
// Get the actual property value based on the property type.
// If the final property value is not null, then set the property.
if (rawRegistryValue != null)
{
Type propertyType = property.PropertyType;
object propertyValue = null;
switch (propertyType)
{
case var _ when propertyType == typeof(bool?):
if (rawRegistryValue is int rawIntValue)
{
if (rawIntValue == 1)
{
propertyValue = true;
}
else if (rawIntValue == 0)
{
propertyValue = false;
}
}
break;
case var _ when propertyType == typeof(string):
if (rawRegistryValue is string rawStringValue)
{
propertyValue = rawStringValue;
}
break;
case var _ when propertyType == typeof(string[]):
if (rawRegistryValue is string[] rawStringArrayValue)
{
propertyValue = rawStringArrayValue;
}
else if (rawRegistryValue is string stringValue)
{
propertyValue = new string[] { stringValue };
}
break;
default:
throw System.Management.Automation.Interpreter.Assert.Unreachable;
}
// Set the property if the value is not null
if (propertyValue != null)
{
property.SetValue(instance, propertyValue);
isAnyPropertySet = true;
}
}
}
}
return isAnyPropertySet;
}
/// <summary>
/// The implementation of fetching a specific kind of policy setting from the given configuration scope.
/// </summary>
private static T GetPolicySettingFromGPOImpl<T>(ConfigScope scope) where T : PolicyBase, new()
{
Type tType = typeof(T);
// SystemWide scope means 'LocalMachine' root key when query from registry
RegistryKey rootKey = (scope == ConfigScope.AllUsers) ? Registry.LocalMachine : Registry.CurrentUser;
GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath);
Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name));
using (RegistryKey gpoKey = rootKey.OpenSubKey(gpoKeyPath))
{
// If the corresponding GPO key doesn't exist, return null
if (gpoKey == null) { return null; }
// The corresponding GPO key exists, then create an instance of T
// and populate its properties with the settings
object tInstance = Activator.CreateInstance(tType, nonPublic: true);
bool isAnyPropertySet = false;
// if PolicySettingFallbackKey is Not set - use PowerShell Core policy reg key
if ((int)gpoKey.GetValue(PolicySettingFallbackKey, 0) == 0)
{
isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, gpoKey);
}
else
{
// when PolicySettingFallbackKey flag is set (REG_DWORD "1") use Windows PS policy reg key
WindowsPowershellGroupPolicyKeys.TryGetValue(tType.Name, out string winPowershellGpoKeyPath);
Diagnostics.Assert(winPowershellGpoKeyPath != null, StringUtil.Format("The Windows PS GPO registry key path should be pre-defined for {0}", tType.Name));
using (RegistryKey winPowershellGpoKey = rootKey.OpenSubKey(winPowershellGpoKeyPath))
{
// If the corresponding Windows PS GPO key doesn't exist, return null
if (winPowershellGpoKey == null) { return null; }
isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, winPowershellGpoKey);
}
}
// If no property is set, then we consider this policy as undefined
return isAnyPropertySet ? (T)tInstance : null;
}
}
/// <summary>
/// Get a specific kind of policy setting from the group policy registry key.
/// </summary>
private static T GetPolicySettingFromGPO<T>(ConfigScope[] preferenceOrder) where T : PolicyBase, new()
{
PolicyBase policy = null;
string policyName = typeof(T).Name;
foreach (ConfigScope scope in preferenceOrder)
{
if (InternalTestHooks.BypassGroupPolicyCaching)
{
policy = GetPolicySettingFromGPOImpl<T>(scope);
}
else
{
var subordinateCache = s_cachedPoliciesFromRegistry.GetOrAdd(scope, s_subCacheCreationDelegate);
if (!subordinateCache.TryGetValue(policyName, out policy))
{
policy = subordinateCache.GetOrAdd(policyName, key => GetPolicySettingFromGPOImpl<T>(scope));
}
}
if (policy != null) { return (T)policy; }
}
return null;
}
#endif
/// <summary>
/// Scheduled job module name.
/// </summary>
internal const string ScheduledJobModuleName = "PSScheduledJob";
internal static void EnsureModuleLoaded(string module, ExecutionContext context)
{
if (context != null && !context.AutoLoadingModuleInProgress.Contains(module))
{
List<PSModuleInfo> loadedModules = context.Modules.GetModules(new string[] { module }, false);
if ((loadedModules == null) || (loadedModules.Count == 0))
{
CommandInfo commandInfo = new CmdletInfo("Import-Module", typeof(Microsoft.PowerShell.Commands.ImportModuleCommand),
null, null, context);
var importModuleCommand = new System.Management.Automation.Runspaces.Command(commandInfo);
context.AutoLoadingModuleInProgress.Add(module);
PowerShell ps = null;
try
{
ps = PowerShell.Create(RunspaceMode.CurrentRunspace)
.AddCommand(importModuleCommand)
.AddParameter("Name", module)
.AddParameter("Scope", StringLiterals.Global)
.AddParameter("ErrorAction", ActionPreference.Ignore)
.AddParameter("WarningAction", ActionPreference.Ignore)
.AddParameter("InformationAction", ActionPreference.Ignore)
.AddParameter("Verbose", false)
.AddParameter("Debug", false)
.AddParameter("PassThru");
ps.Invoke<PSModuleInfo>();
}
catch (Exception)
{
// Call-out to user code, catch-all OK
}
finally
{
context.AutoLoadingModuleInProgress.Remove(module);
ps?.Dispose();
}
}
}
}
/// <summary>
/// Returns modules (either loaded or in available) that match pattern <paramref name="module"/>.
/// Uses Get-Module -ListAvailable cmdlet.
/// </summary>
/// <param name="module"></param>
/// <param name="context"></param>
/// <returns>
/// List of PSModuleInfo's or Null.
/// </returns>
internal static List<PSModuleInfo> GetModules(string module, ExecutionContext context)
{
// first look in the loaded modules and then append the modules from gmo -Listavailable
// Reason: gmo -li looks only the PSModulepath. There may be cases where a module
// is imported directly from a path (that is not in PSModulePath).
List<PSModuleInfo> result = context.Modules.GetModules(new string[] { module }, false);
CommandInfo commandInfo = new CmdletInfo("Get-Module", typeof(Microsoft.PowerShell.Commands.GetModuleCommand),
null, null, context);
var getModuleCommand = new System.Management.Automation.Runspaces.Command(commandInfo);
PowerShell ps = null;
try
{
ps = PowerShell.Create(RunspaceMode.CurrentRunspace)
.AddCommand(getModuleCommand)
.AddParameter("Name", module)
.AddParameter("ErrorAction", ActionPreference.Ignore)
.AddParameter("WarningAction", ActionPreference.Ignore)
.AddParameter("Verbose", false)
.AddParameter("Debug", false)
.AddParameter("ListAvailable");
Collection<PSModuleInfo> gmoOutPut = ps.Invoke<PSModuleInfo>();
if (gmoOutPut != null)
{
if (result == null)
{
result = gmoOutPut.ToList<PSModuleInfo>();
}
else
{
result.AddRange(gmoOutPut);
}
}
}
catch (Exception)
{
// Call-out to user code, catch-all OK
}
finally
{
ps?.Dispose();
}
return result;
}
/// <summary>
/// Returns modules (either loaded or in available) that match FullyQualifiedName <paramref name="fullyQualifiedName"/>.
/// Uses Get-Module -ListAvailable cmdlet.
/// </summary>
/// <param name="fullyQualifiedName"></param>
/// <param name="context"></param>
/// <returns>
/// List of PSModuleInfo's or Null.
/// </returns>
internal static List<PSModuleInfo> GetModules(ModuleSpecification fullyQualifiedName, ExecutionContext context)
{
// first look in the loaded modules and then append the modules from gmo -Listavailable
// Reason: gmo -li looks only the PSModulepath. There may be cases where a module
// is imported directly from a path (that is not in PSModulePath).
List<PSModuleInfo> result = context.Modules.GetModules(new[] { fullyQualifiedName }, false);
CommandInfo commandInfo = new CmdletInfo("Get-Module", typeof(GetModuleCommand),
null, null, context);
var getModuleCommand = new Runspaces.Command(commandInfo);
PowerShell ps = null;
try
{
ps = PowerShell.Create(RunspaceMode.CurrentRunspace)
.AddCommand(getModuleCommand)
.AddParameter("FullyQualifiedName", fullyQualifiedName)
.AddParameter("ErrorAction", ActionPreference.Ignore)
.AddParameter("WarningAction", ActionPreference.Ignore)
.AddParameter("InformationAction", ActionPreference.Ignore)
.AddParameter("Verbose", false)
.AddParameter("Debug", false)
.AddParameter("ListAvailable");
Collection<PSModuleInfo> gmoOutput = ps.Invoke<PSModuleInfo>();
if (gmoOutput != null)
{
if (result == null)
{
result = gmoOutput.ToList();
}
else
{
// append to result
result.AddRange(gmoOutput);
}
}
}
catch (Exception)
{
// Call-out to user code, catch-all OK
}
finally
{
ps?.Dispose();
}
return result;
}
#if !UNIX
private static bool TryGetWindowsCurrentIdentity(out WindowsIdentity currentIdentity)
{
try
{
currentIdentity = WindowsIdentity.GetCurrent();
}
catch (SecurityException)
{
currentIdentity = null;
}
return (currentIdentity != null);
}
/// <summary>
/// Gets the current impersonating Windows identity, if any.
/// </summary>
/// <param name="impersonatedIdentity">Current impersonated Windows identity or null.</param>
/// <returns>True if current identity is impersonated.</returns>
internal static bool TryGetWindowsImpersonatedIdentity(out WindowsIdentity impersonatedIdentity)
{
WindowsIdentity currentIdentity;
if (TryGetWindowsCurrentIdentity(out currentIdentity) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation))
{
impersonatedIdentity = currentIdentity;
return true;
}
impersonatedIdentity = null;
return false;
}
#endif
internal static bool IsAdministrator()
{
// Porting note: only Windows supports the SecurityPrincipal API of .NET. Due to
// advanced privilege models, the correct approach on Unix is to assume the user has
// permissions, attempt the task, and error gracefully if the task fails due to
// permissions. To fit into PowerShell's existing model of preemptively checking
// permissions (which cannot be assumed on Unix), we "assume" the user is an
// administrator by returning true, thus nullifying this check on Unix.
#if UNIX
return true;
#else
WindowsIdentity currentIdentity;
if (TryGetWindowsCurrentIdentity(out currentIdentity))
{
var principal = new WindowsPrincipal(currentIdentity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
return false;
#endif
}
internal static bool IsReservedDeviceName(string destinationPath)
{
#if !UNIX
string[] reservedDeviceNames = { "CON", "PRN", "AUX", "CLOCK$", "NUL", "CONIN$", "CONOUT$",
"COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
string compareName = Path.GetFileName(destinationPath);
string noExtensionCompareName = Path.GetFileNameWithoutExtension(destinationPath);
if (((compareName.Length < 3) || (compareName.Length > 7)) &&
((noExtensionCompareName.Length < 3) || (noExtensionCompareName.Length > 7)))
{
return false;
}
foreach (string deviceName in reservedDeviceNames)
{
if (
string.Equals(deviceName, compareName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(deviceName, noExtensionCompareName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
#endif
return false;
}
internal static bool PathIsUnc(string path, bool networkOnly = false)
{
#if UNIX
return false;
#else
if (string.IsNullOrEmpty(path) || !path.StartsWith('\\'))
{
return false;
}
// handle special cases like '\\wsl$\ubuntu', '\\?\', and '\\.\pipe\' which aren't a UNC path, but we can say it is so the filesystemprovider can use it
if (!networkOnly && (path.StartsWith(WslRootPath, StringComparison.OrdinalIgnoreCase) || path.StartsWith("\\\\?\\") || path.StartsWith("\\\\.\\")))
{
return true;
}
Uri uri;
return Uri.TryCreate(path, UriKind.Absolute, out uri) && uri.IsUnc;
#endif
}
internal static readonly string PowerShellAssemblyStrongNameFormat =
"{0}, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
internal static readonly HashSet<string> PowerShellAssemblies =
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"microsoft.powershell.commands.diagnostics",
"microsoft.powershell.commands.management",
"microsoft.powershell.commands.utility",
"microsoft.powershell.consolehost",
"microsoft.powershell.scheduledjob",
"microsoft.powershell.security",
"microsoft.wsman.management",
"microsoft.wsman.runtime",
"system.management.automation"
};
internal static bool IsPowerShellAssembly(string assemblyName)
{
if (!string.IsNullOrWhiteSpace(assemblyName))
{
// Remove the '.dll' if it's there...
var fixedName = assemblyName.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase)
? Path.GetFileNameWithoutExtension(assemblyName)
: assemblyName;
if ((fixedName != null) && PowerShellAssemblies.Contains(fixedName))
{
return true;
}
}
return false;
}
internal static string GetPowerShellAssemblyStrongName(string assemblyName)
{
if (!string.IsNullOrWhiteSpace(assemblyName))
{
// Remove the '.dll' if it's there...
string fixedName = assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
? Path.GetFileNameWithoutExtension(assemblyName)
: assemblyName;
if ((fixedName != null) && PowerShellAssemblies.Contains(fixedName))
{
return string.Format(CultureInfo.InvariantCulture, PowerShellAssemblyStrongNameFormat, fixedName);
}
}
return assemblyName;
}
/// <summary>
/// If a mutex is abandoned, in our case, it is ok to proceed.
/// </summary>
/// <param name="mutex">The mutex to wait on. If it is null, a new one will be created.</param>
/// <param name="initializer">The initializer to use to recreate the mutex.</param>
/// <returns>A working mutex. If the mutex was abandoned, a new one is created to replace it.</returns>
internal static Mutex SafeWaitMutex(Mutex mutex, MutexInitializer initializer)
{
try
{
mutex.WaitOne();
}
catch (AbandonedMutexException)
{
// If the Mutex has been abandoned, then the process protecting the critical section
// is no longer valid. We need to release to continue normal operations.
mutex.ReleaseMutex();
((IDisposable)mutex).Dispose();
// Try again, throw if it still fails
mutex = initializer();
mutex.WaitOne();
}
return mutex;
}
internal delegate Mutex MutexInitializer();
internal static bool Succeeded(int hresult)
{
return hresult >= 0;
}
// BigEndianUTF32 encoding is possible, but requires creation
internal static readonly Encoding BigEndianUTF32Encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true);
// [System.Text.Encoding]::GetEncodings() | Where-Object { $_.GetEncoding().GetPreamble() } |
// Add-Member ScriptProperty Preamble { $this.GetEncoding().GetPreamble() -join "-" } -PassThru |
// Format-Table -Auto
#if !UNIX
/// <summary>
/// Queues a CLR worker thread with impersonation of provided Windows identity.
/// </summary>
/// <param name="identityToImpersonate">Windows identity to impersonate or null.</param>
/// <param name="threadProc">Thread procedure for thread.</param>
/// <param name="state">Optional state for thread procedure.</param>
internal static void QueueWorkItemWithImpersonation(
WindowsIdentity identityToImpersonate,
WaitCallback threadProc,
object state)
{
object[] args = new object[3];
args[0] = identityToImpersonate;
args[1] = threadProc;
args[2] = state;
Threading.ThreadPool.QueueUserWorkItem(WorkItemCallback, args);
}
private static void WorkItemCallback(object callBackArgs)
{
object[] args = callBackArgs as object[];
WindowsIdentity identityToImpersonate = args[0] as WindowsIdentity;
WaitCallback callback = args[1] as WaitCallback;
object state = args[2];
if (identityToImpersonate != null)
{
WindowsIdentity.RunImpersonated(
identityToImpersonate.AccessToken,
() => callback(state));
return;
}
callback(state);
}
#endif
/// <summary>
/// If the command name is fully qualified then it is split into its component parts
/// E.g., moduleName\commandName.
/// </summary>
/// <param name="commandName"></param>
/// <param name="moduleName"></param>
/// <returns>Command name and as appropriate Module name in out parameter.</returns>
internal static string ParseCommandName(string commandName, out string moduleName)
{
var names = commandName.Split(Separators.Backslash, 2);
if (names.Length == 2)
{
moduleName = names[0];
return names[1];
}
moduleName = null;
return commandName;
}
internal static ReadOnlyCollection<T> EmptyReadOnlyCollection<T>()
{
return EmptyReadOnlyCollectionHolder<T>._instance;
}
private static class EmptyReadOnlyCollectionHolder<T>
{
internal static readonly ReadOnlyCollection<T> _instance =
new ReadOnlyCollection<T>(Array.Empty<T>());
}
internal static class Separators
{
internal static readonly char[] Backslash = new char[] { '\\' };
internal static readonly char[] Directory = new char[] { '\\', '/' };
internal static readonly char[] DirectoryOrDrive = new char[] { '\\', '/', ':' };
internal static readonly char[] Colon = new char[] { ':' };
internal static readonly char[] Dot = new char[] { '.' };
internal static readonly char[] Pipe = new char[] { '|' };
internal static readonly char[] Comma = new char[] { ',' };
internal static readonly char[] Semicolon = new char[] { ';' };
internal static readonly char[] StarOrQuestion = new char[] { '*', '?' };
internal static readonly char[] ColonOrBackslash = new char[] { '\\', ':' };
internal static readonly char[] PathSeparator = new char[] { Path.PathSeparator };
internal static readonly char[] QuoteChars = new char[] { '\'', '"' };
internal static readonly char[] Space = new char[] { ' ' };
internal static readonly char[] QuotesSpaceOrTab = new char[] { ' ', '\t', '\'', '"' };
internal static readonly char[] SpaceOrTab = new char[] { ' ', '\t' };
internal static readonly char[] Newline = new char[] { '\n' };
internal static readonly char[] CrLf = new char[] { '\r', '\n' };
// (Copied from System.IO.Path so we can call TrimEnd in the same way that Directory.EnumerateFiles would on the search patterns).
// Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space.
// String.WhitespaceChars will trim aggressively than what the underlying FS does (for ex, NTFS, FAT).
internal static readonly char[] PathSearchTrimEnd = { (char)0x9, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20, (char)0x85, (char)0xA0 };
}
/// <summary>
/// A COM object could be directly of the type 'System.__ComObject', or it could be a strongly typed RWC,
/// whose specific type derives from 'System.__ComObject'.
/// A strongly typed RWC can be created via the 'new' operation with a Primary Interop Assembly (PIA).
/// For example, with the PIA 'Microsoft.Office.Interop.Excel', you can write the following code:
/// var excelApp = new Microsoft.Office.Interop.Excel.Application();
/// Type type = excelApp.GetType();
/// Type comObjectType = typeof(object).Assembly.GetType("System.__ComObject");
/// Console.WriteLine("excelApp type: {0}", type.FullName);
/// Console.WriteLine("Is __ComObject assignable from? {0}", comObjectType.IsAssignableFrom(type));
/// and the results are:
/// excelApp type: Microsoft.Office.Interop.Excel.ApplicationClass
/// Is __ComObject assignable from? True.
/// </summary>
internal static bool IsComObject(object obj)
{
#if UNIX
return false;
#else
return obj != null && Marshal.IsComObject(obj);
#endif
}
/// <summary>
/// EnforceSystemLockDownLanguageMode
/// FullLangauge -> ConstrainedLanguage
/// RestrictedLanguage -> NoLanguage
/// ConstrainedLanguage -> ConstrainedLanguage
/// NoLanguage -> NoLanguage.
/// </summary>
/// <param name="context">ExecutionContext.</param>
/// <returns>Previous language mode or null for no language mode change.</returns>
internal static PSLanguageMode? EnforceSystemLockDownLanguageMode(ExecutionContext context)
{
PSLanguageMode? oldMode = null;
if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce)
{
switch (context.LanguageMode)
{
case PSLanguageMode.FullLanguage:
oldMode = context.LanguageMode;
context.LanguageMode = PSLanguageMode.ConstrainedLanguage;
break;
case PSLanguageMode.RestrictedLanguage:
oldMode = context.LanguageMode;
context.LanguageMode = PSLanguageMode.NoLanguage;
break;
case PSLanguageMode.ConstrainedLanguage:
case PSLanguageMode.NoLanguage:
break;
default:
Diagnostics.Assert(false, "Unexpected PSLanguageMode");
oldMode = context.LanguageMode;
context.LanguageMode = PSLanguageMode.NoLanguage;
break;
}
}
return oldMode;
}
internal static string DisplayHumanReadableFileSize(long bytes)
{
return bytes switch
{
< 1024 and >= 0 => $"{bytes} Bytes",
< 1048576 and >= 1024 => $"{(bytes / 1024.0).ToString("0.0")} KB",
< 1073741824 and >= 1048576 => $"{(bytes / 1048576.0).ToString("0.0")} MB",
< 1099511627776 and >= 1073741824 => $"{(bytes / 1073741824.0).ToString("0.000")} GB",
< 1125899906842624 and >= 1099511627776 => $"{(bytes / 1099511627776.0).ToString("0.00000")} TB",
< 1152921504606847000 and >= 1125899906842624 => $"{(bytes / 1125899906842624.0).ToString("0.0000000")} PB",
>= 1152921504606847000 => $"{(bytes / 1152921504606847000.0).ToString("0.000000000")} EB",
_ => $"0 Bytes",
};
}
}
}
namespace System.Management.Automation.Internal
{
/// <summary>This class is used for internal test purposes.</summary>
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")]
public static class InternalTestHooks
{
internal static bool BypassGroupPolicyCaching;
internal static bool ForceScriptBlockLogging;
internal static bool UseDebugAmsiImplementation;
internal static bool BypassAppLockerPolicyCaching;
internal static bool BypassOnlineHelpRetrieval;
internal static bool ThrowHelpCultureNotSupported;
internal static bool ForcePromptForChoiceDefaultOption;
internal static bool NoPromptForPassword;
internal static bool ForceFormatListFixedLabelWidth;
// Stop/Restart/Rename Computer tests
internal static bool TestStopComputer;
internal static bool TestWaitStopComputer;
internal static bool TestRenameComputer;
internal static int TestStopComputerResults;
internal static int TestRenameComputerResults;
// It's useful to test that we don't depend on the ScriptBlock and AST objects and can use a re-parsed version.
internal static bool IgnoreScriptBlockCache;
// Simulate 'System.Diagnostics.Stopwatch.IsHighResolution is false' to test Get-Uptime throw
internal static bool StopwatchIsNotHighResolution;
internal static bool DisableGACLoading;
internal static bool SetConsoleWidthToZero;
internal static bool SetConsoleHeightToZero;
internal static bool SetDate;
// A location to test PSEdition compatibility functionality for Windows PowerShell modules with
// since we can't manipulate the System32 directory in a test
internal static string TestWindowsPowerShellPSHomeLocation;
// A version of Windows PS that is installed on the system; normally this is retrieved from a reg key that is write-protected.
internal static string TestWindowsPowerShellVersionString;
internal static bool ShowMarkdownOutputBypass;
internal static bool ThrowExdevErrorOnMoveDirectory;
// To emulate OneDrive behavior we use the hard-coded symlink.
// If OneDriveTestRecurseOn is false then the symlink works as regular symlink.
// If OneDriveTestRecurseOn is true then we recurse into the symlink as OneDrive should work.
// OneDriveTestSymlinkName defines the symlink name used in tests.
internal static bool OneDriveTestOn;
internal static bool OneDriveTestRecurseOn;
internal static string OneDriveTestSymlinkName = "link-Beta";
/// <summary>This member is used for internal test purposes.</summary>
public static void SetTestHook(string property, object value)
{
var fieldInfo = typeof(InternalTestHooks).GetField(property, BindingFlags.Static | BindingFlags.NonPublic);
fieldInfo?.SetValue(null, value);
}
/// <summary>
/// Constructs a custom PSSenderInfo instance that can be assigned to $PSSenderInfo
/// in order to simulate a remoting session with respect to the $PSSenderInfo.ConnectionString (connection URL)
/// and $PSSenderInfo.ApplicationArguments.PSVersionTable.PSVersion (the remoting client's PowerShell version).
/// See Get-FormatDataTest.ps1.
/// </summary>
/// <param name="url">The connection URL to reflect in the returned instance's ConnectionString property.</param>
/// <param name="clientVersion">The version number to report as the remoting client's PowerShell version.</param>
/// <returns>The newly constructed custom PSSenderInfo instance.</returns>
public static PSSenderInfo GetCustomPSSenderInfo(string url, Version clientVersion)
{
var dummyPrincipal = new PSPrincipal(new PSIdentity("none", true, "someuser", null), null);
var pssi = new PSSenderInfo(dummyPrincipal, url);
pssi.ApplicationArguments = new PSPrimitiveDictionary();
pssi.ApplicationArguments.Add("PSVersionTable", new PSObject(new PSPrimitiveDictionary()));
((PSPrimitiveDictionary)PSObject.Base(pssi.ApplicationArguments["PSVersionTable"])).Add("PSVersion", new PSObject(clientVersion));
return pssi;
}
}
/// <summary>
/// Provides undo/redo functionality by using 2 instances of <seealso cref="BoundedStack{T}"/>.
/// </summary>
internal class HistoryStack<T>
{
private readonly BoundedStack<T> _boundedUndoStack;
private readonly BoundedStack<T> _boundedRedoStack;
internal HistoryStack(int capacity)
{
_boundedUndoStack = new BoundedStack<T>(capacity);
_boundedRedoStack = new BoundedStack<T>(capacity);
}
internal void Push(T item)
{
_boundedUndoStack.Push(item);
if (RedoCount >= 0)
{
_boundedRedoStack.Clear();
}
}
/// <summary>
/// Handles bounded history stacks by pushing the current item to the redoStack and returning the item from the popped undoStack.
/// </summary>
internal T Undo(T currentItem)
{
T previousItem = _boundedUndoStack.Pop();
_boundedRedoStack.Push(currentItem);
return previousItem;
}
/// <summary>
/// Handles bounded history stacks by pushing the current item to the undoStack and returning the item from the popped redoStack.
/// </summary>
internal T Redo(T currentItem)
{
var nextItem = _boundedRedoStack.Pop();
_boundedUndoStack.Push(currentItem);
return nextItem;
}
internal int UndoCount => _boundedUndoStack.Count;
internal int RedoCount => _boundedRedoStack.Count;
}
/// <summary>
/// A bounded stack based on a linked list.
/// </summary>
internal class BoundedStack<T> : LinkedList<T>
{
private readonly int _capacity;
/// <summary>
/// Lazy initialisation, i.e. it sets only its limit but does not allocate the memory for the given capacity.
/// </summary>
/// <param name="capacity"></param>
internal BoundedStack(int capacity)
{
_capacity = capacity;
}
/// <summary>
/// Push item.
/// </summary>
/// <param name="item"></param>
internal void Push(T item)
{
this.AddFirst(item);
if (this.Count > _capacity)
{
this.RemoveLast();
}
}
/// <summary>
/// Pop item.
/// </summary>
/// <returns></returns>
internal T Pop()
{
if (this.First == null)
{
throw new InvalidOperationException(SessionStateStrings.BoundedStackIsEmpty);
}
var item = this.First.Value;
try
{
this.RemoveFirst();
}
catch (InvalidOperationException)
{
throw new InvalidOperationException(SessionStateStrings.BoundedStackIsEmpty);
}
return item;
}
}
/// <summary>
/// A readonly Hashset.
/// </summary>
internal sealed class ReadOnlyBag<T> : IEnumerable
{
private readonly HashSet<T> _hashset;
/// <summary>
/// Constructor for the readonly Hashset.
/// </summary>
internal ReadOnlyBag(HashSet<T> hashset)
{
if (hashset == null)
{
throw new ArgumentNullException(nameof(hashset));
}
_hashset = hashset;
}
/// <summary>
/// Get the count of the Hashset.
/// </summary>
public int Count => _hashset.Count;
/// <summary>
/// Indicate if it's a readonly Hashset.
/// </summary>
public bool IsReadOnly => true;
/// <summary>
/// Check if the set contains an item.
/// </summary>
public bool Contains(T item) => _hashset.Contains(item);
/// <summary>
/// GetEnumerator method.
/// </summary>
public IEnumerator GetEnumerator() => _hashset.GetEnumerator();
/// <summary>
/// Get an empty singleton.
/// </summary>
internal static readonly ReadOnlyBag<T> Empty = new ReadOnlyBag<T>(new HashSet<T>(capacity: 0));
}
/// <summary>
/// Helper class for simple argument validations.
/// </summary>
internal static class Requires
{
internal static void NotNull(object value, string paramName)
{
if (value == null)
{
throw new ArgumentNullException(paramName);
}
}
internal static void NotNullOrEmpty(string value, string paramName)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException(paramName);
}
}
internal static void NotNullOrEmpty(ICollection value, string paramName)
{
if (value == null || value.Count == 0)
{
throw new ArgumentNullException(paramName);
}
}
internal static void Condition([DoesNotReturnIf(false)] bool precondition, string paramName)
{
if (!precondition)
{
throw new ArgumentException(paramName);
}
}
}
}