MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Concurrent;6using System.Collections.Generic;7using System.Collections.ObjectModel;8using System.Diagnostics.CodeAnalysis;9using System.Globalization;10using System.IO;11using System.Linq;12using System.Linq.Expressions;13using System.Management.Automation.Internal;14using System.Management.Automation.Language;15using System.Management.Automation.Runspaces;16using System.Reflection;17using System.Runtime.CompilerServices;18using System.Runtime.ExceptionServices;19using System.Runtime.Serialization;20 21namespace System.Management.Automation22{23 /// <summary>24 /// An object representing a pre-compiled block of powershell script.25 /// </summary>26 /// <remarks>27 /// This class track a block of script in a compiled form. It is also28 /// used for direct invocation of the script block.29 ///30 /// 1. Overview31 ///32 /// Script block comes in two forms,33 ///34 /// a. Full form (cmdlet form)35 ///36 /// This comes in following format37 ///38 /// {39 /// begin40 /// {41 /// statementlist;42 /// }43 /// process44 /// {45 /// statementlist;46 /// }47 /// end48 /// {49 /// statementlist;50 /// }51 /// }52 ///53 /// This form is used for running the script in a pipeline like54 /// a cmdlet.55 ///56 /// b. Simple form57 ///58 /// This comes in following format59 ///60 /// {61 /// statementlist;62 /// }63 ///64 /// 2. Script block execution65 ///66 /// For the full form (or cmdlet form) of script block, the script67 /// block itself is part of a pipeline. Its execution is handled through68 /// ScriptCommandProcessor, which involves execution of begin/process/end69 /// blocks like a cmdlet. If a scriptblock in simple form is used in70 /// a pipeline, its execution is done through ScriptCommandProcessor71 /// also, with some of begin/process/end blocks default to be empty.72 ///73 /// A script block in simple form can be directly invoked (outside74 /// of a pipeline context). For example,75 ///76 /// {"text"}.Invoke()77 ///78 /// A scriptblock can be directly invoked internally or externally through79 /// runspace API.80 ///81 /// This class will handle the logic for direct invocation of script blocks.82 /// </remarks>83 public partial class ScriptBlock84 {85 /// <summary>86 /// Create a script block object based on a script string to be parsed immediately.87 /// </summary>88 /// <param name="context">Engine context for this script block.</param>89 /// <param name="script">The string to compile.</param>90 internal static ScriptBlock Create(ExecutionContext context, string script)91 {92 ScriptBlock sb = Create(context.Engine.EngineParser, null, script);93 if (context.EngineSessionState != null && context.EngineSessionState.Module != null)94 {95 sb.SessionStateInternal = context.EngineSessionState;96 }97 98 return sb;99 }100 101 /// <summary>102 /// Create a script block based on a script to be parsed when execution103 /// context is provided.104 /// </summary>105 /// <param name="script">The string to compile.</param>106 public static ScriptBlock Create(string script) => Create(107 parser: new Parser(),108 fileName: null,109 fileContents: script);110 111 internal static ScriptBlock CreateDelayParsedScriptBlock(string script, bool isProductCode)112 => new ScriptBlock(new CompiledScriptBlockData(script, isProductCode)) { DebuggerHidden = true };113 114 /// <summary>115 /// Returns a new scriptblock bound to a module. Any local variables in the116 /// callers context will be copied into the module.117 /// </summary>118 /// <returns></returns>119 public ScriptBlock GetNewClosure()120 {121 PSModuleInfo m = new PSModuleInfo(true);122 m.CaptureLocals();123 return m.NewBoundScriptBlock(this);124 }125 126 /// <summary>127 /// Returns PowerShell object representing the pipeline contained in this ScriptBlock.128 /// </summary>129 /// <remarks>130 /// Some ScriptBlocks are too complicated to be converted into a PowerShell object.131 /// For those ScriptBlocks a <see cref="ScriptBlockToPowerShellNotSupportedException"/> is thrown.132 ///133 /// ScriptBlock cannot be converted into a PowerShell object if134 /// - It contains more than one statement135 /// - It references variables undeclared in <c>param(...)</c> block136 /// - It uses redirection to a file137 /// - It uses dot sourcing138 /// - Command names can't be resolved (i.e. if an element of a pipeline is another scriptblock)139 ///140 /// Declaration of variables in a <c>param(...)</c> block is enforced,141 /// because undeclared variables are assumed to be variables from a remoting server.142 /// Since we need to fully evaluate parameters of commands of a PowerShell object's143 /// we reject all variables references that refer to a variable from a remoting server.144 /// </remarks>145 /// <param name="args">146 /// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);147 /// can be null148 /// </param>149 /// <returns>150 /// PowerShell object representing the pipeline contained in this ScriptBlock151 /// </returns>152 /// <exception cref="ScriptBlockToPowerShellNotSupportedException">153 /// Thrown when this ScriptBlock cannot be expressed as a PowerShell object.154 /// For example thrown when there is more than one statement, if there155 /// are undeclared variables, if redirection to a file is used.156 /// </exception>157 /// <exception cref="RuntimeException">158 /// Thrown when evaluation of command arguments results in an exception.159 /// Might depend on the value of $errorActionPreference variable.160 /// For example trying to translate the following ScriptBlock will result in this exception:161 /// <c>$errorActionPreference = "stop"; $sb = { get-foo $( throw ) }; $sb.GetPowerShell()</c>162 /// </exception>163 /// <exception cref="InvalidOperationException">164 /// Thrown when there is no ExecutionContext associated with this ScriptBlock object.165 /// </exception>166 public PowerShell GetPowerShell(params object[] args) => GetPowerShellImpl(167 context: LocalPipeline.GetExecutionContextFromTLS(),168 variables: null,169 isTrustedInput: false,170 filterNonUsingVariables: false,171 createLocalScope: null,172 args);173 174 /// <summary>175 /// Returns PowerShell object representing the pipeline contained in this ScriptBlock,176 /// similar to the GetPowerShell() method. If the 'isTrustedInput' flag parameter is set177 /// to True, then the GetPowerShell() implementation supports extended conversion operations178 /// (such as replacing variable values with their current values) that might otherwise179 /// be unsafe if applied to untrusted input.180 /// </summary>181 /// <param name="isTrustedInput">182 /// Specifies whether the scriptblock being converted comes from a trusted source.183 /// The default is False.184 /// </param>185 /// <param name="args">186 /// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);187 /// can be null188 /// </param>189 public PowerShell GetPowerShell(bool isTrustedInput, params object[] args)190 => GetPowerShellImpl(191 context: LocalPipeline.GetExecutionContextFromTLS(),192 variables: null,193 isTrustedInput,194 filterNonUsingVariables: false,195 createLocalScope: null,196 args);197 198 /// <summary>199 /// Returns PowerShell object representing the pipeline contained in this ScriptBlock, using variables200 /// supplied in the dictionary.201 /// </summary>202 /// <param name="variables">203 /// variables to be supplied as context to the ScriptBlock (providing values for variables explicitly204 /// requested by the 'using:' prefix.205 /// </param>206 /// <param name="args">207 /// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);208 /// can be null209 /// </param>210 /// <returns>211 /// PowerShell object representing the pipeline contained in this ScriptBlock212 /// </returns>213 /// <exception cref="ScriptBlockToPowerShellNotSupportedException">214 /// Thrown when this ScriptBlock cannot be expressed as a PowerShell object.215 /// For example thrown when there is more than one statement, if there216 /// are undeclared variables, if redirection to a file is used.217 /// </exception>218 /// <exception cref="RuntimeException">219 /// Thrown when evaluation of command arguments results in an exception.220 /// Might depend on the value of $errorActionPreference variable.221 /// For example trying to translate the following ScriptBlock will result in this exception:222 /// <c>$errorActionPreference = "stop"; $sb = { get-foo $( throw ) }; $sb.GetPowerShell()</c>223 /// </exception>224 /// <exception cref="InvalidOperationException">225 /// Thrown when there is no ExecutionContext associated with this ScriptBlock object and no226 /// variables are supplied.227 /// </exception>228 public PowerShell GetPowerShell(Dictionary<string, object> variables, params object[] args)229 {230 ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();231 Dictionary<string, object> suppliedVariables = null;232 233 if (variables != null)234 {235 suppliedVariables = new Dictionary<string, object>(variables, StringComparer.OrdinalIgnoreCase);236 context = null;237 }238 239 return GetPowerShellImpl(context, suppliedVariables, false, false, null, args);240 }241 242 /// <summary>243 /// Returns PowerShell object representing the pipeline contained in this ScriptBlock, using variables244 /// supplied in the dictionary.245 /// </summary>246 /// <param name="variables">247 /// variables to be supplied as context to the ScriptBlock (providing values for variables explicitly248 /// requested by the 'using:' prefix.249 /// </param>250 /// <param name="usingVariables">251 /// key-value pairs from the <para>variables</para> that actually get used by the 'using:' prefix variables252 /// </param>253 /// <param name="args">254 /// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);255 /// can be null256 /// </param>257 /// <returns>258 /// PowerShell object representing the pipeline contained in this ScriptBlock259 /// </returns>260 /// <exception cref="ScriptBlockToPowerShellNotSupportedException">261 /// Thrown when this ScriptBlock cannot be expressed as a PowerShell object.262 /// For example thrown when there is more than one statement, if there263 /// are undeclared variables, if redirection to a file is used.264 /// </exception>265 /// <exception cref="RuntimeException">266 /// Thrown when evaluation of command arguments results in an exception.267 /// Might depend on the value of $errorActionPreference variable.268 /// For example trying to translate the following ScriptBlock will result in this exception:269 /// <c>$errorActionPreference = "stop"; $sb = { get-foo $( throw ) }; $sb.GetPowerShell()</c>270 /// </exception>271 /// <exception cref="InvalidOperationException">272 /// Thrown when there is no ExecutionContext associated with this ScriptBlock object and no273 /// variables are supplied.274 /// </exception>275 public PowerShell GetPowerShell(276 Dictionary<string, object> variables,277 out Dictionary<string, object> usingVariables,278 params object[] args)279 => GetPowerShell(variables, out usingVariables, isTrustedInput: false, args);280 281 /// <summary>282 /// Returns PowerShell object representing the pipeline contained in this ScriptBlock, using variables283 /// supplied in the dictionary.284 /// </summary>285 /// <param name="variables">286 /// variables to be supplied as context to the ScriptBlock (providing values for variables explicitly287 /// requested by the 'using:' prefix.288 /// </param>289 /// <param name="usingVariables">290 /// key-value pairs from the <para>variables</para> that actually get used by the 'using:' prefix variables291 /// </param>292 /// <param name="args">293 /// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);294 /// can be null295 /// </param>296 /// <param name="isTrustedInput">297 /// Specifies whether the scriptblock being converted comes from a trusted source.298 /// The default is False.299 /// </param>300 /// <returns>301 /// PowerShell object representing the pipeline contained in this ScriptBlock302 /// </returns>303 /// <exception cref="ScriptBlockToPowerShellNotSupportedException">304 /// Thrown when this ScriptBlock cannot be expressed as a PowerShell object.305 /// For example thrown when there is more than one statement, if there306 /// are undeclared variables, if redirection to a file is used.307 /// </exception>308 /// <exception cref="RuntimeException">309 /// Thrown when evaluation of command arguments results in an exception.310 /// Might depend on the value of $errorActionPreference variable.311 /// For example trying to translate the following ScriptBlock will result in this exception:312 /// <c>$errorActionPreference = "stop"; $sb = { get-foo $( throw ) }; $sb.GetPowerShell()</c>313 /// </exception>314 /// <exception cref="InvalidOperationException">315 /// Thrown when there is no ExecutionContext associated with this ScriptBlock object and no316 /// variables are supplied.317 /// </exception>318 public PowerShell GetPowerShell(319 Dictionary<string, object> variables,320 out Dictionary<string, object> usingVariables,321 bool isTrustedInput,322 params object[] args)323 {324 ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();325 Dictionary<string, object> suppliedVariables = null;326 327 if (variables != null)328 {329 suppliedVariables = new Dictionary<string, object>(variables, StringComparer.OrdinalIgnoreCase);330 context = null;331 }332 333 PowerShell powershell = GetPowerShellImpl(context, suppliedVariables, isTrustedInput, true, null, args);334 usingVariables = suppliedVariables;335 336 return powershell;337 }338 339 internal PowerShell GetPowerShell(340 ExecutionContext context,341 bool isTrustedInput,342 bool? useLocalScope,343 object[] args)344 => GetPowerShellImpl(345 context,346 variables: null,347 isTrustedInput,348 filterNonUsingVariables: false,349 useLocalScope,350 args);351 352 /// <summary>353 /// Get a steppable pipeline object.354 /// </summary>355 /// <returns>A steppable pipeline object.</returns>356 [SuppressMessage(357 "Microsoft.Naming",358 "CA1704:IdentifiersShouldBeSpelledCorrectly",359 MessageId = "Steppable",360 Justification = "Review this during API naming")]361 public SteppablePipeline GetSteppablePipeline()362 => GetSteppablePipelineImpl(commandOrigin: CommandOrigin.Internal, args: null);363 364 /// <summary>365 /// Get a steppable pipeline object.366 /// </summary>367 /// <returns>A steppable pipeline object.</returns>368 [SuppressMessage(369 "Microsoft.Naming",370 "CA1704:IdentifiersShouldBeSpelledCorrectly",371 MessageId = "Steppable",372 Justification = "Review this during API naming")]373 public SteppablePipeline GetSteppablePipeline(CommandOrigin commandOrigin)374 => GetSteppablePipelineImpl(commandOrigin, args: null);375 376 /// <summary>377 /// Get a steppable pipeline object.378 /// </summary>379 /// <returns>A steppable pipeline object.</returns>380 [SuppressMessage(381 "Microsoft.Naming",382 "CA1704:IdentifiersShouldBeSpelledCorrectly",383 MessageId = "Steppable",384 Justification = "Review this during API naming")]385 public SteppablePipeline GetSteppablePipeline(CommandOrigin commandOrigin, object[] args)386 => GetSteppablePipelineImpl(commandOrigin, args);387 388 /// <summary>389 /// Execute this node with the specified arguments. The arguments show390 /// up in the script as $args with $_ being the first argument.391 /// </summary>392 /// <param name="args">The arguments to this script.</param>393 /// <returns>The object(s) generated during the execution of394 /// the script block returned as a collection of PSObjects.</returns>395 /// <exception cref="RuntimeException">Thrown if a script runtime exceptionexception occurred.</exception>396 /// <exception cref="FlowControlException">An internal (non-public) exception from a flow control statement.</exception>397 public Collection<PSObject> Invoke(params object[] args) =>398 DoInvoke(dollarUnder: AutomationNull.Value, input: AutomationNull.Value, args);399 400 /// <summary>401 /// A method that allows a scriptblock to be invoked with additional context in the form of a402 /// set of local functions and variables to be defined in the scriptblock's scope. The list of403 /// variables may include the special variables $input, $_ and $this.404 ///405 /// This overload of the function takes a hashtable and converts it to the406 /// required dictionary which makes the API easier to use from within a PowerShell script.407 /// </summary>408 /// <param name="functionsToDefine">A dictionary of functions to define.</param>409 /// <param name="variablesToDefine">A list of variables to define.</param>410 /// <param name="args">The arguments to the actual scriptblock.</param>411 /// <returns></returns>412 public Collection<PSObject> InvokeWithContext(413 IDictionary functionsToDefine,414 List<PSVariable> variablesToDefine,415 params object[] args)416 {417 Dictionary<string, ScriptBlock> functionsToDefineDictionary = null;418 if (functionsToDefine != null)419 {420 functionsToDefineDictionary = new Dictionary<string, ScriptBlock>();421 foreach (DictionaryEntry pair in functionsToDefine)422 {423 string functionName = pair.Key as string;424 if (string.IsNullOrWhiteSpace(functionName))425 {426 PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(427 ParserStrings.EmptyFunctionNameInFunctionDefinitionDictionary);428 429 e.SetErrorId("EmptyFunctionNameInFunctionDefinitionDictionary");430 throw e;431 }432 433 ScriptBlock functionBody = pair.Value as ScriptBlock;434 // null check for functionBody is done at the lower layer.435 functionsToDefineDictionary.Add(functionName, functionBody);436 }437 }438 439 return InvokeWithContext(440 functionsToDefineDictionary,441 variablesToDefine,442 args);443 }444 445 /// <summary>446 /// A method that allows a scriptblock to be invoked with additional context in the form of a447 /// set of local functions and variables to be defined in the scriptblock's scope. The list of448 /// variables may include the special variables $input, $_ and $this.449 /// </summary>450 /// <param name="functionsToDefine">A dictionary of functions to define.</param>451 /// <param name="variablesToDefine">A list of variables to define.</param>452 /// <param name="args">The arguments to the actual scriptblock.</param>453 /// <returns></returns>454 public Collection<PSObject> InvokeWithContext(455 Dictionary<string, ScriptBlock> functionsToDefine,456 List<PSVariable> variablesToDefine,457 params object[] args)458 {459 object input = AutomationNull.Value;460 object dollarUnder = AutomationNull.Value;461 object scriptThis = AutomationNull.Value;462 463 if (variablesToDefine != null)464 {465 // Extract the special variables "this", "input" and "_"466 PSVariable located = variablesToDefine.Find(467 v => string.Equals(v.Name, "this", StringComparison.OrdinalIgnoreCase));468 if (located != null)469 {470 scriptThis = located.Value;471 variablesToDefine.Remove(located);472 }473 474 located = variablesToDefine.Find(475 v => string.Equals(v.Name, "_", StringComparison.Ordinal));476 if (located != null)477 {478 dollarUnder = located.Value;479 variablesToDefine.Remove(located);480 }481 482 located = variablesToDefine.Find(483 v => string.Equals(v.Name, "input", StringComparison.OrdinalIgnoreCase));484 if (located != null)485 {486 input = located.Value;487 variablesToDefine.Remove(located);488 }489 }490 491 List<object> result = new List<object>();492 Pipe outputPipe = new Pipe(result);493 494 InvokeWithPipe(495 useLocalScope: true,496 functionsToDefine: functionsToDefine,497 variablesToDefine: variablesToDefine,498 errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe,499 dollarUnder: dollarUnder,500 input: input,501 scriptThis: scriptThis,502 outputPipe: outputPipe,503 invocationInfo: null,504 args: args);505 return GetWrappedResult(result);506 }507 508 /// <summary>509 /// Execute this node with the specified arguments. The arguments show510 /// up in the script as $args. This overload return the raw (unwrapped) result511 /// so it can be more efficient.512 /// </summary>513 /// <param name="args">The arguments to pass to this scriptblock.</param>514 /// <returns>The object(s) generated during the execution of the515 /// script block. They may or may not be wrapped in PSObject. It's up to the caller to check.</returns>516 /// <exception cref="RuntimeException">Thrown if a script runtime exceptionexception occurred.</exception>517 /// <exception cref="FlowControlException">An internal (non-public) exception from a flow control statement.</exception>518 public object InvokeReturnAsIs(params object[] args)519 => DoInvokeReturnAsIs(520 useLocalScope: true,521 errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe,522 dollarUnder: AutomationNull.Value,523 input: AutomationNull.Value,524 scriptThis: AutomationNull.Value,525 args: args);526 527 internal T InvokeAsMemberFunctionT<T>(object instance, object[] args)528 {529 List<object> result = new List<object>();530 Pipe pipe = new Pipe(result);531 532 InvokeWithPipe(533 useLocalScope: true,534 errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe,535 dollarUnder: AutomationNull.Value,536 input: AutomationNull.Value,537 scriptThis: instance ?? AutomationNull.Value,538 outputPipe: pipe,539 invocationInfo: null,540 propagateAllExceptionsToTop: true,541 args: args);542 543 // This is needed only for the case where the544 // method returns [object]. If the argument to 'return'545 // is a pipeline that emits nothing then result.Count will546 // be zero so we catch that and "convert" it to null. Note that547 // the return statement is still required in the method, it548 // just receives nothing from it's argument.549 if (result.Count == 0)550 {551 return default(T);552 }553 554 return (T)result[0];555 }556 557 internal void InvokeAsMemberFunction(object instance, object[] args)558 {559 List<object> result = new List<object>();560 Pipe pipe = new Pipe(result);561 562 InvokeWithPipe(563 useLocalScope: true,564 errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe,565 dollarUnder: AutomationNull.Value,566 input: AutomationNull.Value,567 scriptThis: instance ?? AutomationNull.Value,568 outputPipe: pipe,569 invocationInfo: null,570 propagateAllExceptionsToTop: true,571 args: args);572 Diagnostics.Assert(result.Count == 0, "Code generation ensures we return the correct type");573 }574 575 /// <summary>576 /// Return all attributes on a script block.577 /// </summary>578 public List<Attribute> Attributes { get => GetAttributes(); }579 580 /// <summary>581 /// The script file that defined this script block.582 /// </summary>583 public string File { get => GetFileName(); }584 585 /// <summary>586 /// Get/set whether this scriptblock is a filter.587 /// </summary>588 public bool IsFilter { get => _scriptBlockData.IsFilter; }589 590 /// <summary>591 /// Get/set whether this scriptblock is a Configuration.592 /// </summary>593 public bool IsConfiguration { get => _scriptBlockData.GetIsConfiguration(); }594 595 /// <summary>596 /// Get the PSModuleInfo object for the module that defined this597 /// scriptblock.598 /// </summary>599 public PSModuleInfo Module { get => SessionStateInternal?.Module; }600 601 /// <summary>602 /// Return the PSToken object for this function definition...603 /// </summary>604 public PSToken StartPosition { get => GetStartPosition(); }605 606 // LanguageMode is a nullable PSLanguageMode enumeration because script blocks607 // need to inherit the language mode from the context in which they are executing.608 // We can't assume FullLanguage by default when there is no context, as there are609 // script blocks (such as the script blocks used in Workflow activities) that are610 // created by the host without a "current language mode" to inherit. They ultimately611 // get their language mode set when they are finally invoked in a constrained612 // language runspace.613 // Script blocks that should always be run under FullLanguage mode (i.e.: set in614 // InitialSessionState, etc.) should explicitly set the LanguageMode to FullLanguage615 // when they are created.616 internal PSLanguageMode? LanguageMode { get; set; }617 618 internal enum ErrorHandlingBehavior619 {620 WriteToCurrentErrorPipe = 1,621 WriteToExternalErrorPipe = 2,622 SwallowErrors = 3,623 }624 625 internal ReadOnlyCollection<PSTypeName> OutputType626 {627 get628 {629 List<PSTypeName> result = new List<PSTypeName>();630 foreach (Attribute attribute in Attributes)631 {632 OutputTypeAttribute outputType = attribute as OutputTypeAttribute;633 if (outputType != null)634 {635 result.AddRange(outputType.Type);636 }637 }638 639 return new ReadOnlyCollection<PSTypeName>(result);640 }641 }642 643 /// <summary>644 /// This is a helper function to process script invocation result.645 /// </summary>646 /// <remarks>647 /// This does normal array reduction in the case of a one-element array.648 /// </remarks>649 internal static object GetRawResult(List<object> result, bool wrapToPSObject)650 {651 switch (result.Count)652 {653 case 0:654 return AutomationNull.Value;655 case 1:656 return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(result[0]) : result[0];657 default:658 object resultArray = result.ToArray();659 return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(resultArray) : resultArray;660 }661 }662 663 internal void InvokeUsingCmdlet(664 Cmdlet contextCmdlet,665 bool useLocalScope,666 ErrorHandlingBehavior errorHandlingBehavior,667 object dollarUnder,668 object input,669 object scriptThis,670 object[] args)671 {672 Diagnostics.Assert(contextCmdlet != null, "caller to verify contextCmdlet parameter");673 674 Pipe outputPipe = ((MshCommandRuntime)contextCmdlet.CommandRuntime).OutputPipe;675 ExecutionContext context = GetContextFromTLS();676 var myInv = context.EngineSessionState.CurrentScope.GetAutomaticVariableValue(AutomaticVariable.MyInvocation);677 InvocationInfo inInfo = myInv == AutomationNull.Value ? null : (InvocationInfo)myInv;678 InvokeWithPipe(679 useLocalScope,680 errorHandlingBehavior,681 dollarUnder,682 input,683 scriptThis,684 outputPipe,685 inInfo,686 propagateAllExceptionsToTop: false,687 args: args);688 }689 690 /// <summary>691 /// The internal session state object associated with this scriptblock.692 /// </summary>693 internal SessionStateInternal SessionStateInternal { get; set; }694 695 /// <summary>696 /// The session state instance that should be used when evaluating697 /// this scriptblock.698 /// </summary>699 internal SessionState SessionState700 {701 get702 {703 if (SessionStateInternal == null)704 {705 ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();706 if (context != null)707 {708 SessionStateInternal = context.EngineSessionState.PublicSessionState.Internal;709 }710 }711 712 return SessionStateInternal?.PublicSessionState;713 }714 715 set716 {717 if (value == null)718 {719 throw PSTraceSource.NewArgumentNullException(nameof(value));720 }721 722 SessionStateInternal = value.Internal;723 }724 }725 726 #region Delegates727 728 private static readonly ConditionalWeakTable<ScriptBlock, ConcurrentDictionary<Type, Delegate>> s_delegateTable =729 new ConditionalWeakTable<ScriptBlock, ConcurrentDictionary<Type, Delegate>>();730 731 internal Delegate GetDelegate(Type delegateType)732 => s_delegateTable.GetOrCreateValue(this).GetOrAdd(delegateType, CreateDelegate);733 734 /// <summary>735 /// Get the delegate method as a call back.736 /// </summary>737 internal Delegate CreateDelegate(Type delegateType)738 {739 MethodInfo invokeMethod = delegateType.GetMethod("Invoke");740 ParameterInfo[] parameters = invokeMethod.GetParameters();741 if (invokeMethod.ContainsGenericParameters)742 {743 throw new ScriptBlockToPowerShellNotSupportedException(744 "CantConvertScriptBlockToOpenGenericType",745 null,746 "AutomationExceptions",747 "CantConvertScriptBlockToOpenGenericType",748 delegateType);749 }750 751 var parameterExprs = new List<ParameterExpression>();752 foreach (var parameter in parameters)753 {754 parameterExprs.Add(Expression.Parameter(parameter.ParameterType));755 }756 757 bool returnsSomething = !invokeMethod.ReturnType.Equals(typeof(void));758 759 Expression dollarUnderExpr;760 Expression dollarThisExpr;761 if (parameters.Length == 2 && !returnsSomething)762 {763 // V1 was designed for System.EventHandler and not much else.764 // The first arg (sender) was bound to $this, the second (e or EventArgs) was bound to $_.765 // We do this for backwards compatibility, but we also bind the parameters (or $args) for766 // consistency w/ delegates that take more or fewer parameters.767 dollarUnderExpr = parameterExprs[1].Cast(typeof(object));768 dollarThisExpr = parameterExprs[0].Cast(typeof(object));769 }770 else771 {772 dollarUnderExpr = ExpressionCache.AutomationNullConstant;773 dollarThisExpr = ExpressionCache.AutomationNullConstant;774 }775 776 Expression call = Expression.Call(777 Expression.Constant(this),778 CachedReflectionInfo.ScriptBlock_InvokeAsDelegateHelper,779 dollarUnderExpr,780 dollarThisExpr,781 Expression.NewArrayInit(typeof(object), parameterExprs.Select(static p => p.Cast(typeof(object)))));782 if (returnsSomething)783 {784 call = DynamicExpression.Dynamic(785 PSConvertBinder.Get(invokeMethod.ReturnType),786 invokeMethod.ReturnType,787 call);788 }789 790 return Expression.Lambda(delegateType, call, parameterExprs).Compile();791 }792 793 internal object InvokeAsDelegateHelper(object dollarUnder, object dollarThis, object[] args)794 {795 // Retrieve context and current runspace to ensure that we throw exception, if this is non-default runspace.796 ExecutionContext context = GetContextFromTLS();797 RunspaceBase runspace = (RunspaceBase)context.CurrentRunspace;798 799 List<object> rawResult = new List<object>();800 Pipe outputPipe = new Pipe(rawResult);801 InvokeWithPipe(802 useLocalScope: true,803 errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe,804 dollarUnder: dollarUnder,805 input: null,806 scriptThis: dollarThis,807 outputPipe: outputPipe,808 invocationInfo: null,809 args: args);810 return GetRawResult(rawResult, wrapToPSObject: false);811 }812 813 #endregion814 815 /// <summary>816 /// Returns the current execution context from TLS, or raises an exception if it is null.817 /// </summary>818 /// <exception cref="InvalidOperationException">819 /// An attempt was made to use the scriptblock outside the engine.820 /// </exception>821 internal ExecutionContext GetContextFromTLS()822 {823 ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();824 825 // If ExecutionContext from TLS is null then we are not in powershell engine thread.826 if (context == null)827 {828 string scriptText = this.ToString();829 830 scriptText = ErrorCategoryInfo.Ellipsize(CultureInfo.CurrentUICulture, scriptText);831 832 PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(833 ParserStrings.ScriptBlockDelegateInvokedFromWrongThread,834 scriptText);835 836 e.SetErrorId("ScriptBlockDelegateInvokedFromWrongThread");837 throw e;838 }839 840 return context;841 }842 843 /// <summary>844 /// Execute this node with the specified arguments. The arguments show845 /// up in the script as $args with $_ being the first argument.846 /// </summary>847 /// <param name="dollarUnder">848 /// The value of the $_ variable for the script block. If AutomationNull.Value,849 /// the $_ variable is not created.850 /// </param>851 /// <param name="input">852 /// The value of the $input variable for the script block. If AutomationNull.Value,853 /// the $input variable is not created.854 /// </param>855 /// <param name="args">The arguments to this script.</param>856 /// <returns>The object(s) generated during the execution of857 /// the script block returned as a collection of PSObjects.</returns>858 /// <exception cref="RuntimeException">A script exception occurred.</exception>859 /// <exception cref="FlowControlException">Internal exception from a flow control statement.</exception>860 internal Collection<PSObject> DoInvoke(object dollarUnder, object input, object[] args)861 {862 List<object> result = new List<object>();863 Pipe outputPipe = new Pipe(result);864 InvokeWithPipe(865 useLocalScope: true,866 errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe,867 dollarUnder: dollarUnder,868 input: input,869 scriptThis: AutomationNull.Value,870 outputPipe: outputPipe,871 invocationInfo: null,872 args: args);873 return GetWrappedResult(result);874 }875 876 /// <summary>877 /// This is a helper function to wrap script execution results878 /// in PSObjects.879 /// </summary>880 /// <param name="result"></param>881 /// <returns></returns>882 private static Collection<PSObject> GetWrappedResult(List<object> result)883 {884 if (result == null || result.Count == 0)885 {886 return new Collection<PSObject>();887 }888 889 Collection<PSObject> wrappedResult = new Collection<PSObject>();890 for (int i = 0; i < result.Count; i++)891 {892 wrappedResult.Add(LanguagePrimitives.AsPSObjectOrNull(result[i]));893 }894 895 return wrappedResult;896 }897 898 /// <summary>899 /// Execute this node with the specified arguments. The arguments show900 /// up in the script as $args with $_ being the first argument.901 /// </summary>902 /// <param name="useLocalScope"></param>903 /// <param name="errorHandlingBehavior"></param>904 /// <param name="dollarUnder">905 /// The value of the $_ variable for the script block. If AutomationNull.Value,906 /// the $_ variable is not created.907 /// </param>908 /// <param name="input">909 /// The value of the $input variable for the script block. If AutomationNull.Value,910 /// the $input variable is not created.911 /// </param>912 /// <param name="scriptThis"></param>913 /// <param name="args">The arguments to this script.</param>914 /// <returns>The object(s) generated during the execution of915 /// the script block returned as a collection of PSObjects.</returns>916 /// <exception cref="RuntimeException">A script exception occurred.</exception>917 /// <exception cref="FlowControlException">Internal exception from a flow control statement.</exception>918 internal object DoInvokeReturnAsIs(919 bool useLocalScope,920 ErrorHandlingBehavior errorHandlingBehavior,921 object dollarUnder,922 object input,923 object scriptThis,924 object[] args)925 {926 List<object> result = new List<object>();927 Pipe outputPipe = new Pipe(result);928 InvokeWithPipe(929 useLocalScope: useLocalScope,930 errorHandlingBehavior: errorHandlingBehavior,931 dollarUnder: dollarUnder,932 input: input,933 scriptThis: scriptThis,934 outputPipe: outputPipe,935 invocationInfo: null,936 args: args);937 return GetRawResult(result, wrapToPSObject: true);938 }939 940 internal void InvokeWithPipe(941 bool useLocalScope,942 ErrorHandlingBehavior errorHandlingBehavior,943 object dollarUnder,944 object input,945 object scriptThis,946 Pipe outputPipe,947 InvocationInfo invocationInfo,948 bool propagateAllExceptionsToTop = false,949 List<PSVariable> variablesToDefine = null,950 Dictionary<string, ScriptBlock> functionsToDefine = null,951 object[] args = null)952 {953 bool shouldGenerateEvent = false;954 bool oldPropagateExceptions = false;955 ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();956 957 if (SessionStateInternal != null && SessionStateInternal.ExecutionContext != context)958 {959 context = SessionStateInternal.ExecutionContext;960 shouldGenerateEvent = true;961 }962 else if (context == null)963 {964 // This will throw.965 GetContextFromTLS();966 }967 else968 {969 if (propagateAllExceptionsToTop)970 {971 oldPropagateExceptions = context.PropagateExceptionsToEnclosingStatementBlock;972 context.PropagateExceptionsToEnclosingStatementBlock = true;973 }974 975 try976 {977 var runspace = (RunspaceBase)context.CurrentRunspace;978 if (runspace.CanRunActionInCurrentPipeline())979 {980 InvokeWithPipeImpl(981 useLocalScope,982 functionsToDefine,983 variablesToDefine,984 errorHandlingBehavior,985 dollarUnder,986 input,987 scriptThis,988 outputPipe,989 invocationInfo,990 args);991 }992 else993 {994 shouldGenerateEvent = true;995 }996 }997 finally998 {999 if (propagateAllExceptionsToTop)1000 {1001 context.PropagateExceptionsToEnclosingStatementBlock = oldPropagateExceptions;1002 }1003 }1004 }1005 1006 if (shouldGenerateEvent)1007 {1008 context.Events.SubscribeEvent(1009 source: null,1010 eventName: PSEngineEvent.OnScriptBlockInvoke,1011 sourceIdentifier: PSEngineEvent.OnScriptBlockInvoke,1012 data: null,1013 handlerDelegate: new PSEventReceivedEventHandler(OnScriptBlockInvokeEventHandler),1014 supportEvent: true,1015 forwardEvent: false,1016 shouldQueueAndProcessInExecutionThread: true,1017 maxTriggerCount: 1);1018 1019 var scriptBlockInvocationEventArgs = new ScriptBlockInvocationEventArgs(1020 scriptBlock: this,1021 useLocalScope,1022 errorHandlingBehavior,1023 dollarUnder,1024 input,1025 scriptThis,1026 outputPipe,1027 invocationInfo,1028 args);1029 1030 context.Events.GenerateEvent(1031 sourceIdentifier: PSEngineEvent.OnScriptBlockInvoke,1032 sender: null,1033 args: new object[1] { scriptBlockInvocationEventArgs },1034 extraData: null,1035 processInCurrentThread: true,1036 waitForCompletionInCurrentThread: true);1037 1038 scriptBlockInvocationEventArgs.Exception?.Throw();1039 }1040 }1041 1042 /// <summary>1043 /// Handles OnScriptBlockInvoke event, this is called by the event manager.1044 /// </summary>1045 private static void OnScriptBlockInvokeEventHandler(object sender, PSEventArgs args)1046 {1047 var eventArgs = (object)args.SourceEventArgs as ScriptBlockInvocationEventArgs;1048 Diagnostics.Assert(eventArgs != null,1049 "Event Arguments to OnScriptBlockInvokeEventHandler should not be null");1050 1051 try1052 {1053 ScriptBlock sb = eventArgs.ScriptBlock;1054 sb.InvokeWithPipeImpl(1055 eventArgs.UseLocalScope,1056 functionsToDefine: null,1057 variablesToDefine: null,1058 eventArgs.ErrorHandlingBehavior,1059 eventArgs.DollarUnder,1060 eventArgs.Input,1061 eventArgs.ScriptThis,1062 eventArgs.OutputPipe,1063 eventArgs.InvocationInfo,1064 eventArgs.Args);1065 }1066 catch (Exception e)1067 {1068 eventArgs.Exception = ExceptionDispatchInfo.Capture(e);1069 }1070 }1071 1072 internal void SetPSScriptRootAndPSCommandPath(MutableTuple locals, ExecutionContext context)1073 {1074 var psScriptRoot = string.Empty;1075 var psCommandPath = string.Empty;1076 if (!string.IsNullOrEmpty(File))1077 {1078 psScriptRoot = Path.GetDirectoryName(File);1079 psCommandPath = File;1080 }1081 1082 locals.SetAutomaticVariable(AutomaticVariable.PSScriptRoot, psScriptRoot, context);1083 locals.SetAutomaticVariable(AutomaticVariable.PSCommandPath, psCommandPath, context);1084 }1085 }1086 1087 /// <summary>1088 /// A steppable pipeline wrapper object...1089 /// </summary>1090 [SuppressMessage(1091 "Microsoft.Naming",1092 "CA1704:IdentifiersShouldBeSpelledCorrectly",1093 MessageId = "Steppable",1094 Justification = "Consider Name change during API review")]1095 public sealed class SteppablePipeline : IDisposable1096 {1097 internal SteppablePipeline(ExecutionContext context, PipelineProcessor pipeline)1098 {1099 ArgumentNullException.ThrowIfNull(pipeline);1100 1101 ArgumentNullException.ThrowIfNull(context);1102 1103 _pipeline = pipeline;1104 _context = context;1105 }1106 1107 private readonly PipelineProcessor _pipeline;1108 private readonly ExecutionContext _context;1109 private bool _expectInput;1110 1111 /// <summary>1112 /// Begin execution of a steppable pipeline. This overload doesn't reroute output and error pipes.1113 /// </summary>1114 /// <param name="expectInput"><see langword="true"/> if you plan to write input into this pipe; <see langword="false"/> otherwise.</param>1115 public void Begin(bool expectInput) => Begin(expectInput, commandRuntime: (ICommandRuntime)null);1116 1117 /// <summary>1118 /// Begin execution of a steppable pipeline, using the command running currently in the specified context to figure1119 /// out how to route the output and errors.1120 /// </summary>1121 /// <param name="expectInput"><see langword="true"/> if you plan to write input into this pipe; <see langword="false"/> otherwise.</param>1122 /// <param name="contextToRedirectTo">Context used to figure out how to route the output and errors.</param>1123 public void Begin(bool expectInput, EngineIntrinsics contextToRedirectTo)1124 {1125 ArgumentNullException.ThrowIfNull(contextToRedirectTo);1126 1127 ExecutionContext executionContext = contextToRedirectTo.SessionState.Internal.ExecutionContext;1128 CommandProcessorBase commandProcessor = executionContext.CurrentCommandProcessor;1129 ICommandRuntime crt = commandProcessor?.CommandRuntime;1130 Begin(expectInput, crt);1131 }1132 1133 /// <summary>1134 /// Begin execution of a steppable pipeline, using the calling command to figure1135 /// out how to route the output and errors. This is the most effective1136 /// way to start stepping.1137 /// </summary>1138 /// <param name="command">The command you're calling this from (i.e. instance of PSCmdlet or value of $PSCmdlet variable).</param>1139 public void Begin(InternalCommand command)1140 {1141 if (command is null || command.MyInvocation is null)1142 {1143 throw new ArgumentNullException(nameof(command));1144 }1145 1146 Begin(command.MyInvocation.ExpectingInput, command.commandRuntime);1147 }1148 1149 private void Begin(bool expectInput, ICommandRuntime commandRuntime)1150 {1151 try1152 {1153 _pipeline.ExecutionScope = _context.EngineSessionState.CurrentScope;1154 _context.PushPipelineProcessor(_pipeline);1155 _expectInput = expectInput;1156 1157 // Start the pipeline, if the command calling this pipeline is1158 // not expecting input (as indicated by it's position in the pipeline1159 // then neither should we.1160 MshCommandRuntime crt = commandRuntime as MshCommandRuntime;1161 if (crt != null)1162 {1163 if (crt.OutputPipe != null)1164 {1165 _pipeline.LinkPipelineSuccessOutput(crt.OutputPipe);1166 }1167 1168 if (crt.ErrorOutputPipe != null)1169 {1170 _pipeline.LinkPipelineErrorOutput(crt.ErrorOutputPipe);1171 }1172 }1173 1174 _pipeline.StartStepping(_expectInput);1175 }1176 finally1177 {1178 // then pop this pipeline...1179 _context.PopPipelineProcessor(true);1180 }1181 }1182 1183 /// <summary>1184 /// Process a single input object.1185 /// </summary>1186 /// <param name="input">The object to process.</param>1187 /// <returns>A collection of 0 or more result objects.</returns>1188 public Array Process(object input)1189 {1190 try1191 {1192 _context.PushPipelineProcessor(_pipeline);1193 if (_expectInput)1194 {1195 return _pipeline.Step(input);1196 }1197 else1198 {1199 return _pipeline.Step(AutomationNull.Value);1200 }