mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-15 22:29:08 +00:00
563 lines
21 KiB
C#
563 lines
21 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Management.Automation.Language;
|
|
using System.Net.NetworkInformation;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Xml;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// Defines generic utilities and helper methods for PowerShell.
|
|
/// </summary>
|
|
internal static class PsUtils
|
|
{
|
|
// Cache of the current process' parentId
|
|
private static int? s_currentParentProcessId;
|
|
private static readonly int s_currentProcessId = Environment.ProcessId;
|
|
|
|
/// <summary>
|
|
/// Retrieve the parent process of a process.
|
|
///
|
|
/// Previously this code used WMI, but WMI is causing a CPU spike whenever the query gets called as it results in
|
|
/// tzres.dll and tzres.mui.dll being loaded into every process to convert the time information to local format.
|
|
/// For perf reasons, we resort to P/Invoke.
|
|
/// </summary>
|
|
/// <param name="current">The process we want to find the
|
|
/// parent of</param>
|
|
internal static Process GetParentProcess(Process current)
|
|
{
|
|
var processId = current.Id;
|
|
|
|
// This is a common query (parent id for the current process)
|
|
// Use cached value if available
|
|
var parentProcessId = processId == s_currentProcessId && s_currentParentProcessId.HasValue ?
|
|
s_currentParentProcessId.Value :
|
|
Microsoft.PowerShell.ProcessCodeMethods.GetParentPid(current);
|
|
|
|
// cache the current process parent pid if it hasn't been done yet
|
|
if (processId == s_currentProcessId && !s_currentParentProcessId.HasValue)
|
|
{
|
|
s_currentParentProcessId = parentProcessId;
|
|
}
|
|
|
|
if (parentProcessId == 0)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
Process returnProcess = Process.GetProcessById(parentProcessId);
|
|
|
|
// Ensure the process started before the current
|
|
// process, as it could have gone away and had the
|
|
// PID recycled.
|
|
if (returnProcess.StartTime <= current.StartTime)
|
|
return returnProcess;
|
|
else
|
|
return null;
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
// GetProcessById throws an ArgumentException when
|
|
// you reach the top of the chain -- Explorer.exe
|
|
// has a parent process, but you cannot retrieve it.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return true/false to indicate whether the process architecture is ARM.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal static bool IsRunningOnProcessArchitectureARM()
|
|
{
|
|
Architecture arch = RuntimeInformation.ProcessArchitecture;
|
|
return arch == Architecture.Arm || arch == Architecture.Arm64;
|
|
}
|
|
|
|
internal static string GetHostName()
|
|
{
|
|
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
|
|
|
|
string hostname = ipProperties.HostName;
|
|
string domainName = ipProperties.DomainName;
|
|
|
|
// CoreFX on Unix calls GLibc getdomainname()
|
|
// which returns "(none)" if a domain name is not set by setdomainname()
|
|
if (!string.IsNullOrEmpty(domainName) && !domainName.Equals("(none)", StringComparison.Ordinal))
|
|
{
|
|
hostname = hostname + "." + domainName;
|
|
}
|
|
|
|
return hostname;
|
|
}
|
|
|
|
internal static uint GetNativeThreadId()
|
|
{
|
|
#if UNIX
|
|
return Platform.NonWindowsGetThreadId();
|
|
#else
|
|
return Interop.Windows.GetCurrentThreadId();
|
|
#endif
|
|
}
|
|
|
|
#region ASTUtils
|
|
|
|
/// <summary>
|
|
/// This method is to get the unique key for a UsingExpressionAst. The key is a base64
|
|
/// encoded string based on the text of the UsingExpressionAst.
|
|
///
|
|
/// This method is used when handling a script block that contains $using for Invoke-Command.
|
|
///
|
|
/// When run Invoke-Command targeting a machine that runs PSv3 or above, we pass a dictionary
|
|
/// to the remote end that contains the key of each UsingExpressionAst and its value. This method
|
|
/// is used to generate the key.
|
|
/// </summary>
|
|
/// <param name="usingAst">A using expression.</param>
|
|
/// <returns>Base64 encoded string as the key of the UsingExpressionAst.</returns>
|
|
internal static string GetUsingExpressionKey(Language.UsingExpressionAst usingAst)
|
|
{
|
|
Diagnostics.Assert(usingAst != null, "Caller makes sure the parameter is not null");
|
|
|
|
// We cannot call ToLowerInvariant unconditionally, because usingAst might
|
|
// contain IndexExpressionAst in its SubExpression, such as
|
|
// $using:bar["AAAA"]
|
|
// and the index "AAAA" might not get us the same value as "aaaa".
|
|
//
|
|
// But we do want a unique key to represent the same UsingExpressionAst's as much
|
|
// as possible, so as to avoid sending redundant key-value's to remote machine.
|
|
// As a workaround, we call ToLowerInvariant when the SubExpression of usingAst
|
|
// is a VariableExpressionAst, because:
|
|
// (1) Variable name is case insensitive;
|
|
// (2) People use $using to refer to a variable most of the time.
|
|
string usingAstText = usingAst.ToString();
|
|
if (usingAst.SubExpression is Language.VariableExpressionAst)
|
|
{
|
|
usingAstText = usingAstText.ToLowerInvariant();
|
|
}
|
|
|
|
return StringToBase64Converter.StringToBase64String(usingAstText);
|
|
}
|
|
|
|
#endregion ASTUtils
|
|
|
|
#region EvaluatePowerShellDataFile
|
|
|
|
/// <summary>
|
|
/// Evaluate a powershell data file as if it's a module manifest.
|
|
/// </summary>
|
|
/// <param name="parameterName"></param>
|
|
/// <param name="psDataFilePath"></param>
|
|
/// <param name="context"></param>
|
|
/// <param name="skipPathValidation"></param>
|
|
/// <returns></returns>
|
|
internal static Hashtable EvaluatePowerShellDataFileAsModuleManifest(
|
|
string parameterName,
|
|
string psDataFilePath,
|
|
ExecutionContext context,
|
|
bool skipPathValidation)
|
|
{
|
|
// Use the same capabilities as the module manifest
|
|
// e.g. allow 'PSScriptRoot' variable
|
|
return EvaluatePowerShellDataFile(
|
|
parameterName,
|
|
psDataFilePath,
|
|
context,
|
|
Microsoft.PowerShell.Commands.ModuleCmdletBase.PermittedCmdlets,
|
|
new[] { "PSScriptRoot" },
|
|
allowEnvironmentVariables: true,
|
|
skipPathValidation: skipPathValidation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a Hashtable object out of a PowerShell data file (.psd1)
|
|
/// </summary>
|
|
/// <param name="parameterName">
|
|
/// Name of the parameter that takes the specified .psd1 file as a value
|
|
/// </param>
|
|
/// <param name="psDataFilePath">
|
|
/// Path to the powershell data file
|
|
/// </param>
|
|
/// <param name="context">
|
|
/// ExecutionContext to use
|
|
/// </param>
|
|
/// <param name="allowedCommands">
|
|
/// Set of command names that are allowed to use in the .psd1 file
|
|
/// </param>
|
|
/// <param name="allowedVariables">
|
|
/// Set of variable names that are allowed to use in the .psd1 file
|
|
/// </param>
|
|
/// <param name="allowEnvironmentVariables">
|
|
/// If true, allow to use environment variables in the .psd1 file
|
|
/// </param>
|
|
/// <param name="skipPathValidation">
|
|
/// If true, caller guarantees the path is valid
|
|
/// </param>
|
|
/// <returns></returns>
|
|
internal static Hashtable EvaluatePowerShellDataFile(
|
|
string parameterName,
|
|
string psDataFilePath,
|
|
ExecutionContext context,
|
|
IEnumerable<string> allowedCommands,
|
|
IEnumerable<string> allowedVariables,
|
|
bool allowEnvironmentVariables,
|
|
bool skipPathValidation)
|
|
{
|
|
if (!skipPathValidation && string.IsNullOrEmpty(parameterName))
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(parameterName));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(psDataFilePath))
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(psDataFilePath));
|
|
}
|
|
|
|
if (context == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(context));
|
|
}
|
|
|
|
string resolvedPath;
|
|
if (skipPathValidation)
|
|
{
|
|
resolvedPath = psDataFilePath;
|
|
}
|
|
else
|
|
{
|
|
#region "ValidatePowerShellDataFilePath"
|
|
|
|
bool isPathValid = true;
|
|
|
|
// File extension needs to be .psd1
|
|
string pathExt = Path.GetExtension(psDataFilePath);
|
|
if (string.IsNullOrEmpty(pathExt) ||
|
|
!StringLiterals.PowerShellDataFileExtension.Equals(pathExt, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
isPathValid = false;
|
|
}
|
|
|
|
ProviderInfo provider;
|
|
var resolvedPaths = context.SessionState.Path.GetResolvedProviderPathFromPSPath(psDataFilePath, out provider);
|
|
|
|
// ConfigPath should be resolved as FileSystem provider
|
|
if (provider == null || !Microsoft.PowerShell.Commands.FileSystemProvider.ProviderName.Equals(provider.Name, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
isPathValid = false;
|
|
}
|
|
|
|
// ConfigPath should be resolved to a single path
|
|
if (resolvedPaths.Count != 1)
|
|
{
|
|
isPathValid = false;
|
|
}
|
|
|
|
if (!isPathValid)
|
|
{
|
|
throw PSTraceSource.NewArgumentException(
|
|
parameterName,
|
|
ParserStrings.CannotResolvePowerShellDataFilePath,
|
|
psDataFilePath);
|
|
}
|
|
|
|
resolvedPath = resolvedPaths[0];
|
|
|
|
#endregion "ValidatePowerShellDataFilePath"
|
|
}
|
|
|
|
#region "LoadAndEvaluatePowerShellDataFile"
|
|
|
|
object evaluationResult;
|
|
try
|
|
{
|
|
// Create the scriptInfo for the .psd1 file
|
|
string dataFileName = Path.GetFileName(resolvedPath);
|
|
var dataFileScriptInfo = new ExternalScriptInfo(dataFileName, resolvedPath, context);
|
|
ScriptBlock scriptBlock = dataFileScriptInfo.ScriptBlock;
|
|
|
|
// Validate the scriptblock
|
|
scriptBlock.CheckRestrictedLanguage(allowedCommands, allowedVariables, allowEnvironmentVariables);
|
|
|
|
// Evaluate the scriptblock
|
|
object oldPsScriptRoot = context.GetVariableValue(SpecialVariables.PSScriptRootVarPath);
|
|
try
|
|
{
|
|
// Set the $PSScriptRoot before the evaluation
|
|
context.SetVariable(SpecialVariables.PSScriptRootVarPath, Path.GetDirectoryName(resolvedPath));
|
|
evaluationResult = PSObject.Base(scriptBlock.InvokeReturnAsIs());
|
|
}
|
|
finally
|
|
{
|
|
context.SetVariable(SpecialVariables.PSScriptRootVarPath, oldPsScriptRoot);
|
|
}
|
|
}
|
|
catch (RuntimeException ex)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(
|
|
ex,
|
|
ParserStrings.CannotLoadPowerShellDataFile,
|
|
psDataFilePath,
|
|
ex.Message);
|
|
}
|
|
|
|
if (!(evaluationResult is Hashtable retResult))
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(
|
|
ParserStrings.InvalidPowerShellDataFile,
|
|
psDataFilePath);
|
|
}
|
|
|
|
#endregion "LoadAndEvaluatePowerShellDataFile"
|
|
|
|
return retResult;
|
|
}
|
|
|
|
#endregion EvaluatePowerShellDataFile
|
|
|
|
internal static readonly string[] ManifestModuleVersionPropertyName = new[] { "ModuleVersion" };
|
|
internal static readonly string[] ManifestGuidPropertyName = new[] { "GUID" };
|
|
internal static readonly string[] ManifestPrivateDataPropertyName = new[] { "PrivateData" };
|
|
|
|
internal static readonly string[] FastModuleManifestAnalysisPropertyNames = new[]
|
|
{
|
|
"AliasesToExport",
|
|
"CmdletsToExport",
|
|
"CompatiblePSEditions",
|
|
"FunctionsToExport",
|
|
"NestedModules",
|
|
"RootModule",
|
|
"ModuleToProcess",
|
|
"ModuleVersion"
|
|
};
|
|
|
|
internal static Hashtable GetModuleManifestProperties(string psDataFilePath, string[] keys)
|
|
{
|
|
string dataFileContents = ScriptAnalysis.ReadScript(psDataFilePath);
|
|
ParseError[] parseErrors;
|
|
var ast = (new Parser()).Parse(psDataFilePath, dataFileContents, null, out parseErrors, ParseMode.ModuleAnalysis);
|
|
if (parseErrors.Length > 0)
|
|
{
|
|
var pe = new ParseException(parseErrors);
|
|
throw PSTraceSource.NewInvalidOperationException(
|
|
pe,
|
|
ParserStrings.CannotLoadPowerShellDataFile,
|
|
psDataFilePath,
|
|
pe.Message);
|
|
}
|
|
|
|
var pipeline = ast.GetSimplePipeline(false, out _, out _);
|
|
if (pipeline?.GetPureExpression() is HashtableAst hashtableAst)
|
|
{
|
|
var result = new Hashtable(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var pair in hashtableAst.KeyValuePairs)
|
|
{
|
|
if (pair.Item1 is StringConstantExpressionAst key && keys.Contains(key.Value, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
try
|
|
{
|
|
var val = pair.Item2.SafeGetValue();
|
|
result[key.Value] = val;
|
|
}
|
|
catch
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(
|
|
ParserStrings.InvalidPowerShellDataFile,
|
|
psDataFilePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
throw PSTraceSource.NewInvalidOperationException(
|
|
ParserStrings.InvalidPowerShellDataFile,
|
|
psDataFilePath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class provides helper methods for converting to/fro from
|
|
/// string to base64string.
|
|
/// </summary>
|
|
internal static class StringToBase64Converter
|
|
{
|
|
/// <summary>
|
|
/// Converts string to base64 encoded string.
|
|
/// </summary>
|
|
/// <param name="input">String to encode.</param>
|
|
/// <returns>Base64 encoded string.</returns>
|
|
internal static string StringToBase64String(string input)
|
|
{
|
|
// NTRAID#Windows Out Of Band Releases-926471-2005/12/27-JonN
|
|
// shell crashes if you pass an empty script block to a native command
|
|
if (input == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(input));
|
|
}
|
|
|
|
string base64 = Convert.ToBase64String
|
|
(
|
|
Encoding.Unicode.GetBytes(input.ToCharArray())
|
|
);
|
|
return base64;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decodes base64 encoded string.
|
|
/// </summary>
|
|
/// <param name="base64">Base64 string to decode.</param>
|
|
/// <returns>Decoded string.</returns>
|
|
internal static string Base64ToString(string base64)
|
|
{
|
|
if (string.IsNullOrEmpty(base64))
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(base64));
|
|
}
|
|
|
|
string output = new string(Encoding.Unicode.GetChars(Convert.FromBase64String(base64)));
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decodes base64 encoded string in to args array.
|
|
/// </summary>
|
|
/// <param name="base64"></param>
|
|
/// <returns></returns>
|
|
internal static object[] Base64ToArgsConverter(string base64)
|
|
{
|
|
if (string.IsNullOrEmpty(base64))
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(base64));
|
|
}
|
|
|
|
string decoded = new string(Encoding.Unicode.GetChars(Convert.FromBase64String(base64)));
|
|
|
|
// Deserialize string
|
|
XmlReader reader = XmlReader.Create(new StringReader(decoded), InternalDeserializer.XmlReaderSettingsForCliXml);
|
|
object dso;
|
|
Deserializer deserializer = new Deserializer(reader);
|
|
dso = deserializer.Deserialize();
|
|
if (!deserializer.Done())
|
|
{
|
|
// This helper function should move to host and it should provide appropriate
|
|
// error message there.
|
|
throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter);
|
|
}
|
|
|
|
if (!(dso is PSObject mo))
|
|
{
|
|
// This helper function should move the host. Provide appropriate error message.
|
|
// Format of args parameter is not correct.
|
|
throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter);
|
|
}
|
|
|
|
if (!(mo.BaseObject is ArrayList argsList))
|
|
{
|
|
// This helper function should move the host. Provide appropriate error message.
|
|
// Format of args parameter is not correct.
|
|
throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter);
|
|
}
|
|
|
|
return argsList.ToArray();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A simple implementation of CRC32.
|
|
/// See "CRC-32 algorithm" in https://en.wikipedia.org/wiki/Cyclic_redundancy_check.
|
|
/// </summary>
|
|
internal static class CRC32Hash
|
|
{
|
|
// CRC-32C polynomial representations
|
|
private const uint polynomial = 0x1EDC6F41;
|
|
|
|
private static readonly uint[] table;
|
|
|
|
static CRC32Hash()
|
|
{
|
|
uint temp = 0;
|
|
table = new uint[256];
|
|
|
|
for (int i = 0; i < table.Length; i++)
|
|
{
|
|
temp = (uint)i;
|
|
for (int j = 0; j < 8; j++)
|
|
{
|
|
if ((temp & 1) == 1)
|
|
{
|
|
temp = (temp >> 1) ^ polynomial;
|
|
}
|
|
else
|
|
{
|
|
temp >>= 1;
|
|
}
|
|
}
|
|
|
|
table[i] = temp;
|
|
}
|
|
}
|
|
|
|
private static uint Compute(byte[] buffer)
|
|
{
|
|
uint crc = 0xFFFFFFFF;
|
|
for (int i = 0; i < buffer.Length; ++i)
|
|
{
|
|
var index = (byte)(crc ^ buffer[i] & 0xff);
|
|
crc = (crc >> 8) ^ table[index];
|
|
}
|
|
|
|
return ~crc;
|
|
}
|
|
|
|
internal static byte[] ComputeHash(byte[] buffer)
|
|
{
|
|
uint crcResult = Compute(buffer);
|
|
return BitConverter.GetBytes(crcResult);
|
|
}
|
|
|
|
internal static string ComputeHash(string input)
|
|
{
|
|
byte[] hashBytes = ComputeHash(Encoding.UTF8.GetBytes(input));
|
|
return Convert.ToHexString(hashBytes);
|
|
}
|
|
}
|
|
|
|
#region ReferenceEqualityComparer
|
|
|
|
/// <summary>
|
|
/// Equality comparer based on Object Identity.
|
|
/// </summary>
|
|
internal class ReferenceEqualityComparer : IEqualityComparer
|
|
{
|
|
bool IEqualityComparer.Equals(object x, object y)
|
|
{
|
|
return Object.ReferenceEquals(x, y);
|
|
}
|
|
|
|
int IEqualityComparer.GetHashCode(object obj)
|
|
{
|
|
// The Object.GetHashCode and RuntimeHelpers.GetHashCode methods are used in the following scenarios:
|
|
//
|
|
// Object.GetHashCode is useful in scenarios where you care about object value. Two strings with identical
|
|
// contents will return the same value for Object.GetHashCode.
|
|
//
|
|
// RuntimeHelpers.GetHashCode is useful in scenarios where you care about object identity. Two strings with
|
|
// identical contents will return different values for RuntimeHelpers.GetHashCode, because they are different
|
|
// string objects, although their contents are the same.
|
|
|
|
return RuntimeHelpers.GetHashCode(obj);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|