/// <summary> /// This splits the app's arguments into a string[]. Works for "regular" apps and ClickOnce apps. /// It handles a regular argument string passed to a regular exe (result does not include the exe path), /// a query string passed to a ClickOnce app's URI, or a query string passed to a ClickOnce app's .appref-ms file. /// </summary> public static string[] GetArgs(bool keepQuotes = false) { using (Log.InfoCall()) { string[] args = null; if (ApplicationDeployment.IsNetworkDeployed) { // This means we were called via the ClickOnce shortcut or URL. Log.Info("ApplicationDeployment.IsNetworkDeployed = true."); args = GetClickOnceArgs(keepQuotes); } else { Log.Info("ApplicationDeployment.IsNetworkDeployed = false."); Log.Info("Command line: ", Environment.CommandLine); args = ArgParserTX.SplitCommandLine(Environment.CommandLine, keepQuotes).Skip(1).ToArray(); } return(args ?? new string[0]); } }
// Parses the command line and sets public fields/properties including IsHelpWanted and ErrorMessage. // If canDisplayMsg is true, a message box will be displayed if an error is found or the -help switch is found. // Otherwise, the caller should display any error found and/or call ShowHelp(). // Returns true if no error was found, false if the ErrorMessage property was set. public static bool ParseCommandLine(bool canDisplayMsg) { using (Log.InfoCall()) { string[] args = ArgParserTX.GetArgs(keepQuotes: false); foreach (string arg in args) { Log.Debug("Got argument: ", arg); // Arguments that start with - or / are switches. // Others are "non-switches". char switchFlag = arg[0]; if (switchFlag == '-' || switchFlag == '/') { // It's a switch. // In general switches can have optional values, like "-switch:value". ParseSwitch() sets switchPart // and valuePart. If the syntax is incorrect, ParseSwitch() returns false and sets // msg to an error message. string switchPart; string valuePart; string msg; if (ArgParserTX.ParseSwitch(arg, out switchPart, out valuePart, out msg)) { switch (switchPart.ToLower()) { case "?": case "h": case "help": IsHelpWanted = true; break; case "s": case "server": if (valuePart.NullOrWhiteSpace()) { SetError("The '{0}{1}' switch requires a value (i.e. the server name).".Fmt(switchFlag, switchPart)); } else if (ServerName == null) { if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed) { ServerName = ArgParserTX.PercentDecode(valuePart); } else { ServerName = valuePart; } } else { SetError("Two or more server names have been specified: '{0}' and '{1}'.".Fmt(ServerName, valuePart)); } break; case "ff": case "filefilter": if (valuePart.NullOrWhiteSpace()) { SetError("The '{0}{1}' switch requires a value (i.e. a file name filter).".Fmt(switchFlag, switchPart)); } else if (FileFilter == null) { if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed) { FileFilter = ArgParserTX.PercentDecode(valuePart); } else { FileFilter = valuePart; } } else { SetError("Two or more file filters have been specified: '{0}' and '{1}'.".Fmt(FileFilter, valuePart)); } break; default: SetError("Unrecognized switch: {0}{1}".Fmt(switchFlag, switchPart)); break; } // switch (switchPart) } // Valid switch format else { // Some fundamental error in the format of a "-switch:value" pair // such as the presence of "-switch:" with no value. SetError(msg); } } // Found switch arg else { // It's a non-switch. This program allows a single non-switch to specify the file to view. if (FilePath == null) { FilePath = arg; } else { SetError("More than one <FilePath> argument was specfied. Are quotes needed?\n\n First: {0}\n Second: {1}".Fmt(FilePath, arg)); } } } // foreach arg if (FilePath != null) { if (FilePath.EndsWith(".application", StringComparison.OrdinalIgnoreCase)) { // Assume this means we were launched via our own TracerX-Viewer.application file, when means don't actually have a file arg. FilePath = null; } else { // If this app is installed as a ClickOnce app and the user double-clicks a // file, the FilePath argument will be a URI such as // "file:///c:\logs\may%20have%20blanks.tx1". That is, any blanks will be // percent-encoded. We can construct a Uri object from the string and get the // Url.LocalPath property. Uri uri; string localPath = FilePath; if (Uri.TryCreate(FilePath, UriKind.Absolute, out uri)) { localPath = uri.LocalPath; } // Constructing a FileInfo is the best way I know // to check if the file path syntax is valid. try { FileInfo test = new FileInfo(localPath); FilePath = localPath; } catch (Exception ex) { // Use the original argument in the error message. SetError("Invalid file path syntax:\n\n {0}".Fmt(FilePath)); } } } if (IsHelpWanted || ErrorMessage != null) { // Clear any args that were found so they are not acted on. ServerName = null; FilePath = null; if (canDisplayMsg) { ShowHelp(ErrorMessage); } } else { Log.Info("AppArgs.FilePath = ", FilePath); Log.Info("AppArgs.ServerName = ", ServerName); } return(ErrorMessage == null); } // using Log }
// Sets properties from command line args. May set ErrorMessage. private static void ParseArgs() { string[] args = ArgParserTX.GetArgs(keepQuotes: false); foreach (string arg in args) { Log.Debug("Got argument: ", arg); // Arguments that start with - or / are switches. // Others are "non-switches". char switchFlag = arg[0]; if (switchFlag == '-' || switchFlag == '/') { // The arg is a switch. // Switches can be bare such as "-switchpart" or have values such as "-switchpart:valuepart". // ParseSwitch() checks the syntax of the switch argument and sets switchPart // and valuePart. If the syntax is incorrect, ParseSwitch() returns false and sets // msg to an error message. string switchPart; string valuePart; string msg; if (ArgParserTX.ParseSwitch(arg, out switchPart, out valuePart, out msg)) { switch (switchPart.ToLower()) { case "?": case "h": case "help": SetBool(ref _isHelpWanted, switchPart, valuePart); break; case "i": case "install": SetBool(ref _isInstall, switchPart, valuePart); break; case "u": case "uninstall": SetBool(ref _isUninstall, switchPart, valuePart); break; case "c": case "console": SetBool(ref _isConsoleApp, switchPart, valuePart); break; case "impersonate": SetBool(ref _isImpersonate, switchPart, valuePart); break; case "p": case "port": SetInt(ref _port, switchPart, valuePart); break; case "r": case "retry": SetInt(ref _retryInterval, switchPart, valuePart); break; default: SetError("Unrecognized switch: {0}{1}".Fmt(switchFlag, switchPart)); break; } // switch (switchPart) } // Valid switch format else { // Some fundamental error in the format of a "-switch:value" pair // such as the presence of "-switch:" with no value. SetError(msg); } } // Found switch arg else { // It's a non-switch. SetError("The following argument is not a switch and is therefore invalid.\n{0}".Fmt(arg)); } } // foreach arg }
public static void StartNewViewer(string file = null, string server = null) { using (Log.InfoCall()) { // If this is a ClickOnce app we can't just // call Process.Start(Application.ExecutablePath) because the new process won't use the // same application settings file as the current process. To use the same settings file we need to // start the new process via the same shortcut that started the current process. // Here we try to determine that shortcut. DeploymentDescription clickOnceInfo = Instance; string arguments = ""; Log.Info("file = ", file); Log.Info("server = ", server); Log.Info("clickOnceInfo = ", clickOnceInfo); if (clickOnceInfo == null) { // Not a ClickOnce app. Just invoke the .exe directly. if (file != null) { arguments = "\"" + file + "\""; } if (!server.NullOrWhiteSpace()) { arguments += " -server:\"" + server + "\""; } Process.Start(System.Windows.Forms.Application.ExecutablePath, arguments); } else { // We're a ClickOnce app. Invoke process via the ClickOnce shortcut. In this case the argument string cannot // have embedded spaces or double-quotes. Use the '&' char where we would normally use a space to separate // the arguments and percent-encode each individual argument in case they contain spaces or '&'s. if (file != null) { //arguments = "%22" + file + "%22"; // Truncated at first blank. //arguments = "\"" + file + "\""; // Quotes are removed, embedded blanks look like multiple args. //arguments = "file:" + ArgParser.PercentEncode(file); // Invalid URI and invalid file. //arguments = "-file:\"" + file + "\""; // Truncated at first quote. // The Uri class will correctly percent-encode the file path so there are no embedded spaces. Uri uri = new Uri(file); arguments = uri.AbsoluteUri; } if (!server.NullOrWhiteSpace()) { arguments += "&-server:" + ArgParserTX.PercentEncode(server); } Log.Info("argument string: ", arguments); Process.Start(clickOnceInfo.Shortcut, arguments); } } }
// Parses the command line and sets public fields/properties including IsHelpWanted and ErrorMessage. // If canDisplayMsg is true, a message box will be displayed if an error is found or the -help switch is found. // Otherwise, the caller should display any error found and/or call ShowHelp(). // Returns true if no error was found, false if the ErrorMessage property was set. public static bool ParseCommandLine(bool canDisplayMsg) { using (Log.InfoCall()) { string[] args = ArgParserTX.GetArgs(keepQuotes: false); foreach (string arg in args) { Log.Debug("Got argument: ", arg); // Arguments that start with - or / are switches. // Others are "non-switches". char switchFlag = arg[0]; if (switchFlag == '-' || switchFlag == '/') { // It's a switch. // In general switches can have optional values, like "-switch:value". ParseSwitch() sets switchPart // and valuePart. If the syntax is incorrect, ParseSwitch() returns false and sets // msg to an error message. string switchPart; string valuePart; string msg; if (ArgParserTX.ParseSwitch(arg, out switchPart, out valuePart, out msg)) { switch (switchPart.ToLower()) { case "?": case "h": case "help": IsHelpWanted = true; break; case "s": case "server": if (valuePart.NullOrWhiteSpace()) { SetError("The '{0}{1}' switch requires a value (i.e. the server name).".Fmt(switchFlag, switchPart)); } else if (ServerName == null) { if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed) { ServerName = ArgParserTX.PercentDecode(valuePart); } else { ServerName = valuePart; } } else { SetError("Two or more server names have been specified: '{0}' and '{1}'.".Fmt(ServerName, valuePart)); } break; case "ff": case "filefilter": if (valuePart.NullOrWhiteSpace()) { SetError("The '{0}{1}' switch requires a value (i.e. a file name filter).".Fmt(switchFlag, switchPart)); } else if (FileFilter == null) { if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed) { FileFilter = ArgParserTX.PercentDecode(valuePart); } else { FileFilter = valuePart; } } else { SetError("Two or more file filters have been specified: '{0}' and '{1}'.".Fmt(FileFilter, valuePart)); } break; default: SetError("Unrecognized switch: {0}{1}".Fmt(switchFlag, switchPart)); break; } // switch (switchPart) } // Valid switch format else { // Some fundamental error in the format of a "-switch:value" pair // such as the presence of "-switch:" with no value. SetError(msg); } } // Found switch arg else { // It's a non-switch. This program allows a single non-switch to specify the file to view. if (FilePath == null) { FilePath = arg; } else { SetError("More than one <FilePath> argument was specfied. Are quotes needed?\n\n First: {0}\n Second: {1}".Fmt(FilePath, arg)); } } } // foreach arg if (FilePath != null) { if (FilePath.EndsWith(".application", StringComparison.OrdinalIgnoreCase)) { // Assume this means we were launched via our own TracerX-Viewer.application file, which means we don't actually have a file arg. FilePath = null; } else { // If this app is installed as a ClickOnce app and the user double-clicks a // file, the FilePath argument will be a URI such as // "file:///c:\logs\may%20have%20spaces.tx1" // or // "file://server/share/has%20space%20and%20%25.tx1". // That is, it will be percent-encoded. However, we want a regular // file path that is NOT percent-encoded. // We can construct a Uri object from the string and get the // Url.LocalPath property to do that. If FilePath is not a valid URI // we may need to percent-decode it ourselves but we must not // percent-decode it twice because the original file name may contain the % char. Uri uri; string localPath = null; if (FilePath.StartsWith("file://", StringComparison.InvariantCultureIgnoreCase) && Uri.TryCreate(FilePath, UriKind.Absolute, out uri)) { localPath = uri.LocalPath; Log.Debug("Converted URI '", FilePath, "' to\n", localPath); } else if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed) { // This is a ClickOnce app. It was probably invoked via the command line or Process.Start() using the .appref-ms file. // In that scenario we require the file path to be percent-encoded (also the switch arguments). localPath = ArgParserTX.PercentDecode(FilePath); Log.Debug("PercentDecode(\"", FilePath, "\") returned\n", localPath); } else { // Since this is not a ClickOnce app, regular command line syntax applies. The FilePath // need not be percent-encoded, though the user should have enclosed it in quotes, that are // gone now, if it contains blanks. localPath = FilePath; Log.Debug("Using FilePath as is: ", FilePath); } // Constructing a FileInfo is the best way I know // to check if the file path syntax is valid. try { FileInfo test = new FileInfo(localPath); // If no exception we have a valid path (syntax). FilePath = localPath; } catch (Exception ex) { // Use the original argument in the error message. SetError("Invalid file path syntax:\n\n {0}".Fmt(FilePath)); } } } if (IsHelpWanted || ErrorMessage != null) { // Clear any args that were found so they are not acted on. ServerName = null; FilePath = null; if (canDisplayMsg) { ShowHelp(ErrorMessage); } } else { Log.Info("AppArgs.FilePath = ", FilePath); Log.Info("AppArgs.ServerName = ", ServerName); } return(ErrorMessage == null); } // using Log }