Skip to content

Conversation

@Artoria2e5
Copy link

@Artoria2e5 Artoria2e5 commented Aug 19, 2020

PR Summary

This pull request adds a new experimetal feature PSEscapeForNativeExecutables to 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

* 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--)
Copy link
Author

@Artoria2e5 Artoria2e5 Aug 19, 2020

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

@daxian-dbw
Copy link
Member

/cc @JamesWTruher and @SteveL-MSFT

/// Whether to escpae the argumentList.
/// </summary>
[Parameter]
public bool? EscapeArgs { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not SwitchParameter ?

Copy link
Author

@Artoria2e5 Artoria2e5 Aug 20, 2020

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

Copy link
Member

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

Copy link
Author

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is IsPresent property.

Copy link
Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator

@vexx32 vexx32 Aug 22, 2020

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.


if (!string.IsNullOrEmpty(arg))
if (!ExperimentalFeature.IsEnabled("PSNativePSPathResolution") &&
arg.Length == 0)
Copy link
Collaborator

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?

Copy link
Author

@Artoria2e5 Artoria2e5 Aug 20, 2020

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.

@iSazonov
Copy link
Collaborator

@Artoria2e5 Please resolve the merge conflict.

@ghost ghost added the Review - Needed The PR is being reviewed label Aug 30, 2020
@ghost
Copy link

ghost commented Aug 30, 2020

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

@JamesWTruher
Copy link
Collaborator

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

@JamesWTruher
Copy link
Collaborator

whoops - sorry @Artoria2e5 - i thought this was mine

Copy link
Member

@SteveL-MSFT SteveL-MSFT left a 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") &&
Copy link
Member

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?

Copy link
Author

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 ''
Copy link
Member

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.

Copy link
Author

@Artoria2e5 Artoria2e5 Oct 17, 2020

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.

Copy link
Author

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 }'
Copy link
Member

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?

Copy link
Author

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 }

Copy link
Member

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"); ',
Copy link
Member

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?

Copy link
Author

@Artoria2e5 Artoria2e5 Oct 17, 2020

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
Copy link
Member

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?

Copy link
Author

@Artoria2e5 Artoria2e5 Oct 17, 2020

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.

@ghost ghost added the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Oct 15, 2020
@ghost ghost removed Review - Needed The PR is being reviewed Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept labels Oct 15, 2020
@Artoria2e5
Copy link
Author

Artoria2e5 commented Oct 17, 2020

I should really get some strong coffee and just finish up the tests. As for testing the diverging & behavior, I think I could just add a utility ps function to "over-escape" the input when the feature is off.

@ghost ghost added the Review - Needed The PR is being reviewed label Oct 28, 2020
@ghost
Copy link

ghost commented Oct 28, 2020

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

@SteveL-MSFT SteveL-MSFT added Backport-7.2.x-Consider Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Dec 15, 2020
@SteveL-MSFT
Copy link
Member

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

@ghost ghost removed the Review - Needed The PR is being reviewed label Dec 16, 2020
@SteveL-MSFT SteveL-MSFT added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Jan 6, 2021
@ghost ghost added the Review - Needed The PR is being reviewed label Jan 14, 2021
@ghost
Copy link

ghost commented Jan 14, 2021

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

@Artoria2e5
Copy link
Author

Now that the new native facility has been added, I do not see any possibility for this PR to be accepted. Closing it myself.

@Artoria2e5 Artoria2e5 closed this May 11, 2021
@ghost ghost removed the Review - Needed The PR is being reviewed label Sep 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Committee-Reviewed PS-Committee has reviewed this and made a decision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants