I'm trying to pass command-line arguments to a C# application, but I have problem passing something like this
"C:\Documents and Settings\All Users\Start Menu\Programs\App name"
even if I add " " to the argument.
Here is my code:
public ObjectModel(String[] args)
{
if (args.Length == 0) return; //no command line arg.
//System.Windows.Forms.MessageBox.Show(args.Length.ToString());
//System.Windows.Forms.MessageBox.Show(args[0]);
//System.Windows.Forms.MessageBox.Show(args[1]);
//System.Windows.Forms.MessageBox.Show(args[2]);
//System.Windows.Forms.MessageBox.Show(args[3]);
if (args.Length == 3)
{
try
{
RemoveInstalledFolder(args[0]);
RemoveUserAccount(args[1]);
RemoveShortCutFolder(args[2]);
RemoveRegistryEntry();
}
catch (Exception e)
{
}
}
}
And here is what I'm passing:
C:\WINDOWS\Uninstaller.exe "C:\Program Files\Application name\" "username" "C:\Documents and Settings\All Users\Start Menu\Programs\application name"
The problem is I can get the first and the second args correctly, but the last one it gets as C:\Documents.
Any help?
I just ran a check and verified the problem. It surprised me, but it is the last \ in the first argument.
"C:\Program Files\Application name\" <== remove the last '\'
This needs more explanation, does anybody have an idea? I'm inclined to call it a bug.
Part 2, I ran a few more tests and
"X:\\aa aa\\" "X:\\aa aa\" next
becomes
X:\\aa aa\
X:\\aa aa" next
A little Google action gives some insight from a blog by Jon Galloway, the basic rules are:
the backslash is the escape character
always escape quotes
only escape backslashes when they precede a quote.
To add Ian Kemp's answer
If you assembly is called "myProg.exe" and you pass in the string "C:\Documents and Settings\All Users\Start Menu\Programs\App name" link so
C:\>myprog.exe "C:\Documents and Settings\All Users\Start Menu\Programs\App name"
the string "C:\Documents and Settings\All Users\Start Menu\Programs\App name"
will be at args[0].
To add to what everyone else has already said, It might be an escaping problem. You should escape your backslashes by another backslash.
Should be something like:
C:\>myprog.exe "C:\\Documents and Settings\\All Users\\Start Menu\\Programs\\App name"
I noticed the same annoying issue recently, and decided to write a parser to parse the command line arguments array out myself.
Note: the issue is that the .NET CommandLine Arguments passed to the static void Main(string[] args) function escapes \" and \\. This is by design, since you may actually want to pass an argument that has a quote or backslash in it. One example:
say you wanted to pass the following as a single argument:
-msg:Hey, "Where you at?"
eg.
sampleapp -msg:"Hey, \"Where you
at?\""
Would be how to send it with the default behavior.
If you don't see a reason for anyone to have to escape quotes or backslashes for your program, you could utilize your own parser to parse the command line, as below.
IE.
[program].exe "C:\test\" arg1 arg2
would have a args[0] = c:\test" arg1 arg2
What you would expect is args[0]=c:\test\ and then args[1]=arg1 and args[2]=arg2.
The below function parses the arguments into a list with this simplified behavior.
Note, arg[0] is the program name using the below code. (You call List.ToArray() to convert the resulting list to a string array.)
protected enum enumParseState : int { StartToken, InQuote, InToken };
public static List<String> ManuallyParseCommandLine()
{
String CommandLineArgs = Environment.CommandLine.ToString();
Console.WriteLine("Command entered: " + CommandLineArgs);
List<String> listArgs = new List<String>();
Regex rWhiteSpace = new Regex("[\\s]");
StringBuilder token = new StringBuilder();
enumParseState eps = enumParseState.StartToken;
for (int i = 0; i < CommandLineArgs.Length; i++)
{
char c = CommandLineArgs[i];
// Console.WriteLine(c.ToString() + ", " + eps);
//Looking for beginning of next token
if (eps == enumParseState.StartToken)
{
if (rWhiteSpace.IsMatch(c.ToString()))
{
//Skip whitespace
}
else
{
token.Append(c);
eps = enumParseState.InToken;
}
}
else if (eps == enumParseState.InToken)
{
if (rWhiteSpace.IsMatch(c.ToString()))
{
Console.WriteLine("Token: [" + token.ToString() + "]");
listArgs.Add(token.ToString().Trim());
eps = enumParseState.StartToken;
//Start new token.
token.Remove(0, token.Length);
}
else if (c == '"')
{
// token.Append(c);
eps = enumParseState.InQuote;
}
else
{
token.Append(c);
eps = enumParseState.InToken;
}
}
//When in a quote, white space is included in the token
else if (eps == enumParseState.InQuote)
{
if (c == '"')
{
// token.Append(c);
eps = enumParseState.InToken;
}
else
{
token.Append(c);
eps = enumParseState.InQuote;
}
}
}
if (token.ToString() != "")
{
listArgs.Add(token.ToString());
Console.WriteLine("Final Token: " + token.ToString());
}
return listArgs;
}
In response to WWC's answer, Jamezor commented that his code will fail if the first character is a quote.
To fix that problem, you can replace the StartToken case with this:
if (eps == enumParseState.StartToken)
{
if (rWhiteSpace.IsMatch(c.ToString()))
{
//Skip whitespace
}
else if (c == '"')
{
eps = enumParseState.InQuote;
}
else
{
token.Append(c);
eps = enumParseState.InToken;
}
}
What exactly is the problem? Anyway here's some general advice:
Make sure your Main method (in Program.cs) is defined as:
void Main(string[] args)
Then args is an array containing the command-line arguments.
Related
I made a code to "translate" paths or texts in general to the Git Bash syntax, at the time I didn't know that I could just put them inside '', in the code I use the $ special character on a couple of strings, compile the code via csc filename.cs on cmd, it gives me the .exe and its all great, today I go make some minor changes on the code and when I run the same code to compile it gives me:
GiTranslator.cs(59,62): error CS1056: Unexpected character '$'
GiTranslator.cs(71,25): error CS1056: Unexpected character '$'
GiTranslator.cs(72,25): error CS1056: Unexpected character '$'
GiTranslator.cs(73,25): error CS1056: Unexpected character '$'
so, why? And what can I do to fix it?
note: the minor changes were literally just some spelling on comments.
edit 1: as asked, the code(it is probably terrible, sorry):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Git_Path_Translator
{
class Program
{
#region Functions
static void actuallyResetColors()
{
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.White;
Console.Clear();
Console.Write(".");
Console.Clear();
}
#endregion
#region Global Variables
static string path;
static bool repetTranslation;
static bool repetAfterExcep;
#endregion
[STAThread] // don't know what it does, but its needed, classic right?
static void Main(string[] args)
{
Console.BackgroundColor = ConsoleColor.DarkCyan;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Title = "Path Translator for Git Bash - by Tom Zamataro"; // just some credit to me, that no one is going to see, classic right?
Console.Clear();
// set up for the API loop
do
{
repetTranslation = false;
path = "";
Console.WriteLine("\n Inform the path you would like to translate to Git Bash syntax:");
Console.Write("\n "); // just for aesthetics, class... sory, no more classic joke tho, right? yes, I've a lot of shit in my head
// set up for the exception handling loop
do
{
repetAfterExcep = false;
try
{
if (path == "")
{
path = Console.ReadLine();
}
#region The Translation
string[] metaChars = new string[] { #"\", " ", "<", ">", "&", ";","*",
"?", "#", ".", "~", "|", "!", "$", "(", ")", "[", "]", "{", "}", "'", "\"" }; // the meta characters that I've to deal with
foreach (string meta in metaChars)
{
// backslash is the special case in this contex
if (meta == #"\") { path = path.Replace(meta, "/"); }
// the rest
else { path = path.Replace(meta, $#"\{meta}"); }
}
path = path.Trim(new char[] { '\'', '"' }); // jusr making sure, ..., what? I didn't say anything
/*
* the fist way I did, yes, much worse, you know alredy, right?
* path = path.Trim().Replace(#"\", "/").Replace(" ", #"\ ").Replace("(", #"\(").Replace(")", #"\)"); // taking of the spaces and putting '\' where its needed
* path = path.Replace("[", #"\[").Replace("]", #"\]").Replace("{", #"\{").Replace("}", #"}]").Trim('"'); // nor sure if
*/
#endregion
Clipboard.SetText(path); // coping it to the user's clipboard
Console.WriteLine
(
$"\n The path was translated to:" +
$"\n\n {path}" +
$"\n\n and it is alredy copied to your clipbord."
); // just a bit more of aestheticness, yeah, ..., go on continue throught the code
}
catch (ArgumentNullException)
{
Console.Clear();
Console.WriteLine("\n Please inform a non-null value so the translation is possible," +
"\n if you would like to finish the solution, press enter:");
Console.Write("\n "); // same thing as before, just for aesthetics
if ((path = Console.ReadLine()) == "")
{
// didn't want to a valid translation
actuallyResetColors(); // Console.ResetColor() isn't all that great on doing its job, so, yeah
Console.Write("\n "); // aesthetic for VS console
Environment.Exit(0);
}
else { repetAfterExcep = true; } // did want to do a valid translation
}
} while (repetAfterExcep); // exception handling loop
Console.Write
(
"\n Type anything and press enter to make another translation,\n" +
" or press enter to finish the solution: "
); // asking for the loop
if (Console.ReadLine() == "")
{
// didn't want to do another translation
actuallyResetColors(); // Console.ResetColor() isn't that great on doing its job, so, yeah
Console.Write("\n "); // aesthetics for VS console
Environment.Exit(0);
}
// did want to do another translation
repetTranslation = true;
Console.Clear();
} while (repetTranslation); // API loop
}
}
}
edit 2: maybe useful information, I can normally run the code on VS, but when I try to compile it via csc filename.exe it gives me that error.
edit 3: so,
well, I don't know what to think or say about that.
I am writing a simple shell program and I have written a few commands for the program. Unfortunately, I also want to allow the shell to pipe an echo command out to a text file and also be able to cat said file and output the contents. The issue I am having is the way that I have written how the echo command works.
public static void Main(string[] args)
{
string command;
do
{
Console.ForegroundColor =
ConsoleColor.DarkGreen;
Console.Write("console > ");
Console.ForegroundColor = ConsoleColor.Gray;
command = Console.ReadLine();
Handle(command);
} while (command != "exit");
}
public static string Handle(string command)
{
if (command.StartsWith("echo "))
{
command = command.Replace("\"", "");
Console.WriteLine(command.Substring(5));
}
if (command.Contains("->"))
{
// logic for echo "text" -> output.txt
}
}
}
You can try something like this that takes the content from before and after the ->. You will have to validate the string of course before you run this
string cmd = #"echo ""text"" -> output.txt";
string text = cmd.Split(" -> ").First().Replace("echo ", "").Replace(#"""", "");
string file = cmd.Split(" -> ").Skip(1).First();
Or, a more general solution (to allow extra commands). First I create a dictionary of commands. The Action<string,string> type represents a delegate to a function that takes two strings as as parameters. The StringComparer.CurrentCultureIgnoreCase means that the case (upper or lower) is ignored when matching:
private static readonly Dictionary<string, Action<string, string>> CommandList =
new Dictionary<string, Action<string, string>>(StringComparer.CurrentCultureIgnoreCase)
{
{"echo", EchoCommand},
{"exit", ExitCommand}
};
Now I have to implement the commands. The echo command is close to what you want to do (I don't bother trying to write to the file, but I get it to the point where you could). The exit command is empty (I just let it fall through). You may want to refactor that behavior.
private static void EchoCommand(string commandText, string file)
{
var fileNote = string.IsNullOrEmpty(file) ? string.Empty : $" ({file})";
Console.WriteLine($"{commandText} {fileNote}");
}
private static void ExitCommand(string commandText, string file)
{
}
Now I need something to parse my command line. This is a brute force hack, but it works. The weird return type is a tuple, an entity that contain many values, in this case three named strings.
static (string command, string text, string file) ParseCommandLine(string commandLine)
{
//find the first space:
var endOfCommandIndex = commandLine.IndexOf(" ");
if (endOfCommandIndex < 0)
{
return (commandLine, string.Empty, null);
}
//otherwise
var command = commandLine.Substring(0, endOfCommandIndex);
var rest = commandLine.Substring(endOfCommandIndex);
var redirectIndex = rest.IndexOf("->");
if (redirectIndex < 0)
{
//use the substring to get rid of the "->"
return (command, rest.Substring(2).Trim(), null);
}
//otherwise (the "+ 2" is to get rid of the "->"
return (command, rest.Substring(0, redirectIndex).Trim(), rest.Substring(redirectIndex + 2).Trim());
}
It bangs away at the command line string and parses it into a Command, some Text and possibly a File to redirect to. I only changed a bit of the core of your Main function. It ends up looking like:
string command, text, file;
do
{
Console.ForegroundColor = ConsoleColor.DarkGreen;
Console.Write("console > ");
Console.ForegroundColor = ConsoleColor.Gray;
var commandLine = Console.ReadLine();
(command, text, file) = ParseCommandLine(commandLine);
if (CommandList.TryGetValue(command, out var commandAction))
{
commandAction(text, file);
}
else
{
Console.WriteLine("ERROR: Unknown Command");
}
} while (command != "exit");
The TryGetValue call on the dictionary will return true if the command text is in the dictionary. If it returns true, then the commandAction will represent the function to call (so it calls it). If it returns false, then the command is not in the dictionary, so an error is signaled to the user.
And finally, you don't need to put the string to echo in quotes. It just echoes whatever it finds.
I am reading strings from command line. But end of stream can not be detected by my program. How do I reconstruct this or is there a way to explicitly set EndOfStream to true?
List<String> str = new List<String>();
using (StreamReader reader = new StreamReader(Console.OpenStandardInput()))
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
if (line != string.Empty)
{
str.Add(line);
}
}
That cannot work out by design. The stdin/stdout console streams are opened as long as the program is active. The EndOfStream is executed just before you close the application in this case.
A good solution for your issue is.
using System;
public class Example
{
public static void Main()
{
string line;
do {
line = Console.ReadLine();
if (line != null)
Console.WriteLine("Now I have detected the end of stream.... " + line);
} while (line != null);
}
}
Your code is fine.
When reading from the console, EOF is indicated by entering:
^z then ENTER.
This is a standard as old as DOS. Unix has a similar standard, but it is ^d.
Bill Gates either didn't know Unix or chose to be different but not better. This is why it is ^z not ^d
Likewise, DOS and Windows treat "/" as a command line option delimiter, even though "/" and "" are handled by windows internals as path delimiters.
I'm currently working on a dll library project.
if (!Directory.Exists(MenGinPath))
{
Directory.CreateDirectory(MenGinPath + #"TimedMessages");
File.WriteAllLines(MenGinPath + #"TimedMessages\timedmessages.txt", new string[] { "Seperate each message with a new line" });
}
else if (!File.Exists(MenGinPath + #"TimedMessages\timedmessages.txt"))
{
Directory.CreateDirectory(MenGinPath + #"TimedMessages");
File.WriteAllLines(MenGinPath + #"TimedMessages\timedmessages.txt", new string[] { "Seperate each message with a new line" });
}
As you can see if the statement Directory.Exists is false a specific directory (MenGinPath) will be created. However, if the same path, with another file in addition is false, the second functions will be called.
My question is the following: is there any way to make this shorter?
Because as you can see I'm calling 2 times the same functions:
Directory.CreateDirectory(MenGinPath + #TimedMessages\timedmessages.txt
and
File.WriteAllLines(MenGinPath + #"\TimedMessages\timedmessages.txt"))
Any help would be welcome!!
You don't need to check if directory exists because Directory.CreateDirectory automatically creates the directory if it does not exists and does nothing if the directory already exists.
Also, do not include the filename when creating the directory. Yes, it wont error but just for clarity sake.
Another one is to use Path.Combine instead of hardcoding the path. This will improve readability of your code.
So, here's what I can come up with:
string dir = Path.Combine(MenGinPath, #"Groups\TimesMessages");
string file = Path.Combine(dir, "timedmessages.txt");
// this automatically creates all directories in specified path
// unless it already exists
Directory.CreateDirectory(dir);
//of course, you still need to check if the file exists
if (!File.Exists(file) {
File.WriteAllLines(filePath, new string[] { "Seperate each message with a new line" });
}
/* or if file exists, do your stuff (optional)
* else {
* //do something else? maybe edit the file?
* }
*/
You can make your code shorter given the fact that CreateDirectory does nothing when the directory exists. Moreover do not pullute your code with all that string concatenations to create the path and the file names.
Just do it one time before entering the logic using the appropriate method to create filenames and pathnames (Path.Combine).
string messagePath = Path.Combine(MenGinPath, "TimedMessages");
string fileName = Path.Combine(messagePath, "timedmessages.txt");
// call the create even if it exists. The CreateDirectory checks the fact
// by itself and thus, if you add your own check, you are checking two times.
Directory.CreateDirectory(messagePath);
if (!File.Exists(fileName)
File.WriteAllLines(fileName, new string[] { "Seperate each message with a new line" });
Would something like this work?
string strAppended = string.Empty;
if (!Directory.Exists(MenGinPath))
{
strAppended = MenGinPath + #"Groups\timedmessages.txt";
}
else if (!File.Exists(MenGinPath + #"TimedMessages\timedmessages.txt"))
{
strAppended = MenGinPath + #"TimedMessages\TimedMessages.txt";
}
else
{
return;
}
Directory.CreateDirectory(strAppended);
File.WriteAllLines(strAppended, new string[] { "Seperate each message with a new line" });
I have found that it is a great idea to reuse blocks of code like this instead of hiding them in if statements because it makes code maintenance and debugging easier and less prone to missed bugs.
It seems the only difference between the 2 cases is the path. So just get only this path in your if-else
const string GroupsPath = #"Groups\timedmessages.txt";
const string TimedMessagesTxt = #"TimedMessages\TimedMessages.txt";
string addPath = null;
if (!Directory.Exists(MenGinPath)) {
addPath = GroupsPath;
} else if (!File.Exists(Path.Combine(MenGinPath, TimedMessagesTxt))) {
addPath = TimedMessagesTxt;
}
If (addPath != null) {
Directory.CreateDirectory(Path.Combine(MenGinPath, addPath));
File.WriteAllLines(Path.Combine(MenGinPath, TimedMessagesTxt),
new string[] { "Seperate each message with a new line" });
}
Note: Using Path.Combine instead of string concatenation has the advantage that missig or extra \ are added or removed automatically.
I'm registering a custom protocol handler on my computer, which calls this application:
string prefix = "runapp://";
// The name of this app for user messages
string title = "RunApp URL Protocol Handler";
// Verify the command line arguments
if (args.Length == 0 || !args[0].StartsWith(prefix))
{
MessageBox.Show("Syntax:\nrunapp://<key>", title); return;
}
string key = args[0].Remove(0, "runapp://".Length);
key.TrimEnd('/');
string application = "";
string parameters = "";
string applicationDirectory = "";
if (key.Contains("~"))
{
application = key.Split('~')[0];
parameters = key.Split('~')[1];
}
else
{
application = key;
}
applicationDirectory = Directory.GetParent(application).FullName;
ProcessStartInfo psInfo = new ProcessStartInfo();
psInfo.Arguments = parameters;
psInfo.FileName = application;
MessageBox.Show(key + Environment.NewLine + Environment.NewLine + application + " " + parameters);
// Start the application
Process.Start(psInfo);
What it does is that it retrieves the runapp:// request, split it into two parts: application and the parameters passed, according to the location of the '~' character. (This is probably not a good idea if I ever pass PROGRA~1 or something, but considering I'm the only one using this, it's not a problem), then runs it.
However, a trailing '/' is always added to the string: if I pass
runapp://E:\Emulation\GameBoy\visualboyadvance.exe~E:\Emulation\GameBoy\zelda4.gbc, it will be interpreted as
runapp://E:\Emulation\GameBoy\visualboyadvance.exe E:\Emulation\GameBoy\zelda4.gbc/.
Why would it do this ? And why can't I get rid of this trailing slash ? I tried TrimEnd('/'), Remove(key.IndexOf('/'), 1), Replace("/", ""), yet the slash stays. What is happening ?
You need to assign the result of the TrimEnd:
key = key.TrimEnd('/');
Strings in C# are immutable; therefore string methods which alter the string return a new string with the alterations, rather than changing the original string.