/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System.Collections; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Xml; using Microsoft.Win32; using System.Collections.Generic; using System.Management.Automation.Language; namespace System.Management.Automation { /// /// Defines generic utilities and helper methods for PowerShell /// internal static class PsUtils { internal static string ArmArchitecture = "ARM"; /// /// Safely retrieves the MainModule property of a /// process. Version 2.0 and below of the .NET Framework are /// impacted by a Win32 API usability knot that throws an /// exception if API tries to enumerate the process' modules /// while it is still loading them. This generates the error /// message: Only part of a ReadProcessMemory or /// WriteProcessMemory request was completed. /// The BCL fix in V3 was to just try more, so we do the same /// thing. /// /// Note: If you attempt to retrieve the MainModule of a 64-bit /// process from a WOW64 (32-bit) process, the Win32 API has a fatal /// flaw that causes this to return the same error. /// /// If you need the MainModule of a 64-bit process from a WOW64 /// process, you will need to write the P/Invoke yourself. /// /// /// The process from which to /// retrieve the MainModule /// /// You are trying to access the MainModule property for a process that is running /// on a remote computer. This property is available only for processes that are /// running on the local computer. /// /// /// The process Id is not available (or) The process has exited. /// /// /// /// internal static ProcessModule GetMainModule(Process targetProcess) { int caughtCount = 0; ProcessModule mainModule = null; while (mainModule == null) { try { mainModule = targetProcess.MainModule; } catch (System.ComponentModel.Win32Exception e) { // If this is an Access Denied error (which can happen with thread impersonation) // then re-throw immediately. if (e.NativeErrorCode == 5) throw; // Otherwise retry to ensure module is loaded. caughtCount++; System.Threading.Thread.Sleep(100); if (caughtCount == 5) throw; } } return mainModule; } // Cache of the current process' parentId private static int? s_currentParentProcessId; private static readonly int s_currentProcessId = Process.GetCurrentProcess().Id; /// /// 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. /// /// /// The process we want to find the /// parent of 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; } } /// /// Returns processor architecture for the current process. /// If powershell is running inside Wow64, then is returned. /// /// processor architecture for the current process internal static ProcessorArchitecture GetProcessorArchitecture(out bool isRunningOnArm) { var sysInfo = new NativeMethods.SYSTEM_INFO(); NativeMethods.GetSystemInfo(ref sysInfo); ProcessorArchitecture result; isRunningOnArm = false; switch (sysInfo.wProcessorArchitecture) { case NativeMethods.PROCESSOR_ARCHITECTURE_IA64: result = ProcessorArchitecture.IA64; break; case NativeMethods.PROCESSOR_ARCHITECTURE_AMD64: result = ProcessorArchitecture.Amd64; break; case NativeMethods.PROCESSOR_ARCHITECTURE_INTEL: result = ProcessorArchitecture.X86; break; case NativeMethods.PROCESSOR_ARCHITECTURE_ARM: result = ProcessorArchitecture.None; isRunningOnArm = true; break; default: result = ProcessorArchitecture.None; break; } return result; } /// /// Return true/false to indicate whether the processor architecture is ARM /// /// internal static bool IsRunningOnProcessorArchitectureARM() { Architecture arch = RuntimeInformation.OSArchitecture; return arch == Architecture.Arm || arch == Architecture.Arm64; } /// /// Get a temporary directory to use, needs to be unique to avoid collision /// internal static string GetTemporaryDirectory() { string tempDir = String.Empty; string tempPath = Path.GetTempPath(); do { tempDir = Path.Combine(tempPath,System.Guid.NewGuid().ToString()); } while (Directory.Exists(tempDir)); try { Directory.CreateDirectory(tempDir); } catch (UnauthorizedAccessException) { tempDir = String.Empty; // will become current working directory } return tempDir; } internal static string GetHostName() { // Note: non-windows CoreCLR does not support System.Net yet if (Platform.IsWindows) { return WinGetHostName(); } else { return Platform.NonWindowsGetHostName(); } } internal static string WinGetHostName() { System.Net.NetworkInformation.IPGlobalProperties ipProperties = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties(); string hostname = ipProperties.HostName; if (!String.IsNullOrEmpty(ipProperties.DomainName)) { hostname = hostname + "." + ipProperties.DomainName; } return hostname; } internal static uint GetNativeThreadId() { #if UNIX return Platform.NonWindowsGetThreadId(); #else return NativeMethods.GetCurrentThreadId(); #endif } private static class NativeMethods { // Important: // this clone has a clone in SMA in admin\monad\src\m3p\product\ServiceCore\WorkflowCore\WorkflowRuntimeCompilation.cs // if you are making any changes specific to this class then update the clone as well. internal const ushort PROCESSOR_ARCHITECTURE_INTEL = 0; internal const ushort PROCESSOR_ARCHITECTURE_ARM = 5; internal const ushort PROCESSOR_ARCHITECTURE_IA64 = 6; internal const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9; internal const ushort PROCESSOR_ARCHITECTURE_UNKNOWN = 0xFFFF; [StructLayout(LayoutKind.Sequential)] internal struct SYSTEM_INFO { public ushort wProcessorArchitecture; public ushort wReserved; public uint dwPageSize; public IntPtr lpMinimumApplicationAddress; public IntPtr lpMaximumApplicationAddress; public UIntPtr dwActiveProcessorMask; public uint dwNumberOfProcessors; public uint dwProcessorType; public uint dwAllocationGranularity; public ushort wProcessorLevel; public ushort wProcessorRevision; }; [DllImport(PinvokeDllNames.GetSystemInfoDllName)] internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo); [DllImport(PinvokeDllNames.GetCurrentThreadIdDllName)] internal static extern uint GetCurrentThreadId(); } #region ASTUtils /// /// 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. /// /// A using expression /// Base64 encoded string as the key of the UsingExpressionAst 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 /// /// Evaluate a powershell data file as if it's a module manifest /// /// /// /// /// /// 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); } /// /// Get a Hashtable object out of a PowerShell data file (.psd1) /// /// /// Name of the parameter that takes the specified .psd1 file as a value /// /// /// Path to the powershell data file /// /// /// ExecutionContext to use /// /// /// Set of command names that are allowed to use in the .psd1 file /// /// /// Set of variable names that are allowed to use in the .psd1 file /// /// /// If true, allow to use environment variables in the .psd1 file /// /// /// If true, caller guarantees the path is valid /// /// internal static Hashtable EvaluatePowerShellDataFile( string parameterName, string psDataFilePath, ExecutionContext context, IEnumerable allowedCommands, IEnumerable allowedVariables, bool allowEnvironmentVariables, bool skipPathValidation) { if (!skipPathValidation && string.IsNullOrEmpty(parameterName)) { throw PSTraceSource.NewArgumentNullException("parameterName"); } if (string.IsNullOrEmpty(psDataFilePath)) { throw PSTraceSource.NewArgumentNullException("psDataFilePath"); } if (context == null) { throw PSTraceSource.NewArgumentNullException("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); } var retResult = evaluationResult as Hashtable; if (retResult == null) { 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[] FastModuleManifestAnalysisPropertyNames = new[] { "AliasesToExport", "CmdletsToExport", "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); } string unused1; string unused2; var pipeline = ast.GetSimplePipeline(false, out unused1, out unused2); if (pipeline != null) { var hashtableAst = pipeline.GetPureExpression() as HashtableAst; if (hashtableAst != null) { var result = new Hashtable(StringComparer.OrdinalIgnoreCase); foreach (var pair in hashtableAst.KeyValuePairs) { var key = pair.Item1 as StringConstantExpressionAst; if (key != null && 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); } } /// /// This class provides helper methods for converting to/fro from /// string to base64string /// internal static class StringToBase64Converter { /// /// Converts string to base64 encoded string /// /// string to encode /// base64 encoded string 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 (null == input) { throw PSTraceSource.NewArgumentNullException("input"); } string base64 = Convert.ToBase64String ( Encoding.Unicode.GetBytes(input.ToCharArray()) ); return base64; } /// /// Decodes base64 encoded string /// /// base64 string to decode /// decoded string internal static string Base64ToString(string base64) { if (string.IsNullOrEmpty(base64)) { throw PSTraceSource.NewArgumentNullException("base64"); } string output = new string(Encoding.Unicode.GetChars(Convert.FromBase64String(base64))); return output; } /// /// Decodes base64 encoded string in to args array /// /// /// internal static object[] Base64ToArgsConverter(string base64) { if (string.IsNullOrEmpty(base64)) { throw PSTraceSource.NewArgumentNullException("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() == false) { //This helper function should move to host and it should provide appropriate //error message there. throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } PSObject mo = dso as PSObject; if (mo == null) { //This helper function should move the host. Provide appropriate error message. //Format of args parameter is not correct. throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } var argsList = mo.BaseObject as ArrayList; if (argsList == null) { //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(); } } #region ReferenceEqualityComparer /// /// Equality comparer based on Object Identity /// 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 }