MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Diagnostics;7using System.IO;8using System.Linq;9using System.Management.Automation.Language;10using System.Net.NetworkInformation;11using System.Runtime.CompilerServices;12using System.Runtime.InteropServices;13using System.Text;14using System.Xml;15 16namespace System.Management.Automation17{18 /// <summary>19 /// Defines generic utilities and helper methods for PowerShell.20 /// </summary>21 internal static class PsUtils22 {23 // Cache of the current process' parentId24 private static int? s_currentParentProcessId;25 private static readonly int s_currentProcessId = Environment.ProcessId;26 27 /// <summary>28 /// Retrieve the parent process of a process.29 ///30 /// Previously this code used WMI, but WMI is causing a CPU spike whenever the query gets called as it results in31 /// tzres.dll and tzres.mui.dll being loaded into every process to convert the time information to local format.32 /// For perf reasons, we resort to P/Invoke.33 /// </summary>34 /// <param name="current">The process we want to find the35 /// parent of</param>36 internal static Process GetParentProcess(Process current)37 {38 var processId = current.Id;39 40 // This is a common query (parent id for the current process)41 // Use cached value if available42 var parentProcessId = processId == s_currentProcessId && s_currentParentProcessId.HasValue ?43 s_currentParentProcessId.Value :44 Microsoft.PowerShell.ProcessCodeMethods.GetParentPid(current);45 46 // cache the current process parent pid if it hasn't been done yet47 if (processId == s_currentProcessId && !s_currentParentProcessId.HasValue)48 {49 s_currentParentProcessId = parentProcessId;50 }51 52 if (parentProcessId == 0)53 return null;54 55 try56 {57 Process returnProcess = Process.GetProcessById(parentProcessId);58 59 // Ensure the process started before the current60 // process, as it could have gone away and had the61 // PID recycled.62 if (returnProcess.StartTime <= current.StartTime)63 return returnProcess;64 else65 return null;66 }67 catch (ArgumentException)68 {69 // GetProcessById throws an ArgumentException when70 // you reach the top of the chain -- Explorer.exe71 // has a parent process, but you cannot retrieve it.72 return null;73 }74 }75 76 /// <summary>77 /// Return true/false to indicate whether the process architecture is ARM.78 /// </summary>79 /// <returns></returns>80 internal static bool IsRunningOnProcessArchitectureARM()81 {82 Architecture arch = RuntimeInformation.ProcessArchitecture;83 return arch == Architecture.Arm || arch == Architecture.Arm64;84 }85 86 internal static string GetHostName()87 {88 IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();89 90 string hostname = ipProperties.HostName;91 string domainName = ipProperties.DomainName;92 93 // CoreFX on Unix calls GLibc getdomainname()94 // which returns "(none)" if a domain name is not set by setdomainname()95 if (!string.IsNullOrEmpty(domainName) && !domainName.Equals("(none)", StringComparison.Ordinal))96 {97 hostname = hostname + "." + domainName;98 }99 100 return hostname;101 }102 103 internal static uint GetNativeThreadId()104 {105#if UNIX106 return Platform.NonWindowsGetThreadId();107#else108 return Interop.Windows.GetCurrentThreadId();109#endif110 }111 112 #region ASTUtils113 114 /// <summary>115 /// This method is to get the unique key for a UsingExpressionAst. The key is a base64116 /// encoded string based on the text of the UsingExpressionAst.117 ///118 /// This method is used when handling a script block that contains $using for Invoke-Command.119 ///120 /// When run Invoke-Command targeting a machine that runs PSv3 or above, we pass a dictionary121 /// to the remote end that contains the key of each UsingExpressionAst and its value. This method122 /// is used to generate the key.123 /// </summary>124 /// <param name="usingAst">A using expression.</param>125 /// <returns>Base64 encoded string as the key of the UsingExpressionAst.</returns>126 internal static string GetUsingExpressionKey(Language.UsingExpressionAst usingAst)127 {128 Diagnostics.Assert(usingAst != null, "Caller makes sure the parameter is not null");129 130 // We cannot call ToLowerInvariant unconditionally, because usingAst might131 // contain IndexExpressionAst in its SubExpression, such as132 // $using:bar["AAAA"]133 // and the index "AAAA" might not get us the same value as "aaaa".134 //135 // But we do want a unique key to represent the same UsingExpressionAst's as much136 // as possible, so as to avoid sending redundant key-value's to remote machine.137 // As a workaround, we call ToLowerInvariant when the SubExpression of usingAst138 // is a VariableExpressionAst, because:139 // (1) Variable name is case insensitive;140 // (2) People use $using to refer to a variable most of the time.141 string usingAstText = usingAst.ToString();142 if (usingAst.SubExpression is Language.VariableExpressionAst)143 {144 usingAstText = usingAstText.ToLowerInvariant();145 }146 147 return StringToBase64Converter.StringToBase64String(usingAstText);148 }149 150 #endregion ASTUtils151 152 #region EvaluatePowerShellDataFile153 154 /// <summary>155 /// Evaluate a powershell data file as if it's a module manifest.156 /// </summary>157 /// <param name="parameterName"></param>158 /// <param name="psDataFilePath"></param>159 /// <param name="context"></param>160 /// <param name="skipPathValidation"></param>161 /// <returns></returns>162 internal static Hashtable EvaluatePowerShellDataFileAsModuleManifest(163 string parameterName,164 string psDataFilePath,165 ExecutionContext context,166 bool skipPathValidation)167 {168 // Use the same capabilities as the module manifest169 // e.g. allow 'PSScriptRoot' variable170 return EvaluatePowerShellDataFile(171 parameterName,172 psDataFilePath,173 context,174 Microsoft.PowerShell.Commands.ModuleCmdletBase.PermittedCmdlets,175 new[] { "PSScriptRoot" },176 allowEnvironmentVariables: true,177 skipPathValidation: skipPathValidation);178 }179 180 /// <summary>181 /// Get a Hashtable object out of a PowerShell data file (.psd1)182 /// </summary>183 /// <param name="parameterName">184 /// Name of the parameter that takes the specified .psd1 file as a value185 /// </param>186 /// <param name="psDataFilePath">187 /// Path to the powershell data file188 /// </param>189 /// <param name="context">190 /// ExecutionContext to use191 /// </param>192 /// <param name="allowedCommands">193 /// Set of command names that are allowed to use in the .psd1 file194 /// </param>195 /// <param name="allowedVariables">196 /// Set of variable names that are allowed to use in the .psd1 file197 /// </param>198 /// <param name="allowEnvironmentVariables">199 /// If true, allow to use environment variables in the .psd1 file200 /// </param>201 /// <param name="skipPathValidation">202 /// If true, caller guarantees the path is valid203 /// </param>204 /// <returns></returns>205 internal static Hashtable EvaluatePowerShellDataFile(206 string parameterName,207 string psDataFilePath,208 ExecutionContext context,209 IEnumerable<string> allowedCommands,210 IEnumerable<string> allowedVariables,211 bool allowEnvironmentVariables,212 bool skipPathValidation)213 {214 if (!skipPathValidation && string.IsNullOrEmpty(parameterName))215 {216 throw PSTraceSource.NewArgumentNullException(nameof(parameterName));217 }218 219 if (string.IsNullOrEmpty(psDataFilePath))220 {221 throw PSTraceSource.NewArgumentNullException(nameof(psDataFilePath));222 }223 224 if (context == null)225 {226 throw PSTraceSource.NewArgumentNullException(nameof(context));227 }228 229 string resolvedPath;230 if (skipPathValidation)231 {232 resolvedPath = psDataFilePath;233 }234 else235 {236 #region "ValidatePowerShellDataFilePath"237 238 bool isPathValid = true;239 240 // File extension needs to be .psd1241 string pathExt = Path.GetExtension(psDataFilePath);242 if (string.IsNullOrEmpty(pathExt) ||243 !StringLiterals.PowerShellDataFileExtension.Equals(pathExt, StringComparison.OrdinalIgnoreCase))244 {245 isPathValid = false;246 }247 248 ProviderInfo provider;249 var resolvedPaths = context.SessionState.Path.GetResolvedProviderPathFromPSPath(psDataFilePath, out provider);250 251 // ConfigPath should be resolved as FileSystem provider252 if (provider == null || !Microsoft.PowerShell.Commands.FileSystemProvider.ProviderName.Equals(provider.Name, StringComparison.OrdinalIgnoreCase))253 {254 isPathValid = false;255 }256 257 // ConfigPath should be resolved to a single path258 if (resolvedPaths.Count != 1)259 {260 isPathValid = false;261 }262 263 if (!isPathValid)264 {265 throw PSTraceSource.NewArgumentException(266 parameterName,267 ParserStrings.CannotResolvePowerShellDataFilePath,268 psDataFilePath);269 }270 271 resolvedPath = resolvedPaths[0];272 273 #endregion "ValidatePowerShellDataFilePath"274 }275 276 #region "LoadAndEvaluatePowerShellDataFile"277 278 object evaluationResult;279 try280 {281 // Create the scriptInfo for the .psd1 file282 string dataFileName = Path.GetFileName(resolvedPath);283 var dataFileScriptInfo = new ExternalScriptInfo(dataFileName, resolvedPath, context);284 ScriptBlock scriptBlock = dataFileScriptInfo.ScriptBlock;285 286 // Validate the scriptblock287 scriptBlock.CheckRestrictedLanguage(allowedCommands, allowedVariables, allowEnvironmentVariables);288 289 // Evaluate the scriptblock290 object oldPsScriptRoot = context.GetVariableValue(SpecialVariables.PSScriptRootVarPath);291 try292 {293 // Set the $PSScriptRoot before the evaluation294 context.SetVariable(SpecialVariables.PSScriptRootVarPath, Path.GetDirectoryName(resolvedPath));295 evaluationResult = PSObject.Base(scriptBlock.InvokeReturnAsIs());296 }297 finally298 {299 context.SetVariable(SpecialVariables.PSScriptRootVarPath, oldPsScriptRoot);300 }301 }302 catch (RuntimeException ex)303 {304 throw PSTraceSource.NewInvalidOperationException(305 ex,306 ParserStrings.CannotLoadPowerShellDataFile,307 psDataFilePath,308 ex.Message);309 }310 311 if (evaluationResult is not Hashtable retResult)312 {313 throw PSTraceSource.NewInvalidOperationException(314 ParserStrings.InvalidPowerShellDataFile,315 psDataFilePath);316 }317 318 #endregion "LoadAndEvaluatePowerShellDataFile"319 320 return retResult;321 }322 323 #endregion EvaluatePowerShellDataFile324 325 internal static readonly string[] ManifestModuleVersionPropertyName = new[] { "ModuleVersion" };326 internal static readonly string[] ManifestGuidPropertyName = new[] { "GUID" };327 internal static readonly string[] ManifestPrivateDataPropertyName = new[] { "PrivateData" };328 329 internal static readonly string[] FastModuleManifestAnalysisPropertyNames = new[]330 {331 "AliasesToExport",332 "CmdletsToExport",333 "CompatiblePSEditions",334 "FunctionsToExport",335 "NestedModules",336 "RootModule",337 "ModuleToProcess",338 "ModuleVersion"339 };340 341 internal static Hashtable GetModuleManifestProperties(string psDataFilePath, string[] keys)342 {343 string dataFileContents = File.ReadAllText(psDataFilePath, Encoding.Default);344 ParseError[] parseErrors;345 var ast = (new Parser()).Parse(psDataFilePath, dataFileContents, null, out parseErrors, ParseMode.ModuleAnalysis);346 if (parseErrors.Length > 0)347 {348 var pe = new ParseException(parseErrors);349 throw PSTraceSource.NewInvalidOperationException(350 pe,351 ParserStrings.CannotLoadPowerShellDataFile,352 psDataFilePath,353 pe.Message);354 }355 356 var pipeline = ast.GetSimplePipeline(false, out _, out _);357 if (pipeline?.GetPureExpression() is HashtableAst hashtableAst)358 {359 var result = new Hashtable(StringComparer.OrdinalIgnoreCase);360 foreach (var pair in hashtableAst.KeyValuePairs)361 {362 if (pair.Item1 is StringConstantExpressionAst key && keys.Contains(key.Value, StringComparer.OrdinalIgnoreCase))363 {364 try365 {366 var val = pair.Item2.SafeGetValue();367 result[key.Value] = val;368 }369 catch370 {371 throw PSTraceSource.NewInvalidOperationException(372 ParserStrings.InvalidPowerShellDataFile,373 psDataFilePath);374 }375 }376 }377 378 return result;379 }380 381 throw PSTraceSource.NewInvalidOperationException(382 ParserStrings.InvalidPowerShellDataFile,383 psDataFilePath);384 }385 }386 387 /// <summary>388 /// This class provides helper methods for converting to/fro from389 /// string to base64string.390 /// </summary>391 internal static class StringToBase64Converter392 {393 /// <summary>394 /// Converts string to base64 encoded string.395 /// </summary>396 /// <param name="input">String to encode.</param>397 /// <returns>Base64 encoded string.</returns>398 internal static string StringToBase64String(string input)399 {400 // NTRAID#Windows Out Of Band Releases-926471-2005/12/27-JonN401 // shell crashes if you pass an empty script block to a native command402 if (input == null)403 {404 throw PSTraceSource.NewArgumentNullException(nameof(input));405 }406 407 string base64 = Convert.ToBase64String408 (409 Encoding.Unicode.GetBytes(input.ToCharArray())410 );411 return base64;412 }413 414 /// <summary>415 /// Decodes base64 encoded string.416 /// </summary>417 /// <param name="base64">Base64 string to decode.</param>418 /// <returns>Decoded string.</returns>419 internal static string Base64ToString(string base64)420 {421 if (string.IsNullOrEmpty(base64))422 {423 throw PSTraceSource.NewArgumentNullException(nameof(base64));424 }425 426 string output = new string(Encoding.Unicode.GetChars(Convert.FromBase64String(base64)));427 return output;428 }429 430 /// <summary>431 /// Decodes base64 encoded string in to args array.432 /// </summary>433 /// <param name="base64"></param>434 /// <returns></returns>435 internal static object[] Base64ToArgsConverter(string base64)436 {437 if (string.IsNullOrEmpty(base64))438 {439 throw PSTraceSource.NewArgumentNullException(nameof(base64));440 }441 442 string decoded = new string(Encoding.Unicode.GetChars(Convert.FromBase64String(base64)));443 444 // Deserialize string445 XmlReader reader = XmlReader.Create(new StringReader(decoded), InternalDeserializer.XmlReaderSettingsForCliXml);446 object dso;447 Deserializer deserializer = new Deserializer(reader);448 dso = deserializer.Deserialize();449 if (!deserializer.Done())450 {451 // This helper function should move to host and it should provide appropriate452 // error message there.453 throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter);454 }455 456 if (dso is not PSObject mo)457 {458 // This helper function should move the host. Provide appropriate error message.459 // Format of args parameter is not correct.460 throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter);461 }462 463 if (mo.BaseObject is not ArrayList argsList)464 {465 // This helper function should move the host. Provide appropriate error message.466 // Format of args parameter is not correct.467 throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter);468 }469 470 return argsList.ToArray();471 }472 }473 474 /// <summary>475 /// A simple implementation of CRC32.476 /// See "CRC-32 algorithm" in https://en.wikipedia.org/wiki/Cyclic_redundancy_check.477 /// </summary>478 internal static class CRC32Hash479 {480 // CRC-32C polynomial representations481 private const uint polynomial = 0x1EDC6F41;482 483 private static readonly uint[] table;484 485 static CRC32Hash()486 {487 uint temp = 0;488 table = new uint[256];489 490 for (int i = 0; i < table.Length; i++)491 {492 temp = (uint)i;493 for (int j = 0; j < 8; j++)494 {495 if ((temp & 1) == 1)496 {497 temp = (temp >> 1) ^ polynomial;498 }499 else500 {501 temp >>= 1;502 }503 }504 505 table[i] = temp;506 }507 }508 509 private static uint Compute(byte[] buffer)510 {511 uint crc = 0xFFFFFFFF;512 for (int i = 0; i < buffer.Length; ++i)513 {514 var index = (byte)(crc ^ buffer[i] & 0xff);515 crc = (crc >> 8) ^ table[index];516 }517 518 return ~crc;519 }520 521 internal static byte[] ComputeHash(byte[] buffer)522 {523 uint crcResult = Compute(buffer);524 return BitConverter.GetBytes(crcResult);525 }526 527 internal static string ComputeHash(string input)528 {529 byte[] hashBytes = ComputeHash(Encoding.UTF8.GetBytes(input));530 return Convert.ToHexString(hashBytes);531 }532 }533 534 #region ReferenceEqualityComparer535 536 /// <summary>537 /// Equality comparer based on Object Identity.538 /// </summary>539 internal class ReferenceEqualityComparer : IEqualityComparer540 {541 bool IEqualityComparer.Equals(object x, object y)542 {543 return Object.ReferenceEquals(x, y);544 }545 546 int IEqualityComparer.GetHashCode(object obj)547 {548 // The Object.GetHashCode and RuntimeHelpers.GetHashCode methods are used in the following scenarios:549 //550 // Object.GetHashCode is useful in scenarios where you care about object value. Two strings with identical551 // contents will return the same value for Object.GetHashCode.552 //553 // RuntimeHelpers.GetHashCode is useful in scenarios where you care about object identity. Two strings with554 // identical contents will return different values for RuntimeHelpers.GetHashCode, because they are different555 // string objects, although their contents are the same.556 557 return RuntimeHelpers.GetHashCode(obj);558 }559 }560 561 #endregion562}563 