/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Security;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
using System.Management.Automation.Internal;
using System.Management.Automation.Security;
using System.Reflection;
using Microsoft.PowerShell.Commands;
using Microsoft.Win32;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Text;
using TypeTable = System.Management.Automation.Runspaces.TypeTable;
#if CORECLR
using System.Diagnostics;
using Microsoft.Win32.SafeHandles;
#else
using System.Security.Principal;
using PSUtils = System.Management.Automation.PsUtils;
#endif
namespace System.Management.Automation
{
///
/// helper fns
///
internal static class Utils
{
// 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));
}
///
/// The existence of the following registry confirms that the host machine is a WinPE
/// HKLM\System\CurrentControlSet\Control\MiniNT
///
internal static string WinPEIdentificationRegKey = @"System\CurrentControlSet\Control\MiniNT";
///
/// Allowed PowerShell Editions
///
internal static string[] AllowedEditionValues = { "Desktop", "Core" };
///
/// helper fn to check byte[] arg for null.
///
///
/// arg to check
/// name of the arg
///
/// Does not return a value
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);
}
}
///
/// helper fn to check arg for empty or null.
/// Throws ArgumentNullException on either condition.
///
///
/// arg to check
/// name of the arg
///
/// Does not return a value
internal static void CheckArgForNullOrEmpty(string arg, string argName)
{
if (arg == null)
{
throw PSTraceSource.NewArgumentNullException(argName);
}
else if (arg.Length == 0)
{
throw PSTraceSource.NewArgumentException(argName);
}
}
///
/// helper fn to check arg for null.
/// Throws ArgumentNullException on either condition.
///
///
/// arg to check
/// name of the arg
///
/// Does not return a value
internal static void CheckArgForNull(object arg, string argName)
{
if (arg == null)
{
throw PSTraceSource.NewArgumentNullException(argName);
}
}
///
/// helper fn to check arg for null.
///
///
/// arg to check
/// name of the arg
///
/// Does not return a value
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;
}
///
/// Gets TypeTable by querying the ExecutionContext stored in
/// Thread-Local-Storage. This will return null if ExecutionContext
/// is not available.
///
///
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;
///
/// Get the application base path of the shell from registry
///
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;
}
#endif
internal static string DefaultPowerShellAppBase { get; } = GetApplicationBase(DefaultPowerShellShellID);
internal static string GetApplicationBase(string shellId)
{
#if CORECLR
// Use the location of SMA.dll as the application base.
Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly;
return Path.GetDirectoryName(assembly.Location);
#else
// This code path applies to Windows FullCLR inbox deployments. All CoreCLR
// implementations should use the location of SMA.dll since it must reside in PSHOME.
//
// try to get the path from the registry first
string result = GetApplicationBaseFromRegistry(shellId);
if (result != null)
{
return result;
}
// The default keys aren't installed, so try and use the entry assembly to
// get the application base. This works for managed apps like minishells...
Assembly assem = Assembly.GetEntryAssembly();
if (assem != null)
{
// For minishells, we just return the executable path.
return Path.GetDirectoryName(assem.Location);
}
// For unmanaged host apps, look for the SMA dll, if it's not GAC'ed then
// use it's location as the application base...
assem = typeof(PSObject).GetTypeInfo().Assembly;
string gacRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Microsoft.Net\\assembly");
if (!assem.Location.StartsWith(gacRootPath, StringComparison.OrdinalIgnoreCase))
{
// For other hosts.
return Path.GetDirectoryName(assem.Location);
}
// otherwise, just give up...
return "";
#endif
}
private static string[] s_productFolderDirectories;
///
/// Specifies the per-user configuration settings directory in a platform agnostic manner.
///
/// The current user's configuration settings directory
internal static string GetUserConfigurationDirectory()
{
#if UNIX
return Platform.SelectProductNameForDirectory(Platform.XDG_Type.CONFIG);
#else
string basePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
return IO.Path.Combine(basePath, Utils.ProductNameForDirectory);
#endif
}
private static string[] GetProductFolderDirectories()
{
if (s_productFolderDirectories == null)
{
List baseDirectories = new List();
// Retrieve the application base from the registry
string appBase = Utils.DefaultPowerShellAppBase;
if (!string.IsNullOrEmpty(appBase))
{
baseDirectories.Add(appBase);
}
#if !UNIX
// Win8: 454976
// 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
// And built-in modules
string progFileDir;
// TODO: #1184 will resolve this work-around
// Side-by-side versions of PowerShell use modules from their application base, not
// the system installation path.
#if CORECLR
progFileDir = Path.Combine(appBase, "Modules");
#else
progFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsPowerShell", "Modules");
#endif
if (!string.IsNullOrEmpty(progFileDir))
{
baseDirectories.Add(Path.Combine(progFileDir, "PackageManagement"));
baseDirectories.Add(Path.Combine(progFileDir, "PowerShellGet"));
baseDirectories.Add(Path.Combine(progFileDir, "Pester"));
baseDirectories.Add(Path.Combine(progFileDir, "PSReadLine"));
}
Interlocked.CompareExchange(ref s_productFolderDirectories, baseDirectories.ToArray(), null);
}
return s_productFolderDirectories;
}
///
/// Checks if the filePath represents a file under product folder
/// ie., PowerShell ApplicationBase or $env:windir\system32 or
/// $env:windir\syswow64.
///
///
/// true: if the filePath is under product folder
/// false: otherwise
///
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;
}
///
/// Checks if the current process is using WOW
///
internal static bool IsRunningFromSysWOW64()
{
return DefaultPowerShellAppBase.Contains("SysWOW64");
}
///
/// Checks if host machine is WinPE
///
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(WinPEIdentificationRegKey);
return winPEKey != null;
}
catch (ArgumentException) { }
catch (SecurityException) { }
catch (ObjectDisposedException) { }
finally
{
if (winPEKey != null)
{
winPEKey.Dispose();
}
}
#endif
return false;
}
#region Versioning related methods
///
/// returns current major version of monad ( that is running ) in a string
/// format.
///
/// string
///
/// Cannot return a Version object as minor number is a requirement for
/// version object.
///
internal static string GetCurrentMajorVersion()
{
return PSVersionInfo.PSVersion.Major.ToString(CultureInfo.InvariantCulture);
}
///
/// 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.
///
/// string representing version
/// A Version Object.
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;
}
///
/// Checks whether current monad session supports version specified
/// by ver.
///
/// Version to check
/// true if supported, false otherwise
internal static bool IsPSVersionSupported(string ver)
{
// Convert version to supported format ie., x.x
Version inputVersion = StringToVersion(ver);
return IsPSVersionSupported(inputVersion);
}
///
/// Checks whether current monad session supports version specified
/// by checkVersion.
///
/// Version to check
/// true if supported, false otherwise
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;
}
///
/// Checks whether current monad session supports edition specified
/// by checkEdition.
///
/// Edition to check
/// true if supported, false otherwise
internal static bool IsPSEditionSupported(string checkEdition)
{
return PSVersionInfo.PSEdition.Equals(checkEdition, StringComparison.OrdinalIgnoreCase);
}
///
/// Checks whether the specified edition values is allowed.
///
/// Edition value to check
/// true if allowed, false otherwise
internal static bool IsValidPSEditionValue(string editionValue)
{
return AllowedEditionValues.Contains(editionValue, StringComparer.OrdinalIgnoreCase);
}
#if !CORECLR
///
/// Checks whether current monad session supports NetFrameworkVersion specified
/// by checkVersion. The specified version is treated as the the minimum required
/// version of .NET framework.
///
/// Version to check
/// true if version to check is higher than the known highest version
/// true if supported, false otherwise
internal static bool IsNetFrameworkVersionSupported(Version checkVersion, out bool higherThanKnownHighestVersion)
{
higherThanKnownHighestVersion = false;
bool isSupported = false;
if (checkVersion == null)
{
return false;
}
// Construct a temporary version number with build number and revision number set to 0.
// This is done so as to re-use the version specifications in PSUtils.FrameworkRegistryInstallation
Version tempVersion = new Version(checkVersion.Major, checkVersion.Minor, 0, 0);
// Win8: 840038 - For any version above the highest known .NET version (4.5 for Windows 8), we can't make a call as to
// whether the requirement is satisfied or not because we can't detect that version of .NET.
// We end up erring on the side of app compat by letting it through.
// We will write a message in the Verbose output saying that we cannot detect the specified version of the .NET Framework.
if (checkVersion > PsUtils.FrameworkRegistryInstallation.KnownHighestNetFrameworkVersion)
{
isSupported = true;
higherThanKnownHighestVersion = true;
}
// For a script to have a valid .NET version, the specified version or atleast one of its compatible versions must be installed on the machine.
else if (PSUtils.FrameworkRegistryInstallation.CompatibleNetFrameworkVersions.ContainsKey(tempVersion))
{
if (PSUtils.FrameworkRegistryInstallation.IsFrameworkInstalled(tempVersion.Major, tempVersion.Minor, 0))
{
// If the specified version is installed on the machine, then we return true.
isSupported = true;
}
else
{
// If any of the compatible versions are installed on the machine, then we return true.
HashSet compatibleVersions = PSUtils.FrameworkRegistryInstallation.CompatibleNetFrameworkVersions[tempVersion];
foreach (Version compatibleVersion in compatibleVersions)
{
if (PSUtils.FrameworkRegistryInstallation.IsFrameworkInstalled(compatibleVersion.Major, compatibleVersion.Minor, 0))
{
isSupported = true;
break;
}
}
}
}
return isSupported;
}
#endif
#endregion
///
/// String representing the Default shellID.
///
internal const string DefaultPowerShellShellID = "Microsoft.PowerShell";
///
/// This is used to construct the profile path.
///
#if CORECLR
internal static string ProductNameForDirectory = Platform.IsInbox ? "WindowsPowerShell" : "PowerShell";
#else
internal const string ProductNameForDirectory = "WindowsPowerShell";
#endif
///
/// The subdirectory of module paths
/// e.g. ~\Documents\WindowsPowerShell\Modules and %ProgramFiles%\WindowsPowerShell\Modules
///
internal static string ModuleDirectory = Path.Combine(ProductNameForDirectory, "Modules");
internal static string GetRegistryConfigurationPrefix()
{
// For 3.0 PowerShell, we still use "1" as the registry version key for
// Snapin and Custom shell lookup/discovery.
// For 3.0 PowerShell, we use "3" as the registry version key only for Engine
// related data like ApplicationBase etc.
return "SOFTWARE\\Microsoft\\PowerShell\\" + PSVersionInfo.RegistryVersion1Key + "\\ShellIds";
}
internal static string GetRegistryConfigurationPath(string shellID)
{
return GetRegistryConfigurationPrefix() + "\\" + shellID;
}
// Calling static members of 'Registry' on UNIX will raise 'PlatformNotSupportedException'
#if UNIX
internal static RegistryKey[] RegLocalMachine = null;
internal static RegistryKey[] RegCurrentUser = null;
internal static RegistryKey[] RegLocalMachineThenCurrentUser = null;
internal static RegistryKey[] RegCurrentUserThenLocalMachine = null;
#else
internal static RegistryKey[] RegLocalMachine = new[] { Registry.LocalMachine };
internal static RegistryKey[] RegCurrentUser = new[] { Registry.CurrentUser };
internal static RegistryKey[] RegLocalMachineThenCurrentUser = new[] { Registry.LocalMachine, Registry.CurrentUser };
internal static RegistryKey[] RegCurrentUserThenLocalMachine = new[] { Registry.CurrentUser, Registry.LocalMachine };
#endif
internal static Dictionary GetGroupPolicySetting(string settingName, RegistryKey[] preferenceOrder)
{
string groupPolicyBase = "Software\\Policies\\Microsoft\\Windows\\PowerShell";
return GetGroupPolicySetting(groupPolicyBase, settingName, preferenceOrder);
}
// We use a static to avoid creating "extra garbage."
private static Dictionary s_emptyDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase);
internal static Dictionary GetGroupPolicySetting(string groupPolicyBase, string settingName, RegistryKey[] preferenceOrder)
{
#if UNIX
return s_emptyDictionary;
#else
lock (s_cachedGroupPolicySettings)
{
// Return cached information, if we have it
Dictionary settings;
if ((s_cachedGroupPolicySettings.TryGetValue(settingName, out settings)) &&
!InternalTestHooks.BypassGroupPolicyCaching)
{
return settings;
}
if (!String.Equals(".", settingName, StringComparison.OrdinalIgnoreCase))
{
groupPolicyBase += "\\" + settingName;
}
settings = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (RegistryKey searchKey in preferenceOrder)
{
try
{
// Look up the machine-wide group policy
using (RegistryKey key = searchKey.OpenSubKey(groupPolicyBase))
{
if (key != null)
{
foreach (string subkeyName in key.GetValueNames())
{
// A null or empty subkey name string corresponds to a (Default) key.
// If it is null, make it an empty string which the Dictionary can handle.
string keyName = subkeyName ?? string.Empty;
settings[keyName] = key.GetValue(keyName);
}
foreach (string subkeyName in key.GetSubKeyNames())
{
// A null or empty subkey name string corresponds to a (Default) key.
// If it is null, make it an empty string which the Dictionary can handle.
string keyName = subkeyName ?? string.Empty;
using (RegistryKey subkey = key.OpenSubKey(keyName))
{
if (subkey != null)
{
settings[keyName] = subkey.GetValueNames();
}
}
}
break;
}
}
}
catch (System.Security.SecurityException)
{
// User doesn't have access to open group policy key
}
}
// No group policy settings, then return null
if (settings.Count == 0)
{
settings = null;
}
// Cache the data
if (!InternalTestHooks.BypassGroupPolicyCaching)
{
s_cachedGroupPolicySettings[settingName] = settings;
}
return settings;
}
#endif
}
private static ConcurrentDictionary> s_cachedGroupPolicySettings =
new ConcurrentDictionary>();
///
/// Scheduled job module name.
///
internal const string ScheduledJobModuleName = "PSScheduledJob";
internal const string WorkflowType = "Microsoft.PowerShell.Workflow.AstToWorkflowConverter, Microsoft.PowerShell.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
internal const string WorkflowModule = "PSWorkflow";
internal static IAstToWorkflowConverter GetAstToWorkflowConverterAndEnsureWorkflowModuleLoaded(ExecutionContext context)
{
IAstToWorkflowConverter converterInstance = null;
Type converterType = null;
if (Utils.IsRunningFromSysWOW64())
{
throw new NotSupportedException(AutomationExceptions.WorkflowDoesNotSupportWOW64);
}
// If the current language mode is ConstrainedLanguage but the system lockdown mode is not,
// then also block the conversion - since we can't validate the InlineScript, PowerShellValue,
// etc.
if ((context != null) &&
(context.LanguageMode == PSLanguageMode.ConstrainedLanguage) &&
(SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Enforce))
{
throw new NotSupportedException(Modules.CannotDefineWorkflowInconsistentLanguageMode);
}
EnsureModuleLoaded(WorkflowModule, context);
converterType = Type.GetType(WorkflowType);
if (converterType != null)
{
converterInstance = (IAstToWorkflowConverter)converterType.GetConstructor(PSTypeExtensions.EmptyTypes).Invoke(EmptyArray