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.Collections.ObjectModel;7using System.Collections.Specialized;8using System.Globalization;9using System.IO;10using System.Linq;11using System.Management.Automation.Host;12using System.Management.Automation.Internal;13using System.Management.Automation.Internal.Host;14using System.Management.Automation.Language;15using System.Management.Automation.Runspaces;16using System.Management.Automation.Security;17using System.Numerics;18using System.Reflection;19using System.Runtime.CompilerServices;20using System.Text.RegularExpressions;21 22using Microsoft.PowerShell.Commands;23using Microsoft.PowerShell.Commands.Internal.Format;24 25// ReSharper disable UnusedMember.Global26 27namespace System.Management.Automation28{29 internal static class PipelineOps30 {31 private static CommandProcessorBase AddCommand(PipelineProcessor pipe,32 CommandParameterInternal[] commandElements,33 CommandBaseAst commandBaseAst,34 CommandRedirection[] redirections,35 ExecutionContext context)36 {37 var commandAst = commandBaseAst as CommandAst;38 var invocationToken = commandAst != null ? commandAst.InvocationOperator : TokenKind.Unknown;39 bool dotSource = invocationToken == TokenKind.Dot;40 SessionStateInternal commandSessionState = null;41 int commandIndex = 0;42 43 Diagnostics.Assert(commandElements[0].ArgumentSpecified && !commandElements[0].ParameterNameSpecified,44 "Compiler will pass first parameter as an argument.");45 var mi = PSObject.Base(commandElements[0].ArgumentValue) as PSModuleInfo;46 if (mi != null)47 {48 if (mi.ModuleType == ModuleType.Binary && mi.SessionState == null)49 {50 throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),51 null, "CantInvokeInBinaryModule", ParserStrings.CantInvokeInBinaryModule, mi.Name);52 }53 else if (mi.SessionState == null)54 {55 throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),56 null, "CantInvokeInNonImportedModule", ParserStrings.CantInvokeInNonImportedModule, mi.Name);57 }58 else if ((invocationToken == TokenKind.Ampersand || invocationToken == TokenKind.Dot) && mi.LanguageMode != context.LanguageMode)59 {60 if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit)61 {62 // Disallow FullLanguage "& (Get-Module MyModule) MyPrivateFn" from ConstrainedLanguage because it always63 // runs "internal" origin and so has access to all functions, including non-exported functions.64 // Otherwise we end up leaking non-exported functions that run in FullLanguage.65 throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null,66 "CantInvokeCallOperatorAcrossLanguageBoundaries", ParserStrings.CantInvokeCallOperatorAcrossLanguageBoundaries);67 }68 69 // In audit mode, report but don't enforce.70 SystemPolicy.LogWDACAuditMessage(71 context: context,72 title: ParserStrings.WDACParserModuleScopeCallOperatorLogTitle,73 message: ParserStrings.WDACParserModuleScopeCallOperatorLogMessage,74 fqid: "ModuleScopeCallOperatorNotAllowed",75 dropIntoDebugger: true);76 }77 78 commandSessionState = mi.SessionState.Internal;79 commandIndex += 1;80 }81 82 object command;83 IScriptExtent commandExtent;84 var cpiCommand = commandElements[commandIndex];85 if (cpiCommand.ParameterNameSpecified)86 {87 command = cpiCommand.ParameterText;88 commandExtent = cpiCommand.ParameterExtent;89 if (cpiCommand.ArgumentSpecified)90 {91 // BUG: we've seen something like:92 // & (gmo Module) -foo: bar93 // The command is -foo:, and bar is an argument, but both are in commandElements[commandIndex],94 // so we won't add 'bar' as an argument.95 }96 }97 else98 {99 command = PSObject.Base(cpiCommand.ArgumentValue);100 commandExtent = cpiCommand.ArgumentExtent;101 }102 103 string invocationName = (dotSource) ? "." : invocationToken == TokenKind.Ampersand ? "&" : null;104 CommandProcessorBase commandProcessor;105 var scriptBlock = command as ScriptBlock;106 if (scriptBlock != null)107 {108 commandProcessor = CommandDiscovery.CreateCommandProcessorForScript(scriptBlock, context, !dotSource, commandSessionState);109 }110 else111 {112 var commandInfo = command as CommandInfo;113 if (commandInfo != null)114 {115 commandProcessor = context.CommandDiscovery.LookupCommandProcessor(commandInfo, context.EngineSessionState.CurrentScope.ScopeOrigin, !dotSource, commandSessionState);116 }117 else118 {119 var commandName = command as string ?? PSObject.ToStringParser(context, command);120 invocationName ??= commandName;121 122 if (string.IsNullOrEmpty(commandName))123 {124 throw InterpreterError.NewInterpreterException(125 command,126 typeof(RuntimeException),127 commandExtent,128 "BadExpression",129 ParserStrings.BadExpression,130 dotSource ? "." : "&");131 }132 133 try134 {135 // See if we need to resolve the command in a different session state136 // as will be the case with modules...137 // BUGBUG - this can be cleaned up by fixing the overload on execution context (but not easily.)138 if (commandSessionState != null)139 {140 SessionStateInternal oldSessionState = context.EngineSessionState;141 try142 {143 context.EngineSessionState = commandSessionState;144 commandProcessor = context.CreateCommand(commandName, dotSource);145 }146 finally147 {148 context.EngineSessionState = oldSessionState;149 }150 }151 else152 {153 commandProcessor = context.CreateCommand(commandName, dotSource);154 }155 }156 catch (RuntimeException rte)157 {158 // CreateCommand doesn't have the context to set InvocationInfo properly159 // so we'll do it here instead...160 if (rte.ErrorRecord.InvocationInfo == null)161 {162 InvocationInfo invocationInfo = new InvocationInfo(null, commandExtent, context)163 { InvocationName = invocationName };164 rte.ErrorRecord.SetInvocationInfo(invocationInfo);165 }166 167 throw;168 }169 }170 }171 172 InternalCommand cmd = commandProcessor.Command;173 commandProcessor.UseLocalScope = !dotSource &&174 (cmd is ScriptCommand || cmd is PSScriptCmdlet);175 176 bool isNativeCommand = commandProcessor is NativeCommandProcessor;177 178 for (int i = commandIndex + 1; i < commandElements.Length; ++i)179 {180 var cpi = commandElements[i];181 182 if (cpi.ParameterNameSpecified)183 {184 // Skip adding the special -- parameter unless we're invoking a native command.185 if (cpi.ParameterName.Equals("-", StringComparison.OrdinalIgnoreCase) && !isNativeCommand)186 {187 continue;188 }189 }190 191 if (cpi.ArgumentToBeSplatted)192 {193 foreach (var splattedCpi in Splat(cpi.ArgumentValue, cpi.ArgumentAst))194 {195 commandProcessor.AddParameter(splattedCpi);196 }197 }198 else199 {200 commandProcessor.AddParameter(cpi);201 }202 }203 204 string helpTarget;205 HelpCategory helpCategory;206 if (commandProcessor.IsHelpRequested(out helpTarget, out helpCategory))207 {208 commandProcessor = CommandProcessorBase.CreateGetHelpCommandProcessor(context, helpTarget, helpCategory);209 }210 211 commandProcessor.Command.InvocationExtent = commandBaseAst.Extent;212 commandProcessor.Command.MyInvocation.ScriptPosition = commandBaseAst.Extent;213 commandProcessor.Command.MyInvocation.InvocationName = invocationName;214 215 pipe.Add(commandProcessor);216 217 bool redirectedError = false;218 bool redirectedWarning = false;219 bool redirectedVerbose = false;220 bool redirectedDebug = false;221 bool redirectedInformation = false;222 if (redirections != null)223 {224 if (isNativeCommand)225 {226 foreach (CommandRedirection redirection in redirections)227 {228 if (redirection is MergingRedirection)229 {230 redirection.Bind(pipe, commandProcessor, context);231 }232 }233 }234 235 foreach (CommandRedirection redirection in redirections)236 {237 if (!isNativeCommand || redirection is not MergingRedirection)238 {239 redirection.Bind(pipe, commandProcessor, context);240 }241 242 switch (redirection.FromStream)243 {244 case RedirectionStream.Error:245 redirectedError = true;246 break;247 248 case RedirectionStream.Warning:249 redirectedWarning = true;250 break;251 252 case RedirectionStream.Verbose:253 redirectedVerbose = true;254 break;255 256 case RedirectionStream.Debug:257 redirectedDebug = true;258 break;259 260 case RedirectionStream.Information:261 redirectedInformation = true;262 break;263 264 case RedirectionStream.All:265 redirectedError = true;266 redirectedWarning = true;267 redirectedVerbose = true;268 redirectedDebug = true;269 redirectedInformation = true;270 break;271 }272 }273 }274 275 // Pipe redirection can also be specified via the ExecutionContext pipes.276 if (!redirectedError)277 {278 if (context.ShellFunctionErrorOutputPipe != null)279 {280 commandProcessor.CommandRuntime.ErrorOutputPipe = context.ShellFunctionErrorOutputPipe;281 }282 else283 {284 commandProcessor.CommandRuntime.ErrorOutputPipe.ExternalWriter = context.ExternalErrorOutput;285 }286 }287 288 if (!redirectedWarning && (context.ExpressionWarningOutputPipe != null))289 {290 commandProcessor.CommandRuntime.WarningOutputPipe = context.ExpressionWarningOutputPipe;291 redirectedWarning = true;292 }293 294 if (!redirectedVerbose && (context.ExpressionVerboseOutputPipe != null))295 {296 commandProcessor.CommandRuntime.VerboseOutputPipe = context.ExpressionVerboseOutputPipe;297 redirectedVerbose = true;298 }299 300 if (!redirectedDebug && (context.ExpressionDebugOutputPipe != null))301 {302 commandProcessor.CommandRuntime.DebugOutputPipe = context.ExpressionDebugOutputPipe;303 redirectedDebug = true;304 }305 306 if (!redirectedInformation && (context.ExpressionInformationOutputPipe != null))307 {308 commandProcessor.CommandRuntime.InformationOutputPipe = context.ExpressionInformationOutputPipe;309 redirectedInformation = true;310 }311 312 // Warning, Verbose, Debug should pick up any redirection information from its parent command runtime object.313 if (context.CurrentCommandProcessor != null && context.CurrentCommandProcessor.CommandRuntime != null)314 {315 if (!redirectedWarning &&316 context.CurrentCommandProcessor.CommandRuntime.WarningOutputPipe != null)317 {318 commandProcessor.CommandRuntime.WarningOutputPipe = context.CurrentCommandProcessor.CommandRuntime.WarningOutputPipe;319 }320 321 if (!redirectedVerbose &&322 context.CurrentCommandProcessor.CommandRuntime.VerboseOutputPipe != null)323 {324 commandProcessor.CommandRuntime.VerboseOutputPipe = context.CurrentCommandProcessor.CommandRuntime.VerboseOutputPipe;325 }326 327 if (!redirectedDebug &&328 context.CurrentCommandProcessor.CommandRuntime.DebugOutputPipe != null)329 {330 commandProcessor.CommandRuntime.DebugOutputPipe = context.CurrentCommandProcessor.CommandRuntime.DebugOutputPipe;331 }332 333 if (!redirectedInformation &&334 context.CurrentCommandProcessor.CommandRuntime.InformationOutputPipe != null)335 {336 commandProcessor.CommandRuntime.InformationOutputPipe = context.CurrentCommandProcessor.CommandRuntime.InformationOutputPipe;337 }338 }339 340 return commandProcessor;341 }342 343 internal static IEnumerable<CommandParameterInternal> Splat(object splattedValue, Ast splatAst)344 {345 splattedValue = PSObject.Base(splattedValue);346 347 var markUntrustedData = false;348 if (ExecutionContext.HasEverUsedConstrainedLanguage)349 {350 // If the value to be splatted is untrusted, then make sure sub-values held by it are351 // also marked as untrusted.352 markUntrustedData = ExecutionContext.IsMarkedAsUntrusted(splattedValue);353 }354 355 IDictionary splattedTable = splattedValue as IDictionary;356 if (splattedTable != null)357 {358 foreach (DictionaryEntry de in splattedTable)359 {360 string parameterName = de.Key.ToString();361 object parameterValue = de.Value;362 string parameterText = GetParameterText(parameterName);363 364 if (markUntrustedData)365 {366 ExecutionContext.MarkObjectAsUntrusted(parameterValue);367 }368 369 yield return CommandParameterInternal.CreateParameterWithArgument(370 parameterAst: splatAst,371 parameterName: parameterName,372 parameterText: parameterText,373 argumentAst: splatAst,374 value: parameterValue,375 spaceAfterParameter: false,376 fromSplatting: true);377 }378 }379 else380 {381 IEnumerable enumerableValue = splattedValue as IEnumerable;382 if (enumerableValue != null)383 {384 foreach (object obj in enumerableValue)385 {386 if (markUntrustedData)387 {388 ExecutionContext.MarkObjectAsUntrusted(obj);389 }390 391 yield return SplatEnumerableElement(obj, splatAst);392 }393 }394 else395 {396 yield return SplatEnumerableElement(splattedValue, splatAst);397 }398 }399 }400 401 private static CommandParameterInternal SplatEnumerableElement(object splattedArgument, Ast splatAst)402 {403 var psObject = splattedArgument as PSObject;404 if (psObject != null)405 {406 var prop = psObject.Properties[ScriptParameterBinderController.NotePropertyNameForSplattingParametersInArgs];407 var baseObj = psObject.BaseObject;408 if (prop != null && prop.Value is string && baseObj is string)409 {410 return CommandParameterInternal.CreateParameter((string)prop.Value, (string)baseObj, splatAst);411 }412 }413 414 return CommandParameterInternal.CreateArgument(splattedArgument, splatAst);415 }416 417 private static string GetParameterText(string parameterName)418 {419 Diagnostics.Assert(parameterName != null, "caller makes sure the parameterName is not null");420 int endPosition = parameterName.Length;421 while ((endPosition > 0) && char.IsWhiteSpace(parameterName[endPosition - 1]))422 {423 endPosition--;424 }425 426 if (endPosition == 0 || parameterName[endPosition - 1] == ':')427 {428 return "-" + parameterName;429 }430 431 string parameterText;432 if (endPosition == parameterName.Length)433 {434 parameterText = "-" + parameterName + ":";435 }436 else437 {438 string whitespaces = parameterName.Substring(endPosition);439 parameterText = string.Concat("-", parameterName.AsSpan(0, endPosition), ":", whitespaces);440 }441 442 return parameterText;443 }444 445 internal static void InvokePipeline(object input,446 bool ignoreInput,447 CommandParameterInternal[][] pipeElements,448 CommandBaseAst[] pipeElementAsts,449 CommandRedirection[][] commandRedirections,450 FunctionContext funcContext)451 {452 PipelineProcessor pipelineProcessor = new PipelineProcessor();453 ExecutionContext context = funcContext._executionContext;454 Pipe outputPipe = funcContext._outputPipe;455 456 try457 {458 context.Events?.ProcessPendingActions();459 460 if (input == AutomationNull.Value && !ignoreInput)461 {462 // We have seen something like:463 // $e | measure-object464 // And $e is AutomationNull.Value. We want to ensure465 // measure-object runs w/o sending anything through the pipe,466 // so we'll turn the pipe into Out-Null | ...467 // This cleanly avoids any problems with the pipeline processing468 // code dealing with null/AutomationNull input going directly to469 // the first command (e.g. Measure-Object).470 AddNoopCommandProcessor(pipelineProcessor, context);471 }472 473 CommandProcessorBase commandProcessor = null;474 CommandRedirection[] commandRedirection = null;475 476 for (int i = 0; i < pipeElements.Length; i++)477 {478 commandRedirection = commandRedirections?[i];479 commandProcessor = AddCommand(pipelineProcessor, pipeElements[i], pipeElementAsts[i],480 commandRedirection, context);481 }482 483 var cmdletInfo = commandProcessor?.CommandInfo as CmdletInfo;484 if (cmdletInfo?.ImplementingType == typeof(OutNullCommand))485 {486 var commandsCount = pipelineProcessor.Commands.Count;487 if (commandsCount == 1)488 {489 // Out-Null is the only command, bail without running anything490 return;491 }492 493 // Out-Null is the last command, rewrite command before Out-Null to a null pipe, but494 // only if it didn't redirect anything, e.g. `Get-Stuff > o.txt | Out-Null`495 var nextToLastCommand = pipelineProcessor.Commands[commandsCount - 2];496 if (!nextToLastCommand.CommandRuntime.OutputPipe.IsRedirected)497 {498 pipelineProcessor.Commands.RemoveAt(commandsCount - 1);499 commandProcessor = nextToLastCommand;500 nextToLastCommand.CommandRuntime.OutputPipe = new Pipe { NullPipe = true };501 }502 }503 504 if (commandProcessor != null && !commandProcessor.CommandRuntime.OutputPipe.IsRedirected)505 {506 pipelineProcessor.LinkPipelineSuccessOutput(outputPipe ?? new Pipe(new List<object>()));507 508 // Fix up merge redirection bindings on last command processor.509 if (commandRedirection != null)510 {511 foreach (CommandRedirection redirection in commandRedirection)512 {513 if (redirection is MergingRedirection)514 {515 redirection.Bind(pipelineProcessor, commandProcessor, context);516 }517 }518 }519 }520 521 context.PushPipelineProcessor(pipelineProcessor);522 try523 {524 pipelineProcessor.SynchronousExecuteEnumerate(input);525 }526 finally527 {528 context.PopPipelineProcessor(false);529 }530 }531 finally532 {533 context.QuestionMarkVariableValue = !pipelineProcessor.ExecutionFailed;534 pipelineProcessor.Dispose();535 }536 }537 538 internal static void InvokePipelineInBackground(539 PipelineBaseAst pipelineAst,540 FunctionContext funcContext)541 {542 PipelineProcessor pipelineProcessor = new PipelineProcessor();543 ExecutionContext context = funcContext._executionContext;544 Pipe outputPipe = funcContext._outputPipe;545 546 try547 {548 context.Events?.ProcessPendingActions();549 550 CommandProcessorBase commandProcessor = null;551 552 // For background jobs rewrite the pipeline as a Start-Job command553 var scriptblockBodyString = pipelineAst.Extent.Text;554 var pipelineOffset = pipelineAst.Extent.StartOffset;555 var variables = pipelineAst.FindAll(static x => x is VariableExpressionAst, true);556 557 // Minimize allocations by initializing the stringbuilder to the size of the source string + space for ${using:} * 2558 System.Text.StringBuilder updatedScriptblock = new System.Text.StringBuilder(scriptblockBodyString.Length + 18);559 int position = 0;560 561 // Prefix variables in the scriptblock with $using:562 foreach (var v in variables)563 {564 var variableName = ((VariableExpressionAst)v).VariablePath.UserPath;565 566 // Skip variables that don't exist567 if (funcContext._executionContext.EngineSessionState.GetVariable(variableName) == null)568 {569 continue;570 }571 572 // Skip PowerShell magic variables573 if (!Regex.Match(574 variableName,575 "^(global:){0,1}(PID|PSVersionTable|PSEdition|PSHOME|HOST|TRUE|FALSE|NULL)$",576 RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success)577 {578 updatedScriptblock.Append(scriptblockBodyString.AsSpan(position, v.Extent.StartOffset - pipelineOffset - position));579 updatedScriptblock.Append("${using:");580 updatedScriptblock.Append(CodeGeneration.EscapeVariableName(variableName));581 updatedScriptblock.Append('}');582 position = v.Extent.EndOffset - pipelineOffset;583 }584 }585 586 updatedScriptblock.Append(scriptblockBodyString.AsSpan(position));587 var sb = ScriptBlock.Create(updatedScriptblock.ToString());588 var commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand));589 commandProcessor = context.CommandDiscovery.LookupCommandProcessor(commandInfo, CommandOrigin.Internal, false, context.EngineSessionState);590 591 var workingDirectoryParameter = CommandParameterInternal.CreateParameterWithArgument(592 parameterAst: pipelineAst,593 parameterName: "WorkingDirectory",594 parameterText: null,595 argumentAst: pipelineAst,596 value: context.SessionState.Path.CurrentLocation.Path,597 spaceAfterParameter: false);598 599 var scriptBlockParameter = CommandParameterInternal.CreateParameterWithArgument(600 parameterAst: pipelineAst,601 parameterName: "ScriptBlock",602 parameterText: null,603 argumentAst: pipelineAst,604 value: sb,605 spaceAfterParameter: false);606 607 commandProcessor.AddParameter(workingDirectoryParameter);608 commandProcessor.AddParameter(scriptBlockParameter);609 pipelineProcessor.Add(commandProcessor);610 pipelineProcessor.LinkPipelineSuccessOutput(outputPipe ?? new Pipe(new List<object>()));611 612 context.PushPipelineProcessor(pipelineProcessor);613 try614 {615 pipelineProcessor.SynchronousExecuteEnumerate(AutomationNull.Value);616 }617 finally618 {619 context.PopPipelineProcessor(false);620 }621 }622 finally623 {624 context.QuestionMarkVariableValue = !pipelineProcessor.ExecutionFailed;625 pipelineProcessor.Dispose();626 }627 }628 629 private static void AddNoopCommandProcessor(PipelineProcessor pipelineProcessor, ExecutionContext context)630 {631 var commandInfo = new CmdletInfo("Out-Null", typeof(OutNullCommand));632 var commandProcessor = context.CommandDiscovery.LookupCommandProcessor(commandInfo,633 context.EngineSessionState.CurrentScope.ScopeOrigin,634 useLocalScope: false,635 sessionState: null);636 pipelineProcessor.Add(commandProcessor);637 }638 639 internal static object CheckAutomationNullInCommandArgument(object obj)640 {641 if (obj == AutomationNull.Value)642 {643 return null;644 }645 646 var objAsArray = obj as object[];647 return objAsArray != null ? CheckAutomationNullInCommandArgumentArray(objAsArray) : obj;648 }649 650 internal static object[] CheckAutomationNullInCommandArgumentArray(object[] objArray)651 {652 if (objArray != null)653 {654 for (int i = 0; i < objArray.Length; ++i)655 {656 if (objArray[i] == AutomationNull.Value)657 {658 objArray[i] = null;659 }660 }661 }662 663 return objArray;664 }665 666 internal static SteppablePipeline GetSteppablePipeline(PipelineAst pipelineAst, CommandOrigin commandOrigin, ScriptBlock scriptBlock, object[] args)667 {668 var pipelineProcessor = new PipelineProcessor();669 var commandTuples = new List<Tuple<CommandAst, List<CommandParameterInternal>, List<CommandRedirection>>>();670 671 ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();672 if (context == null)673 {674 // If ExecutionContext from TLS is null then we are not in powershell engine thread.675 string scriptText = scriptBlock.ToString();676 scriptText = ErrorCategoryInfo.Ellipsize(CultureInfo.CurrentUICulture, scriptText);677 678 PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(679 ParserStrings.GetSteppablePipelineFromWrongThread,680 scriptText);681 682 e.SetErrorId("GetSteppablePipelineFromWrongThread");683 throw e;684 }685 686 // We try binding the parameter when following conditions are satisfied:687 // * Arguments are provided688 // * The ScriptBlock has parameters689 // If the script block has no parameter, RuntimeDefinedParameters.Data will be set to RuntimeDefinedParameterDictionary.EmptyParameterArray690 bool useParameter = (args != null && args.Length > 0) &&691 scriptBlock.RuntimeDefinedParameters.Data !=692 RuntimeDefinedParameterDictionary.EmptyParameterArray;693 try694 {695 if (useParameter)696 {697 // If any parameters are used, we create a new scope and bind the parameters.698 699 var newScope = context.EngineSessionState.NewScope(false);700 context.EngineSessionState.CurrentScope = newScope;701 context.EngineSessionState.CurrentScope.ScopeOrigin = CommandOrigin.Internal;702 703 var locals = MutableTuple.MakeTuple(Compiler.DottedLocalsTupleType,704 Compiler.DottedLocalsNameIndexMap);705 object[] remainingArgs =706 ScriptBlock.BindArgumentsForScriptblockInvoke(707 (RuntimeDefinedParameter[])scriptBlock.RuntimeDefinedParameters.Data,708 args, context, false, null, locals);709 locals.SetAutomaticVariable(AutomaticVariable.Args, remainingArgs, context);710 newScope.LocalsTuple = locals;711 }712 713 // GetSteppablePipeline() is called on an arbitrary script block with the intention714 // of invoking it. So the trustworthiness is defined by the trustworthiness of the715 // script block's language mode.716 bool isTrusted = scriptBlock.LanguageMode == PSLanguageMode.FullLanguage;717 if (scriptBlock.LanguageMode == PSLanguageMode.ConstrainedLanguage718 && SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Audit)719 {720 // In audit mode, report but don't enforce.721 isTrusted = true;722 SystemPolicy.LogWDACAuditMessage(723 context: context,724 title: ParserStrings.WDACGetSteppablePipelineLogTitle,725 message: ParserStrings.WDACGetSteppablePipelineLogMessage,726 fqid: "GetSteppablePipelineMayFail",727 dropIntoDebugger: true);728 }729 730 foreach (var commandAst in pipelineAst.PipelineElements.Cast<CommandAst>())731 {732 var commandParameters = new List<CommandParameterInternal>();733 foreach (var commandElement in commandAst.CommandElements)734 {735 var commandParameterAst = commandElement as CommandParameterAst;736 if (commandParameterAst != null)737 {738 commandParameters.Add(GetCommandParameter(commandParameterAst, isTrusted, context));739 continue;740 }741 742 var exprAst = (ExpressionAst)commandElement;743 var argument = Compiler.GetExpressionValue(exprAst, isTrusted, context);744 var splatting = exprAst is VariableExpressionAst && ((VariableExpressionAst)exprAst).Splatted;745 commandParameters.Add(CommandParameterInternal.CreateArgument(argument, exprAst, splatting));746 }747 748 var redirections = new List<CommandRedirection>();749 foreach (var redirection in commandAst.Redirections)750 {751 redirections.Add(GetCommandRedirection(redirection, isTrusted, context));752 }753 754 commandTuples.Add(Tuple.Create(commandAst, commandParameters, redirections));755 }756 }757 finally758 {759 if (useParameter)760 {761 context.EngineSessionState.RemoveScope(context.EngineSessionState.CurrentScope);762 }763 }764 765 foreach (var commandTuple in commandTuples)766 {767 var commandProcessor = AddCommand(pipelineProcessor, commandTuple.Item2.ToArray(), commandTuple.Item1, commandTuple.Item3.ToArray(), context);768 commandProcessor.Command.CommandOriginInternal = commandOrigin;769 commandProcessor.CommandScope.ScopeOrigin = commandOrigin;770 commandProcessor.Command.MyInvocation.CommandOrigin = commandOrigin;771 772 // For nicer error reporting, we want to make it look like errors in the steppable pipeline point back to773 // the caller of the proxy. We don't want errors pointing to the script block created in the proxy.774 // Here we assume (in a safe way) that GetSteppablePipeline is called from script. If that isn't the case,775 // we won't crash, but the error reporting might be a little misleading.776 var callStack = context.Debugger.GetCallStack().ToArray();777 if (callStack.Length > 0 && Regex.IsMatch(callStack[0].Position.Text, "GetSteppablePipeline", RegexOptions.IgnoreCase))778 {779 var myInvocation = commandProcessor.Command.MyInvocation;780 myInvocation.InvocationName = callStack[0].InvocationInfo.InvocationName;781 if (callStack.Length > 1)782 {783 var displayPosition = callStack[1].Position;784 if (displayPosition != null && displayPosition != PositionUtilities.EmptyExtent)785 {786 myInvocation.DisplayScriptPosition = displayPosition;787 }788 }789 }790 791 // Set the data stream merge properties based on ExecutionContext.792 if (context.CurrentCommandProcessor != null && context.CurrentCommandProcessor.CommandRuntime != null)793 {794 commandProcessor.CommandRuntime.SetMergeFromRuntime(context.CurrentCommandProcessor.CommandRuntime);795 }796 }797 798 return new SteppablePipeline(context, pipelineProcessor);799 }800 801 private static CommandParameterInternal GetCommandParameter(CommandParameterAst commandParameterAst, bool isTrusted, ExecutionContext context)802 {803 var argumentAst = commandParameterAst.Argument;804 var errorPos = commandParameterAst.ErrorPosition;805 806 if (argumentAst == null)807 {808 return CommandParameterInternal.CreateParameter(commandParameterAst.ParameterName, errorPos.Text, commandParameterAst);809 }810 811 object argumentValue = Compiler.GetExpressionValue(argumentAst, isTrusted, context);812 bool spaceAfterParameter = errorPos.EndLineNumber != argumentAst.Extent.StartLineNumber ||813 errorPos.EndColumnNumber != argumentAst.Extent.StartColumnNumber;814 return CommandParameterInternal.CreateParameterWithArgument(commandParameterAst, commandParameterAst.ParameterName,815 errorPos.Text, argumentAst, argumentValue,816 spaceAfterParameter);817 }818 819 private static CommandRedirection GetCommandRedirection(RedirectionAst redirectionAst, bool isTrusted, ExecutionContext context)820 {821 var fileRedirection = redirectionAst as FileRedirectionAst;822 if (fileRedirection != null)823 {824 object fileName = Compiler.GetExpressionValue(fileRedirection.Location, isTrusted, context);825 return new FileRedirection(fileRedirection.FromStream, fileRedirection.Append, fileName.ToString());826 }827 828 var mergingRedirectionAst = (MergingRedirectionAst)redirectionAst;829 return new MergingRedirection(mergingRedirectionAst.FromStream, mergingRedirectionAst.ToStream);830 }831 832 internal static object PipelineResult(List<object> resultList)833 {834 var resultCount = resultList.Count;835 if (resultCount == 0)836 {837 return AutomationNull.Value;838 }839 840 var result = resultCount == 1 ? resultList[0] : resultList.ToArray();841 // Clear the array list so that we don't write the results of the pipe when flushing the pipe.842 resultList.Clear();843 return result;844 }845 846 internal static void FlushPipe(Pipe oldPipe, List<object> resultList)847 {848 for (int i = 0; i < resultList.Count; i++)849 {850 oldPipe.Add(resultList[i]);851 }852 }853 854 internal static void ClearPipe(List<object> resultList)855 {856 resultList.Clear();857 }858 859 internal static ExitException GetExitException(object exitCodeObj)860 {861 int exitCode = 0;862 863 try864 {865 if (!LanguagePrimitives.IsNull(exitCodeObj))866 {867 exitCode = ParserOps.ConvertTo<int>(exitCodeObj, PositionUtilities.EmptyExtent);868 }869 }870 catch (Exception) // ignore non-severe exceptions871 {872 }873 874 return new ExitException(exitCode);875 }876 877 internal static void CheckForInterrupts(ExecutionContext context)878 {879 context.Events?.ProcessPendingActions();880 881 if (context.CurrentPipelineStopping)882 {883 throw new PipelineStoppedException();884 }885 }886 887 // This is to work around a DLR problem with gotos in try/catch to the end of a lambda.888 internal static void Nop() { }889 }890 891 #region Redirections892 893 internal abstract class CommandRedirection894 {895 protected CommandRedirection(RedirectionStream from)896 {897 this.FromStream = from;898 }899 900 internal RedirectionStream FromStream { get; }901 902 internal abstract void Bind(PipelineProcessor pipelineProcessor, CommandProcessorBase commandProcessor, ExecutionContext context);903 904 internal void UnbindForExpression(FunctionContext funcContext, Pipe[] pipes)905 {906 if (pipes == null)907 {908 // The pipes can be null if there was an exception (ideally we'd just call unbind909 // from a fault, but that isn't supported in a clr dynamic method.910 return;911 }912 913 var context = funcContext._executionContext;914 switch (FromStream)915 {916 case RedirectionStream.All:917 funcContext._outputPipe = pipes[(int)RedirectionStream.Output];918 context.ShellFunctionErrorOutputPipe = pipes[(int)RedirectionStream.Error];919 context.ExpressionWarningOutputPipe = pipes[(int)RedirectionStream.Warning];920 context.ExpressionVerboseOutputPipe = pipes[(int)RedirectionStream.Verbose];921 context.ExpressionDebugOutputPipe = pipes[(int)RedirectionStream.Debug];922 context.ExpressionInformationOutputPipe = pipes[(int)RedirectionStream.Information];923 break;924 case RedirectionStream.Output:925 funcContext._outputPipe = pipes[(int)RedirectionStream.Output];926 break;927 case RedirectionStream.Error:928 context.ShellFunctionErrorOutputPipe = pipes[(int)FromStream];929 break;930 case RedirectionStream.Warning:931 context.ExpressionWarningOutputPipe = pipes[(int)FromStream];932 break;933 case RedirectionStream.Verbose:934 context.ExpressionVerboseOutputPipe = pipes[(int)FromStream];935 break;936 case RedirectionStream.Debug:937 context.ExpressionDebugOutputPipe = pipes[(int)FromStream];938 break;939 case RedirectionStream.Information:940 context.ExpressionInformationOutputPipe = pipes[(int)FromStream];941 break;942 }943 }944 }945 946 internal class MergingRedirection : CommandRedirection947 {948 internal MergingRedirection(RedirectionStream from, RedirectionStream to)949 : base(from)950 {951 if (to != RedirectionStream.Output)952 {953 throw InterpreterError.NewInterpreterException(to, typeof(ArgumentException),954 null, "RedirectionStreamCanOnlyMergeToOutputStream",955 ParserStrings.RedirectionStreamCanOnlyMergeToOutputStream);956 }957 958 // this.ToStream = to;959 }960 961 public override string ToString()962 {963 return FromStream == RedirectionStream.All964 ? "*>&1"965 : string.Create(CultureInfo.InvariantCulture, $"{(int)FromStream}>&1");966 }967 968 // private RedirectionStream ToStream { get; set; }969 970 // Handle merging redirections for commands, like:971 // dir 2>&1972 // A more realistic example:973 // dir 2>&1 > out974 internal override void Bind(PipelineProcessor pipelineProcessor, CommandProcessorBase commandProcessor, ExecutionContext context)975 {976 Pipe pipe = commandProcessor.CommandRuntime.OutputPipe;977 978 switch (FromStream)979 {980 case RedirectionStream.All:981 commandProcessor.CommandRuntime.ErrorMergeTo = MshCommandRuntime.MergeDataStream.Output;982 commandProcessor.CommandRuntime.WarningOutputPipe = pipe;983 commandProcessor.CommandRuntime.VerboseOutputPipe = pipe;984 commandProcessor.CommandRuntime.DebugOutputPipe = pipe;985 commandProcessor.CommandRuntime.InformationOutputPipe = pipe;986 break;987 case RedirectionStream.Output:988 break;989 case RedirectionStream.Error:990 commandProcessor.CommandRuntime.ErrorMergeTo = MshCommandRuntime.MergeDataStream.Output;991 break;992 case RedirectionStream.Warning:993 commandProcessor.CommandRuntime.WarningOutputPipe = pipe;994 break;995 case RedirectionStream.Verbose:996 commandProcessor.CommandRuntime.VerboseOutputPipe = pipe;997 break;998 case RedirectionStream.Debug:999 commandProcessor.CommandRuntime.DebugOutputPipe = pipe;1000 break;1001 case RedirectionStream.Information:1002 commandProcessor.CommandRuntime.InformationOutputPipe = pipe;1003 break;1004 }1005 }1006 1007 // Handle merging redirections for expressions, like:1008 // $(write-error) 2>&11009 // A more realistic example:1010 // $(write-error) 2>&1 > out1011 internal Pipe[] BindForExpression(ExecutionContext context, FunctionContext funcContext)1012 {1013 Pipe[] oldPipes = new Pipe[(int)RedirectionStream.Information + 1];1014 Pipe pipe = funcContext._outputPipe;1015 1016 // We set the redirection pipe directly in Context because there is no command processor1017 // (which indirectly does the same thing as this code.)1018 1019 switch (FromStream)1020 {1021 case RedirectionStream.All:1022 oldPipes[(int)RedirectionStream.Output] = funcContext._outputPipe;1023 oldPipes[(int)RedirectionStream.Error] = context.ShellFunctionErrorOutputPipe;1024 context.ShellFunctionErrorOutputPipe = pipe;1025 oldPipes[(int)RedirectionStream.Warning] = context.ExpressionWarningOutputPipe;1026 context.ExpressionWarningOutputPipe = pipe;1027 oldPipes[(int)RedirectionStream.Verbose] = context.ExpressionVerboseOutputPipe;1028 context.ExpressionVerboseOutputPipe = pipe;1029 oldPipes[(int)RedirectionStream.Debug] = context.ExpressionDebugOutputPipe;1030 context.ExpressionDebugOutputPipe = pipe;1031 oldPipes[(int)RedirectionStream.Information] = context.ExpressionInformationOutputPipe;1032 context.ExpressionInformationOutputPipe = pipe;1033 break;1034 case RedirectionStream.Output:1035 oldPipes[(int)RedirectionStream.Output] = funcContext._outputPipe;1036 break;1037 case RedirectionStream.Error:1038 oldPipes[(int)FromStream] = context.ShellFunctionErrorOutputPipe;1039 context.ShellFunctionErrorOutputPipe = pipe;1040 break;1041 case RedirectionStream.Warning:1042 oldPipes[(int)FromStream] = context.ExpressionWarningOutputPipe;1043 context.ExpressionWarningOutputPipe = pipe;1044 break;1045 case RedirectionStream.Verbose:1046 oldPipes[(int)FromStream] = context.ExpressionVerboseOutputPipe;1047 context.ExpressionVerboseOutputPipe = pipe;1048 break;1049 case RedirectionStream.Debug:1050 oldPipes[(int)FromStream] = context.ExpressionDebugOutputPipe;1051 context.ExpressionDebugOutputPipe = pipe;1052 break;1053 case RedirectionStream.Information:1054 oldPipes[(int)FromStream] = context.ExpressionInformationOutputPipe;1055 context.ExpressionInformationOutputPipe = pipe;1056 break;1057 }1058 1059 return oldPipes;1060 }1061 }1062 1063 internal class FileRedirection : CommandRedirection, IDisposable1064 {1065 internal FileRedirection(RedirectionStream from, bool appending, string file)1066 : base(from)1067 {1068 this.File = file;1069 this.Appending = appending;1070 }1071 1072 public override string ToString()1073 {1074 return string.Format(1075 CultureInfo.InvariantCulture,1076 "{0}> {1}",1077 FromStream == RedirectionStream.All1078 ? "*"1079 : ((int)FromStream).ToString(CultureInfo.InvariantCulture),1080 File);1081 }1082 1083 internal string File { get; }1084 1085 internal bool Appending { get; }1086 1087 private PipelineProcessor PipelineProcessor { get; set; }1088 1089 // Handle binding file redirection for commands, like:1090 // dir > out1091 internal override void Bind(PipelineProcessor pipelineProcessor, CommandProcessorBase commandProcessor, ExecutionContext context)1092 {1093 // Check first to see if File is a variable path. If so, we'll not create the FileBytePipe1094 bool redirectToVariable = false;1095 1096 context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out ProviderInfo p, out _);1097 if (p != null && p.NameEquals(context.ProviderNames.Variable))1098 {1099 redirectToVariable = true;1100 }1101 1102 if (commandProcessor is NativeCommandProcessor nativeCommand1103 && nativeCommand.CommandRuntime.ErrorMergeTo is not MshCommandRuntime.MergeDataStream.Output1104 && FromStream is RedirectionStream.Output1105 && !string.IsNullOrWhiteSpace(File)1106 && !redirectToVariable)1107 {1108 nativeCommand.StdOutDestination = FileBytePipe.Create(File, Appending);1109 return;1110 }1111 1112 Pipe pipe = GetRedirectionPipe(context, pipelineProcessor);1113 1114 switch (FromStream)1115 {1116 case RedirectionStream.All:1117 // Since a temp output pipe is going to be used, we should pass along the error and warning variable list.1118 // Normally, context.CurrentCommandProcessor will not be null. But in legacy DRTs from ParserTest.cs,1119 // a scriptblock may be invoked through 'DoInvokeReturnAsIs' using .NET reflection. In that case,1120 // context.CurrentCommandProcessor will be null. We don't try passing along variable lists in such case.1121 context.CurrentCommandProcessor?.CommandRuntime.OutputPipe.SetVariableListForTemporaryPipe(pipe);1122 1123 commandProcessor.CommandRuntime.OutputPipe = pipe;1124 commandProcessor.CommandRuntime.ErrorOutputPipe = pipe;1125 commandProcessor.CommandRuntime.WarningOutputPipe = pipe;1126 commandProcessor.CommandRuntime.VerboseOutputPipe = pipe;1127 commandProcessor.CommandRuntime.DebugOutputPipe = pipe;1128 commandProcessor.CommandRuntime.InformationOutputPipe = pipe;1129 break;1130 case RedirectionStream.Output:1131 // Since a temp output pipe is going to be used, we should pass along the error and warning variable list.1132 context.CurrentCommandProcessor?.CommandRuntime.OutputPipe.SetVariableListForTemporaryPipe(pipe);1133 1134 commandProcessor.CommandRuntime.OutputPipe = pipe;1135 break;1136 case RedirectionStream.Error:1137 commandProcessor.CommandRuntime.ErrorOutputPipe = pipe;1138 break;1139 case RedirectionStream.Warning:1140 commandProcessor.CommandRuntime.WarningOutputPipe = pipe;1141 break;1142 case RedirectionStream.Verbose:1143 commandProcessor.CommandRuntime.VerboseOutputPipe = pipe;1144 break;1145 case RedirectionStream.Debug:1146 commandProcessor.CommandRuntime.DebugOutputPipe = pipe;1147 break;1148 case RedirectionStream.Information:1149 commandProcessor.CommandRuntime.InformationOutputPipe = pipe;1150 break;1151 }1152 }1153 1154 // Handle binding file redirections for expressions, like:1155 // $(write-error blah) 2> out1156 internal Pipe[] BindForExpression(FunctionContext funcContext)1157 {1158 var context = funcContext._executionContext;1159 // GetRedirectionPipe can throw if the filename specified can't be written to. In that case,1160 // oldPipes is null, and when unbinding, there is nothing to do.1161 Pipe pipe = GetRedirectionPipe(context, null);1162 var oldPipes = new Pipe[(int)RedirectionStream.Information + 1];1163 1164 switch (FromStream)1165 {1166 case RedirectionStream.All:1167 oldPipes[(int)RedirectionStream.Output] = funcContext._outputPipe;1168 oldPipes[(int)RedirectionStream.Error] = context.ShellFunctionErrorOutputPipe;1169 oldPipes[(int)RedirectionStream.Warning] = context.ExpressionWarningOutputPipe;1170 oldPipes[(int)RedirectionStream.Verbose] = context.ExpressionVerboseOutputPipe;1171 oldPipes[(int)RedirectionStream.Debug] = context.ExpressionDebugOutputPipe;1172 oldPipes[(int)RedirectionStream.Information] = context.ExpressionInformationOutputPipe;1173 1174 // Since a temp output pipe is going to be used, we should pass along the error and warning variable list.1175 funcContext._outputPipe.SetVariableListForTemporaryPipe(pipe);1176 funcContext._outputPipe = pipe;1177 context.ShellFunctionErrorOutputPipe = pipe;1178 context.ExpressionWarningOutputPipe = pipe;1179 context.ExpressionVerboseOutputPipe = pipe;1180 context.ExpressionDebugOutputPipe = pipe;1181 context.ExpressionInformationOutputPipe = pipe;1182 break;1183 case RedirectionStream.Output:1184 oldPipes[(int)RedirectionStream.Output] = funcContext._outputPipe;1185 // Since a temp output pipe is going to be used, we should pass along the error and warning variable list.1186 funcContext._outputPipe.SetVariableListForTemporaryPipe(pipe);1187 funcContext._outputPipe = pipe;1188 break;1189 case RedirectionStream.Error:1190 oldPipes[(int)FromStream] = context.ShellFunctionErrorOutputPipe;1191 context.ShellFunctionErrorOutputPipe = pipe;1192 break;1193 case RedirectionStream.Warning:1194 oldPipes[(int)FromStream] = context.ExpressionWarningOutputPipe;1195 context.ExpressionWarningOutputPipe = pipe;1196 break;1197 case RedirectionStream.Verbose:1198 oldPipes[(int)FromStream] = context.ExpressionVerboseOutputPipe;1199 context.ExpressionVerboseOutputPipe = pipe;1200 break;