Passing string[] from batch file (which contains double quotes ") to powershell script - c#

I have a powershell script which has a string[] parameter and try to pass value to this from batch file
PowerShell Script
[string[]]$ScriptArgs
Batch File
powershell -File Foobar.ps1 -ScriptArgs -versn="""1.0.0.0""",pattern="""FooBar.*"""
(3 Quotes - For escaping/showing 1 quote)
But no matter what I do Write-Host $ScriptArgs.Count prints 1
I want the variable to receive 2 elements
[0] will be -versn="1.0.0.0"
[1] will be -pattern="FooBar.*"
In batch file, I even tried setting variables individually
set #sArgs = -versn="""1.0.0.0"""
set #sArgs = %sArgs%;-pattern="""FooBar.*"""
powershell -File Foobar.ps1 -ScriptArgs %sArgs%
but the console somehow runs as if the variable is not created and run it as
powershell -File Foobar.ps1 -ScriptArgs
and throws error
Missing an argument for parameter 'ScriptArgs'. Specify a parameter of type System.String[] and try again
What should I change to achieve this?

When calling a PowerShell script from from a batch file (from cmd.exe) with -File, there is no way to directly pass an array of values to a PowerShell array parameter:
If you specify the array values without spaces, they are considered a SINGLE element; e.g.:
one,two,three becomes a SINGLE array element
If you do use spaces, the trailing commas become part of the arguments, and only the FIRST value is bound to the array parameter:
one, two, three - one, - including the trailing , - becomes the only array element, two, and three, are considered separate arguments.
The workarounds are to:
Either: use -Command to invoke the script - see bottom section.
Or, with -File, declare the parameter differently and pass arguments differently:
decorate the array parameter with attribute
[Parameter(ValueFromRemainingArguments)]
and to pass the array elements as individual, space-separated arguments, without their parameter name.
In your case:
Declare the parameter:
[Parameter(ValueFromRemainingArguments)]
[string[]] $ScriptArgs
Invoke the script with separate, space-separate arguments not preceded by the parameter name to bind them as an array to $ScriptArgs:
powershell -File Foobar.ps1 "-versn=\"1.0.0.0\"" "pattern=\"FooBar.*\""
Note the quoting:
Each argument as a whole is enclosed in "..." - this isn't strictly necessary, but guards against --prefixed values such as -version being mistaken for parameter names.
" chars. to be retained as literals are \-escaped (which contrasts with the `-escaping that must be used from within PowerShell).
The inherent limitations of this approach:
The approach doesn't support multiple array parameters, because only one can be decorated with ValueFromRemainingArguments.
You cannot pass any other arguments positionally (i.e., you must precede arguments to pass to other parameters with their parameter name).
Alternative: Using -Command instead of -File:
powershell -command "./Foobar.ps1 '-versn=\"1.0.0.0\"', '-pattern=\"FooBar.*\"'"
Note:
What you pass to -Command is treated as a piece of PowerShell code - whether you pass a single argument or multiple ones - in the latter case, they are simply concatenated with spaces and the result is then interpreted as PowerShell code.
The implications are:
To execute a script in the current directory, you must refer to it with a path (.\) - in order to execute it.
Additionally, if the script path contains whitespace or other shell metacharacters, you must quote it and invoke it via &, the call operator; e.g., & '.\Foo Bar.ps1'
You can pass array arguments as usual, except that " chars. must again be escaped as \", not `".
Also note that again the arguments are quoted as a whole (too), which in this is case required to prevent PowerShell from interpreting -versn and -pattern as parameter names.
Given that the overall command must be passed inside "...", performing the embedded quoting with ", which still possible, is quite awkward, because you then have to combine \- and `-escaping; e.g., \"-pattern=`\"FooBar.*`\"\"
Generally, be aware that all usual PowerShell parsing rules apply, so that, for instance, to use a literal $, you must escape it as `$.

Related

How can I use System.Commandline.DragonFruit to parse options if the options are not preceded with double dash

I have an existing console app that takes option args that don't have the -- in front of them. For example:
./myapp businessDate=20230128 type=charley location=nashville
Currently I parse the args[] array tokens and split them around the "=" to get the key/value
I can't change that, because other programs already call it that way. But it appears that DragonFruit needs to have it this way instead.
./myapp --businessDate=20230128 type=charley --location=nashville
So my question is, can DragonFruit be configured to NOT use the -- prefix on options when I use the equals sign to separate the key from the value the way I currently do it in the example above? I believe the answer is "no", because of the Posix compliance. But maybe I missed something.
What I Tried
I ran the example program here Building your first app with System.CommandLine.DragonFruit And it worked fine when I ran it:
$ dotnet run --int-option=123 --bool-option=true
The value for --int-option is: 123
The value for --bool-option is: True
The value for --file-option is: null
But when I tried it without the --, this way instead, it gave me errors, and I was hoping it would work as above:
$ dotnet run int-option=123 bool-option=true
Unrecognized command or argument 'int-option=123'.
Unrecognized command or argument 'bool-option=true'.

Running complex command line from C#

I'm trying to run the following command line from C#:
Process.Start("C:\\Program Files\\GoToTags\\GoToTags Encoder\\GoToTags.Encoder.exe --records "{'Url':'http://petshop.intato.com/index.php?id='" + TxtBoxIDCode.Text + "'','RecordType':'Website'}"");
Obviously it is not working.
The problem is that I need to keep the proper signs such as the : in order to make it work properly.
The original command is:
C:\Program Files\GoToTags\GoToTags Encoder\GoToTags.Encoder.exe --records "{'Url':'http://petshop.intato.com/index.php?id=29','RecordType':'Website'}"
I have to run that command and at the same time, replace that 29 with the content of a textbox
Would anyone be able to help me with that?
The string.Format command is your friend...
string path = #"C:\Program Files\GoToTags\GoToTags Encoder\GoToTags.Encoder.exe";
string args = string.Format("--records \"{'Url':'http://petshop.intato.com/index.php?id={0}','RecordType':'Website'}\"", TxtBoxIDCode.Text);
Process.Start(path, args);
You have several pitfalls awaiting you.
Firstly, as you've already discovered, the backslashes in path names cause problems in the strings, as they could also indicate C# escape sequences. It's usually good practice to use C#'s #"..." syntax for file names, partly to avoid needing to double up the backslashes and make it easier to read, and partly because you could inadvertently leave a \t in there and it'd go unnoticed for ages.
Secondly, the single-parameter call to Process.Start only takes a command - it cannot accept command arguments - so you have to call the two-parameter overload.
Thirdly, the quotes around of the value of the records argument need handling so that the C# syntax knows what you want with them - i.e. to pass them to the command. I've separated out the command arguments into two parts to make that clearer. I have elected to use backslashes to escape them, though using the alternative #"...""..." would be just as good, and the choice is largely down to personal preference unless the context points you strongly one way rather than the other.
string cmd = #"C:\Program Files\GoToTags\GoToTags Encoder\GoToTags.Encoder.exe";
string url = "http://petshop.intato.com/index.php?id=" + TxtBoxIDCode.Text;
string cmdArgs = "--records \"{'Url':'" + url + "','RecordType':'Website'}\"";
Process.Start(cmd, cmdArgs);
[edited to add:]
If for some reason you find you either want or need to use string.Format to help build your cmdArgs, there's a fourth gotcha waiting in the wings for you, in that string.Format looks for the brace ({ and }) characters to delimit insertion parameter specifications, but your records command-line argument wants braces characters in the string. The way to achieve that would be to double up the braces that you want, like this:
string cmdArgs =
string.Format("--records \"{{'Url':'{0}','RecordType':'Website'}}\", url)";
In addition to the other answers, you should use the two-argument overload of Process.Start. The first argument is the executable, and the second argument is the command line arguments.
Normally, if you insist on using a single argument call, you should enclose your executable in double quotes, as such:
"\"C:\\Program Files\\GoToTags\\GoToTags Encoder\\GoToTags.Encoder.exe\" ...arguments here..."
However, this form does not work for Process.Start(string) because it specifically disallows it.

Accessing the commandline using C#

I need to access the commandline from within a C# application. I can't find a way to detect mismatched double quotes if I use the "args" parameter.
The application I'm working on the moment has an option to encrypt a string that you pass to it via the commandline.
eg.
program.exe -encrypt somestring
Results in:
EZs/6rWxvJ82+VE8unJ0Xw==
Now if the user types in this:
program.exe -encrypt somestring extra characters
it ignores everything after "somestring" (you can still access the rest of the values through the "args" parameter).
You can workaround this easily using double quotes:
program.exe -encrypt "somestring extra characters"
And of course if you want to use embedded quotes you can escape them with "\":
program.exe -encrypt "somestring \"extra\" characters"
The problem occurs when the user inputs something like this:
program.exe -encrypt "somestring extra characters
or:
program.exe -encrypt somestring ex"tra characters
The program will completely ignore the double quote, which not be what the user was expecting. I would like to detect cases like these and inform the user about the mismatched/unescaped quote, otherwise they might end up with an encrypted version of the wrong string.
This is how the command line passes arguments to your C# program. You cannot change that. You can however access the command arguments string as a whole and perform the validation yourself.
MSDN: Environment.CommandLine Property
Just do it manually: check that either you have only one argument after the -encrypt argument, and if not that the next argument starts with double quotes and the last argument ends with double quotes. If none of these happens throw a custom error to the user.
When the next argument after -encrypt starts with double quotes and the last argument ends with double quotes build the string yourself using Array.Copy or something of that kind.

Console Application input parameters - string identification

I have a console app that takes in string parameters. The app runs through the command line like so:
C:\ExampleApp.exe this is a "test"
In the above example, there are 4 different strings read in as parameters.. Is there any way to determine which parameter had quotes around it? When I do a Console.WriteLine(args[3]), it prints out as test and not "test".
Environment.CommandLine
returns a complete command line as a single string, just parse it and you will get the original parameters, quoted or not.
As far as I know the quotes are stripped by the framework before beeing passed to your main function, have you tried to escape the qoutes using a backslash?

Possible to pass name/value as parameter into Windows Console Application?

Building one of my first console apps.
This console app will run some stored procedures I'm defining. I would like to be able to pass in parameter values via the command line.
Is there any way to pass in a name value pair? For example:
myConsoleApp.exe sproc_GetLastActives, #LastActiveDate - 11/20/2009
I know how to retreive the parameter values, but I'm noticing that the args[] are split if I put in a / or a ,. How can I pass in name value pair?
Thanks!
Do you want that in a single string? Try this:
myConsoleApp.exe "sproc_GetLastActives, #LastActiveDate - 11/20/2009"
(i.e. just add quotes)
Slashes and commas shouldn't affect things, but the command line parser splits on spaces unless you've quoted it.
There are several solutions to this problem the most common is to use '/' or '-' to prefix parameters and = to delimit them from their arguments for example
consoleapp.exe /spname=sproc_GetLastActives /LastActiveDate="11/20/2009"
in your code your code you can use String.Split(arg[i], new char[] {'='}) to bust up the individual parameters.
consoleapp.exe key1 val1 key2 val2
args[] gives your 4 items, pair them up in your code. :P
Similar to mykhaylo's response, why not pass in the values at one string:
consoleapp.exe sproc_name key1=value1 "key2=Value With Spaces" key3=value3
Then just test for the presence of the = sign and parse it to get your key/value pair.

Categories