command line arguments given to a C# program are parsed wrong - c#

I wrote a little program, that has the following command line for example:
prog.exe -p -z "E:\temp.zip" -v "f:\" -r -o -s –c
prog.exe -p -z "E:\temp.zip" -v f:\ -r -o -s –c
prog.exe -p -z "E:\temp.zip" -v "f:\log" -r -o -s –c
This command line is being generated by another program that inserts the quotation marks around the file and path names automatically to prevent spaces being recognized as argument separators.
The first command line is then being parsed by the .Net framework as:
args {string[5]} string[]
[0] "-p" string
[1] "-z" string
[2] "f:\\temp.zip" string
[3] "-v" string
[4] "f:\" -r -o -s -c" string
But it should be ( the result from the second command line):
args {string[9]} string[]
[0] "-p" string
[1] "-z" string
[2] "f:\\temp.zip" string
[3] "-v" string
[4] "f:\\" string
[5] "-r" string
[6] "-o" string
[7] "-s" string
[8] "-c" string
A possible solution would be to check the file and path names in the calling application if they contain spaces and only then append the quotation marks around the names.
But is there another solution?

I created a little batch file that would display the command line parameters. With your first command line, I get
1 -p
2 -z
3 "E:\temp.zip"
4 -v
5 "f:\"
6 -r
7 -o
8 -s
9 –c
as it should. If .NET parses it differently, that would be a major bug. Just to check, I created a test console app. The results were:
0 -p
1 -z
2 E:\temp.zip
3 -v
4 f:" -r -o -s -c
I didn't think that was possible! So I researched a little and I got to this link: Everyone quotes command line arguments the wrong way which explains why the command line parser is essentially flawed. (I know it's for C++, but it applies) which led me to the rules of parsing for command line parameters which says \" is interpreted as an escaped quote, as opposed to how the operating system sees it (which is gross).
Conclusion: if you want to fix the command line, you need to escape the slash before a quote so instead of "f:\" you need "f:\\". The other solution is to use Environment.CommandLine which gets you the entire command line, executable included, and you can parse it yourself. More on this here: Getting raw (unsplit) command line in .NET.
Just for completeness' sake, I'll throw this in: Split string containing command-line parameters into string[] in C#, which discusses how to split a command line string into parameters using system functions.
After some more research, I realized that there are several standards
of command line argument parsing, depending on compiler and operating
system and its version, and that the only way to be consistent is to
parse the raw command line yourself.
For example, using CommandLineToArgvW on Environment.CommandLine replicated exactly a call to Environment.GetCommandLineArgs() which returns the exact same thing as the args array given to the Main method.
I found a very detailed page about this problem: http://daviddeley.com/autohotkey/parameters/parameters.htm which I believe is the definitive answer to the general question of command line parsing.

Related

How to input command line arguments to a dotnet c# program using bash scripts?

I have a simple c# program that expects command line input (from Console.ReadLine()) when ran. There's a lot of inputs I have to provide, so I was wondering how I could automate this process. I currently have a shell script that attempts to do this, but it's not working.
#!/bin/sh
dotnet run #run the program
1 #input first argument (this failed so I tried echo 1 instead but no luck)
# <- rest of command line inputs on each line
Not really familiar with shell scripts, and I'm not fixed on this solution if you had another solution in mind. My OS is MacOS.
OK, so what I tried was this:
I made a little mcve-Console App: (dotnet 6)
var input = Console.ReadLine();
while( !string.IsNullOrEmpty(input) )
{
Console.WriteLine("User input: {0}", input);
input = Console.ReadLine();
}
Then I made a little input.txt
This is a Text.
This is another Text.
1
2
3
This is the final Text.
and finally run.sh as follows:
#!/bin/sh
dotnet run < "input.txt"
Output was
User input: This is a Text.
User input: This is another Text.
User input: 1
User input: 2
User input: 3
User input: This is the final Text.
/bin/sh is linked to dash shell in my case.
If in run.sh you replace "input.txt" with "$1", then you can call it with ./run.sh whateveryourinputfileis.txt to make a little more flexible.

Let the shell parse or do your own parsing (powershell/cmd)

I develop an application with command line parameters and use it in cmd shell and powershell. There it is obvious that the arguments are received differently in main() of my application.
static void Main(string[] args)
{
// In cmd shell: args[0] == "-ipaddress=127.0.0.1"
// In powershell: args[0] == "-ipaddress=127"
// and args[1] == ".0.0.1"
}
Example:
myApp.exe -ipaddress=127.0.0.1
In my C# application the arguments are interpreted differently depending on the shell I start the app in.
In cmd shell: arg[0]=-ipaddress=127.0.0.1
In powershell: arg[0]=-ipaddress=127 and arg[1]=.0.0.1
What is best practice here?
Should I join all args[] and parse the arguments in my application?
Should I rely on the shell's parser?
I would abandon cmd shell support completely and just created proper PowerShell cmdlet. Writing a Windows PowerShell Cmdlet. But I don't know your exact requirements.
In general calling executable from cmd and PowerShell should work same way. For example line, "> ping -n 3 google.com" just works fine no matter where you put it.
tl;dr:
When calling from PowerShell, enclose the entire argument in '...' (single quotes) to ensure that it is passed as-is:
myApp.exe '-ipaddress=127.0.0.1'
You've run into a parsing bug[1]
where PowerShell breaks an unquoted argument that starts with - in two at the first .
The following simplified example demonstrates that:
# Helper function that echoes all arguments.
function Out-Argument { $i = 0; $Args | % { 'arg[{0}]: {1}' -f ($i++), $_ }}
# Pass an unquoted argument that starts with "-" and has an embedded "."
Out-Argument -foo=bar.baz
The above yields:
arg[0]: -foo=bar
arg[1]: .baz
[1] The presumptive bug is present as of Windows PowerShell v5.1 / PowerShell Core v6.0.1 and has been reported on GitHub.
A PSv2 variation of the bug affects . when enclosed in "..." in the interior of the argument - see this answer of mine.

Parse into Website

I have a file (text or csv) that I generate that has a list of part numbers. I need to take this list, and download some spec sheets for these parts automatically and then print. Once on the website, I need to input the part number, then print the results. What's the best way to do this?
Okay everyone, here's what I was doing before, but it would take over an hour to process on a progress 4gl (version 9.1) database into a QAD environment v8.6e on a Unix Red-Hat server:
FNAME=`date +%y%m%d%H%M%S`
echo requiredmcpartno=$1 | lynx -accept_all_cookies -nolist -dump -post_data 75.144.##.###/specdata/specdata.asp 2>&1 | tee $FNAME | lp -d$2 >>/apps/proedi/####/ftp/log/brownart.log
grep "Unit of Issue" $FNAME | cut --delimiter=: --fields=2 | awk '{print $1}'
grep -q "PACKAGING SPEC IS OBSOLETE FOR THIS PART NUMBER" "$FNAME"
if [ $? -eq 0 ]; then
echo 0
echo nopic
exit
fi
cd /apps/proedi/####/ftp/ftpscripts
rm -fr 184.168.##.###/ 75.144.##.###/ www.google-analytics.com/ 2>&1 >>/apps/proedi/####/ftp/log/brownart.log
wget -p -m -k -K -E -H --cookies=on --post-data="requiredmcpartno=$1" 75.144.##.###/specdata/specdata.asp >/dev/null 2>&1
/apps/proedi/####/ftp/ftpscripts/printspec.sh $1 $2 >>/tmp/printspec.log 2>&1
cat /apps/proedi/####/ftp/ftpscripts/"$1".pt
rm -f /apps/proedi/####/ftp/ftpscripts/"$1".pt
>>/apps/proedi/####/ftp/log/brownart.log
rm $FNAME 2>&1 >>/apps/proedi/ford/ftp/log/brownart.log
Then printspec.sh script:
file=184.168.70.174/partandpackagingphotos/PhotoDetailsSpecdata.aspx\?p\=$1.html
if [ ! -f "$file" ]; then
echo nopic >/apps/proedi/ford/ftp/ftpscripts/"$1".pt
exit
fi
grep -q "No Pictures Available for this Part Number" "$file"
if [ $? -eq 0 ]; then
echo nopic >/apps/proedi/ford/ftp/ftpscripts/"$1".pt
exit
fi
html2ps -i .7 184.168.70.174/partandpackagingphotos/PhotoDetailsSpecdata.aspx\?p\=$1.html | lp d$2 -s
echo picfnd >/apps/proedi/ford/ftp/ftpscripts/"$1".pt
The wget command takes way to long to process in Unix. Our customer may send us a conveyance file with 150-200 parts 8-9 times a day, and we need to download all of the pictures associated with each part every time we receive parts.
I was thinking of just making a flat file(text or csv), then have the user run a batch file on their windows computer to connect to the unix server, and download the file to their computer. After this, then have either the same batch job, or an excel template or something on the windows side download the pictures and print the spec sheets to their default printer.
Sorry for not posting all of this initially.
The first thing that I would try is to break the process into two or more independent pieces and run them in parallel. The scripts above appear to take a part number as a parameter. I would guess that whatever is feeding them the part number is working from a list (the "conveyance file"?) That list would be the obvious place to make the split.
If you do it in such a way that the number of concurrent processes is configurable it should be simple to find the "sweet spot". Supposing that the list of parts to be downloaded is in a table called "part" with fields "needsDownload" and "partNum". For the sake of simplicity we will assume that partNum is an integer and that the actual part numbers needing download are randomly distributed. If you are driving this process with Progress 4GL code you might write a control program something like this:
/* control.p
*
* to run two "threads":
*
* _progres -b dbName -p control.p -param "1,2" > control.1.log 2>&1 & # 1 of 2
* _progres -b dbName -p control.p -param "2,2" > control.2.log 2>&1 & # 2 of 2
*
*
*/
define variable myThread as integer no-undo.
define variable numThreads as integer no-undo.
myThread = integer( entry( 1, session:parameter )) - 1. /* this just allows the "1 of 2" stuff to be more "human sensible" */
numThreads = integer( entry( 2, session:parameter )).
for each part exclusive-lock where needsDownload = true and (( partNum modulo numThreads ) = myThread ):
os-command value( "getpart.sh " + string( partNum )).
needsDownload = false.
end.
Of course the problem might be that the external system is too slow. No amount of programming on your end will fix that.

gpg: public key decryption failed: Bad passphrase

I'm trying to decrypt a file using GPG, for which I use "Starksoft.Cryptography.OpenPGP". I'm getting the following error
Starksoft.Cryptography.OpenPGP.GnuPGException: An error occurred while trying to execute command --list-secret-keys.
But when I execute the command through command prompt ">gpg --list-secret-keys", it does list the keys. I could not get "Starksoft.Cryptography.OpenPGP" to work correctly.
Next I tried to get a solution by running the process directly using cmd.exe. None of the following commands is working however:
>echo gpg --passphrase Mypasspharse -o "C:\successtest.txt" -d "C:\testfile.txt.gpg"
>echo Mypasspharse|gpg.exe --passphrase-fd 0 -o "C:\successtest.txt" --decrypt "C:\testfile.txt.gpg"
>echo Mypasspharse|gpg --keyring "pubring.gpg location" --secret-keyring "secring.gpg location" --batch --yes --passphrase-fd 0 -o "C:\successtest.txt" -d "C:\testfile.txt.gpg"
>echo Mypasspharse|gpg -o C:\successtest.txt --batch --passphrase-fd 0 --decrypt C:\testfile.txt.gpg
>echo Mypasspharse|gpg2 --batch --passphrase-file "PrivateKey.asc location" --output C:\successtest.txt --decrypt C:\testfile.txt.gpg
Error Message : gpg: public key decryption failed: Bad passphrase
gpg: decryption failed: No secret key
Can anybody show me how to decrypt the file?
I figured out the issue with the gpg command line. The second command line worked just fine.
echo Mypasspharse|gpg.exe --passphrase-fd 0 -o "C:\successtest.txt" --decrypt "C:\testfile.txt.gpg"
Issue Was :
Mypassphare contained a character ">" which interpreted as std out redirect in windows command prompt. So, passphase wasn't passing to the next command properly.
Since this command worked properly, I didn't check on other syntax. Please feel free to check other commands and update here, if its wrong.
I have summarized the Q & A here: http://techsharehub.blogspot.com/2014/09/gpg-public-key-decryption-failed-bad.html

Displaying command in cmd console

I'm trying to run a command in cmd using C# and am having some difficulties. I'd like to be able to write the command to the cmd console so I can see what it's trying to run (I think there's some issue with the quotes or something, so if I could see the actual string in the command line, I'd be able to see exactly what the problem is). My code looks like this:
var processStartInfo = new ProcessStartInfo("cmd", "/c"+commandString);
processStartInfo.CreateNoWindow = true;
Process.Start(processStartInfo);
So basically, I just want to see the string commandString written in the console. Any help would be greatly greatly appreciated.
string CommandLineString = #"""C:\Program Files\Microsoft SQL Server\100\Tools\Binn\bcp.exe"" ""SELECT * FROM table where date >= '2009-01-01'"" queryout ""C:\Data\data.dat"" -S DBSW0323 -d CMS -n -T";
In this case, the problem is probably just your lack of a space after "/c".
var processStartInfo = new ProcessStartInfo("cmd", "/c " + commandString);
As for viewing in a command window, instead, you will probably be better off inspecting the Arguments property of your processStartInfo instance.
EDIT
Taking into account the command line details you posted, I believe this is what your issue is. Check out the following from cmd help:
If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:
If all of the following conditions are met, then quote characters
on the command line are preserved:
no /S switch
exactly two quote characters
no special characters between the two quote characters,
where special is one of: &<>()#^|
there are one or more whitespace characters between the
the two quote characters
the string between the two quote characters is the name
of an executable file.
Since you are using /c, you have quote and special char issues still. Try wrapping your entire commandString in a set of quotes.
Take this simple example for instance (creating temp.txt manually of course):
string commandString = #"""C:\WINDOWS\Notepad.exe"" ""C:\temp.txt""";
var processStartInfo = new ProcessStartInfo("cmd", "/c " + commandString);
The command line to be executed will be: /c "C:\WINDOWS\Notepad.exe" "C:\temp.txt", but this will fail since "C:\temp.txt" is not an executable.
If you wrap the whole thing in one last set of quotes, you should see the intended result:
string commandString = #"""""C:\WINDOWS\Notepad.exe"" ""C:\temp.txt""""";
var processStartInfo = new ProcessStartInfo("cmd", "/c " + commandString);
Resulting in a command line of: /c ""C:\WINDOWS\Notepad.exe" "C:\temp.txt"" and ultimately opening notepad with your test file.
That string is not "written" to the console, it's part of the argument list for a program you launch (which in this case happens to be cmd.exe). Since the console created is owned by that program, unless it wants to print its arguments for its own reasons (which it won't) this is not directly doable.
If you simply want to debug then why not inspect the value of commandString, or write it out into a log file?
If you absolutely need the command line to be displayed in the console then you could resort to hacks (run another intermediate program that prints the command line and then calls cmd.exe with it), but unless there is some other good reason to use this approach I would not recommend it.

Categories

Resources