summaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.MakeSfxCA
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-03-31 11:56:14 -0700
committerRob Mensching <rob@firegiant.com>2022-03-31 18:01:06 -0700
commit47582b162368e8edf7a3b11c13b8e9dabc5f0a26 (patch)
tree2c4063eff325684bed39de0edacd7866a257ae02 /src/dtf/WixToolset.Dtf.MakeSfxCA
parent167296c42497c4e95f0d5d71168542d747655981 (diff)
downloadwix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.tar.gz
wix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.tar.bz2
wix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.zip
Provide managed CA and Embedded UI DTF libraries via NuGet
Lots of refactoring to bring the SFX tooling back into the 'dtf' layer since they are (in the end) tightly coupled to some DTF assemblies. Also refactored the DTF tests into their own folder and added a couple integration tests to build using the new CA/UI NuGet package. Closes wixtoolset/issues#6080
Diffstat (limited to 'src/dtf/WixToolset.Dtf.MakeSfxCA')
-rw-r--r--src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs710
-rw-r--r--src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest11
-rw-r--r--src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj19
-rw-r--r--src/dtf/WixToolset.Dtf.MakeSfxCA/app.config7
4 files changed, 747 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs b/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs
new file mode 100644
index 00000000..d701da20
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs
@@ -0,0 +1,710 @@
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.Dtf.MakeSfxCA
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Security;
9 using System.Text;
10 using System.Reflection;
11 using WixToolset.Dtf.Compression;
12 using WixToolset.Dtf.Compression.Cab;
13 using WixToolset.Dtf.Resources;
14 using ResourceCollection = WixToolset.Dtf.Resources.ResourceCollection;
15
16 /// <summary>
17 /// Command-line tool for building self-extracting custom action packages.
18 /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's
19 /// entry-points and file version to look like the CA module.
20 /// </summary>
21 public static class MakeSfxCA
22 {
23 private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll";
24
25 private static TextWriter log;
26
27 /// <summary>
28 /// Prints usage text for the tool.
29 /// </summary>
30 /// <param name="w">Console text writer.</param>
31 public static void Usage(TextWriter w)
32 {
33 w.WriteLine("WiX Toolset custom action packager version {0}", Assembly.GetExecutingAssembly().GetName().Version);
34 w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved.");
35 w.WriteLine();
36 w.WriteLine("Usage: WixToolset.Dtf.MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]");
37 w.WriteLine();
38 w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package.");
39 w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY);
40 w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config");
41 }
42
43 /// <summary>
44 /// Runs the MakeSfxCA command-line tool.
45 /// </summary>
46 /// <param name="args">Command-line arguments.</param>
47 /// <returns>0 on success, nonzero on failure.</returns>
48 public static int Main(string[] args)
49 {
50 if (args.Length < 3)
51 {
52 Usage(Console.Out);
53 return 1;
54 }
55
56 var output = args[0];
57 var sfxDll = args[1];
58 var inputs = new string[args.Length - 2];
59 Array.Copy(args, 2, inputs, 0, inputs.Length);
60
61 try
62 {
63 Build(output, sfxDll, inputs, Console.Out);
64 return 0;
65 }
66 catch (ArgumentException ex)
67 {
68 Console.Error.WriteLine("Error: Invalid argument: " + ex.Message);
69 return 1;
70 }
71 catch (FileNotFoundException ex)
72 {
73 Console.Error.WriteLine("Error: Cannot find file: " + ex.Message);
74 return 1;
75 }
76 catch (Exception ex)
77 {
78 Console.Error.WriteLine("Error: Unexpected error: " + ex);
79 return 1;
80 }
81 }
82
83 /// <summary>
84 /// Packages up all the inputs to the output location.
85 /// </summary>
86 /// <exception cref="Exception">Various exceptions are thrown
87 /// if things go wrong.</exception>
88 public static void Build(string output, string sfxDll, IList<string> inputs, TextWriter log)
89 {
90 MakeSfxCA.log = log;
91
92 if (String.IsNullOrEmpty(output))
93 {
94 throw new ArgumentNullException("output");
95 }
96
97 if (String.IsNullOrEmpty(sfxDll))
98 {
99 throw new ArgumentNullException("sfxDll");
100 }
101
102 if (inputs == null || inputs.Count == 0)
103 {
104 throw new ArgumentNullException("inputs");
105 }
106
107 if (!File.Exists(sfxDll))
108 {
109 throw new FileNotFoundException(sfxDll);
110 }
111
112 var customActionAssembly = inputs[0];
113 if (!File.Exists(customActionAssembly))
114 {
115 throw new FileNotFoundException(customActionAssembly);
116 }
117
118 inputs = MakeSfxCA.SplitList(inputs);
119
120 var inputsMap = MakeSfxCA.GetPackFileMap(inputs);
121
122 var foundWIAssembly = false;
123 foreach (var input in inputsMap.Keys)
124 {
125 if (String.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY,
126 StringComparison.OrdinalIgnoreCase) == 0)
127 {
128 foundWIAssembly = true;
129 }
130 }
131
132 if (!foundWIAssembly)
133 {
134 throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY +
135 " must be included in the list of support files. " +
136 "If using the MSBuild targets, make sure the assembly reference " +
137 "has the Private (Copy Local) flag set.");
138 }
139
140 MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly));
141
142 var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly);
143 var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly);
144
145 if (entryPoints.Count == 0 && uiClass == null)
146 {
147 throw new ArgumentException(
148 "No CA or UI entry points found in module: " + customActionAssembly);
149 }
150 else if (entryPoints.Count > 0 && uiClass != null)
151 {
152 throw new NotSupportedException(
153 "CA and UI entry points cannot be in the same assembly: " + customActionAssembly);
154 }
155
156 var dir = Path.GetDirectoryName(output);
157 if (dir.Length > 0 && !Directory.Exists(dir))
158 {
159 Directory.CreateDirectory(dir);
160 }
161
162 using (Stream outputStream = File.Create(output))
163 {
164 MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass);
165 }
166
167 MakeSfxCA.CopyVersionResource(customActionAssembly, output);
168
169 MakeSfxCA.PackInputFiles(output, inputsMap);
170
171 log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName);
172 }
173
174 /// <summary>
175 /// Splits any list items delimited by semicolons into separate items.
176 /// </summary>
177 /// <param name="list">Read-only input list.</param>
178 /// <returns>New list with resulting split items.</returns>
179 private static IList<string> SplitList(IList<string> list)
180 {
181 var newList = new List<string>(list.Count);
182
183 foreach (var item in list)
184 {
185 if (!String.IsNullOrEmpty(item))
186 {
187 foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
188 {
189 newList.Add(splitItem);
190 }
191 }
192 }
193
194 return newList;
195 }
196
197 /// <summary>
198 /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection.
199 /// </summary>
200 /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param>
201 /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param>
202 /// <remarks>
203 /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them
204 /// to the list of input files if found.
205 /// </remarks>
206 private static void ResolveDependentAssemblies(IDictionary<string, string> inputFiles, string inputDir)
207 {
208 AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args)
209 {
210 AssemblyName resolveName = new AssemblyName(args.Name);
211 Assembly assembly = null;
212
213 // First, try to find the assembly in the list of input files.
214 foreach (var inputFile in inputFiles.Values)
215 {
216 var inputName = Path.GetFileNameWithoutExtension(inputFile);
217 var inputExtension = Path.GetExtension(inputFile);
218 if (String.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) &&
219 (String.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) ||
220 String.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase)))
221 {
222 assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile);
223
224 if (assembly != null)
225 {
226 break;
227 }
228 }
229 }
230
231 // Second, try to find the assembly in the input directory.
232 if (assembly == null && inputDir != null)
233 {
234 string assemblyPath = null;
235 if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll"))
236 {
237 assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll";
238 }
239 else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe"))
240 {
241 assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe";
242 }
243
244 if (assemblyPath != null)
245 {
246 assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath);
247
248 if (assembly != null)
249 {
250 // Add this detected dependency to the list of files to be packed.
251 inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath);
252 }
253 }
254 }
255
256 // Third, try to load the assembly from the GAC.
257 if (assembly == null)
258 {
259 try
260 {
261 assembly = Assembly.ReflectionOnlyLoad(args.Name);
262 }
263 catch (FileNotFoundException)
264 {
265 }
266 }
267
268 if (assembly != null)
269 {
270 if (String.Equals(assembly.GetName().ToString(), resolveName.ToString()))
271 {
272 log.WriteLine(" Loaded dependent assembly: " + assembly.Location);
273 return assembly;
274 }
275
276 log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location);
277 log.WriteLine(" Loaded assembly : " + assembly.GetName());
278 log.WriteLine(" Reference assembly: " + resolveName);
279 }
280 else
281 {
282 log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName);
283 }
284
285 return null;
286 };
287 }
288
289 /// <summary>
290 /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails.
291 /// </summary>
292 /// <param name="assemblyPath">Path of the assembly file to laod.</param>
293 /// <returns>Loaded assembly, or null if the load failed.</returns>
294 private static Assembly TryLoadDependentAssembly(string assemblyPath)
295 {
296 Assembly assembly = null;
297 try
298 {
299 assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
300 }
301 catch (IOException ex)
302 {
303 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
304 }
305 catch (BadImageFormatException ex)
306 {
307 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
308 }
309 catch (SecurityException ex)
310 {
311 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
312 }
313
314 return assembly;
315 }
316
317 /// <summary>
318 /// Searches the types in the input assembly for a type that implements IEmbeddedUI.
319 /// </summary>
320 /// <param name="module"></param>
321 /// <returns></returns>
322 private static string FindEmbeddedUIClass(string module)
323 {
324 log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module));
325
326 string uiClass = null;
327
328 var assembly = Assembly.ReflectionOnlyLoadFrom(module);
329
330 foreach (var type in assembly.GetExportedTypes())
331 {
332 if (!type.IsAbstract)
333 {
334 foreach (var interfaceType in type.GetInterfaces())
335 {
336 if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI")
337 {
338 if (uiClass == null)
339 {
340 uiClass = assembly.GetName().Name + "!" + type.FullName;
341 }
342 else
343 {
344 throw new ArgumentException("Multiple IEmbeddedUI implementations found.");
345 }
346 }
347 }
348 }
349 }
350
351 return uiClass;
352 }
353
354 /// <summary>
355 /// Reflects on an input CA module to locate custom action entry-points.
356 /// </summary>
357 /// <param name="module">Assembly module with CA entry-points.</param>
358 /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns>
359 private static IDictionary<string, string> FindEntryPoints(string module)
360 {
361 log.WriteLine("Searching for custom action entry points " +
362 "in {0}", Path.GetFileName(module));
363
364 var entryPoints = new Dictionary<string, string>();
365
366 var assembly = Assembly.ReflectionOnlyLoadFrom(module);
367
368 foreach (var type in assembly.GetExportedTypes())
369 {
370 foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
371 {
372 var entryPointName = MakeSfxCA.GetEntryPoint(method);
373 if (entryPointName != null)
374 {
375 var entryPointPath = String.Format(
376 "{0}!{1}.{2}",
377 Path.GetFileNameWithoutExtension(module),
378 type.FullName,
379 method.Name);
380 entryPoints.Add(entryPointName, entryPointPath);
381
382 log.WriteLine(" {0}={1}", entryPointName, entryPointPath);
383 }
384 }
385 }
386
387 return entryPoints;
388 }
389
390 /// <summary>
391 /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method.
392 /// </summary>
393 /// <param name="method">A public static method.</param>
394 /// <returns>Entrypoint name for the method as specified by the custom action attribute or just the method name,
395 /// or null if the method is not a custom action method.</returns>
396 private static string GetEntryPoint(MethodInfo method)
397 {
398 IList<CustomAttributeData> attributes;
399 try
400 {
401 attributes = CustomAttributeData.GetCustomAttributes(method);
402 }
403 catch (FileLoadException)
404 {
405 // Already logged load failures in the assembly-resolve-handler.
406 return null;
407 }
408
409 foreach (CustomAttributeData attribute in attributes)
410 {
411 if (attribute.ToString().StartsWith(
412 "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(",
413 StringComparison.Ordinal))
414 {
415 string entryPointName = null;
416 foreach (var argument in attribute.ConstructorArguments)
417 {
418 // The entry point name is the first positional argument, if specified.
419 entryPointName = (string) argument.Value;
420 break;
421 }
422
423 if (String.IsNullOrEmpty(entryPointName))
424 {
425 entryPointName = method.Name;
426 }
427
428 return entryPointName;
429 }
430 }
431
432 return null;
433 }
434
435 /// <summary>
436 /// Counts the number of template entrypoints in SfxCA.dll.
437 /// </summary>
438 /// <remarks>
439 /// Depending on the requirements, SfxCA.dll might be built with
440 /// more entrypoints than the default.
441 /// </remarks>
442 private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat)
443 {
444 for (var count = 0; ; count++)
445 {
446 var templateName = String.Format(entryPointFormat, count);
447 var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
448
449 var nameOffset = FindBytes(fileBytes, templateAsciiBytes);
450 if (nameOffset < 0)
451 {
452 return count;
453 }
454 }
455 }
456
457 /// <summary>
458 /// Writes a modified version of SfxCA.dll to the output stream,
459 /// with the template entry-points mapped to the CA entry-points.
460 /// </summary>
461 /// <remarks>
462 /// To avoid having to recompile SfxCA.dll for every different set of CAs,
463 /// this method looks for a preset number of template entry-points in the
464 /// binary file and overwrites their entrypoint name and string data with
465 /// CA-specific values.
466 /// </remarks>
467 private static void WriteEntryModule(
468 string sfxDll, Stream outputStream, IDictionary<string, string> entryPoints, string uiClass)
469 {
470 log.WriteLine("Modifying SfxCA.dll stub");
471
472 byte[] fileBytes;
473 using (var readStream = File.OpenRead(sfxDll))
474 {
475 fileBytes = new byte[(int) readStream.Length];
476 readStream.Read(fileBytes, 0, fileBytes.Length);
477 }
478
479 const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}";
480 const int MAX_ENTRYPOINT_NAME = 72;
481 const int MAX_ENTRYPOINT_PATH = 160;
482 //var emptyBytes = new byte[0];
483
484 var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT);
485
486 if (slotCount == 0)
487 {
488 throw new ArgumentException("Invalid SfxCA.dll file.");
489 }
490
491 if (entryPoints.Count > slotCount)
492 {
493 throw new ArgumentException(String.Format(
494 "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " +
495 "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.",
496 entryPoints.Count, slotCount));
497 }
498
499 var slotSort = new string[slotCount];
500 for (var i = 0; i < slotCount - entryPoints.Count; i++)
501 {
502 slotSort[i] = String.Empty;
503 }
504
505 entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count);
506 Array.Sort<string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal);
507
508 for (var i = 0; ; i++)
509 {
510 var templateName = String.Format(ENTRYPOINT_FORMAT, i);
511 var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
512 var templateUniBytes = Encoding.Unicode.GetBytes(templateName);
513
514 var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes);
515 if (nameOffset < 0)
516 {
517 break;
518 }
519
520 var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes);
521 if (pathOffset < 0)
522 {
523 break;
524 }
525
526 var entryPointName = slotSort[i];
527 var entryPointPath = entryPointName.Length > 0 ?
528 entryPoints[entryPointName] : String.Empty;
529
530 if (entryPointName.Length > MAX_ENTRYPOINT_NAME)
531 {
532 throw new ArgumentException(String.Format(
533 "Entry point name exceeds limit of {0} characters: {1}",
534 MAX_ENTRYPOINT_NAME,
535 entryPointName));
536 }
537
538 if (entryPointPath.Length > MAX_ENTRYPOINT_PATH)
539 {
540 throw new ArgumentException(String.Format(
541 "Entry point path exceeds limit of {0} characters: {1}",
542 MAX_ENTRYPOINT_PATH,
543 entryPointPath));
544 }
545
546 var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName);
547 var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath);
548
549 MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes);
550 MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes);
551 }
552
553 if (entryPoints.Count == 0 && uiClass != null)
554 {
555 // Remove the zzz prefix from exported EmbeddedUI entry-points.
556 foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" })
557 {
558 var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export);
559
560 var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes);
561 if (exportOffset < 0)
562 {
563 throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export);
564 }
565
566 var replaceNameBytes = Encoding.ASCII.GetBytes(export);
567 MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes);
568 }
569
570 if (uiClass.Length > MAX_ENTRYPOINT_PATH)
571 {
572 throw new ArgumentException(String.Format(
573 "UI class full name exceeds limit of {0} characters: {1}",
574 MAX_ENTRYPOINT_PATH,
575 uiClass));
576 }
577
578 var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName");
579 var replaceBytes = Encoding.Unicode.GetBytes(uiClass);
580
581 // Fill in the embedded UI implementor class so the proxy knows which one to load.
582 var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes);
583 if (replaceOffset >= 0)
584 {
585 MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes);
586 }
587 }
588
589 outputStream.Write(fileBytes, 0, fileBytes.Length);
590 }
591
592 /// <summary>
593 /// Searches for a sub-array of bytes within a larger array of bytes.
594 /// </summary>
595 private static int FindBytes(byte[] source, byte[] find)
596 {
597 for (var i = 0; i < source.Length; i++)
598 {
599 int j;
600 for (j = 0; j < find.Length; j++)
601 {
602 if (source[i + j] != find[j])
603 {
604 break;
605 }
606 }
607
608 if (j == find.Length)
609 {
610 return i;
611 }
612 }
613
614 return -1;
615 }
616
617 /// <summary>
618 /// Replaces a range of bytes with new bytes, padding any extra part
619 /// of the range with zeroes.
620 /// </summary>
621 private static void ReplaceBytes(
622 byte[] source, int offset, int length, byte[] replace)
623 {
624 for (var i = 0; i < length; i++)
625 {
626 if (i < replace.Length)
627 {
628 source[offset + i] = replace[i];
629 }
630 else
631 {
632 source[offset + i] = 0;
633 }
634 }
635 }
636
637 /// <summary>
638 /// Print the name of one file as it is being packed into the cab.
639 /// </summary>
640 private static void PackProgress(object source, ArchiveProgressEventArgs e)
641 {
642 if (e.ProgressType == ArchiveProgressType.StartFile && log != null)
643 {
644 log.WriteLine(" {0}", e.CurrentFileName);
645 }
646 }
647
648 /// <summary>
649 /// Gets a mapping from filenames as they will be in the cab to filenames
650 /// as they are currently on disk.
651 /// </summary>
652 /// <remarks>
653 /// By default, all files will be placed in the root of the cab. But inputs may
654 /// optionally include an alternate inside-cab file path before an equals sign.
655 /// </remarks>
656 private static IDictionary<string, string> GetPackFileMap(IList<string> inputs)
657 {
658 var fileMap = new Dictionary<string, string>();
659 foreach (var inputFile in inputs)
660 {
661 if (inputFile.IndexOf('=') > 0)
662 {
663 var parse = inputFile.Split('=');
664 if (!fileMap.ContainsKey(parse[0]))
665 {
666 fileMap.Add(parse[0], parse[1]);
667 }
668 }
669 else
670 {
671 var fileName = Path.GetFileName(inputFile);
672 if (!fileMap.ContainsKey(fileName))
673 {
674 fileMap.Add(fileName, inputFile);
675 }
676 }
677 }
678 return fileMap;
679 }
680
681 /// <summary>
682 /// Packs the input files into a cab that is appended to the
683 /// output SfxCA.dll.
684 /// </summary>
685 private static void PackInputFiles(string outputFile, IDictionary<string, string> fileMap)
686 {
687 log.WriteLine("Packaging files");
688
689 var cabInfo = new CabInfo(outputFile);
690 cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress);
691 }
692
693 /// <summary>
694 /// Copies the version resource information from the CA module to
695 /// the CA package. This gives the package the file version and
696 /// description of the CA module, instead of the version and
697 /// description of SfxCA.dll.
698 /// </summary>
699 private static void CopyVersionResource(string sourceFile, string destFile)
700 {
701 log.WriteLine("Copying file version info from {0} to {1}",
702 sourceFile, destFile);
703
704 var rc = new ResourceCollection();
705 rc.Find(sourceFile, ResourceType.Version);
706 rc.Load(sourceFile);
707 rc.Save(destFile);
708 }
709 }
710}
diff --git a/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest b/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest
new file mode 100644
index 00000000..5224db50
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3 <assemblyIdentity name="WixToolset.Dtf.MakeSfxCA" version="4.0.0.0" processorArchitecture="x86" type="win32"/>
4 <description>WiX Toolset Compiler</description>
5 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
6 <security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security>
7 </trustInfo>
8 <application xmlns="urn:schemas-microsoft-com:asm.v3">
9 <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"><ws2:longPathAware>true</ws2:longPathAware></windowsSettings>
10 </application>
11</assembly>
diff --git a/src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj b/src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj
new file mode 100644
index 00000000..e62aaed3
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <OutputType>Exe</OutputType>
7 <TargetFrameworks>net472</TargetFrameworks>
8 <IsPackable>false</IsPackable>
9 <DebugType>embedded</DebugType>
10 <AppConfig>app.config</AppConfig>
11 <ApplicationManifest>MakeSfxCA.exe.manifest</ApplicationManifest>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <ProjectReference Include="..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
16 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
17 <ProjectReference Include="..\WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj" />
18 </ItemGroup>
19</Project>
diff --git a/src/dtf/WixToolset.Dtf.MakeSfxCA/app.config b/src/dtf/WixToolset.Dtf.MakeSfxCA/app.config
new file mode 100644
index 00000000..29bbc006
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MakeSfxCA/app.config
@@ -0,0 +1,7 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<configuration>
3 <runtime>
4 <loadFromRemoteSources enabled="true"/>
5 <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
6 </runtime>
7</configuration>