Skip to content

Conversation

@davinci26
Copy link
Contributor

@davinci26 davinci26 commented Aug 1, 2019

PR Summary

  1. Added WorkingDirectory to PSRemotingCmdlet.cs
  2. Inherited and validated the WorkingDirectory in StartJob.cs
  3. Added a Set-Location to the Pipeline of Commands

Thinks I would like to get your feedback on:

  1. Implementation & approach
  2. Test coverage, let me know if you would like to see more thorough testing
  3. Any edge case that I might have missed

Awaiting your feedback and comments!

PR Context

Fix #4287

PR Checklist

@davinci26 davinci26 requested a review from PaulHigin as a code owner August 1, 2019 01:46
set
{
base.WorkingDirectory = value;
}
Copy link
Collaborator

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;

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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

if (_scriptBlockInitialLocation != null)
{
var command = new Command("Set-Location");
command.Parameters.Add("LiteralPath", _scriptBlockInitialLocation);
Copy link
Collaborator

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?

/cc @PaulHigin @TravisEz13

Copy link
Contributor Author

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.

Copy link
Member

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).

@iSazonov iSazonov added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Aug 1, 2019
@iSazonov iSazonov added this to the 7.0.0-preview.3 milestone Aug 1, 2019
@iSazonov
Copy link
Collaborator

iSazonov commented Aug 1, 2019

@davinci26 Please look CodeFactor issues. They should be fixed for new code.

@davinci26 davinci26 changed the title Adding Working Directory to Start-Job cmdlet [WiP] Adding Working Directory to Start-Job cmdlet Aug 1, 2019
{
var command = new Command("Set-Location");
command.Parameters.Add("LiteralPath", WorkingDirectory);
pipeline.Commands.Add(command);
Copy link
Contributor

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.

Copy link
Contributor Author

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)

Copy link
Contributor

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.

Copy link
Contributor

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");
Copy link
Contributor

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");

@PaulHigin
Copy link
Contributor

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.,
pwsh.exe -WorkingDirectory ....

Or it can be done in the dotNet StartInfo property when creating the child process.
_startInfo.WorkingDirectory = ...

Please take a look at PowerShellProcessInstance.cs source file.

@davinci26
Copy link
Contributor Author

@PaulHigin @KirkMunro

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:

  1. The command line argument workingdirectory does not work when the pwsh process runs in server mode (-s flag). See consolehost.cs. The function that initiates the process into server mode does not even take into consideration the working directory parameter provided by the user.

  2. Using _startInfo.WorkingDirectory = ... also did not work either. The surprising part here ( at least for me) was that even though the new process working directory was the one specified by the process _startInfo the initial location did not change. I used process explorer source to verify that the process working directory was passed correctly.

Thoughts & Questions

  1. Since the InitializationScript parameter of the cmdlet is passed to the process startupInfo as part of the process arguments, I do not see how we could have a workingDirectory parameter for Start-Job that would be able to set the working directory also for the InitializationScript. Unless we enable the workingDirectory parameter in serverMode. Is this part of the requirement?

  2. Do you think that enabling the workingDirectory parameter when powershell runs in server mode is a good addition?

  3. Is there documentation (or some old issue that I could read to understand better) on the communication protocol between PowerShell and Powershell server mode?

Let me know what you think :)

@PaulHigin
Copy link
Contributor

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.

System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand);

System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand, s_cpp.WorkingDirectory);

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 Set-Location command before running initialization scripts.

@lzybkr
Copy link
Contributor

lzybkr commented Aug 7, 2019

@PaulHigin - Maybe this blog post answers your question, assuming the process is running elevated.

@PaulHigin
Copy link
Contributor

@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.

@davinci26
Copy link
Contributor Author

@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:

  1. I will abandon this PR in favor of new one
  2. I will update the issue to include a summary of the comments above.

See the PR #10324

@davinci26 davinci26 closed this Aug 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Start-Job needs a -WorkingDirectory parameter

7 participants