Skip to content

Quoting empty string parameter values #100

Description

Proxy functions generated by Crescendo currently do not add quotes around empty strings, if such value is provided for a string parameter. This leads to broken command lines, causing either syntax errors or parameter mismatches.

Context

Consider the "install" command of the Chocolatey command line executable (choco.exe).
The command, like other choco commands, accepts both named and unnamed parameters. Unnamed parameters are package names to install and at least one must be provided. Named parameters can be switches (which do not accept a value, such as --force) or regular parameters, with the value separated from the parameter name either by whitespace (e.g. --timeout 3600) or by the equals sign (e.g. --timeout=3600). For some of the latter parameters, an empty string is a valid value, for example --package-parameters "" (typed in cmd.exe) would mean that no special parameters should be passed to the package install script, same as if the --package-parameters parameter was omitted.

Reproduction

Given this simple Crescendo proxy definition:

$c = New-CrescendoCommand -Verb Test -Noun PassingParametersToChoco
$c.OriginalName = 'choco'
$c.OriginalCommandElements = @('install', '--verbose', '--debug')
$c.Parameters = @(@{ Name = 'Name'; ParameterType = 'string'; OriginalPosition = 1 }, @{ Name = 'PackageParameters'; ParameterType = 'string'; OriginalName = '--package-parameters'; OriginalPosition = 2 }, @{ Name = 'Yes'; ParameterType = 'switch'; OriginalName = '--yes'; OriginalPosition = 3 })
@{ Commands = @($c) } | ConvertTo-Json -Depth 100 | Out-File .\Test-PassingParametersToChoco.def.json
Export-CrescendoModule -ConfigurationFile .\*.def.json -ModuleName .\TestPassingParameters.psm1 -Force

(I set OriginalPosition, even though choco.exe puts no restrictions on parameter order, so that the tests are deterministic.)

I can now invoke Test-PassingParametersToChoco with various parameter combinations and observe the actual command line passed to choco.exe (it is printed by choco.exe thanks to the --debug switch and it can also be seen in Sysinternals Process Monitor). (To avoid making changes to my system, I use a non-elevated PowerShell console; choco.exe will detect the lack of admin rights and ask interactively for permission to continue, which is why it is required to type "y " after invoking Test-PassingParametersToChoco in order to see the output from choco.exe. Alternatively, the command line can be inspected in Process Monitor and the invocation terminated with Ctrl+C.)

  1. Passing only package name

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
  1. Passing empty package parameters

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters ''

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters
(...)
Install Command

Installs a package or a list of packages (sometimes specified as a
(rest of help text for the install command)
(...)
System.ApplicationException: Package name is required. Please pass at least one package name to install.

The argument parser in choco.exe signals an error, because there is no value specified for the "--package-parameters" parameter. Due to unfortunate ordering of code inside choco.exe, a misleading error message about missing package name is printed. It can be noted that because parameter validation failed there was no need to respond to the "continue without admin rights?" prompt.

  1. Passing empty package parameters and an additional switch

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters '' -Yes

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters --yes

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters --yes
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
NotSilent='False'|PackageParameters='--yes'|

In this case, because the --package-parameters parameter with empty value was followed by a switch (--yes), the command line passed to choco.exe resulted in "--yes" being interpreted as the value of the --package-parameters parameter.

Additional experiments

  1. Passing a package parameter (without spaces)

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters 'foo'

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters foo

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters foo
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
NotSilent='False'|PackageParameters='foo'|

The --package-parameters value was passed properly.

  1. Passing some package parameters with a space

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters 'foo bar'

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters "foo bar"

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters "foo bar"
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
NotSilent='False'|PackageParameters='foo bar'|

The --package-parameters value was automatically and correctly quoted.

  1. Passing package parameters consisting of a space only

PowerShell statement:
Test-PassingParametersToChoco -Name cpu-z -PackageParameters ' '

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters " "

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters " "
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
(no debug line with PackageParameters value)

The --package-parameters value was automatically and correctly quoted. In this case choco.exe recognized the whitespace-only value and treated it as if the --package-parameters parameter was not passed.

  1. Without Crescendo

These direct invocations of choco.exe behave incorrectly, in a similar manner (the empty string just disappears), if run from PowerShell:
choco install --verbose --debug cpu-z --package-parameters ''
choco install --verbose --debug cpu-z --package-parameters ([string]::Empty)
$chocoargs = @('install', '--verbose', '--debug', 'cpu-z', '--package-parameters', ''); choco @chocoargs
choco install --verbose --debug cpu-z --package-parameters ""
choco install --verbose --debug cpu-z --package-parameters "" --yes

which means that it is PowerShell itself which fails to quote those empty strings.

On the other hand, the last two lines do work correctly from cmd.exe. Example:

cmd.exe statement:
choco install --verbose --debug cpu-z --package-parameters ""

choco.exe command line in Process Monitor:
"C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters ""

Relevant choco.exe diagnostic output:

Command line: "C:\ProgramData\chocolatey\choco.exe" install --verbose --debug cpu-z --package-parameters ""
(...)
SkipPackageInstallProvider='False'|PackageNames='cpu-z'|
(...)
(no debug line with PackageParameters value)

Conclusion

I believe the proxies generated by Crescendo should recognize empty strings provided as string parameter values and ensure those empty strings are passed as "" to the invoked executable. This would be consistent with how parameters with spaces are handled and would fix the currently broken behavior when the caller provides an empty string to the proxy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Triagedissue was read and triaged

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions