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