-
Notifications
You must be signed in to change notification settings - Fork 8.1k
WIP: Add experimental option for quoting cmdline #13483
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
* do what PowerShell#13089 says. consolidate and fix cmdline escaping. * an empty string check. native commands can accept empty arguments! * attempt wildcard by spaces. now we do it by bareword.
Empty arguments are seen as significant now, so we have to be explicitly passing an empty list in the position. Pester/Start-Process: don't rely on bad argumentList behavior D'oh. Pester/ConsoleHost: Empty command is valid What isn't valid is a missing argv entry after -c. Pester/NativeStreams: remove redundant escape Pester/Get-PSHostProcessInfo: argumentList is for lists Pester/XMLCommand: Args is supposed to be a list.
| // If the stuff is unquoted, we need to double the trailing backslashes since we | ||
| // are putting a quote here. | ||
| int numBackSlash = 0; | ||
| for (int i = stringBuilder.Length - 1; i > 0 && stringBuilder[i] == Backslash; i--) |
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.
StringBuilder really just holds a stack of chunks. So like appends, this is quite efficient given we are doing stuff on the tail, which is on top.
(I was worrying about ending up on some sort of "accidentally O(n log n)" page.)
|
/cc @JamesWTruher and @SteveL-MSFT |
src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
Outdated
Show resolved
Hide resolved
| /// Whether to escpae the argumentList. | ||
| /// </summary> | ||
| [Parameter] | ||
| public bool? EscapeArgs { get; set; } |
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.
Why not SwitchParameter ?
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 want people to be able to explicitly turn it off.
Wait, you can do that with a SwitchParameter? The API on the C# side makes it really non-obvious, since it gives off a 2-state instead of a 3-state with bool?.
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.
It's not obvious, but you can explicitly "turn off" a switch parameter using: "verb-noun -switch:$false" 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.
Yeah, but then I don't think I can tell the difference between not present and set to false. I still think there's some value in having the default follow the experimental option.
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.
There is IsPresent property.
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.
But IsPresent cannot differentiate between a given false and not present. If you check the definition of that struct, you will see it really just holds a bool for two possibilities.
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 the parameter needs to have more than two states (on, off, and not specified) it should be an enum. bool parameters do not have good UX in PowerShell.
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.
use MyInvocation.BoundParameters.ContainsKey(nameof(EscapeArgs)) if you really need to test for whether the user provided it explicitly.
But in general if you want a bool, use a switch. And if you have a switch, name the parameter & code the function such that the default is that not provided == off.
Bool parameters and requiring arcane nonsense like -Switch:$false are both very annoying anti-patterns that are invariably confusing to users.
Opt for simplicity when designing parameters, otherwise PS users will be complaining about this for the next decade.
src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
Outdated
Show resolved
Hide resolved
|
|
||
| if (!string.IsNullOrEmpty(arg)) | ||
| if (!ExperimentalFeature.IsEnabled("PSNativePSPathResolution") && | ||
| arg.Length == 0) |
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.
Do we need a null check for arg?
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.
From a type perspective, I don't see how it can ever be null. I think it either works or throws.
On a second thought, I should use the one that has null just to be sure. Will get it changed when I get back to a physical keyboard.
that's a pretty good point since there can be so many extra things to tweak
I have no idea how laws work
|
@Artoria2e5 Please resolve the merge conflict. |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
I will get to the args and the tests, I promise...
|
based on feedback and the fact that enough has changed underneath this change that I'm going to close this and implement something a little different |
|
whoops - sorry @Artoria2e5 - i thought this was mine |
SteveL-MSFT
left a comment
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.
My main concern is backwards compatibility. I see a number of tests were modified and it's not clear if they were modified because the previous workaround aren't needed or they were modified because the previous workarounds no longer work. Also, it would be great to have a bunch of new tests specific to cover quoting test cases.
| } | ||
|
|
||
| if (!string.IsNullOrEmpty(arg)) | ||
| if (!ExperimentalFeature.IsEnabled("PSNativePSPathResolution") && |
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.
Is this the right experimental feature to check? If so, can you explain the intent of this?
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.
Oops, no!
| } | ||
|
|
||
| It "Empty command should fail" { | ||
| & $powershell -noprofile -c '' |
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.
What does empty command do now? If it does nothing (basically emitting an empty string), should add a test for that.
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.
Agreed. I think I should just flip the expectation of failure.
I didn't actually change the behavior of an empty command -- it used to fail because it was essentially calling 'powershell','-noprofile','-c', and it is an error to not follow -c with an argument.
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.
Looks like I did rename it to "missing command", so I guess I'm good there?
I really need to get the pure-ps escaper in though.
| Remove-Item $profileDataFile -Force | ||
| } | ||
|
|
||
| $loadedAssemblies = & "$PSHOME/pwsh" -noprofile -command '([System.AppDomain]::CurrentDomain.GetAssemblies()).manifestmodule | Where-Object { $_.Name -notlike ""<*>"" } | ForEach-Object { $_.Name }' |
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.
For scripts that currently use double-double-quotes, will they now start failing with this PR?
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 case of over-escaping, and it occurs as a native-command call. So yes, it will fail, since it is literally now passing the wrong command
([System.AppDomain]::CurrentDomain.GetAssemblies()).manifestmodule | Where-Object { $_.Name -notlike ""<*>"" } | ForEach-Object { $_.Name }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 understand that, but the problem is that it's very likely existing production scripts are relying on that behavior. So "fixing" it will break them.
| '[Console]::Error.Write(\"foo`n`nbar`n`nbaz\"); ', | ||
| '[Console]::Error.Write(\"middle\"); ', | ||
| '[Console]::Error.Write(\"foo`n`nbar`n`nbaz\")' | ||
| '[Console]::Error.Write("foo`n`nbar`n`nbaz"); ', |
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.
Is the backslash escape not needed or will it now break existing code that used this pattern?
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.
It will break such over-esacpes. Hence the experimental switch.
| $psFileName = $IsWindows ? 'pwsh.exe' : 'pwsh' | ||
| $psPath = Join-Path -Path $PSHOME -ChildPath $psFileName | ||
| $psProc = Start-Process -FilePath $psPath -ArgumentList "-File $testfilePath -LiveFilePath $aliveFile" -PassThru | ||
| $psProc = Start-Process -FilePath $psPath -ArgumentList '-File',$testfilePath,'-LiveFilePath',$aliveFile -PassThru |
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.
Shouldn't the old value still work?
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.
It should since 99e8f57. Ah well... I should revert these Start-Process test changes.
|
I should really get some strong coffee and just finish up the tests. As for testing the diverging |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
|
@PowerShell/powershell-committee reviewed this, we would like to see the native command parameter binder leverage the new .NET API that passes an array of arguments to processes so that there isn't incorrect handling of args. We would also like to see this change early in 7.2 to gauge compatibility impact of such a change. |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
|
Now that the new native facility has been added, I do not see any possibility for this PR to be accepted. Closing it myself. |
PR Summary
This pull request adds a new experimetal feature
PSEscapeForNativeExecutablesto better handle quotes, backslashes, and whitespace in command arguments.Many cases of existing code already compensate for the lack of quoting by manually adding backslashes before quotes. These code will need to be fixed. An example of what these stuff look like can be found in the Pester changes.
PR Context
See #1995. This is a more cleaned-up version of #13099, now fully aware of the backward compatibility things. Code duplication is kept to a minimum where I can. (Sorry for effing up with the stale bot. But eh, at least this is a chance of squashing commits.)
I am still inclined to think that not engaging with the broken NeedsQuote stuff is a better way of fixing things, unlike #13482's patchy approach.
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.