Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ internal static int MaxNameLength()
"sta",
"mta",
"command",
"commandwithargs",
"configurationname",
"custompipename",
"encodedcommand",
Expand Down Expand Up @@ -230,6 +231,7 @@ internal enum ParameterBitmap : long
WorkingDirectory = 0x02000000, // -WorkingDirectory | -wd
ConfigurationFile = 0x04000000, // -ConfigurationFile
NoProfileLoadTime = 0x08000000, // -NoProfileLoadTime
CommandWithArgs = 0x10000000, // -CommandWithArgs | -cwa
// Enum values for specified ExecutionPolicy
EPUnrestricted = 0x0000000100000000, // ExecutionPolicy unrestricted
EPRemoteSigned = 0x0000000200000000, // ExecutionPolicy remote signed
Expand Down Expand Up @@ -998,6 +1000,19 @@ private void ParseHelper(string[] args)
_customPipeName = args[i];
ParametersUsed |= ParameterBitmap.CustomPipeName;
}
else if (MatchSwitch(switchKey, "commandwithargs", "commandwithargs") || MatchSwitch(switchKey, "cwa", "cwa"))
{
_commandHasArgs = true;

if (!ParseCommand(args, ref i, noexitSeen, false))
{
break;
}

i++;
CollectPSArgs(args, ref i);
ParametersUsed |= ParameterBitmap.CommandWithArgs;
}
else if (MatchSwitch(switchKey, "command", "c"))
{
if (!ParseCommand(args, ref i, noexitSeen, false))
Expand Down Expand Up @@ -1250,15 +1265,6 @@ private void ParseExecutionPolicy(string[] args, ref int i, ref string? executio
// treat -command as an argument to the script...
private bool ParseFile(string[] args, ref int i, bool noexitSeen)
{
// Try parse '$true', 'true', '$false' and 'false' values.
static object ConvertToBoolIfPossible(string arg)
{
// Before parsing we skip '$' if present.
return arg.Length > 0 && bool.TryParse(arg.AsSpan(arg[0] == '$' ? 1 : 0), out bool boolValue)
? (object)boolValue
: (object)arg;
}

++i;
if (i >= args.Length)
{
Expand Down Expand Up @@ -1346,51 +1352,64 @@ static object ConvertToBoolIfPossible(string arg)

i++;

string? pendingParameter = null;
CollectPSArgs(args, ref i);
}

// Accumulate the arguments to this script...
while (i < args.Length)
{
string arg = args[i];
return true;
}

// If there was a pending parameter, add a named parameter
// using the pending parameter and current argument
if (pendingParameter != null)
{
_collectedArgs.Add(new CommandParameter(pendingParameter, arg));
pendingParameter = null;
}
else if (!string.IsNullOrEmpty(arg) && CharExtensions.IsDash(arg[0]) && arg.Length > 1)
private void CollectPSArgs(string[] args, ref int i)
{
// Try parse '$true', 'true', '$false' and 'false' values.
static object ConvertToBoolIfPossible(string arg)
{
// Before parsing we skip '$' if present.
return arg.Length > 0 && bool.TryParse(arg.AsSpan(arg[0] == '$' ? 1 : 0), out bool boolValue)
? (object)boolValue
: (object)arg;
}

string? pendingParameter = null;

while (i < args.Length)
{
string arg = args[i];

// If there was a pending parameter, add a named parameter
// using the pending parameter and current argument
if (pendingParameter != null)
{
_collectedArgs.Add(new CommandParameter(pendingParameter, arg));
pendingParameter = null;
}
else if (!string.IsNullOrEmpty(arg) && CharExtensions.IsDash(arg[0]) && arg.Length > 1)
{
int offset = arg.IndexOf(':');
if (offset >= 0)
{
int offset = arg.IndexOf(':');
if (offset >= 0)
if (offset == arg.Length - 1)
{
if (offset == arg.Length - 1)
{
pendingParameter = arg.TrimEnd(':');
}
else
{
string argValue = arg.Substring(offset + 1);
string argName = arg.Substring(0, offset);
_collectedArgs.Add(new CommandParameter(argName, ConvertToBoolIfPossible(argValue)));
}
pendingParameter = arg.TrimEnd(':');
}
else
{
_collectedArgs.Add(new CommandParameter(arg));
string argValue = arg.Substring(offset + 1);
string argName = arg.Substring(0, offset);
_collectedArgs.Add(new CommandParameter(argName, ConvertToBoolIfPossible(argValue)));
}
}
else
{
_collectedArgs.Add(new CommandParameter(null, arg));
_collectedArgs.Add(new CommandParameter(arg));
}

++i;
}
}
else
{
_collectedArgs.Add(new CommandParameter(null, arg));
}

return true;
++i;
}
}

private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEncoded)
Expand Down Expand Up @@ -1446,23 +1465,15 @@ private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEnco
}
else
{
// Collect the remaining parameters and combine them into a single command to be run.

StringBuilder cmdLineCmdSB = new StringBuilder();

while (i < args.Length)
if (_commandHasArgs)
{
cmdLineCmdSB.Append(args[i] + " ");
++i;
_commandLineCommand = args[i];
}

if (cmdLineCmdSB.Length > 0)
else
{
// remove the last blank
cmdLineCmdSB.Remove(cmdLineCmdSB.Length - 1, 1);
_commandLineCommand = string.Join(' ', args, i, args.Length - i);
i = args.Length;
}

_commandLineCommand = cmdLineCmdSB.ToString();
}

if (!noexitSeen && !_explicitReadCommandsFromStdin)
Expand Down Expand Up @@ -1534,6 +1545,7 @@ private bool CollectArgs(string[] args, ref int i)
private bool _noPrompt;
private string? _commandLineCommand;
private bool _wasCommandEncoded;
private bool _commandHasArgs;
private uint _exitCode = ConsoleHost.ExitCodeSuccess;
private bool _dirty;
private Serialization.DataFormat _outFormat = Serialization.DataFormat.Text;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CommandAlreadySpecified" xml:space="preserve">
<value>Cannot process command because a command is already specified with -Command or -EncodedCommand.</value>
<value>Cannot process command because a command is already specified with -Command, -CommandWithArgs, or -EncodedCommand.</value>
</data>
<data name="MissingCommandParameter" xml:space="preserve">
<value>Cannot process the command because of a missing parameter. A command must follow -Command.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
<value>Usage: pwsh[.exe] [-Login] [[-File] &lt;filePath&gt; [args]]
[-Command { - | &lt;script-block&gt; [-args &lt;arg-array&gt;]
| &lt;string&gt; [&lt;CommandParameters&gt;] } ]
[-CommandWithArgs &lt;string&gt; [&lt;CommandParameters&gt;]
[-ConfigurationName &lt;string&gt;] [-ConfigurationFile &lt;filePath&gt;]
[-CustomPipeName &lt;string&gt;] [-EncodedCommand &lt;Base64EncodedCommand&gt;]
[-ExecutionPolicy &lt;ExecutionPolicy&gt;] [-InputFormat {Text | XML}]
Expand Down Expand Up @@ -281,6 +282,25 @@ All parameters are case-insensitive.</value>
(runspace-terminating) error, such as a throw or -ErrorAction Stop, occurs
or when execution is interrupted with Ctrl-C.

-CommandWithArgs | -cwa

[Experimental]
Executes a PowerShell command with arguments. Unlike `-Command`, this
parameter populates the `$args built-in variable which can be used by the
command.

The first string is the command and subsequent strings delimited by whitespace
are the arguments.

For example:

pwsh -CommandWithArgs '$args | % { "arg: $_" }' arg1 arg2

This example produces the following output:

arg: arg1
arg: arg2

-ConfigurationName | -config

Specifies a configuration endpoint in which PowerShell is run. This can be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class ExperimentalFeature
internal const string PSModuleAutoLoadSkipOfflineFilesFeatureName = "PSModuleAutoLoadSkipOfflineFiles";
internal const string PSCustomTableHeaderLabelDecoration = "PSCustomTableHeaderLabelDecoration";
internal const string PSFeedbackProvider = "PSFeedbackProvider";
internal const string PSCommandWithArgs = "PSCommandWithArgs";

#endregion

Expand Down Expand Up @@ -128,6 +129,9 @@ static ExperimentalFeature()
new ExperimentalFeature(
name: PSFeedbackProvider,
description: "Replace the hard-coded suggestion framework with the extensible feedback provider"),
new ExperimentalFeature(
name: PSCommandWithArgs,
description: "Enable `-CommandWithArgs` parameter for pwsh"),
};

EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);
Expand Down
22 changes: 22 additions & 0 deletions test/powershell/Host/ConsoleHost.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,28 @@ $powershell -c '[System.Management.Automation.Platform]::SelectProductNameForDir
$out[0] | Should -MatchExactly $expectedPromptPattern
}
}

Context 'CommandWithArgs tests' {
It 'Should be able to run a pipeline with arguments using <param>' -TestCases @(
@{ param = '-commandwithargs' }
@{ param = '-cwa' }
){
param($param)
$out = pwsh -nologo -noprofile $param '$args | % { "[$_]" }' '$fun' '@times'
$out.Count | Should -Be 2 -Because ($out | Out-String)
$out[0] | Should -BeExactly '[$fun]'
$out[1] | Should -BeExactly '[@times]'
}

It 'Should be able to handle boolean switch: <param>' -TestCases @(
@{ param = '-switch:$true'; expected = 'True'}
@{ param = '-switch:$false'; expected = 'False'}
){
param($param, $expected)
$out = pwsh -nologo -noprofile -cwa 'param([switch]$switch) $switch.IsPresent' $param
$out | Should -Be $expected
}
}
}

Describe "WindowStyle argument" -Tag Feature {
Expand Down