From ccd13d60a2444d2fa72e008c7bc243fd5c25bc6b Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Tue, 1 Oct 2019 13:30:37 -0700 Subject: [PATCH 1/6] Fix to set current working directory of each parallel running script to the same location as the calling script. --- .../engine/InternalCommands.cs | 10 +++--- .../engine/hostifaces/PSTask.cs | 36 ++++++++++++++++--- .../Foreach-Object-Parallel.Tests.ps1 | 12 +++++++ 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 78338e08cc8..2ae8cefeec2 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -415,7 +415,7 @@ private void InitParallelParameterSet() this)); } } - + if (AsJob) { // Set up for returning a job object. @@ -486,10 +486,10 @@ private void InitParallelParameterSet() } catch (Exception ex) { - // Close the _taskCollection on an unexpected exception so the pool closes and - // lets any running tasks complete. _taskCollection.Complete(); _taskCollectionException = ex; + _taskDataStreamWriter.Close(); + break; } @@ -528,7 +528,8 @@ private void ProcessParallelParameterSet() var taskChildJob = new PSTaskChildJob( Parallel, _usingValuesMap, - InputObject); + InputObject, + SessionState.Internal.CurrentLocation); _taskJob.AddJob(taskChildJob); @@ -550,6 +551,7 @@ private void ProcessParallelParameterSet() Parallel, _usingValuesMap, InputObject, + SessionState.Internal.CurrentLocation, _taskDataStreamWriter)); } catch (InvalidOperationException) diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index e0adf784577..02b03a42473 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -35,16 +35,19 @@ internal sealed class PSTask : PSTaskBase /// Script block to run in task. /// Using values passed into script block. /// Dollar underbar variable value. + /// Current working directory. /// Cmdlet data stream writer. public PSTask( ScriptBlock scriptBlock, Dictionary usingValuesMap, object dollarUnderbar, + PathInfo currentLocation, PSTaskDataStreamWriter dataStreamWriter) : base( scriptBlock, usingValuesMap, - dollarUnderbar) + dollarUnderbar, + currentLocation) { _dataStreamWriter = dataStreamWriter; } @@ -176,15 +179,18 @@ internal sealed class PSJobTask : PSTaskBase /// Script block to run. /// Using variable values passed to script block. /// Dollar underbar variable value for script block. + /// Current working directory. /// Job object associated with task. public PSJobTask( ScriptBlock scriptBlock, Dictionary usingValuesMap, object dollarUnderbar, + PathInfo currentLocation, Job job) : base( scriptBlock, usingValuesMap, - dollarUnderbar) + dollarUnderbar, + currentLocation) { _job = job; } @@ -309,6 +315,7 @@ internal abstract class PSTaskBase : IDisposable private readonly Dictionary _usingValuesMap; private readonly object _dollarUnderbar; private readonly int _id; + private readonly PathInfo _currentLocation; private Runspace _runspace; protected PowerShell _powershell; protected PSDataCollection _output; @@ -372,14 +379,17 @@ private PSTaskBase() /// Script block to run. /// Using variable values passed to script block. /// Dollar underbar variable value. + /// Current working directory. protected PSTaskBase( ScriptBlock scriptBlock, Dictionary usingValuesMap, - object dollarUnderbar) : this() + object dollarUnderbar, + PathInfo currentLocation) : this() { _scriptBlockToRun = scriptBlock; _usingValuesMap = usingValuesMap; _dollarUnderbar = dollarUnderbar; + _currentLocation = currentLocation; } #endregion @@ -427,6 +437,20 @@ public void Start() _runspace = RunspaceFactory.CreateRunspace(iss); _runspace.Name = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", RunspaceName, s_taskId); _runspace.Open(); + Runspace.DefaultRunspace = _runspace; + + // Set current working directory on the runspace to be the same as the calling script. + // Temporarily set the newly created runspace as the thread default runspace for any needed module loading. + var oldDefaultRunspace = Runspace.DefaultRunspace; + try + { + Runspace.DefaultRunspace = _runspace; + _runspace.ExecutionContext.SessionState.Internal.SetLocation(_currentLocation.Path); + } + finally + { + Runspace.DefaultRunspace = oldDefaultRunspace; + } // Create the PowerShell command pipeline for the provided script block // The script will run on the provided Runspace in a new thread by default @@ -1216,15 +1240,17 @@ private PSTaskChildJob() { } /// Script block to run. /// Using variable values passed to script block. /// Dollar underbar variable value. + /// Current working directory. public PSTaskChildJob( ScriptBlock scriptBlock, Dictionary usingValuesMap, - object dollarUnderbar) + object dollarUnderbar, + PathInfo currentLocation) : base(scriptBlock.ToString(), string.Empty) { PSJobTypeName = nameof(PSTaskChildJob); - _task = new PSJobTask(scriptBlock, usingValuesMap, dollarUnderbar, this); + _task = new PSJobTask(scriptBlock, usingValuesMap, dollarUnderbar, currentLocation, this); _task.StateChanged += (sender, args) => HandleTaskStateChange(sender, args); } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 index 1582b8fe2f2..9fece391696 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 @@ -115,6 +115,11 @@ Describe 'ForEach-Object -Parallel Basic Tests' -Tags 'CI' { $results = 1..1 | ForEach-Object -Parallel { $ExecutionContext.SessionState.LanguageMode } $results | Should -BeExactly 'FullLanguage' } + + It 'Verifies that the current working directory is preserved' { + $parallelScriptLocation = 1..1 | ForEach-Object -Parallel { $pwd } + $parallelScriptLocation.Path | Should -BeExactly $pwd.Path + } } Describe 'ForEach-Object -Parallel common parameters' -Tags 'CI' { @@ -344,6 +349,13 @@ Describe 'ForEach-Object -Parallel -AsJob Basic Tests' -Tags 'CI' { $job.ChildJobs[0].Command | Should -BeExactly '"Hello"' $job | Wait-Job | Remove-Job } + + It 'Verifies that the current working directory is preserved' { + $job = 1..1 | ForEach-Object -AsJob -Parallel { $pwd } + $parallelScriptLocation = $job | Wait-Job | Receive-Job + $job | Remove-Job + $parallelScriptLocation.Path | Should -BeExactly $pwd.Path + } } Describe 'ForEach-Object -Parallel Functional Tests' -Tags 'Feature' { From 37f6aa972a30f92b61bfdb37309fe266c36a9bba Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Tue, 1 Oct 2019 15:24:45 -0700 Subject: [PATCH 2/6] Removed duplicate assignment. --- src/System.Management.Automation/engine/hostifaces/PSTask.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index 02b03a42473..adae4841c0f 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -437,7 +437,6 @@ public void Start() _runspace = RunspaceFactory.CreateRunspace(iss); _runspace.Name = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", RunspaceName, s_taskId); _runspace.Open(); - Runspace.DefaultRunspace = _runspace; // Set current working directory on the runspace to be the same as the calling script. // Temporarily set the newly created runspace as the thread default runspace for any needed module loading. From 4ff92a4c1074507cfa2ddd9df9ac5d32def42159 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Wed, 2 Oct 2019 08:43:47 -0700 Subject: [PATCH 3/6] Remove spaces --- src/System.Management.Automation/engine/InternalCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 2ae8cefeec2..96b392cc567 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -415,7 +415,7 @@ private void InitParallelParameterSet() this)); } } - + if (AsJob) { // Set up for returning a job object. From 710000664fdc1ba1801a6a729c569c3219545049 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Thu, 3 Oct 2019 08:27:24 -0700 Subject: [PATCH 4/6] Handle missing current location --- .../engine/InternalCommands.cs | 12 ++++- .../engine/hostifaces/PSTask.cs | 45 ++++++++++--------- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 96b392cc567..d07e1110070 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -375,6 +375,7 @@ public void Dispose() private PSTaskJob _taskJob; private PSDataCollection _taskCollection; private Exception _taskCollectionException; + private string _currentLocationPath; private void InitParallelParameterSet() { @@ -393,6 +394,13 @@ private void InitParallelParameterSet() this)); } + // Get the current working directory location, if available. + try + { + _currentLocationPath = SessionState.Internal.CurrentLocation.Path; + } + catch (PSInvalidOperationException) { } + bool allowUsingExpression = this.Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage; _usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesAsDictionary( Parallel, @@ -529,7 +537,7 @@ private void ProcessParallelParameterSet() Parallel, _usingValuesMap, InputObject, - SessionState.Internal.CurrentLocation); + _currentLocationPath); _taskJob.AddJob(taskChildJob); @@ -551,7 +559,7 @@ private void ProcessParallelParameterSet() Parallel, _usingValuesMap, InputObject, - SessionState.Internal.CurrentLocation, + _currentLocationPath, _taskDataStreamWriter)); } catch (InvalidOperationException) diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index adae4841c0f..77d2b2335e3 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -35,19 +35,19 @@ internal sealed class PSTask : PSTaskBase /// Script block to run in task. /// Using values passed into script block. /// Dollar underbar variable value. - /// Current working directory. + /// Current working directory. /// Cmdlet data stream writer. public PSTask( ScriptBlock scriptBlock, Dictionary usingValuesMap, object dollarUnderbar, - PathInfo currentLocation, + string currentLocationPath, PSTaskDataStreamWriter dataStreamWriter) : base( scriptBlock, usingValuesMap, dollarUnderbar, - currentLocation) + currentLocationPath) { _dataStreamWriter = dataStreamWriter; } @@ -179,18 +179,18 @@ internal sealed class PSJobTask : PSTaskBase /// Script block to run. /// Using variable values passed to script block. /// Dollar underbar variable value for script block. - /// Current working directory. + /// Current working directory. /// Job object associated with task. public PSJobTask( ScriptBlock scriptBlock, Dictionary usingValuesMap, object dollarUnderbar, - PathInfo currentLocation, + string currentLocationPath, Job job) : base( scriptBlock, usingValuesMap, dollarUnderbar, - currentLocation) + currentLocationPath) { _job = job; } @@ -315,7 +315,7 @@ internal abstract class PSTaskBase : IDisposable private readonly Dictionary _usingValuesMap; private readonly object _dollarUnderbar; private readonly int _id; - private readonly PathInfo _currentLocation; + private readonly string _currentLocationPath; private Runspace _runspace; protected PowerShell _powershell; protected PSDataCollection _output; @@ -379,17 +379,17 @@ private PSTaskBase() /// Script block to run. /// Using variable values passed to script block. /// Dollar underbar variable value. - /// Current working directory. + /// Current working directory. protected PSTaskBase( ScriptBlock scriptBlock, Dictionary usingValuesMap, object dollarUnderbar, - PathInfo currentLocation) : this() + string currentLocationPath) : this() { _scriptBlockToRun = scriptBlock; _usingValuesMap = usingValuesMap; _dollarUnderbar = dollarUnderbar; - _currentLocation = currentLocation; + _currentLocationPath = currentLocationPath; } #endregion @@ -440,15 +440,18 @@ public void Start() // Set current working directory on the runspace to be the same as the calling script. // Temporarily set the newly created runspace as the thread default runspace for any needed module loading. - var oldDefaultRunspace = Runspace.DefaultRunspace; - try - { - Runspace.DefaultRunspace = _runspace; - _runspace.ExecutionContext.SessionState.Internal.SetLocation(_currentLocation.Path); - } - finally + if (_currentLocationPath != null) { - Runspace.DefaultRunspace = oldDefaultRunspace; + var oldDefaultRunspace = Runspace.DefaultRunspace; + try + { + Runspace.DefaultRunspace = _runspace; + _runspace.ExecutionContext.SessionState.Internal.SetLocation(_currentLocationPath); + } + finally + { + Runspace.DefaultRunspace = oldDefaultRunspace; + } } // Create the PowerShell command pipeline for the provided script block @@ -1239,17 +1242,17 @@ private PSTaskChildJob() { } /// Script block to run. /// Using variable values passed to script block. /// Dollar underbar variable value. - /// Current working directory. + /// Current working directory. public PSTaskChildJob( ScriptBlock scriptBlock, Dictionary usingValuesMap, object dollarUnderbar, - PathInfo currentLocation) + string currentLocationPath) : base(scriptBlock.ToString(), string.Empty) { PSJobTypeName = nameof(PSTaskChildJob); - _task = new PSJobTask(scriptBlock, usingValuesMap, dollarUnderbar, currentLocation, this); + _task = new PSJobTask(scriptBlock, usingValuesMap, dollarUnderbar, currentLocationPath, this); _task.StateChanged += (sender, args) => HandleTaskStateChange(sender, args); } From d189857984f0a1730d92fca8a9b53afb33e75734 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Thu, 3 Oct 2019 10:46:26 -0700 Subject: [PATCH 5/6] Fix comment --- src/System.Management.Automation/engine/hostifaces/PSTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index 77d2b2335e3..a0018955d72 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -438,7 +438,7 @@ public void Start() _runspace.Name = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", RunspaceName, s_taskId); _runspace.Open(); - // Set current working directory on the runspace to be the same as the calling script. + // If available, set current working directory on the runspace. // Temporarily set the newly created runspace as the thread default runspace for any needed module loading. if (_currentLocationPath != null) { From 51f8b18f7254532700497b7ef844f2194f3464e8 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Thu, 3 Oct 2019 12:17:41 -0700 Subject: [PATCH 6/6] Fix CodeFactor Issues --- src/System.Management.Automation/engine/InternalCommands.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index d07e1110070..528852c7946 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -399,7 +399,9 @@ private void InitParallelParameterSet() { _currentLocationPath = SessionState.Internal.CurrentLocation.Path; } - catch (PSInvalidOperationException) { } + catch (PSInvalidOperationException) + { + } bool allowUsingExpression = this.Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage; _usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesAsDictionary(