aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/CommandLine/CommandLineParser.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2018-07-12 22:27:09 -0700
committerRob Mensching <rob@firegiant.com>2018-07-12 22:38:12 -0700
commitfc92b28f87599ac25d35399dc2df2f356a285960 (patch)
tree0a775850ec5b4ff580b949700b51f5eee3182325 /src/WixToolset.Core/CommandLine/CommandLineParser.cs
parent1a2d7994764060dc6f8936fab1c03e255f2671c5 (diff)
downloadwix-fc92b28f87599ac25d35399dc2df2f356a285960.tar.gz
wix-fc92b28f87599ac25d35399dc2df2f356a285960.tar.bz2
wix-fc92b28f87599ac25d35399dc2df2f356a285960.zip
Refactor command line parsing to enable extensions there in light.exe
Fixes wixtoolset/issues#5845
Diffstat (limited to 'src/WixToolset.Core/CommandLine/CommandLineParser.cs')
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineParser.cs593
1 files changed, 165 insertions, 428 deletions
diff --git a/src/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/WixToolset.Core/CommandLine/CommandLineParser.cs
index f4bc8ade..da0e979c 100644
--- a/src/WixToolset.Core/CommandLine/CommandLineParser.cs
+++ b/src/WixToolset.Core/CommandLine/CommandLineParser.cs
@@ -5,9 +5,6 @@ namespace WixToolset.Core.CommandLine
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using System.Text.RegularExpressions;
11 using WixToolset.Data; 8 using WixToolset.Data;
12 using WixToolset.Extensibility; 9 using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Services; 10 using WixToolset.Extensibility.Services;
@@ -22,7 +19,7 @@ namespace WixToolset.Core.CommandLine
22 Bind, 19 Bind,
23 } 20 }
24 21
25 internal class CommandLineParser : ICommandLine, IParseCommandLine 22 internal class CommandLineParser : ICommandLine
26 { 23 {
27 private IServiceProvider ServiceProvider { get; set; } 24 private IServiceProvider ServiceProvider { get; set; }
28 25
@@ -32,14 +29,8 @@ namespace WixToolset.Core.CommandLine
32 29
33 public string ActiveCommand { get; private set; } 30 public string ActiveCommand { get; private set; }
34 31
35 public string[] OriginalArguments { get; private set; }
36
37 public Queue<string> RemainingArguments { get; } = new Queue<string>();
38
39 public IExtensionManager ExtensionManager { get; private set; } 32 public IExtensionManager ExtensionManager { get; private set; }
40 33
41 public string ErrorArgument { get; set; }
42
43 public bool ShowHelp { get; set; } 34 public bool ShowHelp { get; set; }
44 35
45 public ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context) 36 public ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context)
@@ -50,18 +41,6 @@ namespace WixToolset.Core.CommandLine
50 41
51 this.ExtensionManager = context.ExtensionManager ?? this.ServiceProvider.GetService<IExtensionManager>(); 42 this.ExtensionManager = context.ExtensionManager ?? this.ServiceProvider.GetService<IExtensionManager>();
52 43
53 var args = context.ParsedArguments ?? Array.Empty<string>();
54
55 if (!String.IsNullOrEmpty(context.Arguments))
56 {
57 args = CommandLineParser.ParseArgumentsToArray(context.Arguments).Concat(args).ToArray();
58 }
59
60 return this.ParseStandardCommandLine(context, args);
61 }
62
63 private ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context, string[] args)
64 {
65 var next = String.Empty; 44 var next = String.Empty;
66 45
67 var command = Commands.Unknown; 46 var command = Commands.Unknown;
@@ -89,99 +68,99 @@ namespace WixToolset.Core.CommandLine
89 var outputsFile = String.Empty; 68 var outputsFile = String.Empty;
90 var builtOutputsFile = String.Empty; 69 var builtOutputsFile = String.Empty;
91 70
92 this.Parse(context, args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) => 71 this.Parse(context, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, parser, arg) =>
93 { 72 {
94 if (cmdline.IsSwitch(arg)) 73 if (parser.IsSwitch(arg))
95 { 74 {
96 var parameter = arg.Substring(1); 75 var parameter = arg.Substring(1);
97 switch (parameter.ToLowerInvariant()) 76 switch (parameter.ToLowerInvariant())
98 { 77 {
99 case "?": 78 case "?":
100 case "h": 79 case "h":
101 case "help": 80 case "help":
102 cmdline.ShowHelp = true; 81 cmdline.ShowHelp = true;
103 return true; 82 return true;
104 83
105 case "bindfiles": 84 case "bindfiles":
106 bindFiles = true; 85 bindFiles = true;
107 return true; 86 return true;
108 87
109 case "bindpath": 88 case "bindpath":
110 cmdline.GetNextArgumentOrError(bindPaths); 89 parser.GetNextArgumentOrError(arg, bindPaths);
111 return true; 90 return true;
112 91
113 case "cc": 92 case "cc":
114 cmdline.GetNextArgumentOrError(ref cabCachePath); 93 cabCachePath = parser.GetNextArgumentOrError(arg);
115 return true; 94 return true;
116 95
117 case "culture": 96 case "culture":
118 cmdline.GetNextArgumentOrError(cultures); 97 parser.GetNextArgumentOrError(arg, cultures);
119 return true; 98 return true;
120 case "contentsfile": 99 case "contentsfile":
121 cmdline.GetNextArgumentOrError(ref contentsFile); 100 contentsFile = parser.GetNextArgumentAsFilePathOrError(arg);
122 return true; 101 return true;
123 case "outputsfile": 102 case "outputsfile":
124 cmdline.GetNextArgumentOrError(ref outputsFile); 103 outputsFile = parser.GetNextArgumentAsFilePathOrError(arg);
125 return true; 104 return true;
126 case "builtoutputsfile": 105 case "builtoutputsfile":
127 cmdline.GetNextArgumentOrError(ref builtOutputsFile); 106 builtOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg);
128 return true; 107 return true;
129 108
130 case "d": 109 case "d":
131 case "define": 110 case "define":
132 cmdline.GetNextArgumentOrError(defines); 111 parser.GetNextArgumentOrError(arg, defines);
133 return true; 112 return true;
134 113
135 case "i": 114 case "i":
136 case "includepath": 115 case "includepath":
137 cmdline.GetNextArgumentOrError(includePaths); 116 parser.GetNextArgumentOrError(arg, includePaths);
138 return true; 117 return true;
139 118
140 case "intermediatefolder": 119 case "intermediatefolder":
141 cmdline.GetNextArgumentOrError(ref intermediateFolder); 120 intermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg);
142 return true; 121 return true;
143 122
144 case "loc": 123 case "loc":
145 cmdline.GetNextArgumentAsFilePathOrError(locFiles, "localization files"); 124 parser.GetNextArgumentAsFilePathOrError(arg, "localization files", locFiles);
146 return true; 125 return true;
147 126
148 case "lib": 127 case "lib":
149 cmdline.GetNextArgumentAsFilePathOrError(libraryFiles, "library files"); 128 parser.GetNextArgumentAsFilePathOrError(arg, "library files", libraryFiles);
150 return true; 129 return true;
151 130
152 case "o": 131 case "o":
153 case "out": 132 case "out":
154 cmdline.GetNextArgumentOrError(ref outputFile); 133 outputFile = parser.GetNextArgumentAsFilePathOrError(arg);
155 return true; 134 return true;
156 135
157 case "outputtype": 136 case "outputtype":
158 cmdline.GetNextArgumentOrError(ref outputType); 137 outputType= parser.GetNextArgumentOrError(arg);
159 return true; 138 return true;
160 139
161 case "nologo": 140 case "nologo":
162 showLogo = false; 141 showLogo = false;
163 return true; 142 return true;
164 143
165 case "v": 144 case "v":
166 case "verbose": 145 case "verbose":
167 verbose = true; 146 verbose = true;
168 return true; 147 return true;
169 148
170 case "version": 149 case "version":
171 case "-version": 150 case "-version":
172 showVersion = true; 151 showVersion = true;
173 return true; 152 return true;
174 153
175 case "sval": 154 case "sval":
176 // todo: implement 155 // todo: implement
177 return true; 156 return true;
178 } 157 }
179 158
180 return false; 159 return false;
181 } 160 }
182 else 161 else
183 { 162 {
184 files.AddRange(CommandLineHelper.GetFiles(arg, "source code")); 163 parser.GetArgumentAsFilePathOrError(arg, "source code", files);
185 return true; 164 return true;
186 } 165 }
187 }); 166 });
@@ -205,22 +184,22 @@ namespace WixToolset.Core.CommandLine
205 184
206 switch (command) 185 switch (command)
207 { 186 {
208 case Commands.Build: 187 case Commands.Build:
209 { 188 {
210 var sourceFiles = GatherSourceFiles(files, outputFolder); 189 var sourceFiles = GatherSourceFiles(files, outputFolder);
211 var variables = this.GatherPreprocessorVariables(defines); 190 var variables = this.GatherPreprocessorVariables(defines);
212 var bindPathList = this.GatherBindPaths(bindPaths); 191 var bindPathList = this.GatherBindPaths(bindPaths);
213 var filterCultures = CalculateFilterCultures(cultures); 192 var filterCultures = CalculateFilterCultures(cultures);
214 var type = CalculateOutputType(outputType, outputFile); 193 var type = CalculateOutputType(outputType, outputFile);
215 return new BuildCommand(this.ServiceProvider, sourceFiles, variables, locFiles, libraryFiles, filterCultures, outputFile, type, cabCachePath, bindFiles, bindPathList, includePaths, intermediateFolder, contentsFile, outputsFile, builtOutputsFile); 194 return new BuildCommand(this.ServiceProvider, sourceFiles, variables, locFiles, libraryFiles, filterCultures, outputFile, type, cabCachePath, bindFiles, bindPathList, includePaths, intermediateFolder, contentsFile, outputsFile, builtOutputsFile);
216 } 195 }
217 196
218 case Commands.Compile: 197 case Commands.Compile:
219 { 198 {
220 var sourceFiles = GatherSourceFiles(files, outputFolder); 199 var sourceFiles = GatherSourceFiles(files, outputFolder);
221 var variables = GatherPreprocessorVariables(defines); 200 var variables = GatherPreprocessorVariables(defines);
222 return new CompileCommand(this.ServiceProvider, sourceFiles, variables); 201 return new CompileCommand(this.ServiceProvider, sourceFiles, variables);
223 } 202 }
224 } 203 }
225 204
226 return null; 205 return null;
@@ -262,63 +241,87 @@ namespace WixToolset.Core.CommandLine
262 241
263 switch (outputType.ToLowerInvariant()) 242 switch (outputType.ToLowerInvariant())
264 { 243 {
265 case "bundle": 244 case "bundle":
266 case ".exe": 245 case ".exe":
267 return OutputType.Bundle; 246 return OutputType.Bundle;
268 247
269 case "library": 248 case "library":
270 case ".wixlib": 249 case ".wixlib":
271 return OutputType.Library; 250 return OutputType.Library;
272 251
273 case "module": 252 case "module":
274 case ".msm": 253 case ".msm":
275 return OutputType.Module; 254 return OutputType.Module;
276 255
277 case "patch": 256 case "patch":
278 case ".msp": 257 case ".msp":
279 return OutputType.Patch; 258 return OutputType.Patch;
280 259
281 case ".pcp": 260 case ".pcp":
282 return OutputType.PatchCreation; 261 return OutputType.PatchCreation;
283 262
284 case "product": 263 case "product":
285 case "package": 264 case "package":
286 case ".msi": 265 case ".msi":
287 return OutputType.Product; 266 return OutputType.Product;
288 267
289 case "transform": 268 case "transform":
290 case ".mst": 269 case ".mst":
291 return OutputType.Transform; 270 return OutputType.Transform;
292 271
293 case "intermediatepostlink": 272 case "intermediatepostlink":
294 case ".wixipl": 273 case ".wixipl":
295 return OutputType.IntermediatePostLink; 274 return OutputType.IntermediatePostLink;
296 } 275 }
297 276
298 return OutputType.Unknown; 277 return OutputType.Unknown;
299 } 278 }
300 279
301#if UNUSED 280 private ICommandLine Parse(ICommandLineContext context, Func<CommandLineParser, string, bool> parseCommand, Func<CommandLineParser, IParseCommandLine, string, bool> parseArgument)
302 private static CommandLine Parse(string commandLineString, Func<CommandLine, string, bool> parseArgument)
303 { 281 {
304 var arguments = CommandLine.ParseArgumentsToArray(commandLineString).ToArray(); 282 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
305
306 return CommandLine.Parse(arguments, null, parseArgument);
307 }
308 283
309 private static CommandLine Parse(string[] commandLineArguments, Func<CommandLine, string, bool> parseArgument) 284 foreach (var extension in extensions)
310 { 285 {
311 return CommandLine.Parse(commandLineArguments, null, parseArgument); 286 extension.PreParse(context);
312 } 287 }
313#endif
314 288
315 private ICommandLine Parse(ICommandLineContext context, string[] commandLineArguments, Func<CommandLineParser, string, bool> parseCommand, Func<CommandLineParser, string, bool> parseArgument) 289 var parser = context.Arguments.Parse();
316 {
317 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments);
318 290
319 this.QueueArgumentsAndLoadExtensions(this.OriginalArguments); 291 while (!this.ShowHelp &&
292 String.IsNullOrEmpty(parser.ErrorArgument) &&
293 parser.TryGetNextSwitchOrArgument(out var arg))
294 {
295 if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
296 {
297 continue;
298 }
320 299
321 this.ProcessRemainingArguments(context, parseArgument, parseCommand); 300 if (parser.IsSwitch(arg))
301 {
302 if (!parseArgument(this, parser, arg) &&
303 !this.TryParseCommandLineArgumentWithExtension(arg, parser, extensions))
304 {
305 parser.ErrorArgument = arg;
306 }
307 }
308 else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported.
309 {
310 if (parseCommand(this, arg))
311 {
312 this.ActiveCommand = arg;
313 }
314 else
315 {
316 parser.ErrorArgument = arg;
317 }
318 }
319 else if (!this.TryParseCommandLineArgumentWithExtension(arg, parser, extensions) &&
320 !parseArgument(this, parser, arg))
321 {
322 parser.ErrorArgument = arg;
323 }
324 }
322 325
323 return this; 326 return this;
324 } 327 }
@@ -358,7 +361,7 @@ namespace WixToolset.Core.CommandLine
358 return variables; 361 return variables;
359 } 362 }
360 363
361 private IEnumerable<BindPath> GatherBindPaths(IEnumerable<string> bindPaths) 364 private IEnumerable<BindPath> GatherBindPaths(IEnumerable<string> bindPaths)
362 { 365 {
363 var result = new List<BindPath>(); 366 var result = new List<BindPath>();
364 367
@@ -379,172 +382,11 @@ namespace WixToolset.Core.CommandLine
379 return result; 382 return result;
380 } 383 }
381 384
382 /// <summary> 385 private bool TryParseCommandLineArgumentWithExtension(string arg, IParseCommandLine parse, IEnumerable<IExtensionCommandLine> extensions)
383 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
384 /// </summary>
385 /// <param name="args">The list of strings to check.</param>
386 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
387 /// <returns>True if a valid switch exists there, false if not.</returns>
388 public bool IsSwitch(string arg)
389 {
390 return arg != null && arg.Length > 1 && ('/' == arg[0] || '-' == arg[0]);
391 }
392
393 /// <summary>
394 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
395 /// </summary>
396 /// <param name="args">The list of strings to check.</param>
397 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
398 /// <returns>True if a valid switch exists there, false if not.</returns>
399 public bool IsSwitchAt(IEnumerable<string> args, int index)
400 {
401 var arg = args.ElementAtOrDefault(index);
402 return IsSwitch(arg);
403 }
404
405 public void GetNextArgumentOrError(ref string arg)
406 {
407 this.TryGetNextArgumentOrError(out arg);
408 }
409
410 public void GetNextArgumentOrError(IList<string> args)
411 {
412 if (this.TryGetNextArgumentOrError(out var arg))
413 {
414 args.Add(arg);
415 }
416 }
417
418 public void GetNextArgumentAsFilePathOrError(IList<string> args, string fileType)
419 {
420 if (this.TryGetNextArgumentOrError(out var arg))
421 {
422 foreach (var path in CommandLineHelper.GetFiles(arg, fileType))
423 {
424 args.Add(path);
425 }
426 }
427 }
428
429 public bool TryGetNextArgumentOrError(out string arg)
430 {
431 if (TryDequeue(this.RemainingArguments, out arg) && !this.IsSwitch(arg))
432 {
433 return true;
434 }
435
436 this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument;
437
438 return false;
439 }
440
441 private static bool TryDequeue(Queue<string> q, out string arg)
442 {
443 if (q.Count > 0)
444 {
445 arg = q.Dequeue();
446 return true;
447 }
448
449 arg = null;
450 return false;
451 }
452
453 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
454 {
455 List<string> args = new List<string>();
456
457 foreach (var arg in commandLineArguments)
458 {
459 if ('@' == arg[0])
460 {
461 var responseFileArguments = CommandLineParser.ParseResponseFile(arg.Substring(1));
462 args.AddRange(responseFileArguments);
463 }
464 else
465 {
466 args.Add(arg);
467 }
468 }
469
470 this.OriginalArguments = args.ToArray();
471 }
472
473 private void QueueArgumentsAndLoadExtensions(string[] args)
474 { 386 {
475 for (var i = 0; i < args.Length; ++i)
476 {
477 var arg = args[i];
478
479 if ("-ext" == arg || "/ext" == arg)
480 {
481 if (!this.IsSwitchAt(args, ++i))
482 {
483 this.ExtensionManager.Load(args[i]);
484 }
485 else
486 {
487 this.ErrorArgument = arg;
488 break;
489 }
490 }
491 else
492 {
493 this.RemainingArguments.Enqueue(arg);
494 }
495 }
496 }
497
498 private void ProcessRemainingArguments(ICommandLineContext context, Func<CommandLineParser, string, bool> parseArgument, Func<CommandLineParser, string, bool> parseCommand)
499 {
500 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
501
502 foreach (var extension in extensions) 387 foreach (var extension in extensions)
503 { 388 {
504 extension.PreParse(context); 389 if (extension.TryParseArgument(parse, arg))
505 }
506
507 while (!this.ShowHelp &&
508 String.IsNullOrEmpty(this.ErrorArgument) &&
509 TryDequeue(this.RemainingArguments, out var arg))
510 {
511 if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
512 {
513 continue;
514 }
515
516 if ('-' == arg[0] || '/' == arg[0])
517 {
518 if (!parseArgument(this, arg) &&
519 !this.TryParseCommandLineArgumentWithExtension(arg, extensions))
520 {
521 this.ErrorArgument = arg;
522 }
523 }
524 else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported.
525 {
526 if (parseCommand(this, arg))
527 {
528 this.ActiveCommand = arg;
529 }
530 else
531 {
532 this.ErrorArgument = arg;
533 }
534 }
535 else if (!this.TryParseCommandLineArgumentWithExtension(arg, extensions) &&
536 !parseArgument(this, arg))
537 {
538 this.ErrorArgument = arg;
539 }
540 }
541 }
542
543 private bool TryParseCommandLineArgumentWithExtension(string arg, IEnumerable<IExtensionCommandLine> extensions)
544 {
545 foreach (var extension in extensions)
546 {
547 if (extension.TryParseArgument(this, arg))
548 { 390 {
549 return true; 391 return true;
550 } 392 }
@@ -552,110 +394,5 @@ namespace WixToolset.Core.CommandLine
552 394
553 return false; 395 return false;
554 } 396 }
555
556 private static List<string> ParseResponseFile(string responseFile)
557 {
558 string arguments;
559
560 using (StreamReader reader = new StreamReader(responseFile))
561 {
562 arguments = reader.ReadToEnd();
563 }
564
565 return CommandLineParser.ParseArgumentsToArray(arguments);
566 }
567
568 private static List<string> ParseArgumentsToArray(string arguments)
569 {
570 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
571 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
572 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
573 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
574
575 // Collects the final list of arguments to be returned.
576 var argsList = new List<string>();
577
578 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
579 var insideQuote = false;
580
581 // Index of the start of the current argument substring; either the start of the argument
582 // or the start of a quoted or unquoted sequence within it.
583 var partStart = 0;
584
585 // The current argument string being built; when completed it will be added to the list.
586 var arg = new StringBuilder();
587
588 for (int i = 0; i <= arguments.Length; i++)
589 {
590 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
591 {
592 // Reached a whitespace separator or the end of the string.
593
594 // Finish building the current argument.
595 arg.Append(arguments.Substring(partStart, i - partStart));
596
597 // Skip over the whitespace character.
598 partStart = i + 1;
599
600 // Add the argument to the list if it's not empty.
601 if (arg.Length > 0)
602 {
603 argsList.Add(CommandLineParser.ExpandEnvironmentVariables(arg.ToString()));
604 arg.Length = 0;
605 }
606 }
607 else if (i > partStart && arguments[i - 1] == '\\')
608 {
609 // Check the character following an unprocessed backslash.
610 // Unescape quotes, and backslashes followed by a quote.
611 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
612 {
613 // Unescape the quote or backslash by skipping the preceeding backslash.
614 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
615 arg.Append(arguments[i]);
616 partStart = i + 1;
617 }
618 }
619 else if (arguments[i] == '"')
620 {
621 // Add the quoted or unquoted section to the argument string.
622 arg.Append(arguments.Substring(partStart, i - partStart));
623
624 // And skip over the quote character.
625 partStart = i + 1;
626
627 insideQuote = !insideQuote;
628 }
629 }
630
631 return argsList;
632 }
633
634 private static string ExpandEnvironmentVariables(string arguments)
635 {
636 var id = Environment.GetEnvironmentVariables();
637
638 var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
639 MatchCollection matches = regex.Matches(arguments);
640
641 string value = String.Empty;
642 for (int i = 0; i <= (matches.Count - 1); i++)
643 {
644 try
645 {
646 var key = matches[i].Value;
647 regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)"));
648 value = id[key].ToString();
649 arguments = regex.Replace(arguments, value);
650 }
651 catch (NullReferenceException)
652 {
653 // Collapse unresolved environment variables.
654 arguments = regex.Replace(arguments, value);
655 }
656 }
657
658 return arguments;
659 }
660 } 397 }
661} 398}