-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Adding Working Directory to Start-Job cmdlet #10274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs
Outdated
Show resolved
Hide resolved
| set | ||
| { | ||
| base.WorkingDirectory = value; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified to:
get => base.WorkingDirectory;
set => base.WorkingDirectory = value;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No strong opinion here but: I am tempted to leave this as it is and the reason for that is the rest of the file that I am touching follows this pattern for the get\sets source
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally speaking from what I've seen, when most contributors add new code, they do so using the current practices rather than match existing syntax. This keeps us forward moving. Eventually PRs come in to advance old syntax to catch up to newer syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I will fix it in the next iteration
src/System.Management.Automation/engine/remoting/commands/StartJob.cs
Outdated
Show resolved
Hide resolved
| if (_scriptBlockInitialLocation != null) | ||
| { | ||
| var command = new Command("Set-Location"); | ||
| command.Parameters.Add("LiteralPath", _scriptBlockInitialLocation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here can be a security issue.
Should we check that is really directory and it is impossible inject bad code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a really good point.
I do check the directory to see if its valid in StartJob.cs here and throw if its not.
The reason I did not make the check in the PSRemotingCmdlet is that I wanted to enable the scenario where this parameter is expanded to other cmdlets that inherit from PSRemotingCmdlet.
That being said I could be really defensive and add a command that checks that the string provided by the user is indeed a valid path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a command should be considered like parameterized SQL.
Impossible is the wrong bar here.
@iSazonov I'm not sure what the problem is with setting the working directory with a string parameter (this is not a scriptblock).
|
@davinci26 Please look CodeFactor issues. They should be fixed for new code. |
| { | ||
| var command = new Command("Set-Location"); | ||
| command.Parameters.Add("LiteralPath", WorkingDirectory); | ||
| pipeline.Commands.Add(command); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something about this doesn't feel right. Starting with the test:
Start-Job -ScriptBlock { Join-Path $pwd "" } -WorkingDirectory $tempPath I think this code does the equivalent of:
Set-Location $tempPath | Join-Path $pwd ""Instead of:
Set-Location $tempPath
Join-Path $pwd ""I think you need to call PSCommand.AddStatement on this command you're adding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly what you are saying is that the object Pipeline represents only the powershell pipeline and if I want to create a sequence of commands I would need to add a statement to the powershell instance instead of adding a command to the pipeline.
This means that the correct approach is to have something like this:
_powershellV3 = System.Management.Automation.PowerShell.Create();
if (WorkingDirectory != null)
{
_powershellV3.AddStatement().
AddCommand("Microsoft.PowerShell.Management\\Set-Location").
AddParameter("LiteralPath", WorkingDirectory);
}I would assume that this statement should be added for both when the server is ps2 and ps5+.
(P.S. thanks for the comment it helped me understand the code way better)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what @lzybkr is trying to draw your attention to is what happens if WorkingDirectory != null on line 1852 of PSRemotingCmdlet.cs is true. In that case, you add the Set-Location command to the empty set of pipeline commands (the collection that was previously cleared on line 1850), but then logic continues beyond your if-statement and commands are added as additional commands in that same pipeline that Set-Location is in on lines 1859-1862. If you insert pipeline.Commands.AddStatement(); before line 1857, you'll properly terminate the statement that invokes Set-Location, allowing the commands that are being invoked to be property added as a separate commands following that statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PowerShell api was originally designed for a single pipeline, but you could do more with that api if you used AddScript.
Some secure endpoints disallow script (no language mode), so the PowerShell instance must be expressible as commands. At some point in the past, this also meant a "Pipeline" was exactly that, it was a single pipeline.
I think in V3 we added support for multiple distinct statements via the AddStatement api. You can think of AddCommand as adding a | between every statement unless AddStatement was called, then we'd add a ; instead. I also think that means the correct code is more like:
_powershellV3.
AddCommand("Microsoft.PowerShell.Management\\Set-Location").
AddParameter("LiteralPath", WorkingDirectory).
AddStatement();I do see that the test passed though, and that surprised me. I'd do some more testing to verify my assumptions above.
I don't recall specifics about going between the PowerShell api and the Pipeline api, and to be honest, I find the original code here (before the PR) a little confusing, so I'm reluctant to say that AddStatement is definitely needed.
I should also mention the need for compatibility testing if AddStatement is needed. I recall a bug where the conversion from a script block to a PowerShell instance could not use AddStatement because of a bug in the server side code. If I'm remembering this correctly, the bug was fixed in V5, so it would probably be worth testing across different server versions.
|
|
||
| if (WorkingDirectory != null) | ||
| { | ||
| var command = new Command("Set-Location"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably the module, like:
var command = new Command("Microsoft.PowerShell.Management\\Set-Location");|
I don't think this is the right way to implement working directory. I am wary of modifying any script run by the user ... if there is an error or unexpected behavior, then it becomes confusing for the user to understand what is wrong with their script. I think a better way may be to set the working directory path when the job child process is created. This can be done as a PowerShell (pwsh) argument e.g., Or it can be done in the dotNet StartInfo property when creating the child process. Please take a look at PowerShellProcessInstance.cs source file. |
|
I have looked at the code you mention ( that was my original approach) but to my suprise it does not work. At least with the current implementation. I have already posted some of this on #4287 and I would love to get your input. I re-post it here for convience: Changing the working directory of the pwsh remote server:
Thoughts & Questions
Let me know what you think :) |
|
Ok, well I can't say I understand how dotNet StartInfo works, but it seems like the WorkingDirectory parameter is ignored in some (non ShellExecute) cases. PowerShell does not seem to set initial working directory location except for the case where the -WorkingDirectory command parameter is used. The only alternative I see for Start-Job is to pass the -WorkingDirectory argument to the Run method that starts PowerShell running in server mode.
And then pass it on through to server session creation until it reaches the ServerRunspacePool constructor, similar to configurationName argument. Then in the runspace creation handler, run the PowerShell/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs Line 619 in cf7699b
|
|
@PaulHigin - Maybe this blog post answers your question, assuming the process is running elevated. |
|
@lzybkr Ok, that makes sense to protect against dll planting. So again I think the solution for Start-Job as I outlined above is the right solution, since it basically implements -WorkingDirectory for a job child process. |
|
@PaulHigin @iSazonov @lzybkr @KirkMunro I am following the approach that Paul mentioned above. Since the implementation is completely different Id rather not have them merged in to the same git history and a big portion of the discussion here is implementation specific. That is why I will do the following:
See the PR #10324 |
PR Summary
WorkingDirectorytoPSRemotingCmdlet.csWorkingDirectoryinStartJob.csSet-Locationto the Pipeline ofCommandsThinks I would like to get your feedback on:
Awaiting your feedback and comments!
PR Context
Fix #4287
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.