Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
9 changes: 4 additions & 5 deletions src/System.Management.Automation/engine/InternalCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,10 @@ private void InitParallelParameterSet()
}

bool allowUsingExpression = this.Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage;
_usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesAsDictionary(
Parallel,
allowUsingExpression,
this.Context,
null);
_usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesForEachParallel(
scriptBlock: Parallel,
isTrustedInput: allowUsingExpression,
context: this.Context);

// Validate using values map, which is a map of '$using:' variables referenced in the script.
// Script block variables are not allowed since their behavior is undefined outside the runspace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2433,7 +2433,7 @@ private List<VariableExpressionAst> GetUsingVariables(ScriptBlock localScriptBlo
throw new ArgumentNullException("localScriptBlock", "Caller needs to make sure the parameter value is not null");
}

var allUsingExprs = UsingExpressionAstSearcher.FindAllUsingExpressionExceptForWorkflow(localScriptBlock.Ast);
var allUsingExprs = UsingExpressionAstSearcher.FindAllUsingExpressions(localScriptBlock.Ast);
return allUsingExprs.Select(usingExpr => UsingExpressionAst.ExtractUsingVariable((UsingExpressionAst)usingExpr)).ToList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,14 @@ internal static void ThrowError(ScriptBlockToPowerShellNotSupportedException ex,

internal class UsingExpressionAstSearcher : AstSearcher
{
internal static IEnumerable<Ast> FindAllUsingExpressionExceptForWorkflow(Ast ast)
internal static IEnumerable<Ast> FindAllUsingExpressions(Ast ast)
{
Diagnostics.Assert(ast != null, "caller to verify arguments");

var searcher = new UsingExpressionAstSearcher(astParam => astParam is UsingExpressionAst, stopOnFirst: false, searchNestedScriptBlocks: true);
var searcher = new UsingExpressionAstSearcher(
callback: astParam => astParam is UsingExpressionAst,
stopOnFirst: false,
searchNestedScriptBlocks: true);
ast.InternalVisit(searcher);
return searcher.Results;
}
Expand Down Expand Up @@ -312,6 +315,114 @@ internal static PowerShell Convert(ScriptBlockAst body,
}
}

/// <summary>
/// Get using values as dictionary for the Foreach-Object cmdlet, and limit the search
/// for nested Foreach-Object calls
/// </summary>
/// <param name = "scriptBlock">Scriptblock to search.</param>
/// <param name = "isTrustedInput">True when input is trusted.</param>
/// <param name = "context">Execution context.</param>
/// <returns>Dictionary of using variable map.</returns>
internal static Dictionary<string, object> GetUsingValuesForEachParallel(
ScriptBlock scriptBlock,
bool isTrustedInput,
ExecutionContext context)
{
// Using variables for Foreach-Object -Parallel use are restricted to be within the
// Foreach-Object call scope. This will filter the using variable map to variables
// only within the outer Foreach-Object call.
var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressions(scriptBlock.Ast).ToList();
UsingExpressionAst usingAst = null;
var usingValueMap = new Dictionary<string, object>(usingAsts.Count);
Version oldStrictVersion = null;
try
{
if (context != null)
{
oldStrictVersion = context.EngineSessionState.CurrentScope.StrictModeVersion;
context.EngineSessionState.CurrentScope.StrictModeVersion = PSVersionInfo.PSVersion;
}

for (int i = 0; i < usingAsts.Count; ++i)
{
usingAst = (UsingExpressionAst)usingAsts[i];
if (IsInForeachParallelCallingScope(usingAst))
{
var value = Compiler.GetExpressionValue(usingAst.SubExpression, isTrustedInput, context);
string usingAstKey = PsUtils.GetUsingExpressionKey(usingAst);
usingValueMap.TryAdd(usingAstKey, value);
}
}
}
catch (RuntimeException rte)
{
if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("VariableIsUndefined", StringComparison.Ordinal))
{
throw InterpreterError.NewInterpreterException(
targetObject: null,
exceptionType: typeof(RuntimeException),
errorPosition: usingAst.Extent,
resourceIdAndErrorId: "UsingVariableIsUndefined",
resourceString: AutomationExceptions.UsingVariableIsUndefined,
args: rte.ErrorRecord.TargetObject);
}
}
finally
{
if (context != null)
{
context.EngineSessionState.CurrentScope.StrictModeVersion = oldStrictVersion;
}
}

return usingValueMap;
}

private static bool IsInForeachParallelCallingScope(UsingExpressionAst usingAst)
{
Diagnostics.Assert(usingAst != null, "usingAst argument cannot be null.");

// Search up the parent Ast chain for 'Foreach-Object -Parallel' commands.
// At least one should be found since this is used only for foreach -parallel.
Ast currentParent = usingAst.Parent;
int foreachNestedCount = 0;
while (currentParent != null)
{
// Look for Foreach-Object outer commands
if (currentParent is CommandAst commandAst)
{
foreach (var commandElement in commandAst.CommandElements)
{
if (commandElement is StringConstantExpressionAst commandName)
{
if (commandName.Value.Equals("foreach", StringComparison.OrdinalIgnoreCase) ||
commandName.Value.Equals("foreach-object", StringComparison.OrdinalIgnoreCase) ||
commandName.Value.Equals("%"))
{
// Verify this is foreach-object with parallel parameter set.
var bindingResult = StaticParameterBinder.BindCommand(commandAst);
if (bindingResult.BoundParameters.ContainsKey("Parallel"))
{
foreachNestedCount++;
break;
}
}
}
}
}

if (foreachNestedCount > 1)
{
// This using expression Ast is outside the original calling scope.
return false;
}

currentParent = currentParent.Parent;
}

return true;
}

/// <summary>
/// Get using values in the dictionary form.
/// </summary>
Expand Down Expand Up @@ -342,11 +453,16 @@ internal static object[] GetUsingValuesAsArray(ScriptBlock scriptBlock, bool isT
/// A tuple of the dictionary-form and the array-form using values.
/// If the array-form using value is null, then there are UsingExpressions used in different scopes.
/// </returns>
private static Tuple<Dictionary<string, object>, object[]> GetUsingValues(Ast body, bool isTrustedInput, ExecutionContext context, Dictionary<string, object> variables, bool filterNonUsingVariables)
private static Tuple<Dictionary<string, object>, object[]> GetUsingValues(
Ast body,
bool isTrustedInput,
ExecutionContext context,
Dictionary<string, object> variables,
bool filterNonUsingVariables)
{
Diagnostics.Assert(context != null || variables != null, "can't retrieve variables with no context and no variables");

var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressionExceptForWorkflow(body).ToList();
var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressions(body).ToList();
var usingValueArray = new object[usingAsts.Count];
var usingValueMap = new Dictionary<string, object>(usingAsts.Count);
HashSet<string> usingVarNames = (variables != null && filterNonUsingVariables) ? new HashSet<string>() : null;
Expand Down Expand Up @@ -389,8 +505,13 @@ private static Tuple<Dictionary<string, object>, object[]> GetUsingValues(Ast bo
var variableAst = usingAst.SubExpression as VariableExpressionAst;
if (variableAst == null)
{
throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),
usingAst.Extent, "CantGetUsingExpressionValueWithSpecifiedVariableDictionary", AutomationExceptions.CantGetUsingExpressionValueWithSpecifiedVariableDictionary, usingAst.Extent.Text);
throw InterpreterError.NewInterpreterException(
targetObject: null,
exceptionType: typeof(RuntimeException),
errorPosition: usingAst.Extent,
resourceIdAndErrorId: "CantGetUsingExpressionValueWithSpecifiedVariableDictionary",
resourceString: AutomationExceptions.CantGetUsingExpressionValueWithSpecifiedVariableDictionary,
args: usingAst.Extent.Text);
}

string varName = variableAst.VariablePath.UserPath;
Expand All @@ -416,8 +537,13 @@ private static Tuple<Dictionary<string, object>, object[]> GetUsingValues(Ast bo
{
if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("VariableIsUndefined", StringComparison.Ordinal))
{
throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),
usingAst.Extent, "UsingVariableIsUndefined", AutomationExceptions.UsingVariableIsUndefined, rte.ErrorRecord.TargetObject);
throw InterpreterError.NewInterpreterException(
targetObject: null,
exceptionType: typeof(RuntimeException),
errorPosition: usingAst.Extent,
resourceIdAndErrorId: "UsingVariableIsUndefined",
resourceString: AutomationExceptions.UsingVariableIsUndefined,
args: rte.ErrorRecord.TargetObject);
}
else if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("CantGetUsingExpressionValueWithSpecifiedVariableDictionary", StringComparison.Ordinal))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,67 @@ Describe 'ForEach-Object -Parallel Basic Tests' -Tags 'CI' {
$result[1] | Should -BeExactly $varArray[1]
}

It 'Verifies in scope using variables in nested calls' {

$Test = "Test1"
$results = 1..2 | ForEach-Object -Parallel {
$using:Test
$Test = "Test2"
1..2 | ForEach-Object -Parallel {
$using:Test
$Test = "Test3"
1..2 | ForEach-Object -Parallel {
$using:Test
}
}
}
$results.Count | Should -BeExactly 14
$groups = $results | Group-Object -AsHashTable
$groups['Test1'].Count | Should -BeExactly 2
$groups['Test2'].Count | Should -BeExactly 4
$groups['Test3'].Count | Should -BeExactly 8
}

It 'Verifies in scope using variables with different names in nested calls' {
$Test1 = "TestA"
$results = 1..2 | ForEach-Object -parallel {
$using:Test1
$Test2 = "TestB"
1..2 | ForEach-Object -parallel {
$using:Test2
}
}
$results.Count | Should -BeExactly 6
$groups = $results | Group-Object -AsHashTable
$groups['TestA'].Count | Should -BeExactly 2
$groups['TestB'].Count | Should -BeExactly 4
}

It 'Verifies using variable in nested scriptblock' {

$test = 'testC'
$results = 1..2 | ForEach-Object -parallel {
& { $using:test }
}
$results.Count | Should -BeExactly 2
$groups = $results | Group-Object -AsHashTable
$groups['TestC'].Count | Should -BeExactly 2
}

It 'Verifies expected error for out of scope using variable in nested calls' {

$Test = "TestZ"
1..1 | ForEach-Object -Parallel {
$using:Test
# Variable '$Test' is not defined in this scope.
1..1 | ForEach-Object -Parallel {
$using:Test
}
} -ErrorVariable usingErrors 2>$null

$usingErrors[0].FullyQualifiedErrorId | Should -BeExactly 'UsingVariableIsUndefined,Microsoft.PowerShell.Commands.ForEachObjectCommand'
}

It 'Verifies terminating error streaming' {

$result = 1..1 | ForEach-Object -Parallel { throw 'Terminating Error!'; "Hello" } 2>&1
Expand Down