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