aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core
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
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')
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineArguments.cs211
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineContext.cs4
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineHelper.cs216
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineParser.cs593
-rw-r--r--src/WixToolset.Core/CommandLine/ParseCommandLine.cs257
-rw-r--r--src/WixToolset.Core/Preprocessor.cs2
-rw-r--r--src/WixToolset.Core/WixToolsetServiceProvider.cs1
7 files changed, 636 insertions, 648 deletions
diff --git a/src/WixToolset.Core/CommandLine/CommandLineArguments.cs b/src/WixToolset.Core/CommandLine/CommandLineArguments.cs
new file mode 100644
index 00000000..37adcfd3
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineArguments.cs
@@ -0,0 +1,211 @@
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
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Text.RegularExpressions;
10 using WixToolset.Extensibility.Services;
11
12 internal class CommandLineArguments : ICommandLineArguments
13 {
14 public string[] OriginalArguments { get; set; }
15
16 public string[] Arguments { get; set; }
17
18 public string[] Extensions { get; set; }
19
20 public string ErrorArgument { get; set; }
21
22 private IServiceProvider ServiceProvider { get; }
23
24 public CommandLineArguments(IServiceProvider serviceProvider)
25 {
26 this.ServiceProvider = serviceProvider;
27 }
28
29 public void Populate(string commandLine)
30 {
31 var args = CommandLineArguments.ParseArgumentsToArray(commandLine);
32
33 this.Populate(args.ToArray());
34 }
35
36 public void Populate(string[] args)
37 {
38 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(args);
39
40 this.ProcessArgumentsAndParseExtensions(this.OriginalArguments);
41 }
42
43 public IParseCommandLine Parse()
44 {
45 var messaging = (IMessaging)this.ServiceProvider.GetService(typeof(IMessaging));
46
47 return new ParseCommandLine(messaging, this.Arguments, this.ErrorArgument);
48 }
49
50 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
51 {
52 List<string> args = new List<string>();
53
54 foreach (var arg in commandLineArguments)
55 {
56 if ('@' == arg[0])
57 {
58 var responseFileArguments = CommandLineArguments.ParseResponseFile(arg.Substring(1));
59 args.AddRange(responseFileArguments);
60 }
61 else
62 {
63 args.Add(arg);
64 }
65 }
66
67 this.OriginalArguments = args.ToArray();
68 }
69
70 private void ProcessArgumentsAndParseExtensions(string[] args)
71 {
72 var arguments = new List<string>();
73 var extensions = new List<string>();
74
75 for (var i = 0; i < args.Length; ++i)
76 {
77 var arg = args[i];
78
79 if ("-ext" == arg || "/ext" == arg)
80 {
81 if (!CommandLineArguments.IsSwitchAt(args, ++i))
82 {
83 extensions.Add(args[i]);
84 }
85 else
86 {
87 this.ErrorArgument = arg;
88 break;
89 }
90 }
91 else
92 {
93 arguments.Add(arg);
94 }
95 }
96
97 this.Arguments = arguments.ToArray();
98 this.Extensions = extensions.ToArray();
99 }
100
101 private static List<string> ParseResponseFile(string responseFile)
102 {
103 string arguments;
104
105 using (StreamReader reader = new StreamReader(responseFile))
106 {
107 arguments = reader.ReadToEnd();
108 }
109
110 return CommandLineArguments.ParseArgumentsToArray(arguments);
111 }
112
113 private static List<string> ParseArgumentsToArray(string arguments)
114 {
115 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
116 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
117 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
118 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
119
120 // Collects the final list of arguments to be returned.
121 var argsList = new List<string>();
122
123 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
124 var insideQuote = false;
125
126 // Index of the start of the current argument substring; either the start of the argument
127 // or the start of a quoted or unquoted sequence within it.
128 var partStart = 0;
129
130 // The current argument string being built; when completed it will be added to the list.
131 var arg = new StringBuilder();
132
133 for (int i = 0; i <= arguments.Length; i++)
134 {
135 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
136 {
137 // Reached a whitespace separator or the end of the string.
138
139 // Finish building the current argument.
140 arg.Append(arguments.Substring(partStart, i - partStart));
141
142 // Skip over the whitespace character.
143 partStart = i + 1;
144
145 // Add the argument to the list if it's not empty.
146 if (arg.Length > 0)
147 {
148 argsList.Add(CommandLineArguments.ExpandEnvironmentVariables(arg.ToString()));
149 arg.Length = 0;
150 }
151 }
152 else if (i > partStart && arguments[i - 1] == '\\')
153 {
154 // Check the character following an unprocessed backslash.
155 // Unescape quotes, and backslashes followed by a quote.
156 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
157 {
158 // Unescape the quote or backslash by skipping the preceeding backslash.
159 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
160 arg.Append(arguments[i]);
161 partStart = i + 1;
162 }
163 }
164 else if (arguments[i] == '"')
165 {
166 // Add the quoted or unquoted section to the argument string.
167 arg.Append(arguments.Substring(partStart, i - partStart));
168
169 // And skip over the quote character.
170 partStart = i + 1;
171
172 insideQuote = !insideQuote;
173 }
174 }
175
176 return argsList;
177 }
178
179 private static string ExpandEnvironmentVariables(string arguments)
180 {
181 var id = Environment.GetEnvironmentVariables();
182
183 var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
184 MatchCollection matches = regex.Matches(arguments);
185
186 string value = String.Empty;
187 for (int i = 0; i <= (matches.Count - 1); i++)
188 {
189 try
190 {
191 var key = matches[i].Value;
192 regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)"));
193 value = id[key].ToString();
194 arguments = regex.Replace(arguments, value);
195 }
196 catch (NullReferenceException)
197 {
198 // Collapse unresolved environment variables.
199 arguments = regex.Replace(arguments, value);
200 }
201 }
202
203 return arguments;
204 }
205
206 private static bool IsSwitchAt(string[] args, int index)
207 {
208 return args.Length > index && !String.IsNullOrEmpty(args[index]) && ('/' == args[index][0] || '-' == args[index][0]);
209 }
210 }
211}
diff --git a/src/WixToolset.Core/CommandLine/CommandLineContext.cs b/src/WixToolset.Core/CommandLine/CommandLineContext.cs
index 2ff2c1fd..c589222d 100644
--- a/src/WixToolset.Core/CommandLine/CommandLineContext.cs
+++ b/src/WixToolset.Core/CommandLine/CommandLineContext.cs
@@ -18,8 +18,6 @@ namespace WixToolset.Core.CommandLine
18 18
19 public IExtensionManager ExtensionManager { get; set; } 19 public IExtensionManager ExtensionManager { get; set; }
20 20
21 public string Arguments { get; set; } 21 public ICommandLineArguments Arguments { get; set; }
22
23 public string[] ParsedArguments { get; set; }
24 } 22 }
25} 23}
diff --git a/src/WixToolset.Core/CommandLine/CommandLineHelper.cs b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs
deleted file mode 100644
index 51ece0f7..00000000
--- a/src/WixToolset.Core/CommandLine/CommandLineHelper.cs
+++ /dev/null
@@ -1,216 +0,0 @@
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
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.IO;
7 using WixToolset.Data;
8 using WixToolset.Extensibility.Services;
9
10 public class CommandLineHelper
11 {
12 /// <summary>
13 /// Validates that a string is a valid directory name, and throws appropriate warnings/errors if not
14 /// </summary>
15 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
16 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
17 /// <param name="args">The list of strings to check.</param>
18 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
19 /// <returns>The string if it is valid, null if it is invalid.</returns>
20 public static string GetDirectory(string commandlineSwitch, IMessaging messageHandler, string[] args, int index)
21 {
22 return GetDirectory(commandlineSwitch, messageHandler, args, index, false);
23 }
24
25 /// <summary>
26 /// Validates that a string is a valid directory name, and throws appropriate warnings/errors if not
27 /// </summary>
28 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
29 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
30 /// <param name="args">The list of strings to check.</param>
31 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
32 /// <param name="allowPrefix">Indicates if a colon-delimited prefix is allowed.</param>
33 /// <returns>The string if it is valid, null if it is invalid.</returns>
34 public static string GetDirectory(string commandlineSwitch, IMessaging messageHandler, string[] args, int index, bool allowPrefix)
35 {
36 commandlineSwitch = String.Concat("-", commandlineSwitch);
37
38 if (!IsValidArg(args, index))
39 {
40 messageHandler.Write(ErrorMessages.DirectoryPathRequired(commandlineSwitch));
41 return null;
42 }
43
44 if (File.Exists(args[index]))
45 {
46 messageHandler.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, args[index]));
47 return null;
48 }
49
50 return VerifyPath(messageHandler, args[index], allowPrefix);
51 }
52
53 /// <summary>
54 /// Validates that a string is a valid filename, and throws appropriate warnings/errors if not
55 /// </summary>
56 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
57 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
58 /// <param name="args">The list of strings to check.</param>
59 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
60 /// <returns>The string if it is valid, null if it is invalid.</returns>
61 public static string GetFile(string commandlineSwitch, IMessaging messageHandler, string[] args, int index)
62 {
63 commandlineSwitch = String.Concat("-", commandlineSwitch);
64
65 if (!IsValidArg(args, index))
66 {
67 messageHandler.Write(ErrorMessages.FilePathRequired(commandlineSwitch));
68 return null;
69 }
70
71 if (Directory.Exists(args[index]))
72 {
73 messageHandler.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, args[index]));
74 return null;
75 }
76
77 return VerifyPath(messageHandler, args[index]);
78 }
79
80 /// <summary>
81 /// Get a set of files that possibly have a search pattern in the path (such as '*').
82 /// </summary>
83 /// <param name="searchPath">Search path to find files in.</param>
84 /// <param name="fileType">Type of file; typically "Source".</param>
85 /// <returns>An array of files matching the search path.</returns>
86 /// <remarks>
87 /// This method is written in this verbose way because it needs to support ".." in the path.
88 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
89 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
90 /// Path.GetDirectoryName does not support ".." in the path.
91 /// </remarks>
92 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
93 public static string[] GetFiles(string searchPath, string fileType)
94 {
95 if (null == searchPath)
96 {
97 throw new ArgumentNullException(nameof(searchPath));
98 }
99
100 // Convert alternate directory separators to the standard one.
101 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
102 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
103 string[] files = null;
104
105 try
106 {
107 if (0 > lastSeparator)
108 {
109 files = Directory.GetFiles(".", filePath);
110 }
111 else // found directory separator
112 {
113 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
114 }
115 }
116 catch (DirectoryNotFoundException)
117 {
118 // Don't let this function throw the DirectoryNotFoundException. This exception
119 // occurs for non-existant directories and invalid characters in the searchPattern.
120 }
121 catch (ArgumentException)
122 {
123 // Don't let this function throw the ArgumentException. This exception
124 // occurs in certain situations such as when passing a malformed UNC path.
125 }
126 catch (IOException)
127 {
128 throw new WixFileNotFoundException(searchPath, fileType);
129 }
130
131 if (null == files || 0 == files.Length)
132 {
133 throw new WixFileNotFoundException(searchPath, fileType);
134 }
135
136 return files;
137 }
138
139 /// <summary>
140 /// Validates that a valid string parameter (without "/" or "-"), and returns a bool indicating its validity
141 /// </summary>
142 /// <param name="args">The list of strings to check.</param>
143 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
144 /// <returns>True if a valid string parameter exists there, false if not.</returns>
145 public static bool IsValidArg(string[] args, int index)
146 {
147 if (args.Length <= index || String.IsNullOrEmpty(args[index]) || '/' == args[index][0] || '-' == args[index][0])
148 {
149 return false;
150 }
151 else
152 {
153 return true;
154 }
155 }
156
157 /// <summary>
158 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
159 /// </summary>
160 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
161 /// <param name="path">The path to test.</param>
162 /// <returns>The string if it is valid, null if it is invalid.</returns>
163 public static string VerifyPath(IMessaging messageHandler, string path)
164 {
165 return VerifyPath(messageHandler, path, false);
166 }
167
168 /// <summary>
169 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
170 /// </summary>
171 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
172 /// <param name="path">The path to test.</param>
173 /// <param name="allowPrefix">Indicates if a colon-delimited prefix is allowed.</param>
174 /// <returns>The full path if it is valid, null if it is invalid.</returns>
175 public static string VerifyPath(IMessaging messageHandler, string path, bool allowPrefix)
176 {
177 string fullPath;
178
179 if (0 <= path.IndexOf('\"'))
180 {
181 messageHandler.Write(ErrorMessages.PathCannotContainQuote(path));
182 return null;
183 }
184
185 try
186 {
187 string prefix = null;
188 if (allowPrefix)
189 {
190 int prefixLength = path.IndexOf('=') + 1;
191 if (0 != prefixLength)
192 {
193 prefix = path.Substring(0, prefixLength);
194 path = path.Substring(prefixLength);
195 }
196 }
197
198 if (String.IsNullOrEmpty(prefix))
199 {
200 fullPath = Path.GetFullPath(path);
201 }
202 else
203 {
204 fullPath = String.Concat(prefix, Path.GetFullPath(path));
205 }
206 }
207 catch (Exception e)
208 {
209 messageHandler.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message));
210 return null;
211 }
212
213 return fullPath;
214 }
215 }
216}
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}
diff --git a/src/WixToolset.Core/CommandLine/ParseCommandLine.cs b/src/WixToolset.Core/CommandLine/ParseCommandLine.cs
new file mode 100644
index 00000000..7d0dcbd8
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/ParseCommandLine.cs
@@ -0,0 +1,257 @@
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
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal class ParseCommandLine : IParseCommandLine
12 {
13 private const string ExpectedArgument = "expected argument";
14
15 public string ErrorArgument { get; set; }
16
17 private Queue<string> RemainingArguments { get; }
18
19 private IMessaging Messaging { get; }
20
21 public ParseCommandLine(IMessaging messaging, string[] arguments, string errorArgument)
22 {
23 this.Messaging = messaging;
24 this.RemainingArguments = new Queue<string>(arguments);
25 this.ErrorArgument = errorArgument;
26 }
27
28 public bool IsSwitch(string arg) => !String.IsNullOrEmpty(arg) && ('/' == arg[0] || '-' == arg[0]);
29
30 public void GetArgumentAsFilePathOrError(string argument, string fileType, IList<string> paths)
31 {
32 foreach (var path in GetFiles(argument, fileType))
33 {
34 paths.Add(path);
35 }
36 }
37
38 public string GetNextArgumentOrError(string commandLineSwitch)
39 {
40 if (this.TryGetNextNonSwitchArgumentOrError(out var argument))
41 {
42 return argument;
43 }
44
45 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
46 return null;
47 }
48
49 public bool GetNextArgumentOrError(string commandLineSwitch, IList<string> args)
50 {
51 if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
52 {
53 args.Add(arg);
54 return true;
55 }
56
57 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
58 return false;
59 }
60
61 public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch)
62 {
63 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory))
64 {
65 return directory;
66 }
67
68 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
69 return null;
70 }
71
72 public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList<string> directories)
73 {
74 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory))
75 {
76 directories.Add(directory);
77 return true;
78 }
79
80 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
81 return false;
82 }
83
84 public string GetNextArgumentAsFilePathOrError(string commandLineSwitch)
85 {
86 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path))
87 {
88 return path;
89 }
90
91 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
92 return null;
93 }
94
95 public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList<string> paths)
96 {
97 if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
98 {
99 foreach (var path in GetFiles(arg, fileType))
100 {
101 paths.Add(path);
102 }
103
104 return true;
105 }
106
107 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
108 return false;
109 }
110
111 public bool TryGetNextSwitchOrArgument(out string arg)
112 {
113 return TryDequeue(this.RemainingArguments, out arg);
114 }
115
116 private bool TryGetNextNonSwitchArgumentOrError(out string arg)
117 {
118 var result = this.TryGetNextSwitchOrArgument(out arg);
119
120 if (!result && !this.IsSwitch(arg))
121 {
122 this.ErrorArgument = arg ?? ParseCommandLine.ExpectedArgument;
123 }
124
125 return result;
126 }
127
128 private static bool IsValidArg(string arg) => !(String.IsNullOrEmpty(arg) || '/' == arg[0] || '-' == arg[0]);
129
130 private static bool TryDequeue(Queue<string> q, out string arg)
131 {
132 if (q.Count > 0)
133 {
134 arg = q.Dequeue();
135 return true;
136 }
137
138 arg = null;
139 return false;
140 }
141
142 private bool TryGetDirectory(string commandlineSwitch, IMessaging messageHandler, string arg, out string directory)
143 {
144 directory = null;
145
146 if (File.Exists(arg))
147 {
148 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg));
149 return false;
150 }
151
152 directory = this.VerifyPath(arg);
153 return directory != null;
154 }
155
156 private bool TryGetFile(string commandlineSwitch, string arg, out string path)
157 {
158 path = null;
159
160 if (!IsValidArg(arg))
161 {
162 this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch));
163 }
164 else if (Directory.Exists(arg))
165 {
166 this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg));
167 }
168 else
169 {
170 path = this.VerifyPath(arg);
171 }
172
173 return path != null;
174 }
175
176 /// <summary>
177 /// Get a set of files that possibly have a search pattern in the path (such as '*').
178 /// </summary>
179 /// <param name="searchPath">Search path to find files in.</param>
180 /// <param name="fileType">Type of file; typically "Source".</param>
181 /// <returns>An array of files matching the search path.</returns>
182 /// <remarks>
183 /// This method is written in this verbose way because it needs to support ".." in the path.
184 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
185 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
186 /// Path.GetDirectoryName does not support ".." in the path.
187 /// </remarks>
188 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
189 private string[] GetFiles(string searchPath, string fileType)
190 {
191 if (null == searchPath)
192 {
193 throw new ArgumentNullException(nameof(searchPath));
194 }
195
196 // Convert alternate directory separators to the standard one.
197 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
198 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
199 var files = new string[0];
200
201 try
202 {
203 if (0 > lastSeparator)
204 {
205 files = Directory.GetFiles(".", filePath);
206 }
207 else // found directory separator
208 {
209 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
210 }
211 }
212 catch (DirectoryNotFoundException)
213 {
214 // Don't let this function throw the DirectoryNotFoundException. This exception
215 // occurs for non-existant directories and invalid characters in the searchPattern.
216 }
217 catch (ArgumentException)
218 {
219 // Don't let this function throw the ArgumentException. This exception
220 // occurs in certain situations such as when passing a malformed UNC path.
221 }
222 catch (IOException)
223 {
224 }
225
226 if (0 == files.Length)
227 {
228 this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType));
229 }
230
231 return files;
232 }
233
234 private string VerifyPath(string path)
235 {
236 string fullPath;
237
238 if (0 <= path.IndexOf('\"'))
239 {
240 this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path));
241 return null;
242 }
243
244 try
245 {
246 fullPath = Path.GetFullPath(path);
247 }
248 catch (Exception e)
249 {
250 this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message));
251 return null;
252 }
253
254 return fullPath;
255 }
256 }
257}
diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs
index 6733f493..23d3f205 100644
--- a/src/WixToolset.Core/Preprocessor.cs
+++ b/src/WixToolset.Core/Preprocessor.cs
@@ -644,7 +644,7 @@ namespace WixToolset.Core
644 644
645 if (null == includeFile) 645 if (null == includeFile)
646 { 646 {
647 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include")); 647 throw new WixFileNotFoundException(sourceLineNumbers, includePath, "include");
648 } 648 }
649 649
650 using (XmlReader reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings)) 650 using (XmlReader reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings))
diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs
index 20c6c309..7d318648 100644
--- a/src/WixToolset.Core/WixToolsetServiceProvider.cs
+++ b/src/WixToolset.Core/WixToolsetServiceProvider.cs
@@ -25,6 +25,7 @@ namespace WixToolset.Core
25 { typeof(IWindowsInstallerBackendHelper), (provider, singletons) => AddSingleton(singletons, typeof(IWindowsInstallerBackendHelper), new WindowsInstallerBackendHelper(provider)) }, 25 { typeof(IWindowsInstallerBackendHelper), (provider, singletons) => AddSingleton(singletons, typeof(IWindowsInstallerBackendHelper), new WindowsInstallerBackendHelper(provider)) },
26 26
27 // Transients. 27 // Transients.
28 { typeof(ICommandLineArguments), (provider, singletons) => new CommandLineArguments(provider) },
28 { typeof(ICommandLineContext), (provider, singletons) => new CommandLineContext(provider) }, 29 { typeof(ICommandLineContext), (provider, singletons) => new CommandLineContext(provider) },
29 { typeof(ICommandLine), (provider, singletons) => new CommandLineParser() }, 30 { typeof(ICommandLine), (provider, singletons) => new CommandLineParser() },
30 { typeof(IPreprocessContext), (provider, singletons) => new PreprocessContext(provider) }, 31 { typeof(IPreprocessContext), (provider, singletons) => new PreprocessContext(provider) },