mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-14 21:59:20 +00:00
1465 lines
60 KiB
C#
1465 lines
60 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Language;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Runtime.Serialization;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// An object representing a pre-compiled block of powershell script.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This class track a block of script in a compiled form. It is also
|
|
/// used for direct invocation of the script block.
|
|
///
|
|
/// 1. Overview
|
|
///
|
|
/// Script block comes in two forms,
|
|
///
|
|
/// a. Full form (cmdlet form)
|
|
///
|
|
/// This comes in following format
|
|
///
|
|
/// {
|
|
/// begin
|
|
/// {
|
|
/// statementlist;
|
|
/// }
|
|
/// process
|
|
/// {
|
|
/// statementlist;
|
|
/// }
|
|
/// end
|
|
/// {
|
|
/// statementlist;
|
|
/// }
|
|
/// }
|
|
///
|
|
/// This form is used for running the script in a pipeline like
|
|
/// a cmdlet.
|
|
///
|
|
/// b. Simple form
|
|
///
|
|
/// This comes in following format
|
|
///
|
|
/// {
|
|
/// statementlist;
|
|
/// }
|
|
///
|
|
/// 2. Script block execution
|
|
///
|
|
/// For the full form (or cmdlet form) of script block, the script
|
|
/// block itself is part of a pipeline. Its execution is handled through
|
|
/// ScriptCommandProcessor, which involves execution of begin/process/end
|
|
/// blocks like a cmdlet. If a scriptblock in simple form is used in
|
|
/// a pipeline, its execution is done through ScriptCommandProcessor
|
|
/// also, with some of begin/process/end blocks default to be empty.
|
|
///
|
|
/// A script block in simple form can be directly invoked (outside
|
|
/// of a pipeline context). For example,
|
|
///
|
|
/// {"text"}.Invoke()
|
|
///
|
|
/// A scriptblock can be directly invoked internally or externally through
|
|
/// runspace API.
|
|
///
|
|
/// This class will handle the logic for direct invocation of script blocks.
|
|
/// </remarks>
|
|
public partial class ScriptBlock
|
|
{
|
|
/// <summary>
|
|
/// Create a script block object based on a script string to be parsed immediately.
|
|
/// </summary>
|
|
/// <param name="context">Engine context for this script block.</param>
|
|
/// <param name="script">The string to compile.</param>
|
|
internal static ScriptBlock Create(ExecutionContext context, string script)
|
|
{
|
|
ScriptBlock sb = Create(context.Engine.EngineParser, null, script);
|
|
if (context.EngineSessionState != null && context.EngineSessionState.Module != null)
|
|
{
|
|
sb.SessionStateInternal = context.EngineSessionState;
|
|
}
|
|
|
|
return sb;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a script block based on a script to be parsed when execution
|
|
/// context is provided.
|
|
/// </summary>
|
|
/// <param name="script">The string to compile.</param>
|
|
public static ScriptBlock Create(string script) => Create(
|
|
parser: new Language.Parser(),
|
|
fileName: null,
|
|
fileContents: script);
|
|
|
|
internal static ScriptBlock CreateDelayParsedScriptBlock(string script, bool isProductCode)
|
|
=> new ScriptBlock(new CompiledScriptBlockData(script, isProductCode)) { DebuggerHidden = true };
|
|
|
|
/// <summary>
|
|
/// Returns a new scriptblock bound to a module. Any local variables in the
|
|
/// callers context will be copied into the module.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public ScriptBlock GetNewClosure()
|
|
{
|
|
PSModuleInfo m = new PSModuleInfo(true);
|
|
m.CaptureLocals();
|
|
return m.NewBoundScriptBlock(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns PowerShell object representing the pipeline contained in this ScriptBlock.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Some ScriptBlocks are too complicated to be converted into a PowerShell object.
|
|
/// For those ScriptBlocks a <see cref="ScriptBlockToPowerShellNotSupportedException"/> is thrown.
|
|
///
|
|
/// ScriptBlock cannot be converted into a PowerShell object if
|
|
/// - It contains more than one statement
|
|
/// - It references variables undeclared in <c>param(...)</c> block
|
|
/// - It uses redirection to a file
|
|
/// - It uses dot sourcing
|
|
/// - Command names can't be resolved (i.e. if an element of a pipeline is another scriptblock)
|
|
///
|
|
/// Declaration of variables in a <c>param(...)</c> block is enforced,
|
|
/// because undeclared variables are assumed to be variables from a remoting server.
|
|
/// Since we need to fully evaluate parameters of commands of a PowerShell object's
|
|
/// we reject all variables references that refer to a variable from a remoting server.
|
|
/// </remarks>
|
|
/// <param name="args">
|
|
/// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);
|
|
/// can be null
|
|
/// </param>
|
|
/// <returns>
|
|
/// PowerShell object representing the pipeline contained in this ScriptBlock
|
|
/// </returns>
|
|
/// <exception cref="ScriptBlockToPowerShellNotSupportedException">
|
|
/// Thrown when this ScriptBlock cannot be expressed as a PowerShell object.
|
|
/// For example thrown when there is more than one statement, if there
|
|
/// are undeclared variables, if redirection to a file is used.
|
|
/// </exception>
|
|
/// <exception cref="RuntimeException">
|
|
/// Thrown when evaluation of command arguments results in an exception.
|
|
/// Might depend on the value of $errorActionPreference variable.
|
|
/// For example trying to translate the following ScriptBlock will result in this exception:
|
|
/// <c>$errorActionPreference = "stop"; $sb = { get-foo $( throw ) }; $sb.GetPowerShell()</c>
|
|
/// </exception>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when there is no ExecutionContext associated with this ScriptBlock object.
|
|
/// </exception>
|
|
public PowerShell GetPowerShell(params object[] args) => GetPowerShellImpl(
|
|
context: LocalPipeline.GetExecutionContextFromTLS(),
|
|
variables: null,
|
|
isTrustedInput: false,
|
|
filterNonUsingVariables: false,
|
|
createLocalScope: null,
|
|
args);
|
|
|
|
/// <summary>
|
|
/// Returns PowerShell object representing the pipeline contained in this ScriptBlock,
|
|
/// similar to the GetPowerShell() method. If the 'isTrustedInput' flag parameter is set
|
|
/// to True, then the GetPowerShell() implementation supports extended conversion operations
|
|
/// (such as replacing variable values with their current values) that might otherwise
|
|
/// be unsafe if applied to untrusted input.
|
|
/// </summary>
|
|
/// <param name="isTrustedInput">
|
|
/// Specifies whether the scriptblock being converted comes from a trusted source.
|
|
/// The default is False.
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);
|
|
/// can be null
|
|
/// </param>
|
|
public PowerShell GetPowerShell(bool isTrustedInput, params object[] args)
|
|
=> GetPowerShellImpl(
|
|
context: LocalPipeline.GetExecutionContextFromTLS(),
|
|
variables: null,
|
|
isTrustedInput,
|
|
filterNonUsingVariables: false,
|
|
createLocalScope: null,
|
|
args);
|
|
|
|
/// <summary>
|
|
/// Returns PowerShell object representing the pipeline contained in this ScriptBlock, using variables
|
|
/// supplied in the dictionary.
|
|
/// </summary>
|
|
/// <param name="variables">
|
|
/// variables to be supplied as context to the ScriptBlock (providing values for variables explicitly
|
|
/// requested by the 'using:' prefix.
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);
|
|
/// can be null
|
|
/// </param>
|
|
/// <returns>
|
|
/// PowerShell object representing the pipeline contained in this ScriptBlock
|
|
/// </returns>
|
|
/// <exception cref="ScriptBlockToPowerShellNotSupportedException">
|
|
/// Thrown when this ScriptBlock cannot be expressed as a PowerShell object.
|
|
/// For example thrown when there is more than one statement, if there
|
|
/// are undeclared variables, if redirection to a file is used.
|
|
/// </exception>
|
|
/// <exception cref="RuntimeException">
|
|
/// Thrown when evaluation of command arguments results in an exception.
|
|
/// Might depend on the value of $errorActionPreference variable.
|
|
/// For example trying to translate the following ScriptBlock will result in this exception:
|
|
/// <c>$errorActionPreference = "stop"; $sb = { get-foo $( throw ) }; $sb.GetPowerShell()</c>
|
|
/// </exception>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when there is no ExecutionContext associated with this ScriptBlock object and no
|
|
/// variables are supplied.
|
|
/// </exception>
|
|
public PowerShell GetPowerShell(Dictionary<string, object> variables, params object[] args)
|
|
{
|
|
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
|
|
Dictionary<string, object> suppliedVariables = null;
|
|
|
|
if (variables != null)
|
|
{
|
|
suppliedVariables = new Dictionary<string, object>(variables, StringComparer.OrdinalIgnoreCase);
|
|
context = null;
|
|
}
|
|
|
|
return GetPowerShellImpl(context, suppliedVariables, false, false, null, args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns PowerShell object representing the pipeline contained in this ScriptBlock, using variables
|
|
/// supplied in the dictionary.
|
|
/// </summary>
|
|
/// <param name="variables">
|
|
/// variables to be supplied as context to the ScriptBlock (providing values for variables explicitly
|
|
/// requested by the 'using:' prefix.
|
|
/// </param>
|
|
/// <param name="usingVariables">
|
|
/// key-value pairs from the <para>variables</para> that actually get used by the 'using:' prefix variables
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);
|
|
/// can be null
|
|
/// </param>
|
|
/// <returns>
|
|
/// PowerShell object representing the pipeline contained in this ScriptBlock
|
|
/// </returns>
|
|
/// <exception cref="ScriptBlockToPowerShellNotSupportedException">
|
|
/// Thrown when this ScriptBlock cannot be expressed as a PowerShell object.
|
|
/// For example thrown when there is more than one statement, if there
|
|
/// are undeclared variables, if redirection to a file is used.
|
|
/// </exception>
|
|
/// <exception cref="RuntimeException">
|
|
/// Thrown when evaluation of command arguments results in an exception.
|
|
/// Might depend on the value of $errorActionPreference variable.
|
|
/// For example trying to translate the following ScriptBlock will result in this exception:
|
|
/// <c>$errorActionPreference = "stop"; $sb = { get-foo $( throw ) }; $sb.GetPowerShell()</c>
|
|
/// </exception>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when there is no ExecutionContext associated with this ScriptBlock object and no
|
|
/// variables are supplied.
|
|
/// </exception>
|
|
public PowerShell GetPowerShell(
|
|
Dictionary<string, object> variables,
|
|
out Dictionary<string, object> usingVariables,
|
|
params object[] args)
|
|
=> GetPowerShell(variables, out usingVariables, isTrustedInput: false, args);
|
|
|
|
/// <summary>
|
|
/// Returns PowerShell object representing the pipeline contained in this ScriptBlock, using variables
|
|
/// supplied in the dictionary.
|
|
/// </summary>
|
|
/// <param name="variables">
|
|
/// variables to be supplied as context to the ScriptBlock (providing values for variables explicitly
|
|
/// requested by the 'using:' prefix.
|
|
/// </param>
|
|
/// <param name="usingVariables">
|
|
/// key-value pairs from the <para>variables</para> that actually get used by the 'using:' prefix variables
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// arguments for the ScriptBlock (providing values for variables used within the ScriptBlock);
|
|
/// can be null
|
|
/// </param>
|
|
/// <param name="isTrustedInput">
|
|
/// Specifies whether the scriptblock being converted comes from a trusted source.
|
|
/// The default is False.
|
|
/// </param>
|
|
/// <returns>
|
|
/// PowerShell object representing the pipeline contained in this ScriptBlock
|
|
/// </returns>
|
|
/// <exception cref="ScriptBlockToPowerShellNotSupportedException">
|
|
/// Thrown when this ScriptBlock cannot be expressed as a PowerShell object.
|
|
/// For example thrown when there is more than one statement, if there
|
|
/// are undeclared variables, if redirection to a file is used.
|
|
/// </exception>
|
|
/// <exception cref="RuntimeException">
|
|
/// Thrown when evaluation of command arguments results in an exception.
|
|
/// Might depend on the value of $errorActionPreference variable.
|
|
/// For example trying to translate the following ScriptBlock will result in this exception:
|
|
/// <c>$errorActionPreference = "stop"; $sb = { get-foo $( throw ) }; $sb.GetPowerShell()</c>
|
|
/// </exception>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when there is no ExecutionContext associated with this ScriptBlock object and no
|
|
/// variables are supplied.
|
|
/// </exception>
|
|
public PowerShell GetPowerShell(
|
|
Dictionary<string, object> variables,
|
|
out Dictionary<string, object> usingVariables,
|
|
bool isTrustedInput,
|
|
params object[] args)
|
|
{
|
|
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
|
|
Dictionary<string, object> suppliedVariables = null;
|
|
|
|
if (variables != null)
|
|
{
|
|
suppliedVariables = new Dictionary<string, object>(variables, StringComparer.OrdinalIgnoreCase);
|
|
context = null;
|
|
}
|
|
|
|
PowerShell powershell = GetPowerShellImpl(context, suppliedVariables, isTrustedInput, true, null, args);
|
|
usingVariables = suppliedVariables;
|
|
|
|
return powershell;
|
|
}
|
|
|
|
internal PowerShell GetPowerShell(
|
|
ExecutionContext context,
|
|
bool isTrustedInput,
|
|
bool? useLocalScope,
|
|
object[] args)
|
|
=> GetPowerShellImpl(
|
|
context,
|
|
variables: null,
|
|
isTrustedInput,
|
|
filterNonUsingVariables: false,
|
|
useLocalScope,
|
|
args);
|
|
|
|
/// <summary>
|
|
/// Get a steppable pipeline object.
|
|
/// </summary>
|
|
/// <returns>A steppable pipeline object.</returns>
|
|
[SuppressMessage(
|
|
"Microsoft.Naming",
|
|
"CA1704:IdentifiersShouldBeSpelledCorrectly",
|
|
MessageId = "Steppable",
|
|
Justification = "Review this during API naming")]
|
|
public SteppablePipeline GetSteppablePipeline()
|
|
=> GetSteppablePipelineImpl(commandOrigin: CommandOrigin.Internal, args: null);
|
|
|
|
/// <summary>
|
|
/// Get a steppable pipeline object.
|
|
/// </summary>
|
|
/// <returns>A steppable pipeline object.</returns>
|
|
[SuppressMessage(
|
|
"Microsoft.Naming",
|
|
"CA1704:IdentifiersShouldBeSpelledCorrectly",
|
|
MessageId = "Steppable",
|
|
Justification = "Review this during API naming")]
|
|
public SteppablePipeline GetSteppablePipeline(CommandOrigin commandOrigin)
|
|
=> GetSteppablePipelineImpl(commandOrigin, args: null);
|
|
|
|
/// <summary>
|
|
/// Get a steppable pipeline object.
|
|
/// </summary>
|
|
/// <returns>A steppable pipeline object.</returns>
|
|
[SuppressMessage(
|
|
"Microsoft.Naming",
|
|
"CA1704:IdentifiersShouldBeSpelledCorrectly",
|
|
MessageId = "Steppable",
|
|
Justification = "Review this during API naming")]
|
|
public SteppablePipeline GetSteppablePipeline(CommandOrigin commandOrigin, object[] args)
|
|
=> GetSteppablePipelineImpl(commandOrigin, args);
|
|
|
|
/// <summary>
|
|
/// Execute this node with the specified arguments. The arguments show
|
|
/// up in the script as $args with $_ being the first argument.
|
|
/// </summary>
|
|
/// <param name="args">The arguments to this script.</param>
|
|
/// <returns>The object(s) generated during the execution of
|
|
/// the script block returned as a collection of PSObjects.</returns>
|
|
/// <exception cref="RuntimeException">Thrown if a script runtime exceptionexception occurred.</exception>
|
|
/// <exception cref="FlowControlException">An internal (non-public) exception from a flow control statement.</exception>
|
|
public Collection<PSObject> Invoke(params object[] args) =>
|
|
DoInvoke(dollarUnder: AutomationNull.Value, input: AutomationNull.Value, args);
|
|
|
|
/// <summary>
|
|
/// A method that allows a scriptblock to be invoked with additional context in the form of a
|
|
/// set of local functions and variables to be defined in the scriptblock's scope. The list of
|
|
/// variables may include the special variables $input, $_ and $this.
|
|
///
|
|
/// This overload of the function takes a hashtable and converts it to the
|
|
/// required dictionary which makes the API easier to use from within a PowerShell script.
|
|
/// </summary>
|
|
/// <param name="functionsToDefine">A dictionary of functions to define.</param>
|
|
/// <param name="variablesToDefine">A list of variables to define.</param>
|
|
/// <param name="args">The arguments to the actual scriptblock.</param>
|
|
/// <returns></returns>
|
|
public Collection<PSObject> InvokeWithContext(
|
|
IDictionary functionsToDefine,
|
|
List<PSVariable> variablesToDefine,
|
|
params object[] args)
|
|
{
|
|
Dictionary<string, ScriptBlock> functionsToDefineDictionary = null;
|
|
if (functionsToDefine != null)
|
|
{
|
|
functionsToDefineDictionary = new Dictionary<string, ScriptBlock>();
|
|
foreach (DictionaryEntry pair in functionsToDefine)
|
|
{
|
|
string functionName = pair.Key as string;
|
|
if (string.IsNullOrWhiteSpace(functionName))
|
|
{
|
|
PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
|
|
ParserStrings.EmptyFunctionNameInFunctionDefinitionDictionary);
|
|
|
|
e.SetErrorId("EmptyFunctionNameInFunctionDefinitionDictionary");
|
|
throw e;
|
|
}
|
|
|
|
ScriptBlock functionBody = pair.Value as ScriptBlock;
|
|
// null check for functionBody is done at the lower layer.
|
|
functionsToDefineDictionary.Add(functionName, functionBody);
|
|
}
|
|
}
|
|
|
|
return InvokeWithContext(
|
|
functionsToDefineDictionary,
|
|
variablesToDefine,
|
|
args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A method that allows a scriptblock to be invoked with additional context in the form of a
|
|
/// set of local functions and variables to be defined in the scriptblock's scope. The list of
|
|
/// variables may include the special variables $input, $_ and $this.
|
|
/// </summary>
|
|
/// <param name="functionsToDefine">A dictionary of functions to define.</param>
|
|
/// <param name="variablesToDefine">A list of variables to define.</param>
|
|
/// <param name="args">The arguments to the actual scriptblock.</param>
|
|
/// <returns></returns>
|
|
public Collection<PSObject> InvokeWithContext(
|
|
Dictionary<string, ScriptBlock> functionsToDefine,
|
|
List<PSVariable> variablesToDefine,
|
|
params object[] args)
|
|
{
|
|
object input = AutomationNull.Value;
|
|
object dollarUnder = AutomationNull.Value;
|
|
object scriptThis = AutomationNull.Value;
|
|
|
|
if (variablesToDefine != null)
|
|
{
|
|
// Extract the special variables "this", "input" and "_"
|
|
PSVariable located = variablesToDefine.Find(
|
|
v => string.Equals(v.Name, "this", StringComparison.OrdinalIgnoreCase));
|
|
if (located != null)
|
|
{
|
|
scriptThis = located.Value;
|
|
variablesToDefine.Remove(located);
|
|
}
|
|
|
|
located = variablesToDefine.Find(
|
|
v => string.Equals(v.Name, "_", StringComparison.Ordinal));
|
|
if (located != null)
|
|
{
|
|
dollarUnder = located.Value;
|
|
variablesToDefine.Remove(located);
|
|
}
|
|
|
|
located = variablesToDefine.Find(
|
|
v => string.Equals(v.Name, "input", StringComparison.OrdinalIgnoreCase));
|
|
if (located != null)
|
|
{
|
|
input = located.Value;
|
|
variablesToDefine.Remove(located);
|
|
}
|
|
}
|
|
|
|
List<object> result = new List<object>();
|
|
Pipe outputPipe = new Pipe(result);
|
|
|
|
InvokeWithPipe(
|
|
useLocalScope: true,
|
|
functionsToDefine: functionsToDefine,
|
|
variablesToDefine: variablesToDefine,
|
|
errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe,
|
|
dollarUnder: dollarUnder,
|
|
input: input,
|
|
scriptThis: scriptThis,
|
|
outputPipe: outputPipe,
|
|
invocationInfo: null,
|
|
args: args);
|
|
return GetWrappedResult(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute this node with the specified arguments. The arguments show
|
|
/// up in the script as $args. This overload return the raw (unwrapped) result
|
|
/// so it can be more efficient.
|
|
/// </summary>
|
|
/// <param name="args">The arguments to pass to this scriptblock.</param>
|
|
/// <returns>The object(s) generated during the execution of the
|
|
/// script block. They may or may not be wrapped in PSObject. It's up to the caller to check.</returns>
|
|
/// <exception cref="RuntimeException">Thrown if a script runtime exceptionexception occurred.</exception>
|
|
/// <exception cref="FlowControlException">An internal (non-public) exception from a flow control statement.</exception>
|
|
public object InvokeReturnAsIs(params object[] args)
|
|
=> DoInvokeReturnAsIs(
|
|
useLocalScope: true,
|
|
errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe,
|
|
dollarUnder: AutomationNull.Value,
|
|
input: AutomationNull.Value,
|
|
scriptThis: AutomationNull.Value,
|
|
args: args);
|
|
|
|
internal T InvokeAsMemberFunctionT<T>(object instance, object[] args)
|
|
{
|
|
List<object> result = new List<object>();
|
|
Pipe pipe = new Pipe(result);
|
|
|
|
InvokeWithPipe(
|
|
useLocalScope: true,
|
|
errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe,
|
|
dollarUnder: AutomationNull.Value,
|
|
input: AutomationNull.Value,
|
|
scriptThis: instance ?? AutomationNull.Value,
|
|
outputPipe: pipe,
|
|
invocationInfo: null,
|
|
propagateAllExceptionsToTop: true,
|
|
args: args);
|
|
|
|
// This is needed only for the case where the
|
|
// method returns [object]. If the argument to 'return'
|
|
// is a pipeline that emits nothing then result.Count will
|
|
// be zero so we catch that and "convert" it to null. Note that
|
|
// the return statement is still required in the method, it
|
|
// just recieves nothing from it's argument.
|
|
if (result.Count == 0)
|
|
{
|
|
return default(T);
|
|
}
|
|
|
|
return (T)result[0];
|
|
}
|
|
|
|
internal void InvokeAsMemberFunction(object instance, object[] args)
|
|
{
|
|
List<object> result = new List<object>();
|
|
Pipe pipe = new Pipe(result);
|
|
|
|
InvokeWithPipe(
|
|
useLocalScope: true,
|
|
errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe,
|
|
dollarUnder: AutomationNull.Value,
|
|
input: AutomationNull.Value,
|
|
scriptThis: instance ?? AutomationNull.Value,
|
|
outputPipe: pipe,
|
|
invocationInfo: null,
|
|
propagateAllExceptionsToTop: true,
|
|
args: args);
|
|
Diagnostics.Assert(result.Count == 0, "Code generation ensures we return the correct type");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return all attributes on a script block.
|
|
/// </summary>
|
|
public List<Attribute> Attributes { get => GetAttributes(); }
|
|
|
|
/// <summary>
|
|
/// The script file that defined this script block.
|
|
/// </summary>
|
|
public string File { get => GetFileName(); }
|
|
|
|
/// <summary>
|
|
/// Get/set whether this scriptblock is a filter.
|
|
/// </summary>
|
|
public bool IsFilter { get => _scriptBlockData.IsFilter; }
|
|
|
|
/// <summary>
|
|
/// Get/set whether this scriptblock is a Configuration.
|
|
/// </summary>
|
|
public bool IsConfiguration { get => _scriptBlockData.GetIsConfiguration(); }
|
|
|
|
/// <summary>
|
|
/// Get the PSModuleInfo object for the module that defined this
|
|
/// scriptblock.
|
|
/// </summary>
|
|
public PSModuleInfo Module { get => SessionStateInternal != null ? SessionStateInternal.Module : null; }
|
|
|
|
/// <summary>
|
|
/// Return the PSToken object for this function definition...
|
|
/// </summary>
|
|
public PSToken StartPosition { get => GetStartPosition(); }
|
|
|
|
// LanguageMode is a nullable PSLanguageMode enumeration because script blocks
|
|
// need to inherit the language mode from the context in which they are executing.
|
|
// We can't assume FullLanguage by default when there is no context, as there are
|
|
// script blocks (such as the script blocks used in Workflow activities) that are
|
|
// created by the host without a "current language mode" to inherit. They ultimately
|
|
// get their language mode set when they are finally invoked in a constrained
|
|
// language runspace.
|
|
// Script blocks that should always be run under FullLanguage mode (i.e.: set in
|
|
// InitialSessionState, etc.) should explicitly set the LanguageMode to FullLanguage
|
|
// when they are created.
|
|
internal PSLanguageMode? LanguageMode { get; set; }
|
|
|
|
internal enum ErrorHandlingBehavior
|
|
{
|
|
WriteToCurrentErrorPipe = 1,
|
|
WriteToExternalErrorPipe = 2,
|
|
SwallowErrors = 3,
|
|
}
|
|
|
|
internal ReadOnlyCollection<PSTypeName> OutputType
|
|
{
|
|
get
|
|
{
|
|
List<PSTypeName> result = new List<PSTypeName>();
|
|
foreach (Attribute attribute in Attributes)
|
|
{
|
|
OutputTypeAttribute outputType = attribute as OutputTypeAttribute;
|
|
if (outputType != null)
|
|
{
|
|
result.AddRange(outputType.Type);
|
|
}
|
|
}
|
|
|
|
return new ReadOnlyCollection<PSTypeName>(result);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a helper function to process script invocation result.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This does normal array reduction in the case of a one-element array.
|
|
/// </remarks>
|
|
internal static object GetRawResult(List<object> result, bool wrapToPSObject)
|
|
{
|
|
switch (result.Count)
|
|
{
|
|
case 0:
|
|
return AutomationNull.Value;
|
|
case 1:
|
|
return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(result[0]) : result[0];
|
|
default:
|
|
object resultArray = result.ToArray();
|
|
return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(resultArray) : resultArray;
|
|
}
|
|
}
|
|
|
|
internal void InvokeUsingCmdlet(
|
|
Cmdlet contextCmdlet,
|
|
bool useLocalScope,
|
|
ErrorHandlingBehavior errorHandlingBehavior,
|
|
object dollarUnder,
|
|
object input,
|
|
object scriptThis,
|
|
object[] args)
|
|
{
|
|
Diagnostics.Assert(contextCmdlet != null, "caller to verify contextCmdlet parameter");
|
|
|
|
Pipe outputPipe = ((MshCommandRuntime)contextCmdlet.CommandRuntime).OutputPipe;
|
|
ExecutionContext context = GetContextFromTLS();
|
|
var myInv = context.EngineSessionState.CurrentScope.GetAutomaticVariableValue(AutomaticVariable.MyInvocation);
|
|
InvocationInfo inInfo = myInv == AutomationNull.Value ? null : (InvocationInfo)myInv;
|
|
InvokeWithPipe(
|
|
useLocalScope,
|
|
errorHandlingBehavior,
|
|
dollarUnder,
|
|
input,
|
|
scriptThis,
|
|
outputPipe,
|
|
inInfo,
|
|
propagateAllExceptionsToTop: false,
|
|
args: args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The internal session state object associated with this scriptblock.
|
|
/// </summary>
|
|
internal SessionStateInternal SessionStateInternal { get; set; }
|
|
|
|
/// <summary>
|
|
/// The session state instance that should be used when evaluating
|
|
/// this scriptblock.
|
|
/// </summary>
|
|
internal SessionState SessionState
|
|
{
|
|
get
|
|
{
|
|
if (SessionStateInternal == null)
|
|
{
|
|
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
|
|
if (context != null)
|
|
{
|
|
SessionStateInternal = context.EngineSessionState.PublicSessionState.Internal;
|
|
}
|
|
}
|
|
|
|
return SessionStateInternal != null ? SessionStateInternal.PublicSessionState : null;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(value));
|
|
}
|
|
|
|
SessionStateInternal = value.Internal;
|
|
}
|
|
}
|
|
|
|
#region Delegates
|
|
|
|
private static readonly ConditionalWeakTable<ScriptBlock, ConcurrentDictionary<Type, Delegate>> s_delegateTable =
|
|
new ConditionalWeakTable<ScriptBlock, ConcurrentDictionary<Type, Delegate>>();
|
|
|
|
internal Delegate GetDelegate(Type delegateType)
|
|
=> s_delegateTable.GetOrCreateValue(this).GetOrAdd(delegateType, CreateDelegate);
|
|
|
|
/// <summary>
|
|
/// Get the delegate method as a call back.
|
|
/// </summary>
|
|
internal Delegate CreateDelegate(Type delegateType)
|
|
{
|
|
MethodInfo invokeMethod = delegateType.GetMethod("Invoke");
|
|
ParameterInfo[] parameters = invokeMethod.GetParameters();
|
|
if (invokeMethod.ContainsGenericParameters)
|
|
{
|
|
throw new ScriptBlockToPowerShellNotSupportedException(
|
|
"CantConvertScriptBlockToOpenGenericType",
|
|
null,
|
|
"AutomationExceptions",
|
|
"CantConvertScriptBlockToOpenGenericType",
|
|
delegateType);
|
|
}
|
|
|
|
var parameterExprs = new List<ParameterExpression>();
|
|
foreach (var parameter in parameters)
|
|
{
|
|
parameterExprs.Add(Expression.Parameter(parameter.ParameterType));
|
|
}
|
|
|
|
bool returnsSomething = !invokeMethod.ReturnType.Equals(typeof(void));
|
|
|
|
Expression dollarUnderExpr;
|
|
Expression dollarThisExpr;
|
|
if (parameters.Length == 2 && !returnsSomething)
|
|
{
|
|
// V1 was designed for System.EventHandler and not much else.
|
|
// The first arg (sender) was bound to $this, the second (e or EventArgs) was bound to $_.
|
|
// We do this for backwards compatibility, but we also bind the parameters (or $args) for
|
|
// consistency w/ delegates that take more or fewer parameters.
|
|
dollarUnderExpr = parameterExprs[1].Cast(typeof(object));
|
|
dollarThisExpr = parameterExprs[0].Cast(typeof(object));
|
|
}
|
|
else
|
|
{
|
|
dollarUnderExpr = ExpressionCache.AutomationNullConstant;
|
|
dollarThisExpr = ExpressionCache.AutomationNullConstant;
|
|
}
|
|
|
|
Expression call = Expression.Call(
|
|
Expression.Constant(this),
|
|
CachedReflectionInfo.ScriptBlock_InvokeAsDelegateHelper,
|
|
dollarUnderExpr,
|
|
dollarThisExpr,
|
|
Expression.NewArrayInit(typeof(object), parameterExprs.Select(p => p.Cast(typeof(object)))));
|
|
if (returnsSomething)
|
|
{
|
|
call = DynamicExpression.Dynamic(
|
|
PSConvertBinder.Get(invokeMethod.ReturnType),
|
|
invokeMethod.ReturnType,
|
|
call);
|
|
}
|
|
|
|
return Expression.Lambda(delegateType, call, parameterExprs).Compile();
|
|
}
|
|
|
|
internal object InvokeAsDelegateHelper(object dollarUnder, object dollarThis, object[] args)
|
|
{
|
|
// Retrieve context and current runspace to ensure that we throw exception, if this is non-default runspace.
|
|
ExecutionContext context = GetContextFromTLS();
|
|
RunspaceBase runspace = (RunspaceBase)context.CurrentRunspace;
|
|
|
|
List<object> rawResult = new List<object>();
|
|
Pipe outputPipe = new Pipe(rawResult);
|
|
InvokeWithPipe(
|
|
useLocalScope: true,
|
|
errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe,
|
|
dollarUnder: dollarUnder,
|
|
input: null,
|
|
scriptThis: dollarThis,
|
|
outputPipe: outputPipe,
|
|
invocationInfo: null,
|
|
args: args);
|
|
return GetRawResult(rawResult, wrapToPSObject: false);
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Returns the current execution context from TLS, or raises an exception if it is null.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// An attempt was made to use the scriptblock outside the engine.
|
|
/// </exception>
|
|
internal ExecutionContext GetContextFromTLS()
|
|
{
|
|
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
|
|
|
|
// If ExecutionContext from TLS is null then we are not in powershell engine thread.
|
|
if (context == null)
|
|
{
|
|
string scriptText = this.ToString();
|
|
|
|
scriptText = ErrorCategoryInfo.Ellipsize(CultureInfo.CurrentUICulture, scriptText);
|
|
|
|
PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
|
|
ParserStrings.ScriptBlockDelegateInvokedFromWrongThread,
|
|
scriptText);
|
|
|
|
e.SetErrorId("ScriptBlockDelegateInvokedFromWrongThread");
|
|
throw e;
|
|
}
|
|
|
|
return context;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute this node with the specified arguments. The arguments show
|
|
/// up in the script as $args with $_ being the first argument.
|
|
/// </summary>
|
|
/// <param name="dollarUnder">
|
|
/// The value of the $_ variable for the script block. If AutomationNull.Value,
|
|
/// the $_ variable is not created.
|
|
/// </param>
|
|
/// <param name="input">
|
|
/// The value of the $input variable for the script block. If AutomationNull.Value,
|
|
/// the $input variable is not created.
|
|
/// </param>
|
|
/// <param name="args">The arguments to this script.</param>
|
|
/// <returns>The object(s) generated during the execution of
|
|
/// the script block returned as a collection of PSObjects.</returns>
|
|
/// <exception cref="RuntimeException">A script exception occurred.</exception>
|
|
/// <exception cref="FlowControlException">Internal exception from a flow control statement.</exception>
|
|
internal Collection<PSObject> DoInvoke(object dollarUnder, object input, object[] args)
|
|
{
|
|
List<object> result = new List<object>();
|
|
Pipe outputPipe = new Pipe(result);
|
|
InvokeWithPipe(
|
|
useLocalScope: true,
|
|
errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe,
|
|
dollarUnder: dollarUnder,
|
|
input: input,
|
|
scriptThis: AutomationNull.Value,
|
|
outputPipe: outputPipe,
|
|
invocationInfo: null,
|
|
args: args);
|
|
return GetWrappedResult(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a helper function to wrap script execution results
|
|
/// in PSObjects.
|
|
/// </summary>
|
|
/// <param name="result"></param>
|
|
/// <returns></returns>
|
|
private static Collection<PSObject> GetWrappedResult(List<object> result)
|
|
{
|
|
if (result == null || result.Count == 0)
|
|
{
|
|
return new Collection<PSObject>();
|
|
}
|
|
|
|
Collection<PSObject> wrappedResult = new Collection<PSObject>();
|
|
for (int i = 0; i < result.Count; i++)
|
|
{
|
|
wrappedResult.Add(LanguagePrimitives.AsPSObjectOrNull(result[i]));
|
|
}
|
|
|
|
return wrappedResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute this node with the specified arguments. The arguments show
|
|
/// up in the script as $args with $_ being the first argument.
|
|
/// </summary>
|
|
/// <param name="useLocalScope"></param>
|
|
/// <param name="errorHandlingBehavior"></param>
|
|
/// <param name="dollarUnder">
|
|
/// The value of the $_ variable for the script block. If AutomationNull.Value,
|
|
/// the $_ variable is not created.
|
|
/// </param>
|
|
/// <param name="input">
|
|
/// The value of the $input variable for the script block. If AutomationNull.Value,
|
|
/// the $input variable is not created.
|
|
/// </param>
|
|
/// <param name="scriptThis"></param>
|
|
/// <param name="args">The arguments to this script.</param>
|
|
/// <returns>The object(s) generated during the execution of
|
|
/// the script block returned as a collection of PSObjects.</returns>
|
|
/// <exception cref="RuntimeException">A script exception occurred.</exception>
|
|
/// <exception cref="FlowControlException">Internal exception from a flow control statement.</exception>
|
|
internal object DoInvokeReturnAsIs(
|
|
bool useLocalScope,
|
|
ErrorHandlingBehavior errorHandlingBehavior,
|
|
object dollarUnder,
|
|
object input,
|
|
object scriptThis,
|
|
object[] args)
|
|
{
|
|
List<object> result = new List<object>();
|
|
Pipe outputPipe = new Pipe(result);
|
|
InvokeWithPipe(
|
|
useLocalScope: useLocalScope,
|
|
errorHandlingBehavior: errorHandlingBehavior,
|
|
dollarUnder: dollarUnder,
|
|
input: input,
|
|
scriptThis: scriptThis,
|
|
outputPipe: outputPipe,
|
|
invocationInfo: null,
|
|
args: args);
|
|
return GetRawResult(result, wrapToPSObject: true);
|
|
}
|
|
|
|
internal void InvokeWithPipe(
|
|
bool useLocalScope,
|
|
ErrorHandlingBehavior errorHandlingBehavior,
|
|
object dollarUnder,
|
|
object input,
|
|
object scriptThis,
|
|
Pipe outputPipe,
|
|
InvocationInfo invocationInfo,
|
|
bool propagateAllExceptionsToTop = false,
|
|
List<PSVariable> variablesToDefine = null,
|
|
Dictionary<string, ScriptBlock> functionsToDefine = null,
|
|
object[] args = null)
|
|
{
|
|
bool shouldGenerateEvent = false;
|
|
bool oldPropagateExceptions = false;
|
|
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
|
|
|
|
if (SessionStateInternal != null && SessionStateInternal.ExecutionContext != context)
|
|
{
|
|
context = SessionStateInternal.ExecutionContext;
|
|
shouldGenerateEvent = true;
|
|
}
|
|
else if (context == null)
|
|
{
|
|
// This will throw.
|
|
GetContextFromTLS();
|
|
}
|
|
else
|
|
{
|
|
if (propagateAllExceptionsToTop)
|
|
{
|
|
oldPropagateExceptions = context.PropagateExceptionsToEnclosingStatementBlock;
|
|
context.PropagateExceptionsToEnclosingStatementBlock = true;
|
|
}
|
|
|
|
try
|
|
{
|
|
var runspace = (RunspaceBase)context.CurrentRunspace;
|
|
if (runspace.CanRunActionInCurrentPipeline())
|
|
{
|
|
InvokeWithPipeImpl(
|
|
useLocalScope,
|
|
functionsToDefine,
|
|
variablesToDefine,
|
|
errorHandlingBehavior,
|
|
dollarUnder,
|
|
input,
|
|
scriptThis,
|
|
outputPipe,
|
|
invocationInfo,
|
|
args);
|
|
}
|
|
else
|
|
{
|
|
shouldGenerateEvent = true;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (propagateAllExceptionsToTop)
|
|
{
|
|
context.PropagateExceptionsToEnclosingStatementBlock = oldPropagateExceptions;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shouldGenerateEvent)
|
|
{
|
|
context.Events.SubscribeEvent(
|
|
source: null,
|
|
eventName: PSEngineEvent.OnScriptBlockInvoke,
|
|
sourceIdentifier: PSEngineEvent.OnScriptBlockInvoke,
|
|
data: null,
|
|
handlerDelegate: new PSEventReceivedEventHandler(OnScriptBlockInvokeEventHandler),
|
|
supportEvent: true,
|
|
forwardEvent: false,
|
|
shouldQueueAndProcessInExecutionThread: true,
|
|
maxTriggerCount: 1);
|
|
|
|
var scriptBlockInvocationEventArgs = new ScriptBlockInvocationEventArgs(
|
|
scriptBlock: this,
|
|
useLocalScope,
|
|
errorHandlingBehavior,
|
|
dollarUnder,
|
|
input,
|
|
scriptThis,
|
|
outputPipe,
|
|
invocationInfo,
|
|
args);
|
|
|
|
context.Events.GenerateEvent(
|
|
sourceIdentifier: PSEngineEvent.OnScriptBlockInvoke,
|
|
sender: null,
|
|
args: new object[1] { scriptBlockInvocationEventArgs },
|
|
extraData: null,
|
|
processInCurrentThread: true,
|
|
waitForCompletionInCurrentThread: true);
|
|
|
|
if (scriptBlockInvocationEventArgs.Exception != null)
|
|
{
|
|
scriptBlockInvocationEventArgs.Exception.Throw();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles OnScriptBlockInvoke event, this is called by the event manager.
|
|
/// </summary>
|
|
private static void OnScriptBlockInvokeEventHandler(object sender, PSEventArgs args)
|
|
{
|
|
var eventArgs = (object)args.SourceEventArgs as ScriptBlockInvocationEventArgs;
|
|
Diagnostics.Assert(eventArgs != null,
|
|
"Event Arguments to OnScriptBlockInvokeEventHandler should not be null");
|
|
|
|
try
|
|
{
|
|
ScriptBlock sb = eventArgs.ScriptBlock;
|
|
sb.InvokeWithPipeImpl(
|
|
eventArgs.UseLocalScope,
|
|
functionsToDefine: null,
|
|
variablesToDefine: null,
|
|
eventArgs.ErrorHandlingBehavior,
|
|
eventArgs.DollarUnder,
|
|
eventArgs.Input,
|
|
eventArgs.ScriptThis,
|
|
eventArgs.OutputPipe,
|
|
eventArgs.InvocationInfo,
|
|
eventArgs.Args);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
eventArgs.Exception = ExceptionDispatchInfo.Capture(e);
|
|
}
|
|
}
|
|
|
|
internal void SetPSScriptRootAndPSCommandPath(MutableTuple locals, ExecutionContext context)
|
|
{
|
|
var psScriptRoot = string.Empty;
|
|
var psCommandPath = string.Empty;
|
|
if (!string.IsNullOrEmpty(File))
|
|
{
|
|
psScriptRoot = Path.GetDirectoryName(File);
|
|
psCommandPath = File;
|
|
}
|
|
|
|
locals.SetAutomaticVariable(AutomaticVariable.PSScriptRoot, psScriptRoot, context);
|
|
locals.SetAutomaticVariable(AutomaticVariable.PSCommandPath, psCommandPath, context);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A steppable pipeline wrapper object...
|
|
/// </summary>
|
|
[SuppressMessage(
|
|
"Microsoft.Naming",
|
|
"CA1704:IdentifiersShouldBeSpelledCorrectly",
|
|
MessageId = "Steppable",
|
|
Justification = "Consider Name change during API review")]
|
|
public sealed class SteppablePipeline : IDisposable
|
|
{
|
|
internal SteppablePipeline(ExecutionContext context, PipelineProcessor pipeline)
|
|
{
|
|
if (pipeline == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(pipeline));
|
|
}
|
|
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
_pipeline = pipeline;
|
|
_context = context;
|
|
}
|
|
|
|
private PipelineProcessor _pipeline;
|
|
private ExecutionContext _context;
|
|
private bool _expectInput;
|
|
|
|
/// <summary>
|
|
/// Begin execution of a steppable pipeline. This overload doesn't reroute output and error pipes.
|
|
/// </summary>
|
|
/// <param name="expectInput"><c>true</c> if you plan to write input into this pipe; <c>false</c> otherwise.</param>
|
|
public void Begin(bool expectInput) => Begin(expectInput, commandRuntime: (ICommandRuntime)null);
|
|
|
|
/// <summary>
|
|
/// Begin execution of a steppable pipeline, using the command running currently in the specified context to figure
|
|
/// out how to route the output and errors.
|
|
/// </summary>
|
|
/// <param name="expectInput"><c>true</c> if you plan to write input into this pipe; <c>false</c> otherwise.</param>
|
|
/// <param name="contextToRedirectTo">Context used to figure out how to route the output and errors.</param>
|
|
public void Begin(bool expectInput, EngineIntrinsics contextToRedirectTo)
|
|
{
|
|
if (contextToRedirectTo == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(contextToRedirectTo));
|
|
}
|
|
|
|
ExecutionContext executionContext = contextToRedirectTo.SessionState.Internal.ExecutionContext;
|
|
CommandProcessorBase commandProcessor = executionContext.CurrentCommandProcessor;
|
|
ICommandRuntime crt = commandProcessor == null ? null : commandProcessor.CommandRuntime;
|
|
Begin(expectInput, crt);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin execution of a steppable pipeline, using the calling command to figure
|
|
/// out how to route the output and errors. This is the most effective
|
|
/// way to start stepping.
|
|
/// </summary>
|
|
/// <param name="command">The command you're calling this from (i.e. instance of PSCmdlet or value of $PSCmdlet variable).</param>
|
|
public void Begin(InternalCommand command)
|
|
{
|
|
if (command == null || command.MyInvocation == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(command));
|
|
}
|
|
|
|
Begin(command.MyInvocation.ExpectingInput, command.commandRuntime);
|
|
}
|
|
|
|
private void Begin(bool expectInput, ICommandRuntime commandRuntime)
|
|
{
|
|
try
|
|
{
|
|
_pipeline.ExecutionScope = _context.EngineSessionState.CurrentScope;
|
|
_context.PushPipelineProcessor(_pipeline);
|
|
_expectInput = expectInput;
|
|
|
|
// Start the pipeline, if the command calling this pipeline is
|
|
// not expecting input (as indicated by it's position in the pipeline
|
|
// then neither should we.
|
|
MshCommandRuntime crt = commandRuntime as MshCommandRuntime;
|
|
if (crt != null)
|
|
{
|
|
if (crt.OutputPipe != null)
|
|
{
|
|
_pipeline.LinkPipelineSuccessOutput(crt.OutputPipe);
|
|
}
|
|
|
|
if (crt.ErrorOutputPipe != null)
|
|
{
|
|
_pipeline.LinkPipelineErrorOutput(crt.ErrorOutputPipe);
|
|
}
|
|
}
|
|
|
|
_pipeline.StartStepping(_expectInput);
|
|
}
|
|
finally
|
|
{
|
|
// then pop this pipeline...
|
|
_context.PopPipelineProcessor(true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process a single input object.
|
|
/// </summary>
|
|
/// <param name="input">The object to process.</param>
|
|
/// <returns>A collection of 0 or more result objects.</returns>
|
|
public Array Process(object input)
|
|
{
|
|
try
|
|
{
|
|
_context.PushPipelineProcessor(_pipeline);
|
|
if (_expectInput)
|
|
{
|
|
return _pipeline.Step(input);
|
|
}
|
|
else
|
|
{
|
|
return _pipeline.Step(AutomationNull.Value);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// then pop this pipeline...
|
|
_context.PopPipelineProcessor(true);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Process a single PSObject. This overload exists to deal with the fact
|
|
/// that the PowerShell runtime will PSBase an object before passing it to
|
|
/// a .NET API call with argument type object.
|
|
/// </summary>
|
|
/// <param name="input">The input object to process.</param>
|
|
/// <returns></returns>
|
|
public Array Process(PSObject input)
|
|
{
|
|
try
|
|
{
|
|
_context.PushPipelineProcessor(_pipeline);
|
|
if (_expectInput)
|
|
{
|
|
return _pipeline.Step(input);
|
|
}
|
|
else
|
|
{
|
|
return _pipeline.Step(AutomationNull.Value);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// then pop this pipeline...
|
|
_context.PopPipelineProcessor(true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process with no input. This is used in the case where
|
|
/// Begin() was called with $false so we won't send any
|
|
/// input to be processed.
|
|
/// </summary>
|
|
/// <returns>The result of the execution.</returns>
|
|
public Array Process()
|
|
{
|
|
try
|
|
{
|
|
_context.PushPipelineProcessor(_pipeline);
|
|
return _pipeline.Step(AutomationNull.Value);
|
|
}
|
|
finally
|
|
{
|
|
// then pop this pipeline...
|
|
_context.PopPipelineProcessor(true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// End the execution of this steppable pipeline. This will
|
|
/// complete the execution and dispose the results.
|
|
/// </summary>
|
|
public Array End()
|
|
{
|
|
try
|
|
{
|
|
_context.PushPipelineProcessor(_pipeline);
|
|
return _pipeline.DoComplete();
|
|
}
|
|
finally
|
|
{
|
|
// then pop this pipeline and dispose it...
|
|
_context.PopPipelineProcessor(true);
|
|
_pipeline.Dispose();
|
|
}
|
|
}
|
|
|
|
#region IDispose
|
|
|
|
private bool _disposed;
|
|
|
|
/// <summary>
|
|
/// IDisposable implementation
|
|
/// When this object is disposed, the contained pipeline should also be disposed.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (disposing)
|
|
{
|
|
_pipeline.Dispose();
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finalizer for class SteppablePipeline.
|
|
/// </summary>
|
|
~SteppablePipeline()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
#endregion IDispose
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the exception thrown when conversion from ScriptBlock to PowerShell is forbidden
|
|
/// (i.e. when the script block has undeclared variables or more than one statement)
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ScriptBlockToPowerShellNotSupportedException : RuntimeException
|
|
{
|
|
#region ctor
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException
|
|
/// with the message set to typeof(ScriptBlockToPowerShellNotSupportedException).FullName.
|
|
/// </summary>
|
|
public ScriptBlockToPowerShellNotSupportedException()
|
|
: base(typeof(ScriptBlockToPowerShellNotSupportedException).FullName)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message.
|
|
/// </summary>
|
|
/// <param name="message">The exception's message.</param>
|
|
public ScriptBlockToPowerShellNotSupportedException(string message)
|
|
: base(message)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message and innerException.
|
|
/// </summary>
|
|
/// <param name="message">The exception's message.</param>
|
|
/// <param name="innerException">The exceptions's inner exception.</param>
|
|
public ScriptBlockToPowerShellNotSupportedException(string message, Exception innerException)
|
|
: base(message, innerException)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recommended constructor for the class.
|
|
/// </summary>
|
|
/// <param name="errorId">String that uniquely identifies each thrown Exception.</param>
|
|
/// <param name="innerException">The inner exception.</param>
|
|
/// <param name="message">The error message.</param>
|
|
/// <param name="arguments">Arguments to the resource string.</param>
|
|
internal ScriptBlockToPowerShellNotSupportedException(
|
|
string errorId,
|
|
Exception innerException,
|
|
string message,
|
|
params object[] arguments)
|
|
: base(string.Format(CultureInfo.CurrentCulture, message, arguments), innerException)
|
|
=> this.SetErrorId(errorId);
|
|
|
|
#region Serialization
|
|
/// <summary>
|
|
/// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException with serialization parameters.
|
|
/// </summary>
|
|
/// <param name="info">Serialization information.</param>
|
|
/// <param name="context">Streaming context.</param>
|
|
protected ScriptBlockToPowerShellNotSupportedException(SerializationInfo info, StreamingContext context)
|
|
: base(info, context)
|
|
{
|
|
}
|
|
#endregion Serialization
|
|
|
|
#endregion ctor
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines Event arguments passed to OnScriptBlockInvocationEventHandler.
|
|
/// </summary>
|
|
internal sealed class ScriptBlockInvocationEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Constructs ScriptBlockInvocationEventArgs.
|
|
/// </summary>
|
|
/// <param name="scriptBlock">The scriptblock to invoke
|
|
/// </param>
|
|
/// /// <param name="useLocalScope"></param>
|
|
/// <param name="errorHandlingBehavior"></param>
|
|
/// <param name="dollarUnder">
|
|
/// The value of the $_ variable for the script block. If AutomationNull.Value,
|
|
/// the $_ variable is not created.
|
|
/// </param>
|
|
/// <param name="input">
|
|
/// The value of the $input variable for the script block. If AutomationNull.Value,
|
|
/// the $input variable is not created.
|
|
/// </param>
|
|
/// <param name="scriptThis"></param>
|
|
/// <param name="outputPipe">The output pipe which has the results of the invocation
|
|
/// </param>
|
|
/// <param name="invocationInfo">The information about current state of the runspace.</param>
|
|
/// <param name="args">The arguments to this script.</param>
|
|
/// <exception cref="ArgumentNullException">ScriptBlock is null
|
|
/// </exception>
|
|
internal ScriptBlockInvocationEventArgs(
|
|
ScriptBlock scriptBlock,
|
|
bool useLocalScope,
|
|
ScriptBlock.ErrorHandlingBehavior errorHandlingBehavior,
|
|
object dollarUnder,
|
|
object input,
|
|
object scriptThis,
|
|
Pipe outputPipe,
|
|
InvocationInfo invocationInfo,
|
|
object[] args)
|
|
{
|
|
if (scriptBlock == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock));
|
|
}
|
|
|
|
ScriptBlock = scriptBlock;
|
|
OutputPipe = outputPipe;
|
|
UseLocalScope = useLocalScope;
|
|
ErrorHandlingBehavior = errorHandlingBehavior;
|
|
DollarUnder = dollarUnder;
|
|
Input = input;
|
|
ScriptThis = scriptThis;
|
|
InvocationInfo = invocationInfo;
|
|
Args = args;
|
|
}
|
|
|
|
internal ScriptBlock ScriptBlock { get; set; }
|
|
internal bool UseLocalScope { get; set; }
|
|
internal ScriptBlock.ErrorHandlingBehavior ErrorHandlingBehavior { get; set; }
|
|
internal object DollarUnder { get; set; }
|
|
internal object Input { get; set; }
|
|
internal object ScriptThis { get; set; }
|
|
internal Pipe OutputPipe { get; set; }
|
|
internal InvocationInfo InvocationInfo { get; set; }
|
|
internal object[] Args { get; set; }
|
|
|
|
/// <summary>
|
|
/// Holds the exception thrown during scriptblock invocation.
|
|
/// </summary>
|
|
internal ExceptionDispatchInfo Exception { get; set; }
|
|
}
|
|
}
|