main
cs 240 lines 9.67 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.Core.Burn.Bundles
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Reflection;
9 using System.Runtime.InteropServices;
10 using WixToolset.Data;
11 using WixToolset.Data.Burn;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Dtf.Resources;
14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services;
16 using WixToolset.Versioning;
17
18 internal class CreateBundleExeCommand
19 {
20 public CreateBundleExeCommand(IMessaging messaging, IFileSystem fileSystem, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable<WixBundleContainerSymbol> containers)
21 {
22 this.Messaging = messaging;
23 this.FileSystem = fileSystem;
24 this.BackendHelper = backendHelper;
25 this.IntermediateFolder = intermediateFolder;
26 this.OutputPath = outputPath;
27 this.BundleSymbol = bundleSymbol;
28 this.UXContainer = uxContainer;
29 this.Containers = containers;
30 }
31
32 public IFileTransfer Transfer { get; private set; }
33
34 private IMessaging Messaging { get; }
35
36 private IFileSystem FileSystem { get; }
37
38 private IBackendHelper BackendHelper { get; }
39
40 private string IntermediateFolder { get; }
41
42 private string OutputPath { get; }
43
44 private WixBundleSymbol BundleSymbol { get; }
45
46 private WixBundleContainerSymbol UXContainer { get; }
47
48 private IEnumerable<WixBundleContainerSymbol> Containers { get; }
49
50 public void Execute()
51 {
52 var bundleFilename = Path.GetFileName(this.OutputPath);
53
54 // Copy the burn.exe to a writable location then mark it to be moved to its final build location.
55
56 var stubPlatform = this.BundleSymbol.Platform.ToString();
57 var stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe");
58 var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename);
59
60 this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile));
61
62 if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase))
63 {
64 this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename));
65 }
66
67 this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers);
68
69 this.FileSystem.CopyFile(this.BundleSymbol.SourceLineNumbers, stubFile, bundleTempPath, allowHardlink: false);
70 File.SetAttributes(bundleTempPath, FileAttributes.Normal);
71
72 var fourPartVersion = this.GetFourPartVersion(this.BundleSymbol);
73
74 this.UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleSymbol, fourPartVersion);
75
76 // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers
77 // if they should be attached.
78 using (var writer = BurnWriter.Open(this.Messaging, this.FileSystem, bundleTempPath))
79 {
80 var burnStubFile = new FileInfo(bundleTempPath);
81 writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleSymbol.BundleId);
82
83 // Always attach the UX container first
84 writer.AppendContainer(this.UXContainer.SourceLineNumbers, this.UXContainer.WorkingPath, BurnWriter.Container.UX);
85
86 // Now append all other attached containers
87 foreach (var container in this.Containers)
88 {
89 if (ContainerType.Attached == container.Type)
90 {
91 // The container was only created if it had payloads.
92 if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id)
93 {
94 writer.AppendContainer(container.SourceLineNumbers, container.WorkingPath, BurnWriter.Container.Attached);
95 }
96 }
97 }
98 }
99 }
100
101 private Version GetFourPartVersion(WixBundleSymbol bundleSymbol)
102 {
103 // Ensure the bundle info provides a full four-part version.
104
105 if (!WixVersion.TryParse(bundleSymbol.Version, out var wixVersion))
106 {
107 // Display an error message indicating that we will require a four-part version number
108 // not just a WixVersion.
109 this.Messaging.Write(ErrorMessages.IllegalVersionValue(bundleSymbol.SourceLineNumbers, "Bundle", "Version", bundleSymbol.Version));
110 return new Version(0, 0);
111 }
112
113 var major = wixVersion.Major;
114 var minor = wixVersion.Minor;
115 var build = wixVersion.Patch;
116 var revision = wixVersion.Revision;
117
118 if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision)
119 {
120 major = Math.Max(major, UInt16.MaxValue);
121 minor = Math.Max(minor, UInt16.MaxValue);
122 build = Math.Max(build, UInt16.MaxValue);
123 revision = Math.Max(revision, UInt16.MaxValue);
124
125 this.Messaging.Write(BurnBackendWarnings.CannotParseBundleVersionAsFourPartVersion(bundleSymbol.SourceLineNumbers, bundleSymbol.Version));
126 }
127
128 return new Version((int)major, (int)minor, (int)build, (int)revision);
129 }
130
131 private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleSymbol bundleInfo, Version fourPartVersion)
132 {
133 const int burnLocale = 1033;
134 var resources = new ResourceCollection();
135 var version = new VersionResource("#1", burnLocale);
136
137 version.Load(bundleTempPath);
138 resources.Add(version);
139
140 version.FileVersion = fourPartVersion;
141 version.ProductVersion = fourPartVersion;
142
143 var strings = version[burnLocale] ?? version.Add(burnLocale);
144 strings["LegalCopyright"] = bundleInfo.Copyright;
145 strings["OriginalFilename"] = Path.GetFileName(outputPath);
146 strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
147 strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
148
149 if (!String.IsNullOrEmpty(bundleInfo.Name))
150 {
151 strings["ProductName"] = bundleInfo.Name;
152 strings["FileDescription"] = bundleInfo.Name;
153 }
154
155 if (!String.IsNullOrEmpty(bundleInfo.Manufacturer))
156 {
157 strings["CompanyName"] = bundleInfo.Manufacturer;
158 }
159 else
160 {
161 strings["CompanyName"] = String.Empty;
162 }
163
164 if (bundleInfo.IconSourceFile != null)
165 {
166 var iconGroup = new GroupIconResource("#1", burnLocale);
167 iconGroup.ReadFromFile(bundleInfo.IconSourceFile.Path);
168 resources.Add(iconGroup);
169
170 foreach (var icon in iconGroup.Icons)
171 {
172 resources.Add(icon);
173 }
174 }
175
176 var splashScreenType = BURN_SPLASH_SCREEN_TYPE.BURN_SPLASH_SCREEN_TYPE_NONE;
177
178 if (bundleInfo.SplashScreenSourceFile != null)
179 {
180 var bitmap = new BitmapResource("#1", burnLocale);
181 bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile.Path);
182 resources.Add(bitmap);
183
184 splashScreenType = BURN_SPLASH_SCREEN_TYPE.BURN_SPLASH_SCREEN_TYPE_BITMAP_RESOURCE;
185 }
186
187 var splashScreenConfig = new BURN_SPLASH_SCREEN_CONFIGURATION
188 {
189 Type = splashScreenType,
190 ResourceId = 1,
191 };
192
193 var splashScreenConfigResource = new Resource(ResourceType.RCData, "#1", burnLocale, splashScreenConfig.ToBytes());
194 resources.Add(splashScreenConfigResource);
195
196 try
197 {
198 this.FileSystem.ExecuteWithRetries(() => resources.Save(bundleTempPath));
199 }
200 catch (IOException e)
201 {
202 this.Messaging.Write(BurnBackendErrors.FailedToUpdateBundleResources(bundleInfo.SourceLineNumbers, bundleInfo.IconSourceFile?.Path, bundleInfo.SplashScreenSourceFile?.Path, e.Message));
203 }
204 }
205
206 enum BURN_SPLASH_SCREEN_TYPE
207 {
208 BURN_SPLASH_SCREEN_TYPE_NONE,
209 BURN_SPLASH_SCREEN_TYPE_BITMAP_RESOURCE,
210 }
211
212 [StructLayout(LayoutKind.Sequential)]
213 struct BURN_SPLASH_SCREEN_CONFIGURATION
214 {
215 [MarshalAs(UnmanagedType.I4)]
216 public BURN_SPLASH_SCREEN_TYPE Type;
217
218 [MarshalAs(UnmanagedType.U2)]
219 public ushort ResourceId;
220
221 public byte[] ToBytes()
222 {
223 var cb = Marshal.SizeOf(this);
224 var data = new byte[cb];
225 var pBuffer = Marshal.AllocHGlobal(cb);
226
227 try
228 {
229 Marshal.StructureToPtr(this, pBuffer, true);
230 Marshal.Copy(pBuffer, data, 0, cb);
231 return data;
232 }
233 finally
234 {
235 Marshal.FreeHGlobal(pBuffer);
236 }
237 }
238 }
239 }
240 }