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