diff options
| author | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
| commit | dbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch) | |
| tree | 0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core.WindowsInstaller/Bind | |
| parent | fbf986eb97f68396797a89fc7d40dec07b775440 (diff) | |
| download | wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2 wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip | |
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind')
19 files changed, 5719 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs new file mode 100644 index 00000000..23c481b7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs | |||
| @@ -0,0 +1,314 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using WixToolset.Core.Bind; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Rows; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. | ||
| 14 | /// </summary> | ||
| 15 | public class AssignMediaCommand | ||
| 16 | { | ||
| 17 | public AssignMediaCommand() | ||
| 18 | { | ||
| 19 | this.CabinetNameTemplate = "Cab{0}.cab"; | ||
| 20 | } | ||
| 21 | |||
| 22 | public Output Output { private get; set; } | ||
| 23 | |||
| 24 | public bool FilesCompressed { private get; set; } | ||
| 25 | |||
| 26 | public string CabinetNameTemplate { private get; set; } | ||
| 27 | |||
| 28 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 29 | |||
| 30 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 31 | |||
| 32 | /// <summary> | ||
| 33 | /// Gets cabinets with their file rows. | ||
| 34 | /// </summary> | ||
| 35 | public Dictionary<MediaRow, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; } | ||
| 36 | |||
| 37 | /// <summary> | ||
| 38 | /// Get media rows. | ||
| 39 | /// </summary> | ||
| 40 | public RowDictionary<MediaRow> MediaRows { get; private set; } | ||
| 41 | |||
| 42 | /// <summary> | ||
| 43 | /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. | ||
| 44 | /// This contains all the files when Package element is marked with compression=no | ||
| 45 | /// </summary> | ||
| 46 | public IEnumerable<FileFacade> UncompressedFileFacades { get; private set; } | ||
| 47 | |||
| 48 | public void Execute() | ||
| 49 | { | ||
| 50 | Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia = new Dictionary<MediaRow, List<FileFacade>>(); | ||
| 51 | |||
| 52 | RowDictionary<MediaRow> mediaRows = new RowDictionary<MediaRow>(); | ||
| 53 | |||
| 54 | List<FileFacade> uncompressedFiles = new List<FileFacade>(); | ||
| 55 | |||
| 56 | MediaRow mergeModuleMediaRow = null; | ||
| 57 | Table mediaTable = this.Output.Tables["Media"]; | ||
| 58 | Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; | ||
| 59 | |||
| 60 | // If both tables are authored, it is an error. | ||
| 61 | if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1)) | ||
| 62 | { | ||
| 63 | throw new WixException(WixErrors.MediaTableCollision(null)); | ||
| 64 | } | ||
| 65 | |||
| 66 | // When building merge module, all the files go to "#MergeModule.CABinet". | ||
| 67 | if (OutputType.Module == this.Output.Type) | ||
| 68 | { | ||
| 69 | Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]); | ||
| 70 | mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null); | ||
| 71 | mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet"; | ||
| 72 | |||
| 73 | filesByCabinetMedia.Add(mergeModuleMediaRow, new List<FileFacade>()); | ||
| 74 | } | ||
| 75 | |||
| 76 | if (OutputType.Module == this.Output.Type || null == mediaTemplateTable) | ||
| 77 | { | ||
| 78 | this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); | ||
| 79 | } | ||
| 80 | else | ||
| 81 | { | ||
| 82 | this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); | ||
| 83 | } | ||
| 84 | |||
| 85 | this.FileFacadesByCabinetMedia = new Dictionary<MediaRow, IEnumerable<FileFacade>>(); | ||
| 86 | |||
| 87 | foreach (var mediaRowWithFiles in filesByCabinetMedia) | ||
| 88 | { | ||
| 89 | this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value); | ||
| 90 | } | ||
| 91 | |||
| 92 | this.MediaRows = mediaRows; | ||
| 93 | |||
| 94 | this.UncompressedFileFacades = uncompressedFiles; | ||
| 95 | } | ||
| 96 | |||
| 97 | /// <summary> | ||
| 98 | /// Assign files to cabinets based on MediaTemplate authoring. | ||
| 99 | /// </summary> | ||
| 100 | /// <param name="fileFacades">FileRowCollection</param> | ||
| 101 | private void AutoAssignFiles(Table mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles) | ||
| 102 | { | ||
| 103 | const int MaxCabIndex = 999; | ||
| 104 | |||
| 105 | ulong currentPreCabSize = 0; | ||
| 106 | ulong maxPreCabSizeInBytes; | ||
| 107 | int maxPreCabSizeInMB = 0; | ||
| 108 | int currentCabIndex = 0; | ||
| 109 | |||
| 110 | MediaRow currentMediaRow = null; | ||
| 111 | |||
| 112 | Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; | ||
| 113 | |||
| 114 | // Auto assign files to cabinets based on maximum uncompressed media size | ||
| 115 | mediaTable.Rows.Clear(); | ||
| 116 | WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; | ||
| 117 | |||
| 118 | if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) | ||
| 119 | { | ||
| 120 | this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; | ||
| 121 | } | ||
| 122 | |||
| 123 | string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); | ||
| 124 | |||
| 125 | try | ||
| 126 | { | ||
| 127 | // Override authored mums value if environment variable is authored. | ||
| 128 | if (!String.IsNullOrEmpty(mumsString)) | ||
| 129 | { | ||
| 130 | maxPreCabSizeInMB = Int32.Parse(mumsString); | ||
| 131 | } | ||
| 132 | else | ||
| 133 | { | ||
| 134 | maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; | ||
| 135 | } | ||
| 136 | |||
| 137 | maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; | ||
| 138 | } | ||
| 139 | catch (FormatException) | ||
| 140 | { | ||
| 141 | throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); | ||
| 142 | } | ||
| 143 | catch (OverflowException) | ||
| 144 | { | ||
| 145 | throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); | ||
| 146 | } | ||
| 147 | |||
| 148 | foreach (FileFacade facade in this.FileFacades) | ||
| 149 | { | ||
| 150 | // When building a product, if the current file is not to be compressed or if | ||
| 151 | // the package set not to be compressed, don't cab it. | ||
| 152 | if (OutputType.Product == this.Output.Type && | ||
| 153 | (YesNoType.No == facade.File.Compressed || | ||
| 154 | (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed))) | ||
| 155 | { | ||
| 156 | uncompressedFiles.Add(facade); | ||
| 157 | continue; | ||
| 158 | } | ||
| 159 | |||
| 160 | if (currentCabIndex == MaxCabIndex) | ||
| 161 | { | ||
| 162 | // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. | ||
| 163 | List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow]; | ||
| 164 | facade.WixFile.DiskId = currentCabIndex; | ||
| 165 | cabinetFiles.Add(facade); | ||
| 166 | continue; | ||
| 167 | } | ||
| 168 | |||
| 169 | // Update current cab size. | ||
| 170 | currentPreCabSize += (ulong)facade.File.FileSize; | ||
| 171 | |||
| 172 | if (currentPreCabSize > maxPreCabSizeInBytes) | ||
| 173 | { | ||
| 174 | // Overflow due to current file | ||
| 175 | currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); | ||
| 176 | mediaRows.Add(currentMediaRow); | ||
| 177 | filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>()); | ||
| 178 | |||
| 179 | List<FileFacade> cabinetFileRows = filesByCabinetMedia[currentMediaRow]; | ||
| 180 | facade.WixFile.DiskId = currentCabIndex; | ||
| 181 | cabinetFileRows.Add(facade); | ||
| 182 | // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize | ||
| 183 | currentPreCabSize = (ulong)facade.File.FileSize; | ||
| 184 | } | ||
| 185 | else | ||
| 186 | { | ||
| 187 | // File fits in the current cab. | ||
| 188 | if (currentMediaRow == null) | ||
| 189 | { | ||
| 190 | // Create new cab and MediaRow | ||
| 191 | currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); | ||
| 192 | mediaRows.Add(currentMediaRow); | ||
| 193 | filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>()); | ||
| 194 | } | ||
| 195 | |||
| 196 | // Associate current file with current cab. | ||
| 197 | List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow]; | ||
| 198 | facade.WixFile.DiskId = currentCabIndex; | ||
| 199 | cabinetFiles.Add(facade); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | // If there are uncompressed files and no MediaRow, create a default one. | ||
| 204 | if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0) | ||
| 205 | { | ||
| 206 | MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null); | ||
| 207 | defaultMediaRow.DiskId = 1; | ||
| 208 | mediaRows.Add(defaultMediaRow); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | /// <summary> | ||
| 213 | /// Assign files to cabinets based on Media authoring. | ||
| 214 | /// </summary> | ||
| 215 | /// <param name="mediaTable"></param> | ||
| 216 | /// <param name="mergeModuleMediaRow"></param> | ||
| 217 | /// <param name="fileFacades"></param> | ||
| 218 | private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles) | ||
| 219 | { | ||
| 220 | if (OutputType.Module != this.Output.Type) | ||
| 221 | { | ||
| 222 | if (null != mediaTable) | ||
| 223 | { | ||
| 224 | Dictionary<string, MediaRow> cabinetMediaRows = new Dictionary<string, MediaRow>(StringComparer.InvariantCultureIgnoreCase); | ||
| 225 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
| 226 | { | ||
| 227 | // If the Media row has a cabinet, make sure it is unique across all Media rows. | ||
| 228 | if (!String.IsNullOrEmpty(mediaRow.Cabinet)) | ||
| 229 | { | ||
| 230 | MediaRow existingRow; | ||
| 231 | if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow)) | ||
| 232 | { | ||
| 233 | Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); | ||
| 234 | Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); | ||
| 235 | } | ||
| 236 | else | ||
| 237 | { | ||
| 238 | cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow); | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | mediaRows.Add(mediaRow); | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | foreach (MediaRow mediaRow in mediaRows.Values) | ||
| 247 | { | ||
| 248 | if (null != mediaRow.Cabinet) | ||
| 249 | { | ||
| 250 | filesByCabinetMedia.Add(mediaRow, new List<FileFacade>()); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | foreach (FileFacade facade in fileFacades) | ||
| 256 | { | ||
| 257 | if (OutputType.Module == this.Output.Type) | ||
| 258 | { | ||
| 259 | filesByCabinetMedia[mergeModuleMediaRow].Add(facade); | ||
| 260 | } | ||
| 261 | else | ||
| 262 | { | ||
| 263 | MediaRow mediaRow; | ||
| 264 | if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow)) | ||
| 265 | { | ||
| 266 | Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId)); | ||
| 267 | continue; | ||
| 268 | } | ||
| 269 | |||
| 270 | // When building a product, if the current file is not to be compressed or if | ||
| 271 | // the package set not to be compressed, don't cab it. | ||
| 272 | if (OutputType.Product == this.Output.Type && | ||
| 273 | (YesNoType.No == facade.File.Compressed || | ||
| 274 | (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed))) | ||
| 275 | { | ||
| 276 | uncompressedFiles.Add(facade); | ||
| 277 | } | ||
| 278 | else // file is marked compressed. | ||
| 279 | { | ||
| 280 | List<FileFacade> cabinetFiles; | ||
| 281 | if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles)) | ||
| 282 | { | ||
| 283 | cabinetFiles.Add(facade); | ||
| 284 | } | ||
| 285 | else | ||
| 286 | { | ||
| 287 | Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId)); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | } | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | /// <summary> | ||
| 295 | /// Adds a row to the media table with cab name template filled in. | ||
| 296 | /// </summary> | ||
| 297 | /// <param name="mediaTable"></param> | ||
| 298 | /// <param name="cabIndex"></param> | ||
| 299 | /// <returns></returns> | ||
| 300 | private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex) | ||
| 301 | { | ||
| 302 | MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); | ||
| 303 | currentMediaRow.DiskId = cabIndex; | ||
| 304 | currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex); | ||
| 305 | |||
| 306 | Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]); | ||
| 307 | WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); | ||
| 308 | row.DiskId = cabIndex; | ||
| 309 | row.CompressionLevel = mediaTemplateRow.CompressionLevel; | ||
| 310 | |||
| 311 | return currentMediaRow; | ||
| 312 | } | ||
| 313 | } | ||
| 314 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs new file mode 100644 index 00000000..2e2c5417 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs | |||
| @@ -0,0 +1,1282 @@ | |||
| 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.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Diagnostics; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.IO; | ||
| 11 | using System.Linq; | ||
| 12 | using WixToolset.Bind; | ||
| 13 | using WixToolset.Core.Bind; | ||
| 14 | using WixToolset.Core.WindowsInstaller.Databases; | ||
| 15 | using WixToolset.Data; | ||
| 16 | using WixToolset.Data.Bind; | ||
| 17 | using WixToolset.Data.Rows; | ||
| 18 | using WixToolset.Extensibility; | ||
| 19 | using WixToolset.Msi; | ||
| 20 | |||
| 21 | /// <summary> | ||
| 22 | /// Binds a databse. | ||
| 23 | /// </summary> | ||
| 24 | internal class BindDatabaseCommand | ||
| 25 | { | ||
| 26 | // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. | ||
| 27 | private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); | ||
| 28 | |||
| 29 | public BindDatabaseCommand(IBindContext context, Validator validator) | ||
| 30 | { | ||
| 31 | this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); | ||
| 32 | |||
| 33 | this.BindPaths = context.BindPaths; | ||
| 34 | this.CabbingThreadCount = context.CabbingThreadCount; | ||
| 35 | this.CabCachePath = context.CabCachePath; | ||
| 36 | this.Codepage = context.Codepage; | ||
| 37 | this.DefaultCompressionLevel = context.DefaultCompressionLevel; | ||
| 38 | this.DelayedFields = context.DelayedFields; | ||
| 39 | this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; | ||
| 40 | this.Extensions = context.Extensions; | ||
| 41 | this.Output = context.IntermediateRepresentation; | ||
| 42 | this.OutputPath = context.OutputPath; | ||
| 43 | this.PdbFile = context.OutputPdbPath; | ||
| 44 | this.IntermediateFolder = context.IntermediateFolder; | ||
| 45 | this.Validator = validator; | ||
| 46 | this.WixVariableResolver = context.WixVariableResolver; | ||
| 47 | |||
| 48 | this.BackendExtensions = context.ExtensionManager.Create<IWindowsInstallerBackendExtension>(); | ||
| 49 | } | ||
| 50 | |||
| 51 | private IEnumerable<BindPath> BindPaths { get; } | ||
| 52 | |||
| 53 | private int Codepage { get; } | ||
| 54 | |||
| 55 | private int CabbingThreadCount { get; } | ||
| 56 | |||
| 57 | private string CabCachePath { get; } | ||
| 58 | |||
| 59 | private CompressionLevel DefaultCompressionLevel { get; } | ||
| 60 | |||
| 61 | public IEnumerable<IDelayedField> DelayedFields { get; } | ||
| 62 | |||
| 63 | public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; } | ||
| 64 | |||
| 65 | public bool DeltaBinaryPatch { get; set; } | ||
| 66 | |||
| 67 | private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; } | ||
| 68 | |||
| 69 | private IEnumerable<IBinderExtension> Extensions { get; } | ||
| 70 | |||
| 71 | private IEnumerable<InspectorExtension> InspectorExtensions { get; } | ||
| 72 | |||
| 73 | private string PdbFile { get; } | ||
| 74 | |||
| 75 | private Output Output { get; } | ||
| 76 | |||
| 77 | private string OutputPath { get; } | ||
| 78 | |||
| 79 | private bool SuppressAddingValidationRows { get; } | ||
| 80 | |||
| 81 | private bool SuppressLayout { get; } | ||
| 82 | |||
| 83 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 84 | |||
| 85 | private string IntermediateFolder { get; } | ||
| 86 | |||
| 87 | private Validator Validator { get; } | ||
| 88 | |||
| 89 | private IBindVariableResolver WixVariableResolver { get; } | ||
| 90 | |||
| 91 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
| 92 | |||
| 93 | public IEnumerable<string> ContentFilePaths { get; private set; } | ||
| 94 | |||
| 95 | public void Execute() | ||
| 96 | { | ||
| 97 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
| 98 | |||
| 99 | HashSet<string> suppressedTableNames = new HashSet<string>(); | ||
| 100 | |||
| 101 | // If there are any fields to resolve later, create the cache to populate during bind. | ||
| 102 | IDictionary<string, string> variableCache = null; | ||
| 103 | if (this.DelayedFields.Any()) | ||
| 104 | { | ||
| 105 | variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); | ||
| 106 | } | ||
| 107 | |||
| 108 | this.LocalizeUI(this.Output.Tables); | ||
| 109 | |||
| 110 | // Process the summary information table before the other tables. | ||
| 111 | bool compressed; | ||
| 112 | bool longNames; | ||
| 113 | int installerVersion; | ||
| 114 | string modularizationGuid; | ||
| 115 | { | ||
| 116 | BindSummaryInfoCommand command = new BindSummaryInfoCommand(); | ||
| 117 | command.Output = this.Output; | ||
| 118 | command.Execute(); | ||
| 119 | |||
| 120 | compressed = command.Compressed; | ||
| 121 | longNames = command.LongNames; | ||
| 122 | installerVersion = command.InstallerVersion; | ||
| 123 | modularizationGuid = command.ModularizationGuid; | ||
| 124 | } | ||
| 125 | |||
| 126 | // Stop processing if an error previously occurred. | ||
| 127 | if (Messaging.Instance.EncounteredError) | ||
| 128 | { | ||
| 129 | return; | ||
| 130 | } | ||
| 131 | |||
| 132 | // Modularize identifiers and add tables with real streams to the import tables. | ||
| 133 | if (OutputType.Module == this.Output.Type) | ||
| 134 | { | ||
| 135 | // Gather all the suppress modularization identifiers | ||
| 136 | HashSet<string> suppressModularizationIdentifiers = null; | ||
| 137 | Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"]; | ||
| 138 | if (null != wixSuppressModularizationTable) | ||
| 139 | { | ||
| 140 | suppressModularizationIdentifiers = new HashSet<string>(wixSuppressModularizationTable.Rows.Select(row => (string)row[0])); | ||
| 141 | } | ||
| 142 | |||
| 143 | foreach (Table table in this.Output.Tables) | ||
| 144 | { | ||
| 145 | table.Modularize(modularizationGuid, suppressModularizationIdentifiers); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | // This must occur after all variables and source paths have been resolved and after modularization. | ||
| 150 | List<FileFacade> fileFacades; | ||
| 151 | { | ||
| 152 | GetFileFacadesCommand command = new GetFileFacadesCommand(); | ||
| 153 | command.FileTable = this.Output.Tables["File"]; | ||
| 154 | command.WixFileTable = this.Output.Tables["WixFile"]; | ||
| 155 | command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"]; | ||
| 156 | command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"]; | ||
| 157 | command.Execute(); | ||
| 158 | |||
| 159 | fileFacades = command.FileFacades; | ||
| 160 | } | ||
| 161 | |||
| 162 | ////if (OutputType.Patch == this.Output.Type) | ||
| 163 | ////{ | ||
| 164 | //// foreach (SubStorage substorage in this.Output.SubStorages) | ||
| 165 | //// { | ||
| 166 | //// Output transform = substorage.Data; | ||
| 167 | |||
| 168 | //// ResolveFieldsCommand command = new ResolveFieldsCommand(); | ||
| 169 | //// command.Tables = transform.Tables; | ||
| 170 | //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 171 | //// command.FileManagerCore = this.FileManagerCore; | ||
| 172 | //// command.FileManagers = this.FileManagers; | ||
| 173 | //// command.SupportDelayedResolution = false; | ||
| 174 | //// command.TempFilesLocation = this.TempFilesLocation; | ||
| 175 | //// command.WixVariableResolver = this.WixVariableResolver; | ||
| 176 | //// command.Execute(); | ||
| 177 | |||
| 178 | //// this.MergeUnrealTables(transform.Tables); | ||
| 179 | //// } | ||
| 180 | ////} | ||
| 181 | |||
| 182 | { | ||
| 183 | CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand(); | ||
| 184 | command.PropertyTable = this.Output.Tables["Property"]; | ||
| 185 | command.WixPropertyTable = this.Output.Tables["WixProperty"]; | ||
| 186 | command.Execute(); | ||
| 187 | } | ||
| 188 | |||
| 189 | if (Messaging.Instance.EncounteredError) | ||
| 190 | { | ||
| 191 | return; | ||
| 192 | } | ||
| 193 | |||
| 194 | // Add binder variables for all properties. | ||
| 195 | Table propertyTable = this.Output.Tables["Property"]; | ||
| 196 | if (null != propertyTable) | ||
| 197 | { | ||
| 198 | foreach (PropertyRow propertyRow in propertyTable.Rows) | ||
| 199 | { | ||
| 200 | // Set the ProductCode if it is to be generated. | ||
| 201 | if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal)) | ||
| 202 | { | ||
| 203 | propertyRow.Value = Common.GenerateGuid(); | ||
| 204 | |||
| 205 | // Update the target ProductCode in any instance transforms. | ||
| 206 | foreach (SubStorage subStorage in this.Output.SubStorages) | ||
| 207 | { | ||
| 208 | Output subStorageOutput = subStorage.Data; | ||
| 209 | if (OutputType.Transform != subStorageOutput.Type) | ||
| 210 | { | ||
| 211 | continue; | ||
| 212 | } | ||
| 213 | |||
| 214 | Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; | ||
| 215 | foreach (Row row in instanceSummaryInformationTable.Rows) | ||
| 216 | { | ||
| 217 | if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) | ||
| 218 | { | ||
| 219 | row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); | ||
| 220 | break; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | // Add the property name and value to the variableCache. | ||
| 227 | if (null != variableCache) | ||
| 228 | { | ||
| 229 | string key = String.Concat("property.", Common.Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property)); | ||
| 230 | variableCache[key] = propertyRow.Value; | ||
| 231 | } | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | // Extract files that come from cabinet files (this does not extract files from merge modules). | ||
| 236 | { | ||
| 237 | ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand(); | ||
| 238 | command.FilesWithEmbeddedFiles = this.ExpectedEmbeddedFiles; | ||
| 239 | command.Execute(); | ||
| 240 | } | ||
| 241 | |||
| 242 | if (OutputType.Product == this.Output.Type) | ||
| 243 | { | ||
| 244 | // Retrieve files and their information from merge modules. | ||
| 245 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
| 246 | |||
| 247 | if (null != wixMergeTable) | ||
| 248 | { | ||
| 249 | ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand(); | ||
| 250 | command.FileFacades = fileFacades; | ||
| 251 | command.FileTable = this.Output.Tables["File"]; | ||
| 252 | command.WixFileTable = this.Output.Tables["WixFile"]; | ||
| 253 | command.WixMergeTable = wixMergeTable; | ||
| 254 | command.OutputInstallerVersion = installerVersion; | ||
| 255 | command.SuppressLayout = this.SuppressLayout; | ||
| 256 | command.TempFilesLocation = this.IntermediateFolder; | ||
| 257 | command.Execute(); | ||
| 258 | |||
| 259 | fileFacades.AddRange(command.MergeModulesFileFacades); | ||
| 260 | } | ||
| 261 | } | ||
| 262 | else if (OutputType.Patch == this.Output.Type) | ||
| 263 | { | ||
| 264 | // Merge transform data into the output object. | ||
| 265 | IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output); | ||
| 266 | |||
| 267 | fileFacades.AddRange(filesFromTransform); | ||
| 268 | } | ||
| 269 | |||
| 270 | // stop processing if an error previously occurred | ||
| 271 | if (Messaging.Instance.EncounteredError) | ||
| 272 | { | ||
| 273 | return; | ||
| 274 | } | ||
| 275 | |||
| 276 | Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation()); | ||
| 277 | |||
| 278 | // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table). | ||
| 279 | { | ||
| 280 | UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); | ||
| 281 | command.FileFacades = fileFacades; | ||
| 282 | command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule); | ||
| 283 | command.ModularizationGuid = modularizationGuid; | ||
| 284 | command.Output = this.Output; | ||
| 285 | command.OverwriteHash = true; | ||
| 286 | command.TableDefinitions = this.TableDefinitions; | ||
| 287 | command.VariableCache = variableCache; | ||
| 288 | command.Execute(); | ||
| 289 | } | ||
| 290 | |||
| 291 | // Set generated component guids. | ||
| 292 | this.SetComponentGuids(this.Output); | ||
| 293 | |||
| 294 | // With the Component Guids set now we can create instance transforms. | ||
| 295 | this.CreateInstanceTransforms(this.Output); | ||
| 296 | |||
| 297 | this.ValidateComponentGuids(this.Output); | ||
| 298 | |||
| 299 | this.UpdateControlText(this.Output); | ||
| 300 | |||
| 301 | if (this.DelayedFields.Any()) | ||
| 302 | { | ||
| 303 | ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand(); | ||
| 304 | command.OutputType = this.Output.Type; | ||
| 305 | command.DelayedFields = this.DelayedFields; | ||
| 306 | command.ModularizationGuid = null; | ||
| 307 | command.VariableCache = variableCache; | ||
| 308 | command.Execute(); | ||
| 309 | } | ||
| 310 | |||
| 311 | // Assign files to media. | ||
| 312 | RowDictionary<MediaRow> assignedMediaRows; | ||
| 313 | Dictionary<MediaRow, IEnumerable<FileFacade>> filesByCabinetMedia; | ||
| 314 | IEnumerable<FileFacade> uncompressedFiles; | ||
| 315 | { | ||
| 316 | AssignMediaCommand command = new AssignMediaCommand(); | ||
| 317 | command.FilesCompressed = compressed; | ||
| 318 | command.FileFacades = fileFacades; | ||
| 319 | command.Output = this.Output; | ||
| 320 | command.TableDefinitions = this.TableDefinitions; | ||
| 321 | command.Execute(); | ||
| 322 | |||
| 323 | assignedMediaRows = command.MediaRows; | ||
| 324 | filesByCabinetMedia = command.FileFacadesByCabinetMedia; | ||
| 325 | uncompressedFiles = command.UncompressedFileFacades; | ||
| 326 | } | ||
| 327 | |||
| 328 | // Update file sequence. | ||
| 329 | this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows); | ||
| 330 | |||
| 331 | // stop processing if an error previously occurred | ||
| 332 | if (Messaging.Instance.EncounteredError) | ||
| 333 | { | ||
| 334 | return; | ||
| 335 | } | ||
| 336 | |||
| 337 | // Extended binder extensions can be called now that fields are resolved. | ||
| 338 | { | ||
| 339 | Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]); | ||
| 340 | |||
| 341 | foreach (IBinderExtension extension in this.Extensions) | ||
| 342 | { | ||
| 343 | extension.AfterResolvedFields(this.Output); | ||
| 344 | } | ||
| 345 | |||
| 346 | List<FileFacade> updatedFileFacades = new List<FileFacade>(); | ||
| 347 | |||
| 348 | foreach (Row updatedFile in updatedFiles.Rows) | ||
| 349 | { | ||
| 350 | string updatedId = updatedFile.FieldAsString(0); | ||
| 351 | |||
| 352 | FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId)); | ||
| 353 | |||
| 354 | updatedFileFacades.Add(updatedFacade); | ||
| 355 | } | ||
| 356 | |||
| 357 | if (updatedFileFacades.Any()) | ||
| 358 | { | ||
| 359 | UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); | ||
| 360 | command.FileFacades = fileFacades; | ||
| 361 | command.UpdateFileFacades = updatedFileFacades; | ||
| 362 | command.ModularizationGuid = modularizationGuid; | ||
| 363 | command.Output = this.Output; | ||
| 364 | command.OverwriteHash = true; | ||
| 365 | command.TableDefinitions = this.TableDefinitions; | ||
| 366 | command.VariableCache = variableCache; | ||
| 367 | command.Execute(); | ||
| 368 | } | ||
| 369 | } | ||
| 370 | |||
| 371 | // stop processing if an error previously occurred | ||
| 372 | if (Messaging.Instance.EncounteredError) | ||
| 373 | { | ||
| 374 | return; | ||
| 375 | } | ||
| 376 | |||
| 377 | Directory.CreateDirectory(this.IntermediateFolder); | ||
| 378 | |||
| 379 | if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch) | ||
| 380 | { | ||
| 381 | CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand(); | ||
| 382 | command.FileFacades = fileFacades; | ||
| 383 | command.WixPatchIdTable = this.Output.Tables["WixPatchId"]; | ||
| 384 | command.TempFilesLocation = this.IntermediateFolder; | ||
| 385 | command.Execute(); | ||
| 386 | } | ||
| 387 | |||
| 388 | // create cabinet files and process uncompressed files | ||
| 389 | string layoutDirectory = Path.GetDirectoryName(this.OutputPath); | ||
| 390 | if (!this.SuppressLayout || OutputType.Module == this.Output.Type) | ||
| 391 | { | ||
| 392 | Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles()); | ||
| 393 | |||
| 394 | var command = new CreateCabinetsCommand(); | ||
| 395 | command.CabbingThreadCount = this.CabbingThreadCount; | ||
| 396 | command.CabCachePath = this.CabCachePath; | ||
| 397 | command.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
| 398 | command.Output = this.Output; | ||
| 399 | command.BackendExtensions = this.BackendExtensions; | ||
| 400 | command.LayoutDirectory = layoutDirectory; | ||
| 401 | command.Compressed = compressed; | ||
| 402 | command.FileRowsByCabinet = filesByCabinetMedia; | ||
| 403 | command.ResolveMedia = this.ResolveMedia; | ||
| 404 | command.TableDefinitions = this.TableDefinitions; | ||
| 405 | command.TempFilesLocation = this.IntermediateFolder; | ||
| 406 | command.WixMediaTable = this.Output.Tables["WixMedia"]; | ||
| 407 | command.Execute(); | ||
| 408 | |||
| 409 | fileTransfers.AddRange(command.FileTransfers); | ||
| 410 | } | ||
| 411 | |||
| 412 | if (OutputType.Patch == this.Output.Type) | ||
| 413 | { | ||
| 414 | // copy output data back into the transforms | ||
| 415 | this.CopyToTransformData(this.Output); | ||
| 416 | } | ||
| 417 | |||
| 418 | // stop processing if an error previously occurred | ||
| 419 | if (Messaging.Instance.EncounteredError) | ||
| 420 | { | ||
| 421 | return; | ||
| 422 | } | ||
| 423 | |||
| 424 | // add back suppressed tables which must be present prior to merging in modules | ||
| 425 | if (OutputType.Product == this.Output.Type) | ||
| 426 | { | ||
| 427 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
| 428 | |||
| 429 | if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count) | ||
| 430 | { | ||
| 431 | foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) | ||
| 432 | { | ||
| 433 | string sequenceTableName = sequence.ToString(); | ||
| 434 | Table sequenceTable = this.Output.Tables[sequenceTableName]; | ||
| 435 | |||
| 436 | if (null == sequenceTable) | ||
| 437 | { | ||
| 438 | sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); | ||
| 439 | } | ||
| 440 | |||
| 441 | if (0 == sequenceTable.Rows.Count) | ||
| 442 | { | ||
| 443 | suppressedTableNames.Add(sequenceTableName); | ||
| 444 | } | ||
| 445 | } | ||
| 446 | } | ||
| 447 | } | ||
| 448 | |||
| 449 | //foreach (BinderExtension extension in this.Extensions) | ||
| 450 | //{ | ||
| 451 | // extension.PostBind(this.Context); | ||
| 452 | //} | ||
| 453 | |||
| 454 | // generate database file | ||
| 455 | Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase()); | ||
| 456 | string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); | ||
| 457 | this.GenerateDatabase(this.Output, tempDatabaseFile, false, false); | ||
| 458 | |||
| 459 | FileTransfer transfer; | ||
| 460 | if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future | ||
| 461 | { | ||
| 462 | transfer.Built = true; | ||
| 463 | fileTransfers.Add(transfer); | ||
| 464 | } | ||
| 465 | |||
| 466 | // stop processing if an error previously occurred | ||
| 467 | if (Messaging.Instance.EncounteredError) | ||
| 468 | { | ||
| 469 | return; | ||
| 470 | } | ||
| 471 | |||
| 472 | // Output the output to a file | ||
| 473 | Pdb pdb = new Pdb(); | ||
| 474 | pdb.Output = this.Output; | ||
| 475 | if (!String.IsNullOrEmpty(this.PdbFile)) | ||
| 476 | { | ||
| 477 | pdb.Save(this.PdbFile); | ||
| 478 | } | ||
| 479 | |||
| 480 | // Merge modules. | ||
| 481 | if (OutputType.Product == this.Output.Type) | ||
| 482 | { | ||
| 483 | Messaging.Instance.OnMessage(WixVerboses.MergingModules()); | ||
| 484 | |||
| 485 | MergeModulesCommand command = new MergeModulesCommand(); | ||
| 486 | command.FileFacades = fileFacades; | ||
| 487 | command.Output = this.Output; | ||
| 488 | command.OutputPath = tempDatabaseFile; | ||
| 489 | command.SuppressedTableNames = suppressedTableNames; | ||
| 490 | command.Execute(); | ||
| 491 | |||
| 492 | // stop processing if an error previously occurred | ||
| 493 | if (Messaging.Instance.EncounteredError) | ||
| 494 | { | ||
| 495 | return; | ||
| 496 | } | ||
| 497 | } | ||
| 498 | |||
| 499 | // inspect the MSI prior to running ICEs | ||
| 500 | //InspectorCore inspectorCore = new InspectorCore(); | ||
| 501 | //foreach (InspectorExtension inspectorExtension in this.InspectorExtensions) | ||
| 502 | //{ | ||
| 503 | // inspectorExtension.Core = inspectorCore; | ||
| 504 | // inspectorExtension.InspectDatabase(tempDatabaseFile, pdb); | ||
| 505 | |||
| 506 | // inspectorExtension.Core = null; // reset. | ||
| 507 | //} | ||
| 508 | |||
| 509 | if (Messaging.Instance.EncounteredError) | ||
| 510 | { | ||
| 511 | return; | ||
| 512 | } | ||
| 513 | |||
| 514 | // validate the output if there is an MSI validator | ||
| 515 | if (null != this.Validator) | ||
| 516 | { | ||
| 517 | Stopwatch stopwatch = Stopwatch.StartNew(); | ||
| 518 | |||
| 519 | // set the output file for source line information | ||
| 520 | this.Validator.Output = this.Output; | ||
| 521 | |||
| 522 | Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase()); | ||
| 523 | |||
| 524 | this.Validator.Validate(tempDatabaseFile); | ||
| 525 | |||
| 526 | stopwatch.Stop(); | ||
| 527 | Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); | ||
| 528 | |||
| 529 | // Stop processing if an error occurred. | ||
| 530 | if (Messaging.Instance.EncounteredError) | ||
| 531 | { | ||
| 532 | return; | ||
| 533 | } | ||
| 534 | } | ||
| 535 | |||
| 536 | // Process uncompressed files. | ||
| 537 | if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) | ||
| 538 | { | ||
| 539 | var command = new ProcessUncompressedFilesCommand(); | ||
| 540 | command.Compressed = compressed; | ||
| 541 | command.FileFacades = uncompressedFiles; | ||
| 542 | command.LayoutDirectory = layoutDirectory; | ||
| 543 | command.LongNamesInImage = longNames; | ||
| 544 | command.MediaRows = assignedMediaRows; | ||
| 545 | command.ResolveMedia = this.ResolveMedia; | ||
| 546 | command.DatabasePath = tempDatabaseFile; | ||
| 547 | command.WixMediaTable = this.Output.Tables["WixMedia"]; | ||
| 548 | command.Execute(); | ||
| 549 | |||
| 550 | fileTransfers.AddRange(command.FileTransfers); | ||
| 551 | } | ||
| 552 | |||
| 553 | this.FileTransfers = fileTransfers; | ||
| 554 | this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList(); | ||
| 555 | } | ||
| 556 | |||
| 557 | /// <summary> | ||
| 558 | /// Localize dialogs and controls. | ||
| 559 | /// </summary> | ||
| 560 | /// <param name="tables">The tables to localize.</param> | ||
| 561 | private void LocalizeUI(TableIndexedCollection tables) | ||
| 562 | { | ||
| 563 | Table dialogTable = tables["Dialog"]; | ||
| 564 | if (null != dialogTable) | ||
| 565 | { | ||
| 566 | foreach (Row row in dialogTable.Rows) | ||
| 567 | { | ||
| 568 | string dialog = (string)row[0]; | ||
| 569 | |||
| 570 | if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl)) | ||
| 571 | { | ||
| 572 | if (CompilerConstants.IntegerNotSet != localizedControl.X) | ||
| 573 | { | ||
| 574 | row[1] = localizedControl.X; | ||
| 575 | } | ||
| 576 | |||
| 577 | if (CompilerConstants.IntegerNotSet != localizedControl.Y) | ||
| 578 | { | ||
| 579 | row[2] = localizedControl.Y; | ||
| 580 | } | ||
| 581 | |||
| 582 | if (CompilerConstants.IntegerNotSet != localizedControl.Width) | ||
| 583 | { | ||
| 584 | row[3] = localizedControl.Width; | ||
| 585 | } | ||
| 586 | |||
| 587 | if (CompilerConstants.IntegerNotSet != localizedControl.Height) | ||
| 588 | { | ||
| 589 | row[4] = localizedControl.Height; | ||
| 590 | } | ||
| 591 | |||
| 592 | row[5] = (int)row[5] | localizedControl.Attributes; | ||
| 593 | |||
| 594 | if (!String.IsNullOrEmpty(localizedControl.Text)) | ||
| 595 | { | ||
| 596 | row[6] = localizedControl.Text; | ||
| 597 | } | ||
| 598 | } | ||
| 599 | } | ||
| 600 | } | ||
| 601 | |||
| 602 | Table controlTable = tables["Control"]; | ||
| 603 | if (null != controlTable) | ||
| 604 | { | ||
| 605 | foreach (Row row in controlTable.Rows) | ||
| 606 | { | ||
| 607 | string dialog = (string)row[0]; | ||
| 608 | string control = (string)row[1]; | ||
| 609 | |||
| 610 | if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl)) | ||
| 611 | { | ||
| 612 | if (CompilerConstants.IntegerNotSet != localizedControl.X) | ||
| 613 | { | ||
| 614 | row[3] = localizedControl.X.ToString(); | ||
| 615 | } | ||
| 616 | |||
| 617 | if (CompilerConstants.IntegerNotSet != localizedControl.Y) | ||
| 618 | { | ||
| 619 | row[4] = localizedControl.Y.ToString(); | ||
| 620 | } | ||
| 621 | |||
| 622 | if (CompilerConstants.IntegerNotSet != localizedControl.Width) | ||
| 623 | { | ||
| 624 | row[5] = localizedControl.Width.ToString(); | ||
| 625 | } | ||
| 626 | |||
| 627 | if (CompilerConstants.IntegerNotSet != localizedControl.Height) | ||
| 628 | { | ||
| 629 | row[6] = localizedControl.Height.ToString(); | ||
| 630 | } | ||
| 631 | |||
| 632 | row[7] = (int)row[7] | localizedControl.Attributes; | ||
| 633 | |||
| 634 | if (!String.IsNullOrEmpty(localizedControl.Text)) | ||
| 635 | { | ||
| 636 | row[9] = localizedControl.Text; | ||
| 637 | } | ||
| 638 | } | ||
| 639 | } | ||
| 640 | } | ||
| 641 | } | ||
| 642 | |||
| 643 | /// <summary> | ||
| 644 | /// Copy file data between transform substorages and the patch output object | ||
| 645 | /// </summary> | ||
| 646 | /// <param name="output">The output to bind.</param> | ||
| 647 | /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param> | ||
| 648 | private IEnumerable<FileFacade> CopyFromTransformData(Output output) | ||
| 649 | { | ||
| 650 | var command = new CopyTransformDataCommand(); | ||
| 651 | command.CopyOutFileRows = true; | ||
| 652 | command.Output = output; | ||
| 653 | command.TableDefinitions = this.TableDefinitions; | ||
| 654 | command.Execute(); | ||
| 655 | |||
| 656 | return command.FileFacades; | ||
| 657 | } | ||
| 658 | |||
| 659 | /// <summary> | ||
| 660 | /// Copy file data between transform substorages and the patch output object | ||
| 661 | /// </summary> | ||
| 662 | /// <param name="output">The output to bind.</param> | ||
| 663 | /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param> | ||
| 664 | private void CopyToTransformData(Output output) | ||
| 665 | { | ||
| 666 | var command = new CopyTransformDataCommand(); | ||
| 667 | command.CopyOutFileRows = false; | ||
| 668 | command.Output = output; | ||
| 669 | command.TableDefinitions = this.TableDefinitions; | ||
| 670 | command.Execute(); | ||
| 671 | } | ||
| 672 | |||
| 673 | private void UpdateMediaSequences(OutputType outputType, IEnumerable<FileFacade> fileFacades, RowDictionary<MediaRow> mediaRows) | ||
| 674 | { | ||
| 675 | // Calculate sequence numbers and media disk id layout for all file media information objects. | ||
| 676 | if (OutputType.Module == outputType) | ||
| 677 | { | ||
| 678 | int lastSequence = 0; | ||
| 679 | foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. | ||
| 680 | { | ||
| 681 | facade.File.Sequence = ++lastSequence; | ||
| 682 | } | ||
| 683 | } | ||
| 684 | else | ||
| 685 | { | ||
| 686 | int lastSequence = 0; | ||
| 687 | MediaRow mediaRow = null; | ||
| 688 | Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>(); | ||
| 689 | |||
| 690 | // sequence the non-patch-added files | ||
| 691 | foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. | ||
| 692 | { | ||
| 693 | if (null == mediaRow) | ||
| 694 | { | ||
| 695 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
| 696 | if (OutputType.Patch == outputType) | ||
| 697 | { | ||
| 698 | // patch Media cannot start at zero | ||
| 699 | lastSequence = mediaRow.LastSequence; | ||
| 700 | } | ||
| 701 | } | ||
| 702 | else if (mediaRow.DiskId != facade.WixFile.DiskId) | ||
| 703 | { | ||
| 704 | mediaRow.LastSequence = lastSequence; | ||
| 705 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
| 706 | } | ||
| 707 | |||
| 708 | if (0 < facade.WixFile.PatchGroup) | ||
| 709 | { | ||
| 710 | List<FileFacade> patchGroup = patchGroups[facade.WixFile.PatchGroup]; | ||
| 711 | |||
| 712 | if (null == patchGroup) | ||
| 713 | { | ||
| 714 | patchGroup = new List<FileFacade>(); | ||
| 715 | patchGroups.Add(facade.WixFile.PatchGroup, patchGroup); | ||
| 716 | } | ||
| 717 | |||
| 718 | patchGroup.Add(facade); | ||
| 719 | } | ||
| 720 | else | ||
| 721 | { | ||
| 722 | facade.File.Sequence = ++lastSequence; | ||
| 723 | } | ||
| 724 | } | ||
| 725 | |||
| 726 | if (null != mediaRow) | ||
| 727 | { | ||
| 728 | mediaRow.LastSequence = lastSequence; | ||
| 729 | mediaRow = null; | ||
| 730 | } | ||
| 731 | |||
| 732 | // sequence the patch-added files | ||
| 733 | foreach (List<FileFacade> patchGroup in patchGroups.Values) | ||
| 734 | { | ||
| 735 | foreach (FileFacade facade in patchGroup) | ||
| 736 | { | ||
| 737 | if (null == mediaRow) | ||
| 738 | { | ||
| 739 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
| 740 | } | ||
| 741 | else if (mediaRow.DiskId != facade.WixFile.DiskId) | ||
| 742 | { | ||
| 743 | mediaRow.LastSequence = lastSequence; | ||
| 744 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
| 745 | } | ||
| 746 | |||
| 747 | facade.File.Sequence = ++lastSequence; | ||
| 748 | } | ||
| 749 | } | ||
| 750 | |||
| 751 | if (null != mediaRow) | ||
| 752 | { | ||
| 753 | mediaRow.LastSequence = lastSequence; | ||
| 754 | } | ||
| 755 | } | ||
| 756 | } | ||
| 757 | |||
| 758 | /// <summary> | ||
| 759 | /// Set the guids for components with generatable guids. | ||
| 760 | /// </summary> | ||
| 761 | /// <param name="output">Internal representation of the database to operate on.</param> | ||
| 762 | private void SetComponentGuids(Output output) | ||
| 763 | { | ||
| 764 | Table componentTable = output.Tables["Component"]; | ||
| 765 | if (null != componentTable) | ||
| 766 | { | ||
| 767 | Hashtable registryKeyRows = null; | ||
| 768 | Hashtable directories = null; | ||
| 769 | Hashtable componentIdGenSeeds = null; | ||
| 770 | Dictionary<string, List<FileRow>> fileRows = null; | ||
| 771 | |||
| 772 | // find components with generatable guids | ||
| 773 | foreach (ComponentRow componentRow in componentTable.Rows) | ||
| 774 | { | ||
| 775 | // component guid will be generated | ||
| 776 | if ("*" == componentRow.Guid) | ||
| 777 | { | ||
| 778 | if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath) | ||
| 779 | { | ||
| 780 | Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers)); | ||
| 781 | } | ||
| 782 | else if (componentRow.IsRegistryKeyPath) | ||
| 783 | { | ||
| 784 | if (null == registryKeyRows) | ||
| 785 | { | ||
| 786 | Table registryTable = output.Tables["Registry"]; | ||
| 787 | |||
| 788 | registryKeyRows = new Hashtable(registryTable.Rows.Count); | ||
| 789 | |||
| 790 | foreach (Row registryRow in registryTable.Rows) | ||
| 791 | { | ||
| 792 | registryKeyRows.Add((string)registryRow[0], registryRow); | ||
| 793 | } | ||
| 794 | } | ||
| 795 | |||
| 796 | Row foundRow = registryKeyRows[componentRow.KeyPath] as Row; | ||
| 797 | |||
| 798 | string bitness = componentRow.Is64Bit ? "64" : String.Empty; | ||
| 799 | if (null != foundRow) | ||
| 800 | { | ||
| 801 | string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]); | ||
| 802 | componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant(); | ||
| 803 | } | ||
| 804 | } | ||
| 805 | else // must be a File KeyPath | ||
| 806 | { | ||
| 807 | // if the directory table hasn't been loaded into an indexed hash | ||
| 808 | // of directory ids to target names do that now. | ||
| 809 | if (null == directories) | ||
| 810 | { | ||
| 811 | Table directoryTable = output.Tables["Directory"]; | ||
| 812 | |||
| 813 | int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0; | ||
| 814 | |||
| 815 | directories = new Hashtable(numDirectoryTableRows); | ||
| 816 | |||
| 817 | // get the target paths for all directories | ||
| 818 | if (null != directoryTable) | ||
| 819 | { | ||
| 820 | foreach (Row row in directoryTable.Rows) | ||
| 821 | { | ||
| 822 | // if the directory Id already exists, we will skip it here since | ||
| 823 | // checking for duplicate primary keys is done later when importing tables | ||
| 824 | // into database | ||
| 825 | if (directories.ContainsKey(row[0])) | ||
| 826 | { | ||
| 827 | continue; | ||
| 828 | } | ||
| 829 | |||
| 830 | string targetName = Common.GetName((string)row[2], false, true); | ||
| 831 | directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName)); | ||
| 832 | } | ||
| 833 | } | ||
| 834 | } | ||
| 835 | |||
| 836 | // if the component id generation seeds have not been indexed | ||
| 837 | // from the WixDirectory table do that now. | ||
| 838 | if (null == componentIdGenSeeds) | ||
| 839 | { | ||
| 840 | Table wixDirectoryTable = output.Tables["WixDirectory"]; | ||
| 841 | |||
| 842 | int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0; | ||
| 843 | |||
| 844 | componentIdGenSeeds = new Hashtable(numWixDirectoryRows); | ||
| 845 | |||
| 846 | // if there are any WixDirectory rows, build up the Component Guid | ||
| 847 | // generation seeds indexed by Directory/@Id. | ||
| 848 | if (null != wixDirectoryTable) | ||
| 849 | { | ||
| 850 | foreach (Row row in wixDirectoryTable.Rows) | ||
| 851 | { | ||
| 852 | componentIdGenSeeds.Add(row[0], (string)row[1]); | ||
| 853 | } | ||
| 854 | } | ||
| 855 | } | ||
| 856 | |||
| 857 | // if the file rows have not been indexed by File.Component yet | ||
| 858 | // then do that now | ||
| 859 | if (null == fileRows) | ||
| 860 | { | ||
| 861 | Table fileTable = output.Tables["File"]; | ||
| 862 | |||
| 863 | int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0; | ||
| 864 | |||
| 865 | fileRows = new Dictionary<string, List<FileRow>>(numFileRows); | ||
| 866 | |||
| 867 | if (null != fileTable) | ||
| 868 | { | ||
| 869 | foreach (FileRow file in fileTable.Rows) | ||
| 870 | { | ||
| 871 | List<FileRow> files; | ||
| 872 | if (!fileRows.TryGetValue(file.Component, out files)) | ||
| 873 | { | ||
| 874 | files = new List<FileRow>(); | ||
| 875 | fileRows.Add(file.Component, files); | ||
| 876 | } | ||
| 877 | |||
| 878 | files.Add(file); | ||
| 879 | } | ||
| 880 | } | ||
| 881 | } | ||
| 882 | |||
| 883 | // validate component meets all the conditions to have a generated guid | ||
| 884 | List<FileRow> currentComponentFiles = fileRows[componentRow.Component]; | ||
| 885 | int numFilesInComponent = currentComponentFiles.Count; | ||
| 886 | string path = null; | ||
| 887 | |||
| 888 | foreach (FileRow fileRow in currentComponentFiles) | ||
| 889 | { | ||
| 890 | if (fileRow.File == componentRow.KeyPath) | ||
| 891 | { | ||
| 892 | // calculate the key file's canonical target path | ||
| 893 | string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true); | ||
| 894 | string fileName = Common.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture); | ||
| 895 | path = Path.Combine(directoryPath, fileName); | ||
| 896 | |||
| 897 | // find paths that are not canonicalized | ||
| 898 | if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || | ||
| 899 | path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || | ||
| 900 | path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || | ||
| 901 | path.StartsWith("TARGETDIR", StringComparison.Ordinal) || | ||
| 902 | path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || | ||
| 903 | path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) | ||
| 904 | { | ||
| 905 | Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path)); | ||
| 906 | } | ||
| 907 | |||
| 908 | // if component has more than one file, the key path must be versioned | ||
| 909 | if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) | ||
| 910 | { | ||
| 911 | Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers)); | ||
| 912 | } | ||
| 913 | } | ||
| 914 | else | ||
| 915 | { | ||
| 916 | // not a key path, so it must be an unversioned file if component has more than one file | ||
| 917 | if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) | ||
| 918 | { | ||
| 919 | Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers)); | ||
| 920 | } | ||
| 921 | } | ||
| 922 | } | ||
| 923 | |||
| 924 | // if the rules were followed, reward with a generated guid | ||
| 925 | if (!Messaging.Instance.EncounteredError) | ||
| 926 | { | ||
| 927 | componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant(); | ||
| 928 | } | ||
| 929 | } | ||
| 930 | } | ||
| 931 | } | ||
| 932 | } | ||
| 933 | } | ||
| 934 | |||
| 935 | /// <summary> | ||
| 936 | /// Creates instance transform substorages in the output. | ||
| 937 | /// </summary> | ||
| 938 | /// <param name="output">Output containing instance transform definitions.</param> | ||
| 939 | private void CreateInstanceTransforms(Output output) | ||
| 940 | { | ||
| 941 | // Create and add substorages for instance transforms. | ||
| 942 | Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"]; | ||
| 943 | if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count) | ||
| 944 | { | ||
| 945 | string targetProductCode = null; | ||
| 946 | string targetUpgradeCode = null; | ||
| 947 | string targetProductVersion = null; | ||
| 948 | |||
| 949 | Table targetSummaryInformationTable = output.Tables["_SummaryInformation"]; | ||
| 950 | Table targetPropertyTable = output.Tables["Property"]; | ||
| 951 | |||
| 952 | // Get the data from target database | ||
| 953 | foreach (Row propertyRow in targetPropertyTable.Rows) | ||
| 954 | { | ||
| 955 | if ("ProductCode" == (string)propertyRow[0]) | ||
| 956 | { | ||
| 957 | targetProductCode = (string)propertyRow[1]; | ||
| 958 | } | ||
| 959 | else if ("ProductVersion" == (string)propertyRow[0]) | ||
| 960 | { | ||
| 961 | targetProductVersion = (string)propertyRow[1]; | ||
| 962 | } | ||
| 963 | else if ("UpgradeCode" == (string)propertyRow[0]) | ||
| 964 | { | ||
| 965 | targetUpgradeCode = (string)propertyRow[1]; | ||
| 966 | } | ||
| 967 | } | ||
| 968 | |||
| 969 | // Index the Instance Component Rows. | ||
| 970 | Dictionary<string, ComponentRow> instanceComponentGuids = new Dictionary<string, ComponentRow>(); | ||
| 971 | Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"]; | ||
| 972 | if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count) | ||
| 973 | { | ||
| 974 | foreach (Row row in targetInstanceComponentTable.Rows) | ||
| 975 | { | ||
| 976 | // Build up all the instances, we'll get the Components rows from the real Component table. | ||
| 977 | instanceComponentGuids.Add((string)row[0], null); | ||
| 978 | } | ||
| 979 | |||
| 980 | Table targetComponentTable = output.Tables["Component"]; | ||
| 981 | foreach (ComponentRow componentRow in targetComponentTable.Rows) | ||
| 982 | { | ||
| 983 | string component = (string)componentRow[0]; | ||
| 984 | if (instanceComponentGuids.ContainsKey(component)) | ||
| 985 | { | ||
| 986 | instanceComponentGuids[component] = componentRow; | ||
| 987 | } | ||
| 988 | } | ||
| 989 | } | ||
| 990 | |||
| 991 | // Generate the instance transforms | ||
| 992 | foreach (Row instanceRow in wixInstanceTransformsTable.Rows) | ||
| 993 | { | ||
| 994 | string instanceId = (string)instanceRow[0]; | ||
| 995 | |||
| 996 | Output instanceTransform = new Output(instanceRow.SourceLineNumbers); | ||
| 997 | instanceTransform.Type = OutputType.Transform; | ||
| 998 | instanceTransform.Codepage = output.Codepage; | ||
| 999 | |||
| 1000 | Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 1001 | string targetPlatformAndLanguage = null; | ||
| 1002 | |||
| 1003 | foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows) | ||
| 1004 | { | ||
| 1005 | if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE | ||
| 1006 | { | ||
| 1007 | targetPlatformAndLanguage = (string)summaryInformationRow[1]; | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | // Copy the row's data to the transform. | ||
| 1011 | Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
| 1012 | copyOfSummaryRow[0] = summaryInformationRow[0]; | ||
| 1013 | copyOfSummaryRow[1] = summaryInformationRow[1]; | ||
| 1014 | } | ||
| 1015 | |||
| 1016 | // Modify the appropriate properties. | ||
| 1017 | Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); | ||
| 1018 | |||
| 1019 | // Change the ProductCode property | ||
| 1020 | string productCode = (string)instanceRow[2]; | ||
| 1021 | if ("*" == productCode) | ||
| 1022 | { | ||
| 1023 | productCode = Common.GenerateGuid(); | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
| 1027 | productCodeRow.Operation = RowOperation.Modify; | ||
| 1028 | productCodeRow.Fields[1].Modified = true; | ||
| 1029 | productCodeRow[0] = "ProductCode"; | ||
| 1030 | productCodeRow[1] = productCode; | ||
| 1031 | |||
| 1032 | // Change the instance property | ||
| 1033 | Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
| 1034 | instanceIdRow.Operation = RowOperation.Modify; | ||
| 1035 | instanceIdRow.Fields[1].Modified = true; | ||
| 1036 | instanceIdRow[0] = (string)instanceRow[1]; | ||
| 1037 | instanceIdRow[1] = instanceId; | ||
| 1038 | |||
| 1039 | if (null != instanceRow[3]) | ||
| 1040 | { | ||
| 1041 | // Change the ProductName property | ||
| 1042 | Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
| 1043 | productNameRow.Operation = RowOperation.Modify; | ||
| 1044 | productNameRow.Fields[1].Modified = true; | ||
| 1045 | productNameRow[0] = "ProductName"; | ||
| 1046 | productNameRow[1] = (string)instanceRow[3]; | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | if (null != instanceRow[4]) | ||
| 1050 | { | ||
| 1051 | // Change the UpgradeCode property | ||
| 1052 | Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
| 1053 | upgradeCodeRow.Operation = RowOperation.Modify; | ||
| 1054 | upgradeCodeRow.Fields[1].Modified = true; | ||
| 1055 | upgradeCodeRow[0] = "UpgradeCode"; | ||
| 1056 | upgradeCodeRow[1] = instanceRow[4]; | ||
| 1057 | |||
| 1058 | // Change the Upgrade table | ||
| 1059 | Table targetUpgradeTable = output.Tables["Upgrade"]; | ||
| 1060 | if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) | ||
| 1061 | { | ||
| 1062 | string upgradeId = (string)instanceRow[4]; | ||
| 1063 | Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); | ||
| 1064 | foreach (Row row in targetUpgradeTable.Rows) | ||
| 1065 | { | ||
| 1066 | // In case they are upgrading other codes to this new product, leave the ones that don't match the | ||
| 1067 | // Product.UpgradeCode intact. | ||
| 1068 | if (targetUpgradeCode == (string)row[0]) | ||
| 1069 | { | ||
| 1070 | Row upgradeRow = upgradeTable.CreateRow(null); | ||
| 1071 | upgradeRow.Operation = RowOperation.Add; | ||
| 1072 | upgradeRow.Fields[0].Modified = true; | ||
| 1073 | // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. | ||
| 1074 | // upgradeRow.Fields[0].PreviousData = (string)row[0]; | ||
| 1075 | |||
| 1076 | // Inserting a new Upgrade record with the updated UpgradeCode | ||
| 1077 | upgradeRow[0] = upgradeId; | ||
| 1078 | upgradeRow[1] = row[1]; | ||
| 1079 | upgradeRow[2] = row[2]; | ||
| 1080 | upgradeRow[3] = row[3]; | ||
| 1081 | upgradeRow[4] = row[4]; | ||
| 1082 | upgradeRow[5] = row[5]; | ||
| 1083 | upgradeRow[6] = row[6]; | ||
| 1084 | |||
| 1085 | // Delete the old row | ||
| 1086 | Row upgradeRemoveRow = upgradeTable.CreateRow(null); | ||
| 1087 | upgradeRemoveRow.Operation = RowOperation.Delete; | ||
| 1088 | upgradeRemoveRow[0] = row[0]; | ||
| 1089 | upgradeRemoveRow[1] = row[1]; | ||
| 1090 | upgradeRemoveRow[2] = row[2]; | ||
| 1091 | upgradeRemoveRow[3] = row[3]; | ||
| 1092 | upgradeRemoveRow[4] = row[4]; | ||
| 1093 | upgradeRemoveRow[5] = row[5]; | ||
| 1094 | upgradeRemoveRow[6] = row[6]; | ||
| 1095 | } | ||
| 1096 | } | ||
| 1097 | } | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | // If there are instance Components generate new GUIDs for them. | ||
| 1101 | if (0 < instanceComponentGuids.Count) | ||
| 1102 | { | ||
| 1103 | Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); | ||
| 1104 | foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values) | ||
| 1105 | { | ||
| 1106 | string guid = targetComponentRow.Guid; | ||
| 1107 | if (!String.IsNullOrEmpty(guid)) | ||
| 1108 | { | ||
| 1109 | Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); | ||
| 1110 | instanceComponentRow.Operation = RowOperation.Modify; | ||
| 1111 | instanceComponentRow.Fields[1].Modified = true; | ||
| 1112 | instanceComponentRow[0] = targetComponentRow[0]; | ||
| 1113 | instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture); | ||
| 1114 | instanceComponentRow[2] = targetComponentRow[2]; | ||
| 1115 | instanceComponentRow[3] = targetComponentRow[3]; | ||
| 1116 | instanceComponentRow[4] = targetComponentRow[4]; | ||
| 1117 | instanceComponentRow[5] = targetComponentRow[5]; | ||
| 1118 | } | ||
| 1119 | } | ||
| 1120 | } | ||
| 1121 | |||
| 1122 | // Update the summary information | ||
| 1123 | Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count); | ||
| 1124 | foreach (Row row in instanceSummaryInformationTable.Rows) | ||
| 1125 | { | ||
| 1126 | summaryRows[row[0]] = row; | ||
| 1127 | |||
| 1128 | if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
| 1129 | { | ||
| 1130 | row[1] = targetPlatformAndLanguage; | ||
| 1131 | } | ||
| 1132 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
| 1133 | { | ||
| 1134 | row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); | ||
| 1135 | } | ||
| 1136 | else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) | ||
| 1137 | { | ||
| 1138 | row[1] = 0; | ||
| 1139 | } | ||
| 1140 | else if ((int)SummaryInformation.Transform.Security == (int)row[0]) | ||
| 1141 | { | ||
| 1142 | row[1] = "4"; | ||
| 1143 | } | ||
| 1144 | } | ||
| 1145 | |||
| 1146 | if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) | ||
| 1147 | { | ||
| 1148 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
| 1149 | summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; | ||
| 1150 | summaryRow[1] = targetPlatformAndLanguage; | ||
| 1151 | } | ||
| 1152 | else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) | ||
| 1153 | { | ||
| 1154 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
| 1155 | summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
| 1156 | summaryRow[1] = "0"; | ||
| 1157 | } | ||
| 1158 | else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) | ||
| 1159 | { | ||
| 1160 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
| 1161 | summaryRow[0] = (int)SummaryInformation.Transform.Security; | ||
| 1162 | summaryRow[1] = "4"; | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); | ||
| 1166 | } | ||
| 1167 | } | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | /// <summary> | ||
| 1171 | /// Validate that there are no duplicate GUIDs in the output. | ||
| 1172 | /// </summary> | ||
| 1173 | /// <remarks> | ||
| 1174 | /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a | ||
| 1175 | /// warning, as the conditions might be mutually exclusive. | ||
| 1176 | /// </remarks> | ||
| 1177 | private void ValidateComponentGuids(Output output) | ||
| 1178 | { | ||
| 1179 | Table componentTable = output.Tables["Component"]; | ||
| 1180 | if (null != componentTable) | ||
| 1181 | { | ||
| 1182 | Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count); | ||
| 1183 | |||
| 1184 | foreach (ComponentRow row in componentTable.Rows) | ||
| 1185 | { | ||
| 1186 | // we don't care about unmanaged components and if there's a * GUID remaining, | ||
| 1187 | // there's already an error that prevented it from being replaced with a real GUID. | ||
| 1188 | if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid) | ||
| 1189 | { | ||
| 1190 | bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition); | ||
| 1191 | bool allComponentsHaveConditions = thisComponentHasCondition; | ||
| 1192 | |||
| 1193 | if (componentGuidConditions.ContainsKey(row.Guid)) | ||
| 1194 | { | ||
| 1195 | allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition; | ||
| 1196 | |||
| 1197 | if (allComponentsHaveConditions) | ||
| 1198 | { | ||
| 1199 | Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid)); | ||
| 1200 | } | ||
| 1201 | else | ||
| 1202 | { | ||
| 1203 | Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid)); | ||
| 1204 | } | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | componentGuidConditions[row.Guid] = allComponentsHaveConditions; | ||
| 1208 | } | ||
| 1209 | } | ||
| 1210 | } | ||
| 1211 | } | ||
| 1212 | |||
| 1213 | /// <summary> | ||
| 1214 | /// Update Control and BBControl text by reading from files when necessary. | ||
| 1215 | /// </summary> | ||
| 1216 | /// <param name="output">Internal representation of the msi database to operate upon.</param> | ||
| 1217 | private void UpdateControlText(Output output) | ||
| 1218 | { | ||
| 1219 | UpdateControlTextCommand command = new UpdateControlTextCommand(); | ||
| 1220 | command.BBControlTable = output.Tables["BBControl"]; | ||
| 1221 | command.WixBBControlTable = output.Tables["WixBBControl"]; | ||
| 1222 | command.ControlTable = output.Tables["Control"]; | ||
| 1223 | command.WixControlTable = output.Tables["WixControl"]; | ||
| 1224 | command.Execute(); | ||
| 1225 | } | ||
| 1226 | |||
| 1227 | private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory) | ||
| 1228 | { | ||
| 1229 | string layout = null; | ||
| 1230 | |||
| 1231 | foreach (var extension in this.BackendExtensions) | ||
| 1232 | { | ||
| 1233 | layout = extension.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory); | ||
| 1234 | if (!String.IsNullOrEmpty(layout)) | ||
| 1235 | { | ||
| 1236 | break; | ||
| 1237 | } | ||
| 1238 | } | ||
| 1239 | |||
| 1240 | // If no binder file manager resolved the layout, do the default behavior. | ||
| 1241 | if (String.IsNullOrEmpty(layout)) | ||
| 1242 | { | ||
| 1243 | if (String.IsNullOrEmpty(mediaLayoutDirectory)) | ||
| 1244 | { | ||
| 1245 | layout = layoutDirectory; | ||
| 1246 | } | ||
| 1247 | else if (Path.IsPathRooted(mediaLayoutDirectory)) | ||
| 1248 | { | ||
| 1249 | layout = mediaLayoutDirectory; | ||
| 1250 | } | ||
| 1251 | else | ||
| 1252 | { | ||
| 1253 | layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); | ||
| 1254 | } | ||
| 1255 | } | ||
| 1256 | |||
| 1257 | return layout; | ||
| 1258 | } | ||
| 1259 | |||
| 1260 | /// <summary> | ||
| 1261 | /// Creates the MSI/MSM/PCP database. | ||
| 1262 | /// </summary> | ||
| 1263 | /// <param name="output">Output to create database for.</param> | ||
| 1264 | /// <param name="databaseFile">The database file to create.</param> | ||
| 1265 | /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param> | ||
| 1266 | /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param> | ||
| 1267 | private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) | ||
| 1268 | { | ||
| 1269 | var command = new GenerateDatabaseCommand(); | ||
| 1270 | command.Extensions = this.Extensions; | ||
| 1271 | command.Output = output; | ||
| 1272 | command.OutputPath = databaseFile; | ||
| 1273 | command.KeepAddedColumns = keepAddedColumns; | ||
| 1274 | command.UseSubDirectory = useSubdirectory; | ||
| 1275 | command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; | ||
| 1276 | command.TableDefinitions = this.TableDefinitions; | ||
| 1277 | command.TempFilesLocation = this.IntermediateFolder; | ||
| 1278 | command.Codepage = this.Codepage; | ||
| 1279 | command.Execute(); | ||
| 1280 | } | ||
| 1281 | } | ||
| 1282 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs new file mode 100644 index 00000000..5471792d --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs | |||
| @@ -0,0 +1,135 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Globalization; | ||
| 7 | using WixToolset.Data; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Binds the summary information table of a database. | ||
| 11 | /// </summary> | ||
| 12 | internal class BindSummaryInfoCommand | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// The output to bind. | ||
| 16 | /// </summary> | ||
| 17 | public Output Output { private get; set; } | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Returns a flag indicating if files are compressed by default. | ||
| 21 | /// </summary> | ||
| 22 | public bool Compressed { get; private set; } | ||
| 23 | |||
| 24 | /// <summary> | ||
| 25 | /// Returns a flag indicating if uncompressed files use long filenames. | ||
| 26 | /// </summary> | ||
| 27 | public bool LongNames { get; private set; } | ||
| 28 | |||
| 29 | public int InstallerVersion { get; private set; } | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Modularization guid, or null if the output is not a module. | ||
| 33 | /// </summary> | ||
| 34 | public string ModularizationGuid { get; private set; } | ||
| 35 | |||
| 36 | public void Execute() | ||
| 37 | { | ||
| 38 | this.Compressed = false; | ||
| 39 | this.LongNames = false; | ||
| 40 | this.InstallerVersion = 0; | ||
| 41 | this.ModularizationGuid = null; | ||
| 42 | |||
| 43 | Table summaryInformationTable = this.Output.Tables["_SummaryInformation"]; | ||
| 44 | |||
| 45 | if (null != summaryInformationTable) | ||
| 46 | { | ||
| 47 | bool foundCreateDataTime = false; | ||
| 48 | bool foundLastSaveDataTime = false; | ||
| 49 | bool foundCreatingApplication = false; | ||
| 50 | string now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); | ||
| 51 | |||
| 52 | foreach (Row summaryInformationRow in summaryInformationTable.Rows) | ||
| 53 | { | ||
| 54 | switch (summaryInformationRow.FieldAsInteger(0)) | ||
| 55 | { | ||
| 56 | case 1: // PID_CODEPAGE | ||
| 57 | // make sure the code page is an int and not a web name or null | ||
| 58 | string codepage = summaryInformationRow.FieldAsString(1); | ||
| 59 | |||
| 60 | if (null == codepage) | ||
| 61 | { | ||
| 62 | codepage = "0"; | ||
| 63 | } | ||
| 64 | else | ||
| 65 | { | ||
| 66 | summaryInformationRow[1] = Common.GetValidCodePage(codepage, false, false, summaryInformationRow.SourceLineNumbers).ToString(CultureInfo.InvariantCulture); | ||
| 67 | } | ||
| 68 | break; | ||
| 69 | case 9: // PID_REVNUMBER | ||
| 70 | string packageCode = (string)summaryInformationRow[1]; | ||
| 71 | |||
| 72 | if (OutputType.Module == this.Output.Type) | ||
| 73 | { | ||
| 74 | this.ModularizationGuid = packageCode.Substring(1, 36).Replace('-', '_'); | ||
| 75 | } | ||
| 76 | else if ("*" == packageCode) | ||
| 77 | { | ||
| 78 | // set the revision number (package/patch code) if it should be automatically generated | ||
| 79 | summaryInformationRow[1] = Common.GenerateGuid(); | ||
| 80 | } | ||
| 81 | break; | ||
| 82 | case 12: // PID_CREATE_DTM | ||
| 83 | foundCreateDataTime = true; | ||
| 84 | break; | ||
| 85 | case 13: // PID_LASTSAVE_DTM | ||
| 86 | foundLastSaveDataTime = true; | ||
| 87 | break; | ||
| 88 | case 14: | ||
| 89 | this.InstallerVersion = summaryInformationRow.FieldAsInteger(1); | ||
| 90 | break; | ||
| 91 | case 15: // PID_WORDCOUNT | ||
| 92 | if (OutputType.Patch == this.Output.Type) | ||
| 93 | { | ||
| 94 | this.LongNames = true; | ||
| 95 | this.Compressed = true; | ||
| 96 | } | ||
| 97 | else | ||
| 98 | { | ||
| 99 | this.LongNames = (0 == (summaryInformationRow.FieldAsInteger(1) & 1)); | ||
| 100 | this.Compressed = (2 == (summaryInformationRow.FieldAsInteger(1) & 2)); | ||
| 101 | } | ||
| 102 | break; | ||
| 103 | case 18: // PID_APPNAME | ||
| 104 | foundCreatingApplication = true; | ||
| 105 | break; | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | // add a summary information row for the create time/date property if its not already set | ||
| 110 | if (!foundCreateDataTime) | ||
| 111 | { | ||
| 112 | Row createTimeDateRow = summaryInformationTable.CreateRow(null); | ||
| 113 | createTimeDateRow[0] = 12; | ||
| 114 | createTimeDateRow[1] = now; | ||
| 115 | } | ||
| 116 | |||
| 117 | // add a summary information row for the last save time/date property if its not already set | ||
| 118 | if (!foundLastSaveDataTime) | ||
| 119 | { | ||
| 120 | Row lastSaveTimeDateRow = summaryInformationTable.CreateRow(null); | ||
| 121 | lastSaveTimeDateRow[0] = 13; | ||
| 122 | lastSaveTimeDateRow[1] = now; | ||
| 123 | } | ||
| 124 | |||
| 125 | // add a summary information row for the creating application property if its not already set | ||
| 126 | if (!foundCreatingApplication) | ||
| 127 | { | ||
| 128 | Row creatingApplicationRow = summaryInformationTable.CreateRow(null); | ||
| 129 | creatingApplicationRow[0] = 18; | ||
| 130 | creatingApplicationRow[1] = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString()); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs new file mode 100644 index 00000000..425d1f9c --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs | |||
| @@ -0,0 +1,470 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Msi; | ||
| 12 | using WixToolset.Core.Native; | ||
| 13 | |||
| 14 | internal class BindTransformCommand | ||
| 15 | { | ||
| 16 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
| 17 | |||
| 18 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 19 | |||
| 20 | public string TempFilesLocation { private get; set; } | ||
| 21 | |||
| 22 | public Output Transform { private get; set; } | ||
| 23 | |||
| 24 | public string OutputPath { private get; set; } | ||
| 25 | |||
| 26 | public void Execute() | ||
| 27 | { | ||
| 28 | int transformFlags = 0; | ||
| 29 | |||
| 30 | Output targetOutput = new Output(null); | ||
| 31 | Output updatedOutput = new Output(null); | ||
| 32 | |||
| 33 | // TODO: handle added columns | ||
| 34 | |||
| 35 | // to generate a localized transform, both the target and updated | ||
| 36 | // databases need to have the same code page. the only reason to | ||
| 37 | // set different code pages is to support localized primary key | ||
| 38 | // columns, but that would only support deleting rows. if this | ||
| 39 | // becomes necessary, define a PreviousCodepage property on the | ||
| 40 | // Output class and persist this throughout transform generation. | ||
| 41 | targetOutput.Codepage = this.Transform.Codepage; | ||
| 42 | updatedOutput.Codepage = this.Transform.Codepage; | ||
| 43 | |||
| 44 | // remove certain Property rows which will be populated from summary information values | ||
| 45 | string targetUpgradeCode = null; | ||
| 46 | string updatedUpgradeCode = null; | ||
| 47 | |||
| 48 | Table propertyTable = this.Transform.Tables["Property"]; | ||
| 49 | if (null != propertyTable) | ||
| 50 | { | ||
| 51 | for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) | ||
| 52 | { | ||
| 53 | Row row = propertyTable.Rows[i]; | ||
| 54 | |||
| 55 | if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) | ||
| 56 | { | ||
| 57 | propertyTable.Rows.RemoveAt(i); | ||
| 58 | |||
| 59 | if ("UpgradeCode" == (string)row[0]) | ||
| 60 | { | ||
| 61 | updatedUpgradeCode = (string)row[1]; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 68 | Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 69 | Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
| 70 | Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
| 71 | |||
| 72 | // process special summary information values | ||
| 73 | foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows) | ||
| 74 | { | ||
| 75 | if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) | ||
| 76 | { | ||
| 77 | // convert from a web name if provided | ||
| 78 | string codePage = (string)row.Fields[1].Data; | ||
| 79 | if (null == codePage) | ||
| 80 | { | ||
| 81 | codePage = "0"; | ||
| 82 | } | ||
| 83 | else | ||
| 84 | { | ||
| 85 | codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); | ||
| 86 | } | ||
| 87 | |||
| 88 | string previousCodePage = (string)row.Fields[1].PreviousData; | ||
| 89 | if (null == previousCodePage) | ||
| 90 | { | ||
| 91 | previousCodePage = "0"; | ||
| 92 | } | ||
| 93 | else | ||
| 94 | { | ||
| 95 | previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); | ||
| 96 | } | ||
| 97 | |||
| 98 | Row targetCodePageRow = targetSummaryInfo.CreateRow(null); | ||
| 99 | targetCodePageRow[0] = 1; // PID_CODEPAGE | ||
| 100 | targetCodePageRow[1] = previousCodePage; | ||
| 101 | |||
| 102 | Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null); | ||
| 103 | updatedCodePageRow[0] = 1; // PID_CODEPAGE | ||
| 104 | updatedCodePageRow[1] = codePage; | ||
| 105 | } | ||
| 106 | else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] || | ||
| 107 | (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
| 108 | { | ||
| 109 | // the target language | ||
| 110 | string[] propertyData = ((string)row[1]).Split(';'); | ||
| 111 | string lang = 2 == propertyData.Length ? propertyData[1] : "0"; | ||
| 112 | |||
| 113 | Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo; | ||
| 114 | Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable; | ||
| 115 | |||
| 116 | Row productLanguageRow = tempPropertyTable.CreateRow(null); | ||
| 117 | productLanguageRow[0] = "ProductLanguage"; | ||
| 118 | productLanguageRow[1] = lang; | ||
| 119 | |||
| 120 | // set the platform;language on the MSI to be generated | ||
| 121 | Row templateRow = tempSummaryInfo.CreateRow(null); | ||
| 122 | templateRow[0] = 7; // PID_TEMPLATE | ||
| 123 | templateRow[1] = (string)row[1]; | ||
| 124 | } | ||
| 125 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
| 126 | { | ||
| 127 | string[] propertyData = ((string)row[1]).Split(';'); | ||
| 128 | |||
| 129 | Row targetProductCodeRow = targetPropertyTable.CreateRow(null); | ||
| 130 | targetProductCodeRow[0] = "ProductCode"; | ||
| 131 | targetProductCodeRow[1] = propertyData[0].Substring(0, 38); | ||
| 132 | |||
| 133 | Row targetProductVersionRow = targetPropertyTable.CreateRow(null); | ||
| 134 | targetProductVersionRow[0] = "ProductVersion"; | ||
| 135 | targetProductVersionRow[1] = propertyData[0].Substring(38); | ||
| 136 | |||
| 137 | Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null); | ||
| 138 | updatedProductCodeRow[0] = "ProductCode"; | ||
| 139 | updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); | ||
| 140 | |||
| 141 | Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null); | ||
| 142 | updatedProductVersionRow[0] = "ProductVersion"; | ||
| 143 | updatedProductVersionRow[1] = propertyData[1].Substring(38); | ||
| 144 | |||
| 145 | // UpgradeCode is optional and may not exists in the target | ||
| 146 | // or upgraded databases, so do not include a null-valued | ||
| 147 | // UpgradeCode property. | ||
| 148 | |||
| 149 | targetUpgradeCode = propertyData[2]; | ||
| 150 | if (!String.IsNullOrEmpty(targetUpgradeCode)) | ||
| 151 | { | ||
| 152 | Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); | ||
| 153 | targetUpgradeCodeRow[0] = "UpgradeCode"; | ||
| 154 | targetUpgradeCodeRow[1] = targetUpgradeCode; | ||
| 155 | |||
| 156 | // If the target UpgradeCode is specified, an updated | ||
| 157 | // UpgradeCode is required. | ||
| 158 | if (String.IsNullOrEmpty(updatedUpgradeCode)) | ||
| 159 | { | ||
| 160 | updatedUpgradeCode = targetUpgradeCode; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | if (!String.IsNullOrEmpty(updatedUpgradeCode)) | ||
| 165 | { | ||
| 166 | Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); | ||
| 167 | updatedUpgradeCodeRow[0] = "UpgradeCode"; | ||
| 168 | updatedUpgradeCodeRow[1] = updatedUpgradeCode; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) | ||
| 172 | { | ||
| 173 | transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); | ||
| 174 | } | ||
| 175 | else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0]) | ||
| 176 | { | ||
| 177 | // PID_LASTPRINTED should be null for transforms | ||
| 178 | row.Operation = RowOperation.None; | ||
| 179 | } | ||
| 180 | else | ||
| 181 | { | ||
| 182 | // add everything else as is | ||
| 183 | Row targetRow = targetSummaryInfo.CreateRow(null); | ||
| 184 | targetRow[0] = row[0]; | ||
| 185 | targetRow[1] = row[1]; | ||
| 186 | |||
| 187 | Row updatedRow = updatedSummaryInfo.CreateRow(null); | ||
| 188 | updatedRow[0] = row[0]; | ||
| 189 | updatedRow[1] = row[1]; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | // Validate that both databases have an UpgradeCode if the | ||
| 194 | // authoring transform will validate the UpgradeCode; otherwise, | ||
| 195 | // MsiCreateTransformSummaryinfo() will fail with 1620. | ||
| 196 | if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && | ||
| 197 | (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) | ||
| 198 | { | ||
| 199 | Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired()); | ||
| 200 | } | ||
| 201 | |||
| 202 | string emptyFile = null; | ||
| 203 | |||
| 204 | foreach (Table table in this.Transform.Tables) | ||
| 205 | { | ||
| 206 | // Ignore unreal tables when building transforms except the _Stream table. | ||
| 207 | // These tables are ignored when generating the database so there is no reason | ||
| 208 | // to process them here. | ||
| 209 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
| 210 | { | ||
| 211 | continue; | ||
| 212 | } | ||
| 213 | |||
| 214 | // process table operations | ||
| 215 | switch (table.Operation) | ||
| 216 | { | ||
| 217 | case TableOperation.Add: | ||
| 218 | updatedOutput.EnsureTable(table.Definition); | ||
| 219 | break; | ||
| 220 | case TableOperation.Drop: | ||
| 221 | targetOutput.EnsureTable(table.Definition); | ||
| 222 | continue; | ||
| 223 | default: | ||
| 224 | targetOutput.EnsureTable(table.Definition); | ||
| 225 | updatedOutput.EnsureTable(table.Definition); | ||
| 226 | break; | ||
| 227 | } | ||
| 228 | |||
| 229 | // process row operations | ||
| 230 | foreach (Row row in table.Rows) | ||
| 231 | { | ||
| 232 | switch (row.Operation) | ||
| 233 | { | ||
| 234 | case RowOperation.Add: | ||
| 235 | Table updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
| 236 | updatedTable.Rows.Add(row); | ||
| 237 | continue; | ||
| 238 | case RowOperation.Delete: | ||
| 239 | Table targetTable = targetOutput.EnsureTable(table.Definition); | ||
| 240 | targetTable.Rows.Add(row); | ||
| 241 | |||
| 242 | // fill-in non-primary key values | ||
| 243 | foreach (Field field in row.Fields) | ||
| 244 | { | ||
| 245 | if (!field.Column.PrimaryKey) | ||
| 246 | { | ||
| 247 | if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) | ||
| 248 | { | ||
| 249 | field.Data = field.Column.MinValue; | ||
| 250 | } | ||
| 251 | else if (ColumnType.Object == field.Column.Type) | ||
| 252 | { | ||
| 253 | if (null == emptyFile) | ||
| 254 | { | ||
| 255 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
| 256 | } | ||
| 257 | |||
| 258 | field.Data = emptyFile; | ||
| 259 | } | ||
| 260 | else | ||
| 261 | { | ||
| 262 | field.Data = "0"; | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | continue; | ||
| 267 | } | ||
| 268 | |||
| 269 | // Assure that the file table's sequence is populated | ||
| 270 | if ("File" == table.Name) | ||
| 271 | { | ||
| 272 | foreach (Row fileRow in table.Rows) | ||
| 273 | { | ||
| 274 | if (null == fileRow[7]) | ||
| 275 | { | ||
| 276 | if (RowOperation.Add == fileRow.Operation) | ||
| 277 | { | ||
| 278 | Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); | ||
| 279 | break; | ||
| 280 | } | ||
| 281 | |||
| 282 | // Set to 1 to prevent invalid IDT file from being generated | ||
| 283 | fileRow[7] = 1; | ||
| 284 | } | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | // process modified and unmodified rows | ||
| 289 | bool modifiedRow = false; | ||
| 290 | Row targetRow = new Row(null, table.Definition); | ||
| 291 | Row updatedRow = row; | ||
| 292 | for (int i = 0; i < row.Fields.Length; i++) | ||
| 293 | { | ||
| 294 | Field updatedField = row.Fields[i]; | ||
| 295 | |||
| 296 | if (updatedField.Modified) | ||
| 297 | { | ||
| 298 | // set a different value in the target row to ensure this value will be modified during transform generation | ||
| 299 | if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) | ||
| 300 | { | ||
| 301 | if (null == updatedField.Data || 1 != (int)updatedField.Data) | ||
| 302 | { | ||
| 303 | targetRow[i] = 1; | ||
| 304 | } | ||
| 305 | else | ||
| 306 | { | ||
| 307 | targetRow[i] = 2; | ||
| 308 | } | ||
| 309 | } | ||
| 310 | else if (ColumnType.Object == updatedField.Column.Type) | ||
| 311 | { | ||
| 312 | if (null == emptyFile) | ||
| 313 | { | ||
| 314 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
| 315 | } | ||
| 316 | |||
| 317 | targetRow[i] = emptyFile; | ||
| 318 | } | ||
| 319 | else | ||
| 320 | { | ||
| 321 | if ("0" != (string)updatedField.Data) | ||
| 322 | { | ||
| 323 | targetRow[i] = "0"; | ||
| 324 | } | ||
| 325 | else | ||
| 326 | { | ||
| 327 | targetRow[i] = "1"; | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | modifiedRow = true; | ||
| 332 | } | ||
| 333 | else if (ColumnType.Object == updatedField.Column.Type) | ||
| 334 | { | ||
| 335 | ObjectField objectField = (ObjectField)updatedField; | ||
| 336 | |||
| 337 | // create an empty file for comparing against | ||
| 338 | if (null == objectField.PreviousData) | ||
| 339 | { | ||
| 340 | if (null == emptyFile) | ||
| 341 | { | ||
| 342 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
| 343 | } | ||
| 344 | |||
| 345 | targetRow[i] = emptyFile; | ||
| 346 | modifiedRow = true; | ||
| 347 | } | ||
| 348 | else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data)) | ||
| 349 | { | ||
| 350 | targetRow[i] = objectField.PreviousData; | ||
| 351 | modifiedRow = true; | ||
| 352 | } | ||
| 353 | } | ||
| 354 | else // unmodified | ||
| 355 | { | ||
| 356 | if (null != updatedField.Data) | ||
| 357 | { | ||
| 358 | targetRow[i] = updatedField.Data; | ||
| 359 | } | ||
| 360 | } | ||
| 361 | } | ||
| 362 | |||
| 363 | // modified rows and certain special rows go in the target and updated msi databases | ||
| 364 | if (modifiedRow || | ||
| 365 | ("Property" == table.Name && | ||
| 366 | ("ProductCode" == (string)row[0] || | ||
| 367 | "ProductLanguage" == (string)row[0] || | ||
| 368 | "ProductVersion" == (string)row[0] || | ||
| 369 | "UpgradeCode" == (string)row[0]))) | ||
| 370 | { | ||
| 371 | Table targetTable = targetOutput.EnsureTable(table.Definition); | ||
| 372 | targetTable.Rows.Add(targetRow); | ||
| 373 | |||
| 374 | Table updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
| 375 | updatedTable.Rows.Add(updatedRow); | ||
| 376 | } | ||
| 377 | } | ||
| 378 | } | ||
| 379 | |||
| 380 | //foreach (BinderExtension extension in this.Extensions) | ||
| 381 | //{ | ||
| 382 | // extension.PostBind(this.Context); | ||
| 383 | //} | ||
| 384 | |||
| 385 | // Any errors encountered up to this point can cause errors during generation. | ||
| 386 | if (Messaging.Instance.EncounteredError) | ||
| 387 | { | ||
| 388 | return; | ||
| 389 | } | ||
| 390 | |||
| 391 | string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
| 392 | string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi")); | ||
| 393 | string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi")); | ||
| 394 | |||
| 395 | try | ||
| 396 | { | ||
| 397 | if (!String.IsNullOrEmpty(emptyFile)) | ||
| 398 | { | ||
| 399 | using (FileStream fileStream = File.Create(emptyFile)) | ||
| 400 | { | ||
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | this.GenerateDatabase(targetOutput, targetDatabaseFile, false); | ||
| 405 | this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true); | ||
| 406 | |||
| 407 | // make sure the directory exists | ||
| 408 | Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); | ||
| 409 | |||
| 410 | // create the transform file | ||
| 411 | using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) | ||
| 412 | { | ||
| 413 | using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) | ||
| 414 | { | ||
| 415 | if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) | ||
| 416 | { | ||
| 417 | updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); | ||
| 418 | } | ||
| 419 | else | ||
| 420 | { | ||
| 421 | Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); | ||
| 422 | } | ||
| 423 | } | ||
| 424 | } | ||
| 425 | } | ||
| 426 | finally | ||
| 427 | { | ||
| 428 | if (!String.IsNullOrEmpty(emptyFile)) | ||
| 429 | { | ||
| 430 | File.Delete(emptyFile); | ||
| 431 | } | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | private bool CompareFiles(string targetFile, string updatedFile) | ||
| 436 | { | ||
| 437 | bool? compared = null; | ||
| 438 | foreach (var extension in this.Extensions) | ||
| 439 | { | ||
| 440 | compared = extension.CompareFiles(targetFile, updatedFile); | ||
| 441 | if (compared.HasValue) | ||
| 442 | { | ||
| 443 | break; | ||
| 444 | } | ||
| 445 | } | ||
| 446 | |||
| 447 | if (!compared.HasValue) | ||
| 448 | { | ||
| 449 | throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. | ||
| 450 | } | ||
| 451 | |||
| 452 | return compared.Value; | ||
| 453 | } | ||
| 454 | |||
| 455 | private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns) | ||
| 456 | { | ||
| 457 | var command = new GenerateDatabaseCommand(); | ||
| 458 | command.Codepage = output.Codepage; | ||
| 459 | command.Extensions = this.Extensions; | ||
| 460 | command.KeepAddedColumns = keepAddedColumns; | ||
| 461 | command.Output = output; | ||
| 462 | command.OutputPath = outputPath; | ||
| 463 | command.TableDefinitions = this.TableDefinitions; | ||
| 464 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 465 | command.SuppressAddingValidationRows = true; | ||
| 466 | command.UseSubDirectory = true; | ||
| 467 | command.Execute(); | ||
| 468 | } | ||
| 469 | } | ||
| 470 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs new file mode 100644 index 00000000..b2cc76fc --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs | |||
| @@ -0,0 +1,177 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Threading; | ||
| 10 | using WixToolset.Core.Bind; | ||
| 11 | using WixToolset.Core.Cab; | ||
| 12 | using WixToolset.Data; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple | ||
| 16 | /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished. | ||
| 17 | /// </summary> | ||
| 18 | internal sealed class CabinetBuilder | ||
| 19 | { | ||
| 20 | private Queue cabinetWorkItems; | ||
| 21 | private object lockObject; | ||
| 22 | private int threadCount; | ||
| 23 | |||
| 24 | // Address of Binder's callback function for Cabinet Splitting | ||
| 25 | private IntPtr newCabNamesCallBackAddress; | ||
| 26 | |||
| 27 | public int MaximumCabinetSizeForLargeFileSplitting { get; set; } | ||
| 28 | |||
| 29 | public int MaximumUncompressedMediaSize { get; set; } | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Instantiate a new CabinetBuilder. | ||
| 33 | /// </summary> | ||
| 34 | /// <param name="threadCount">number of threads to use</param> | ||
| 35 | /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param> | ||
| 36 | public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress) | ||
| 37 | { | ||
| 38 | if (0 >= threadCount) | ||
| 39 | { | ||
| 40 | throw new ArgumentOutOfRangeException("threadCount"); | ||
| 41 | } | ||
| 42 | |||
| 43 | this.cabinetWorkItems = new Queue(); | ||
| 44 | this.lockObject = new object(); | ||
| 45 | |||
| 46 | this.threadCount = threadCount; | ||
| 47 | |||
| 48 | // Set Address of Binder's callback function for Cabinet Splitting | ||
| 49 | this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; | ||
| 50 | } | ||
| 51 | |||
| 52 | /// <summary> | ||
| 53 | /// Enqueues a CabinetWorkItem to the queue. | ||
| 54 | /// </summary> | ||
| 55 | /// <param name="cabinetWorkItem">cabinet work item</param> | ||
| 56 | public void Enqueue(CabinetWorkItem cabinetWorkItem) | ||
| 57 | { | ||
| 58 | this.cabinetWorkItems.Enqueue(cabinetWorkItem); | ||
| 59 | } | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// Create the queued cabinets. | ||
| 63 | /// </summary> | ||
| 64 | /// <returns>error message number (zero if no error)</returns> | ||
| 65 | public void CreateQueuedCabinets() | ||
| 66 | { | ||
| 67 | // don't create more threads than the number of cabinets to build | ||
| 68 | if (this.cabinetWorkItems.Count < this.threadCount) | ||
| 69 | { | ||
| 70 | this.threadCount = this.cabinetWorkItems.Count; | ||
| 71 | } | ||
| 72 | |||
| 73 | if (0 < this.threadCount) | ||
| 74 | { | ||
| 75 | Thread[] threads = new Thread[this.threadCount]; | ||
| 76 | |||
| 77 | for (int i = 0; i < threads.Length; i++) | ||
| 78 | { | ||
| 79 | threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); | ||
| 80 | threads[i].Start(); | ||
| 81 | } | ||
| 82 | |||
| 83 | // wait for all threads to finish | ||
| 84 | foreach (Thread thread in threads) | ||
| 85 | { | ||
| 86 | thread.Join(); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | /// <summary> | ||
| 92 | /// This function gets called by multiple threads to do actual work. | ||
| 93 | /// It takes one work item at a time and calls this.CreateCabinet(). | ||
| 94 | /// It does not return until cabinetWorkItems queue is empty | ||
| 95 | /// </summary> | ||
| 96 | private void ProcessWorkItems() | ||
| 97 | { | ||
| 98 | try | ||
| 99 | { | ||
| 100 | while (true) | ||
| 101 | { | ||
| 102 | CabinetWorkItem cabinetWorkItem; | ||
| 103 | |||
| 104 | lock (this.cabinetWorkItems) | ||
| 105 | { | ||
| 106 | // check if there are any more cabinets to create | ||
| 107 | if (0 == this.cabinetWorkItems.Count) | ||
| 108 | { | ||
| 109 | break; | ||
| 110 | } | ||
| 111 | |||
| 112 | cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue(); | ||
| 113 | } | ||
| 114 | |||
| 115 | // create a cabinet | ||
| 116 | this.CreateCabinet(cabinetWorkItem); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | catch (WixException we) | ||
| 120 | { | ||
| 121 | Messaging.Instance.OnMessage(we.Error); | ||
| 122 | } | ||
| 123 | catch (Exception e) | ||
| 124 | { | ||
| 125 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace)); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | /// <summary> | ||
| 130 | /// Creates a cabinet using the wixcab.dll interop layer. | ||
| 131 | /// </summary> | ||
| 132 | /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param> | ||
| 133 | private void CreateCabinet(CabinetWorkItem cabinetWorkItem) | ||
| 134 | { | ||
| 135 | Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile)); | ||
| 136 | |||
| 137 | int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting | ||
| 138 | ulong maxPreCompressedSizeInBytes = 0; | ||
| 139 | |||
| 140 | if (MaximumCabinetSizeForLargeFileSplitting != 0) | ||
| 141 | { | ||
| 142 | // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize | ||
| 143 | // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file | ||
| 144 | if (1 == cabinetWorkItem.FileFacades.Count()) | ||
| 145 | { | ||
| 146 | // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs | ||
| 147 | // Get the Value for Max Uncompressed Media Size | ||
| 148 | maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024; | ||
| 149 | |||
| 150 | foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row | ||
| 151 | { | ||
| 152 | if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes) | ||
| 153 | { | ||
| 154 | // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting | ||
| 155 | maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | // create the cabinet file | ||
| 162 | string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile); | ||
| 163 | string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile); | ||
| 164 | |||
| 165 | using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel)) | ||
| 166 | { | ||
| 167 | foreach (FileFacade facade in cabinetWorkItem.FileFacades) | ||
| 168 | { | ||
| 169 | cab.AddFile(facade); | ||
| 170 | } | ||
| 171 | |||
| 172 | cab.Complete(newCabNamesCallBackAddress); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs new file mode 100644 index 00000000..df1ccecf --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs | |||
| @@ -0,0 +1,122 @@ | |||
| 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.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.Cab; | ||
| 10 | using WixToolset.Core.Bind; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Extensibility; | ||
| 13 | |||
| 14 | public class CabinetResolver | ||
| 15 | { | ||
| 16 | public CabinetResolver(string cabCachePath, IEnumerable<IWindowsInstallerBackendExtension> backendExtensions) | ||
| 17 | { | ||
| 18 | this.CabCachePath = cabCachePath; | ||
| 19 | |||
| 20 | this.BackendExtensions = backendExtensions; | ||
| 21 | } | ||
| 22 | |||
| 23 | private string CabCachePath { get; } | ||
| 24 | |||
| 25 | private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; } | ||
| 26 | |||
| 27 | public ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades) | ||
| 28 | { | ||
| 29 | var filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); | ||
| 30 | |||
| 31 | ResolvedCabinet resolved = null; | ||
| 32 | |||
| 33 | foreach (var extension in this.BackendExtensions) | ||
| 34 | { | ||
| 35 | resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); | ||
| 36 | |||
| 37 | if (null != resolved) | ||
| 38 | { | ||
| 39 | return resolved; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | // By default cabinet should be built and moved to the suggested location. | ||
| 44 | resolved = new ResolvedCabinet() { BuildOption = CabinetBuildOption.BuildAndMove, Path = cabinetPath }; | ||
| 45 | |||
| 46 | // If a cabinet cache path was provided, change the location for the cabinet | ||
| 47 | // to be built to and check if there is a cabinet that can be reused. | ||
| 48 | if (!String.IsNullOrEmpty(this.CabCachePath)) | ||
| 49 | { | ||
| 50 | string cabinetName = Path.GetFileName(cabinetPath); | ||
| 51 | resolved.Path = Path.Combine(this.CabCachePath, cabinetName); | ||
| 52 | |||
| 53 | if (CheckFileExists(resolved.Path)) | ||
| 54 | { | ||
| 55 | // Assume that none of the following are true: | ||
| 56 | // 1. any files are added or removed | ||
| 57 | // 2. order of files changed or names changed | ||
| 58 | // 3. modified time changed | ||
| 59 | bool cabinetValid = true; | ||
| 60 | |||
| 61 | // Need to force garbage collection of WixEnumerateCab to ensure the handle | ||
| 62 | // associated with it is closed before it is reused. | ||
| 63 | using (var wixEnumerateCab = new WixEnumerateCab()) | ||
| 64 | { | ||
| 65 | List<CabinetFileInfo> fileList = wixEnumerateCab.Enumerate(resolved.Path); | ||
| 66 | |||
| 67 | if (filesWithPath.Count() != fileList.Count) | ||
| 68 | { | ||
| 69 | cabinetValid = false; | ||
| 70 | } | ||
| 71 | else | ||
| 72 | { | ||
| 73 | int i = 0; | ||
| 74 | foreach (BindFileWithPath file in filesWithPath) | ||
| 75 | { | ||
| 76 | // First check that the file identifiers match because that is quick and easy. | ||
| 77 | CabinetFileInfo cabFileInfo = fileList[i]; | ||
| 78 | cabinetValid = (cabFileInfo.FileId == file.Id); | ||
| 79 | if (cabinetValid) | ||
| 80 | { | ||
| 81 | // Still valid so ensure the file sizes are the same. | ||
| 82 | FileInfo fileInfo = new FileInfo(file.Path); | ||
| 83 | cabinetValid = (cabFileInfo.Size == fileInfo.Length); | ||
| 84 | if (cabinetValid) | ||
| 85 | { | ||
| 86 | // Still valid so ensure the source time stamp hasn't changed. Thus we need | ||
| 87 | // to convert the source file time stamp into a cabinet compatible data/time. | ||
| 88 | Native.CabInterop.DateTimeToCabDateAndTime(fileInfo.LastWriteTime, out var sourceCabDate, out var sourceCabTime); | ||
| 89 | cabinetValid = (cabFileInfo.Date == sourceCabDate && cabFileInfo.Time == sourceCabTime); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | if (!cabinetValid) | ||
| 94 | { | ||
| 95 | break; | ||
| 96 | } | ||
| 97 | |||
| 98 | i++; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | return resolved; | ||
| 108 | } | ||
| 109 | |||
| 110 | private static bool CheckFileExists(string path) | ||
| 111 | { | ||
| 112 | try | ||
| 113 | { | ||
| 114 | return File.Exists(path); | ||
| 115 | } | ||
| 116 | catch (ArgumentException) | ||
| 117 | { | ||
| 118 | throw new WixException(WixErrors.IllegalCharactersInPath(path)); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs new file mode 100644 index 00000000..dcafcd36 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs | |||
| @@ -0,0 +1,79 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Core.Bind; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Rows; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// A cabinet builder work item. | ||
| 12 | /// </summary> | ||
| 13 | internal sealed class CabinetWorkItem | ||
| 14 | { | ||
| 15 | private string cabinetFile; | ||
| 16 | private CompressionLevel compressionLevel; | ||
| 17 | //private BinderFileManager binderFileManager; | ||
| 18 | private int maxThreshold; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Instantiate a new CabinetWorkItem. | ||
| 22 | /// </summary> | ||
| 23 | /// <param name="fileFacades">The collection of files in this cabinet.</param> | ||
| 24 | /// <param name="cabinetFile">The cabinet file.</param> | ||
| 25 | /// <param name="maxThreshold">Maximum threshold for each cabinet.</param> | ||
| 26 | /// <param name="compressionLevel">The compression level of the cabinet.</param> | ||
| 27 | /// <param name="binderFileManager">The binder file manager.</param> | ||
| 28 | public CabinetWorkItem(IEnumerable<FileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/) | ||
| 29 | { | ||
| 30 | this.cabinetFile = cabinetFile; | ||
| 31 | this.compressionLevel = compressionLevel; | ||
| 32 | this.FileFacades = fileFacades; | ||
| 33 | //this.binderFileManager = binderFileManager; | ||
| 34 | this.maxThreshold = maxThreshold; | ||
| 35 | } | ||
| 36 | |||
| 37 | /// <summary> | ||
| 38 | /// Gets the cabinet file. | ||
| 39 | /// </summary> | ||
| 40 | /// <value>The cabinet file.</value> | ||
| 41 | public string CabinetFile | ||
| 42 | { | ||
| 43 | get { return this.cabinetFile; } | ||
| 44 | } | ||
| 45 | |||
| 46 | /// <summary> | ||
| 47 | /// Gets the compression level of the cabinet. | ||
| 48 | /// </summary> | ||
| 49 | /// <value>The compression level of the cabinet.</value> | ||
| 50 | public CompressionLevel CompressionLevel | ||
| 51 | { | ||
| 52 | get { return this.compressionLevel; } | ||
| 53 | } | ||
| 54 | |||
| 55 | /// <summary> | ||
| 56 | /// Gets the collection of files in this cabinet. | ||
| 57 | /// </summary> | ||
| 58 | /// <value>The collection of files in this cabinet.</value> | ||
| 59 | public IEnumerable<FileFacade> FileFacades { get; private set; } | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// Gets the binder file manager. | ||
| 63 | /// </summary> | ||
| 64 | /// <value>The binder file manager.</value> | ||
| 65 | //public BinderFileManager BinderFileManager | ||
| 66 | //{ | ||
| 67 | // get { return this.binderFileManager; } | ||
| 68 | //} | ||
| 69 | |||
| 70 | /// <summary> | ||
| 71 | /// Gets the max threshold. | ||
| 72 | /// </summary> | ||
| 73 | /// <value>The maximum threshold for a folder in a cabinet.</value> | ||
| 74 | public int MaxThreshold | ||
| 75 | { | ||
| 76 | get { return this.maxThreshold; } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs new file mode 100644 index 00000000..d4d3799f --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs | |||
| @@ -0,0 +1,91 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Globalization; | ||
| 8 | using WixToolset.MergeMod; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Callback object for configurable merge modules. | ||
| 12 | /// </summary> | ||
| 13 | internal sealed class ConfigurationCallback : IMsmConfigureModule | ||
| 14 | { | ||
| 15 | private const int SOk = 0x0; | ||
| 16 | private const int SFalse = 0x1; | ||
| 17 | private Hashtable configurationData; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Creates a ConfigurationCallback object. | ||
| 21 | /// </summary> | ||
| 22 | /// <param name="configData">String to break up into name/value pairs.</param> | ||
| 23 | public ConfigurationCallback(string configData) | ||
| 24 | { | ||
| 25 | if (String.IsNullOrEmpty(configData)) | ||
| 26 | { | ||
| 27 | throw new ArgumentNullException("configData"); | ||
| 28 | } | ||
| 29 | |||
| 30 | string[] pairs = configData.Split(','); | ||
| 31 | this.configurationData = new Hashtable(pairs.Length); | ||
| 32 | for (int i = 0; i < pairs.Length; ++i) | ||
| 33 | { | ||
| 34 | string[] nameVal = pairs[i].Split('='); | ||
| 35 | string name = nameVal[0]; | ||
| 36 | string value = nameVal[1]; | ||
| 37 | |||
| 38 | name = name.Replace("%2C", ","); | ||
| 39 | name = name.Replace("%3D", "="); | ||
| 40 | name = name.Replace("%25", "%"); | ||
| 41 | |||
| 42 | value = value.Replace("%2C", ","); | ||
| 43 | value = value.Replace("%3D", "="); | ||
| 44 | value = value.Replace("%25", "%"); | ||
| 45 | |||
| 46 | this.configurationData[name] = value; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | /// <summary> | ||
| 51 | /// Returns text data based on name. | ||
| 52 | /// </summary> | ||
| 53 | /// <param name="name">Name of value to return.</param> | ||
| 54 | /// <param name="configData">Out param to put configuration data into.</param> | ||
| 55 | /// <returns>S_OK if value provided, S_FALSE if not.</returns> | ||
| 56 | public int ProvideTextData(string name, out string configData) | ||
| 57 | { | ||
| 58 | if (this.configurationData.Contains(name)) | ||
| 59 | { | ||
| 60 | configData = (string)this.configurationData[name]; | ||
| 61 | return SOk; | ||
| 62 | } | ||
| 63 | else | ||
| 64 | { | ||
| 65 | configData = null; | ||
| 66 | return SFalse; | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | /// <summary> | ||
| 71 | /// Returns integer data based on name. | ||
| 72 | /// </summary> | ||
| 73 | /// <param name="name">Name of value to return.</param> | ||
| 74 | /// <param name="configData">Out param to put configuration data into.</param> | ||
| 75 | /// <returns>S_OK if value provided, S_FALSE if not.</returns> | ||
| 76 | public int ProvideIntegerData(string name, out int configData) | ||
| 77 | { | ||
| 78 | if (this.configurationData.Contains(name)) | ||
| 79 | { | ||
| 80 | string val = (string)this.configurationData[name]; | ||
| 81 | configData = Convert.ToInt32(val, CultureInfo.InvariantCulture); | ||
| 82 | return SOk; | ||
| 83 | } | ||
| 84 | else | ||
| 85 | { | ||
| 86 | configData = 0; | ||
| 87 | return SFalse; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs new file mode 100644 index 00000000..6388a352 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs | |||
| @@ -0,0 +1,606 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Rows; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Core.Native; | ||
| 12 | using WixToolset.Core.Bind; | ||
| 13 | |||
| 14 | internal class CopyTransformDataCommand | ||
| 15 | { | ||
| 16 | public bool CopyOutFileRows { private get; set; } | ||
| 17 | |||
| 18 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
| 19 | |||
| 20 | public Output Output { private get; set; } | ||
| 21 | |||
| 22 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 23 | |||
| 24 | public IEnumerable<FileFacade> FileFacades { get; private set; } | ||
| 25 | |||
| 26 | public void Execute() | ||
| 27 | { | ||
| 28 | Debug.Assert(OutputType.Patch != this.Output.Type); | ||
| 29 | |||
| 30 | List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null; | ||
| 31 | |||
| 32 | #if false // TODO: Fix this patching related code to work correctly with FileFacades. | ||
| 33 | bool copyToPatch = (allFileRows != null); | ||
| 34 | bool copyFromPatch = !copyToPatch; | ||
| 35 | |||
| 36 | RowDictionary<MediaRow> patchMediaRows = new RowDictionary<MediaRow>(); | ||
| 37 | |||
| 38 | Dictionary<int, RowDictionary<WixFileRow>> patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>(); | ||
| 39 | |||
| 40 | Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); | ||
| 41 | Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); | ||
| 42 | |||
| 43 | if (copyFromPatch) | ||
| 44 | { | ||
| 45 | // index patch files by diskId+fileId | ||
| 46 | foreach (WixFileRow patchFileRow in patchFileTable.Rows) | ||
| 47 | { | ||
| 48 | int diskId = patchFileRow.DiskId; | ||
| 49 | RowDictionary<WixFileRow> mediaFileRows; | ||
| 50 | if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) | ||
| 51 | { | ||
| 52 | mediaFileRows = new RowDictionary<WixFileRow>(); | ||
| 53 | patchMediaFileRows.Add(diskId, mediaFileRows); | ||
| 54 | } | ||
| 55 | |||
| 56 | mediaFileRows.Add(patchFileRow); | ||
| 57 | } | ||
| 58 | |||
| 59 | Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); | ||
| 60 | patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable); | ||
| 61 | } | ||
| 62 | |||
| 63 | // index paired transforms | ||
| 64 | Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>(); | ||
| 65 | foreach (SubStorage substorage in this.Output.SubStorages) | ||
| 66 | { | ||
| 67 | if (substorage.Name.StartsWith("#")) | ||
| 68 | { | ||
| 69 | pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | try | ||
| 74 | { | ||
| 75 | // copy File bind data into substorages | ||
| 76 | foreach (SubStorage substorage in this.Output.SubStorages) | ||
| 77 | { | ||
| 78 | if (substorage.Name.StartsWith("#")) | ||
| 79 | { | ||
| 80 | // no changes necessary for paired transforms | ||
| 81 | continue; | ||
| 82 | } | ||
| 83 | |||
| 84 | Output mainTransform = substorage.Data; | ||
| 85 | Table mainWixFileTable = mainTransform.Tables["WixFile"]; | ||
| 86 | Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"]; | ||
| 87 | |||
| 88 | this.FileManagerCore.ActiveSubStorage = substorage; | ||
| 89 | |||
| 90 | RowDictionary<WixFileRow> mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable); | ||
| 91 | RowDictionary<Row> mainMsiFileHashIndex = new RowDictionary<Row>(); | ||
| 92 | |||
| 93 | Table mainFileTable = mainTransform.Tables["File"]; | ||
| 94 | Output pairedTransform = (Output)pairedTransforms[substorage.Name]; | ||
| 95 | |||
| 96 | // copy Media.LastSequence and index the MsiFileHash table if it exists. | ||
| 97 | if (copyFromPatch) | ||
| 98 | { | ||
| 99 | Table pairedMediaTable = pairedTransform.Tables["Media"]; | ||
| 100 | foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) | ||
| 101 | { | ||
| 102 | MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); | ||
| 103 | pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; | ||
| 104 | } | ||
| 105 | |||
| 106 | if (null != mainMsiFileHashTable) | ||
| 107 | { | ||
| 108 | mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable); | ||
| 109 | } | ||
| 110 | |||
| 111 | // Validate file row changes for keypath-related issues | ||
| 112 | this.ValidateFileRowChanges(mainTransform); | ||
| 113 | } | ||
| 114 | |||
| 115 | // Index File table of pairedTransform | ||
| 116 | Table pairedFileTable = pairedTransform.Tables["File"]; | ||
| 117 | RowDictionary<FileRow> pairedFileRows = new RowDictionary<FileRow>(pairedFileTable); | ||
| 118 | |||
| 119 | if (null != mainFileTable) | ||
| 120 | { | ||
| 121 | if (copyFromPatch) | ||
| 122 | { | ||
| 123 | // Remove the MsiFileHash table because it will be updated later with the final file hash for each file | ||
| 124 | mainTransform.Tables.Remove("MsiFileHash"); | ||
| 125 | } | ||
| 126 | |||
| 127 | foreach (FileRow mainFileRow in mainFileTable.Rows) | ||
| 128 | { | ||
| 129 | if (RowOperation.Delete == mainFileRow.Operation) | ||
| 130 | { | ||
| 131 | continue; | ||
| 132 | } | ||
| 133 | else if (RowOperation.None == mainFileRow.Operation && !copyToPatch) | ||
| 134 | { | ||
| 135 | continue; | ||
| 136 | } | ||
| 137 | |||
| 138 | WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File); | ||
| 139 | |||
| 140 | if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes. | ||
| 141 | { | ||
| 142 | ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6]; | ||
| 143 | FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File); | ||
| 144 | |||
| 145 | // If the file is new, we always need to add it to the patch. | ||
| 146 | if (mainFileRow.Operation != RowOperation.Add) | ||
| 147 | { | ||
| 148 | // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. | ||
| 149 | if (null == objectField.PreviousData) | ||
| 150 | { | ||
| 151 | if (mainFileRow.Operation == RowOperation.None) | ||
| 152 | { | ||
| 153 | continue; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | else | ||
| 157 | { | ||
| 158 | // TODO: should this entire condition be placed in the binder file manager? | ||
| 159 | if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) && | ||
| 160 | !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) | ||
| 161 | { | ||
| 162 | // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. | ||
| 163 | mainFileRow.Operation = RowOperation.Modify; | ||
| 164 | if (null != pairedFileRow) | ||
| 165 | { | ||
| 166 | // Always patch-added, but never non-compressed. | ||
| 167 | pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; | ||
| 168 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 169 | pairedFileRow.Fields[6].Modified = true; | ||
| 170 | pairedFileRow.Operation = RowOperation.Modify; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | else | ||
| 174 | { | ||
| 175 | // The File is same. We need mark all the attributes as unchanged. | ||
| 176 | mainFileRow.Operation = RowOperation.None; | ||
| 177 | foreach (Field field in mainFileRow.Fields) | ||
| 178 | { | ||
| 179 | field.Modified = false; | ||
| 180 | } | ||
| 181 | |||
| 182 | if (null != pairedFileRow) | ||
| 183 | { | ||
| 184 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded; | ||
| 185 | pairedFileRow.Fields[6].Modified = false; | ||
| 186 | pairedFileRow.Operation = RowOperation.None; | ||
| 187 | } | ||
| 188 | continue; | ||
| 189 | } | ||
| 190 | } | ||
| 191 | } | ||
| 192 | else if (null != pairedFileRow) // RowOperation.Add | ||
| 193 | { | ||
| 194 | // Always patch-added, but never non-compressed. | ||
| 195 | pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; | ||
| 196 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 197 | pairedFileRow.Fields[6].Modified = true; | ||
| 198 | pairedFileRow.Operation = RowOperation.Add; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | // index patch files by diskId+fileId | ||
| 203 | int diskId = mainWixFileRow.DiskId; | ||
| 204 | |||
| 205 | RowDictionary<WixFileRow> mediaFileRows; | ||
| 206 | if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) | ||
| 207 | { | ||
| 208 | mediaFileRows = new RowDictionary<WixFileRow>(); | ||
| 209 | patchMediaFileRows.Add(diskId, mediaFileRows); | ||
| 210 | } | ||
| 211 | |||
| 212 | string fileId = mainFileRow.File; | ||
| 213 | WixFileRow patchFileRow = mediaFileRows.Get(fileId); | ||
| 214 | if (copyToPatch) | ||
| 215 | { | ||
| 216 | if (null == patchFileRow) | ||
| 217 | { | ||
| 218 | FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 219 | patchActualFileRow.CopyFrom(mainFileRow); | ||
| 220 | |||
| 221 | patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 222 | patchFileRow.CopyFrom(mainWixFileRow); | ||
| 223 | |||
| 224 | mediaFileRows.Add(patchFileRow); | ||
| 225 | |||
| 226 | allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right? | ||
| 227 | } | ||
| 228 | else | ||
| 229 | { | ||
| 230 | // TODO: confirm the rest of data is identical? | ||
| 231 | |||
| 232 | // make sure Source is same. Otherwise we are silently ignoring a file. | ||
| 233 | if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase)) | ||
| 234 | { | ||
| 235 | Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); | ||
| 236 | } | ||
| 237 | |||
| 238 | // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. | ||
| 239 | patchFileRow.AppendPreviousDataFrom(mainWixFileRow); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | else | ||
| 243 | { | ||
| 244 | // copy data from the patch back to the transform | ||
| 245 | if (null != patchFileRow) | ||
| 246 | { | ||
| 247 | FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId); | ||
| 248 | for (int i = 0; i < patchFileRow.Fields.Length; i++) | ||
| 249 | { | ||
| 250 | string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString(); | ||
| 251 | string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString(); | ||
| 252 | |||
| 253 | if (1 == i) | ||
| 254 | { | ||
| 255 | // File.Component_ changes should not come from the shared file rows | ||
| 256 | // that contain the file information as each individual transform might | ||
| 257 | // have different changes (or no changes at all). | ||
| 258 | } | ||
| 259 | // File.Attributes should not changed for binary deltas | ||
| 260 | else if (6 == i) | ||
| 261 | { | ||
| 262 | if (null != patchFileRow.Patch) | ||
| 263 | { | ||
| 264 | // File.Attribute should not change for binary deltas | ||
| 265 | pairedFileRow.Attributes = mainFileRow.Attributes; | ||
| 266 | mainFileRow.Fields[i].Modified = false; | ||
| 267 | } | ||
| 268 | } | ||
| 269 | // File.Sequence is updated in pairedTransform, not mainTransform | ||
| 270 | else if (7 == i) | ||
| 271 | { | ||
| 272 | // file sequence is updated in Patch table instead of File table for delta patches | ||
| 273 | if (null != patchFileRow.Patch) | ||
| 274 | { | ||
| 275 | pairedFileRow.Fields[i].Modified = false; | ||
| 276 | } | ||
| 277 | else | ||
| 278 | { | ||
| 279 | pairedFileRow[i] = patchFileRow[i]; | ||
| 280 | pairedFileRow.Fields[i].Modified = true; | ||
| 281 | } | ||
| 282 | mainFileRow.Fields[i].Modified = false; | ||
| 283 | } | ||
| 284 | else if (patchValue != mainValue) | ||
| 285 | { | ||
| 286 | mainFileRow[i] = patchFileRow[i]; | ||
| 287 | mainFileRow.Fields[i].Modified = true; | ||
| 288 | if (mainFileRow.Operation == RowOperation.None) | ||
| 289 | { | ||
| 290 | mainFileRow.Operation = RowOperation.Modify; | ||
| 291 | } | ||
| 292 | } | ||
| 293 | } | ||
| 294 | |||
| 295 | // copy MsiFileHash row for this File | ||
| 296 | Row patchHashRow; | ||
| 297 | if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow)) | ||
| 298 | { | ||
| 299 | patchHashRow = patchFileRow.Hash; | ||
| 300 | } | ||
| 301 | |||
| 302 | if (null != patchHashRow) | ||
| 303 | { | ||
| 304 | Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); | ||
| 305 | Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 306 | for (int i = 0; i < patchHashRow.Fields.Length; i++) | ||
| 307 | { | ||
| 308 | mainHashRow[i] = patchHashRow[i]; | ||
| 309 | if (i > 1) | ||
| 310 | { | ||
| 311 | // assume all hash fields have been modified | ||
| 312 | mainHashRow.Fields[i].Modified = true; | ||
| 313 | } | ||
| 314 | } | ||
| 315 | |||
| 316 | // assume the MsiFileHash operation follows the File one | ||
| 317 | mainHashRow.Operation = mainFileRow.Operation; | ||
| 318 | } | ||
| 319 | |||
| 320 | // copy MsiAssemblyName rows for this File | ||
| 321 | List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames; | ||
| 322 | if (null != patchAssemblyNameRows) | ||
| 323 | { | ||
| 324 | Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
| 325 | foreach (Row patchAssemblyNameRow in patchAssemblyNameRows) | ||
| 326 | { | ||
| 327 | // Copy if there isn't an identical modified/added row already in the transform. | ||
| 328 | bool foundMatchingModifiedRow = false; | ||
| 329 | foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows) | ||
| 330 | { | ||
| 331 | if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) | ||
| 332 | { | ||
| 333 | foundMatchingModifiedRow = true; | ||
| 334 | break; | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | if (!foundMatchingModifiedRow) | ||
| 339 | { | ||
| 340 | Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 341 | for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++) | ||
| 342 | { | ||
| 343 | mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; | ||
| 344 | } | ||
| 345 | |||
| 346 | // assume value field has been modified | ||
| 347 | mainAssemblyNameRow.Fields[2].Modified = true; | ||
| 348 | mainAssemblyNameRow.Operation = mainFileRow.Operation; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 353 | // Add patch header for this file | ||
| 354 | if (null != patchFileRow.Patch) | ||
| 355 | { | ||
| 356 | // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. | ||
| 357 | AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); | ||
| 358 | AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); | ||
| 359 | |||
| 360 | // Add to Patch table | ||
| 361 | Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); | ||
| 362 | if (0 == patchTable.Rows.Count) | ||
| 363 | { | ||
| 364 | patchTable.Operation = TableOperation.Add; | ||
| 365 | } | ||
| 366 | |||
| 367 | Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 368 | patchRow[0] = patchFileRow.File; | ||
| 369 | patchRow[1] = patchFileRow.Sequence; | ||
| 370 | |||
| 371 | FileInfo patchFile = new FileInfo(patchFileRow.Source); | ||
| 372 | patchRow[2] = (int)patchFile.Length; | ||
| 373 | patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; | ||
| 374 | |||
| 375 | string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; | ||
| 376 | if (MsiInterop.MsiMaxStreamNameLength < streamName.Length) | ||
| 377 | { | ||
| 378 | streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); | ||
| 379 | Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); | ||
| 380 | if (0 == patchHeadersTable.Rows.Count) | ||
| 381 | { | ||
| 382 | patchHeadersTable.Operation = TableOperation.Add; | ||
| 383 | } | ||
| 384 | Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 385 | patchHeadersRow[0] = streamName; | ||
| 386 | patchHeadersRow[1] = patchFileRow.Patch; | ||
| 387 | patchRow[5] = streamName; | ||
| 388 | patchHeadersRow.Operation = RowOperation.Add; | ||
| 389 | } | ||
| 390 | else | ||
| 391 | { | ||
| 392 | patchRow[4] = patchFileRow.Patch; | ||
| 393 | } | ||
| 394 | patchRow.Operation = RowOperation.Add; | ||
| 395 | } | ||
| 396 | } | ||
| 397 | else | ||
| 398 | { | ||
| 399 | // TODO: throw because all transform rows should have made it into the patch | ||
| 400 | } | ||
| 401 | } | ||
| 402 | } | ||
| 403 | } | ||
| 404 | |||
| 405 | if (copyFromPatch) | ||
| 406 | { | ||
| 407 | this.Output.Tables.Remove("Media"); | ||
| 408 | this.Output.Tables.Remove("File"); | ||
| 409 | this.Output.Tables.Remove("MsiFileHash"); | ||
| 410 | this.Output.Tables.Remove("MsiAssemblyName"); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | } | ||
| 414 | finally | ||
| 415 | { | ||
| 416 | this.FileManagerCore.ActiveSubStorage = null; | ||
| 417 | } | ||
| 418 | #endif | ||
| 419 | this.FileFacades = allFileRows; | ||
| 420 | } | ||
| 421 | |||
| 422 | /// <summary> | ||
| 423 | /// Adds the PatchFiles action to the sequence table if it does not already exist. | ||
| 424 | /// </summary> | ||
| 425 | /// <param name="table">The sequence table to check or modify.</param> | ||
| 426 | /// <param name="mainTransform">The primary authoring transform.</param> | ||
| 427 | /// <param name="pairedTransform">The secondary patch transform.</param> | ||
| 428 | /// <param name="mainFileRow">The file row that contains information about the patched file.</param> | ||
| 429 | private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow) | ||
| 430 | { | ||
| 431 | // Find/add PatchFiles action (also determine sequence for it). | ||
| 432 | // Search mainTransform first, then pairedTransform (pairedTransform overrides). | ||
| 433 | bool hasPatchFilesAction = false; | ||
| 434 | int seqInstallFiles = 0; | ||
| 435 | int seqDuplicateFiles = 0; | ||
| 436 | string tableName = table.ToString(); | ||
| 437 | |||
| 438 | TestSequenceTableForPatchFilesAction( | ||
| 439 | mainTransform.Tables[tableName], | ||
| 440 | ref hasPatchFilesAction, | ||
| 441 | ref seqInstallFiles, | ||
| 442 | ref seqDuplicateFiles); | ||
| 443 | TestSequenceTableForPatchFilesAction( | ||
| 444 | pairedTransform.Tables[tableName], | ||
| 445 | ref hasPatchFilesAction, | ||
| 446 | ref seqInstallFiles, | ||
| 447 | ref seqDuplicateFiles); | ||
| 448 | if (!hasPatchFilesAction) | ||
| 449 | { | ||
| 450 | Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); | ||
| 451 | if (0 == iesTable.Rows.Count) | ||
| 452 | { | ||
| 453 | iesTable.Operation = TableOperation.Add; | ||
| 454 | } | ||
| 455 | |||
| 456 | Row patchAction = iesTable.CreateRow(null); | ||
| 457 | WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"]; | ||
| 458 | int sequence = wixPatchAction.Sequence; | ||
| 459 | // Test for default sequence value's appropriateness | ||
| 460 | if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence)) | ||
| 461 | { | ||
| 462 | if (0 != seqDuplicateFiles) | ||
| 463 | { | ||
| 464 | if (seqDuplicateFiles < seqInstallFiles) | ||
| 465 | { | ||
| 466 | throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); | ||
| 467 | } | ||
| 468 | else | ||
| 469 | { | ||
| 470 | sequence = (seqDuplicateFiles + seqInstallFiles) / 2; | ||
| 471 | if (seqInstallFiles == sequence || seqDuplicateFiles == sequence) | ||
| 472 | { | ||
| 473 | throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); | ||
| 474 | } | ||
| 475 | } | ||
| 476 | } | ||
| 477 | else | ||
| 478 | { | ||
| 479 | sequence = seqInstallFiles + 1; | ||
| 480 | } | ||
| 481 | } | ||
| 482 | patchAction[0] = wixPatchAction.Action; | ||
| 483 | patchAction[1] = wixPatchAction.Condition; | ||
| 484 | patchAction[2] = sequence; | ||
| 485 | patchAction.Operation = RowOperation.Add; | ||
| 486 | } | ||
| 487 | } | ||
| 488 | |||
| 489 | /// <summary> | ||
| 490 | /// Tests sequence table for PatchFiles and associated actions | ||
| 491 | /// </summary> | ||
| 492 | /// <param name="iesTable">The table to test.</param> | ||
| 493 | /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param> | ||
| 494 | /// <param name="seqInstallFiles">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param> | ||
| 495 | /// <param name="seqDuplicateFiles">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param> | ||
| 496 | private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles) | ||
| 497 | { | ||
| 498 | if (null != iesTable) | ||
| 499 | { | ||
| 500 | foreach (Row iesRow in iesTable.Rows) | ||
| 501 | { | ||
| 502 | if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
| 503 | { | ||
| 504 | hasPatchFilesAction = true; | ||
| 505 | } | ||
| 506 | if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
| 507 | { | ||
| 508 | seqInstallFiles = (int)iesRow.Fields[2].Data; | ||
| 509 | } | ||
| 510 | if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
| 511 | { | ||
| 512 | seqDuplicateFiles = (int)iesRow.Fields[2].Data; | ||
| 513 | } | ||
| 514 | } | ||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | /// <summary> | ||
| 519 | /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. | ||
| 520 | /// </summary> | ||
| 521 | /// <param name="output">The output to validate.</param> | ||
| 522 | private void ValidateFileRowChanges(Output transform) | ||
| 523 | { | ||
| 524 | Table componentTable = transform.Tables["Component"]; | ||
| 525 | Table fileTable = transform.Tables["File"]; | ||
| 526 | |||
| 527 | // There's no sense validating keypaths if the transform has no component or file table | ||
| 528 | if (componentTable == null || fileTable == null) | ||
| 529 | { | ||
| 530 | return; | ||
| 531 | } | ||
| 532 | |||
| 533 | Dictionary<string, string> componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count); | ||
| 534 | |||
| 535 | // Index the Component table for non-directory & non-registry key paths. | ||
| 536 | foreach (Row row in componentTable.Rows) | ||
| 537 | { | ||
| 538 | if (null != row.Fields[5].Data && | ||
| 539 | 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath)) | ||
| 540 | { | ||
| 541 | componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString()); | ||
| 542 | } | ||
| 543 | } | ||
| 544 | |||
| 545 | Dictionary<string, string> componentWithChangedKeyPath = new Dictionary<string, string>(); | ||
| 546 | Dictionary<string, string> componentWithNonKeyPathChanged = new Dictionary<string, string>(); | ||
| 547 | // Verify changes in the file table, now that file diffing has occurred | ||
| 548 | foreach (FileRow row in fileTable.Rows) | ||
| 549 | { | ||
| 550 | string fileId = row.Fields[0].Data.ToString(); | ||
| 551 | string componentId = row.Fields[1].Data.ToString(); | ||
| 552 | |||
| 553 | if (RowOperation.Modify != row.Operation) | ||
| 554 | { | ||
| 555 | continue; | ||
| 556 | } | ||
| 557 | |||
| 558 | // If this file is the keypath of a component | ||
| 559 | if (componentKeyPath.ContainsValue(fileId)) | ||
| 560 | { | ||
| 561 | if (!componentWithChangedKeyPath.ContainsKey(componentId)) | ||
| 562 | { | ||
| 563 | componentWithChangedKeyPath.Add(componentId, fileId); | ||
| 564 | } | ||
| 565 | } | ||
| 566 | else | ||
| 567 | { | ||
| 568 | if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) | ||
| 569 | { | ||
| 570 | componentWithNonKeyPathChanged.Add(componentId, fileId); | ||
| 571 | } | ||
| 572 | } | ||
| 573 | } | ||
| 574 | |||
| 575 | foreach (KeyValuePair<string, string> componentFile in componentWithNonKeyPathChanged) | ||
| 576 | { | ||
| 577 | // Make sure all changes to non keypath files also had a change in the keypath. | ||
| 578 | if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key)) | ||
| 579 | { | ||
| 580 | Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key])); | ||
| 581 | } | ||
| 582 | } | ||
| 583 | } | ||
| 584 | |||
| 585 | private bool CompareFiles(string targetFile, string updatedFile) | ||
| 586 | { | ||
| 587 | bool? compared = null; | ||
| 588 | foreach (var extension in this.Extensions) | ||
| 589 | { | ||
| 590 | compared = extension.CompareFiles(targetFile, updatedFile); | ||
| 591 | |||
| 592 | if (compared.HasValue) | ||
| 593 | { | ||
| 594 | break; | ||
| 595 | } | ||
| 596 | } | ||
| 597 | |||
| 598 | if (!compared.HasValue) | ||
| 599 | { | ||
| 600 | throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. | ||
| 601 | } | ||
| 602 | |||
| 603 | return compared.Value; | ||
| 604 | } | ||
| 605 | } | ||
| 606 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs new file mode 100644 index 00000000..02015744 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs | |||
| @@ -0,0 +1,499 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Runtime.InteropServices; | ||
| 11 | using System.Threading; | ||
| 12 | using WixToolset.Core.Bind; | ||
| 13 | using WixToolset.Core.WindowsInstaller.Bind; | ||
| 14 | using WixToolset.Data; | ||
| 15 | using WixToolset.Data.Bind; | ||
| 16 | using WixToolset.Data.Rows; | ||
| 17 | using WixToolset.Extensibility; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Creates cabinet files. | ||
| 21 | /// </summary> | ||
| 22 | internal class CreateCabinetsCommand | ||
| 23 | { | ||
| 24 | public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB | ||
| 25 | public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) | ||
| 26 | |||
| 27 | private List<FileTransfer> fileTransfers; | ||
| 28 | |||
| 29 | private FileSplitCabNamesCallback newCabNamesCallBack; | ||
| 30 | |||
| 31 | private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence | ||
| 32 | |||
| 33 | public CreateCabinetsCommand() | ||
| 34 | { | ||
| 35 | this.fileTransfers = new List<FileTransfer>(); | ||
| 36 | |||
| 37 | this.newCabNamesCallBack = this.NewCabNamesCallBack; | ||
| 38 | } | ||
| 39 | |||
| 40 | /// <summary> | ||
| 41 | /// Sets the number of threads to use for cabinet creation. | ||
| 42 | /// </summary> | ||
| 43 | public int CabbingThreadCount { private get; set; } | ||
| 44 | |||
| 45 | public string CabCachePath { private get; set; } | ||
| 46 | |||
| 47 | public string TempFilesLocation { private get; set; } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// Sets the default compression level to use for cabinets | ||
| 51 | /// that don't have their compression level explicitly set. | ||
| 52 | /// </summary> | ||
| 53 | public CompressionLevel DefaultCompressionLevel { private get; set; } | ||
| 54 | |||
| 55 | public IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { private get; set; } | ||
| 56 | |||
| 57 | public Output Output { private get; set; } | ||
| 58 | |||
| 59 | public string LayoutDirectory { private get; set; } | ||
| 60 | |||
| 61 | public bool Compressed { private get; set; } | ||
| 62 | |||
| 63 | public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; } | ||
| 64 | |||
| 65 | public Func<MediaRow, string, string, string> ResolveMedia { private get; set; } | ||
| 66 | |||
| 67 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 68 | |||
| 69 | public Table WixMediaTable { private get; set; } | ||
| 70 | |||
| 71 | public IEnumerable<FileTransfer> FileTransfers => this.fileTransfers; | ||
| 72 | |||
| 73 | /// <param name="output">Output to generate image for.</param> | ||
| 74 | /// <param name="fileTransfers">Array of files to be transfered.</param> | ||
| 75 | /// <param name="layoutDirectory">The directory in which the image should be layed out.</param> | ||
| 76 | /// <param name="compressed">Flag if source image should be compressed.</param> | ||
| 77 | /// <returns>The uncompressed file rows.</returns> | ||
| 78 | public void Execute() | ||
| 79 | { | ||
| 80 | RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable); | ||
| 81 | |||
| 82 | this.lastCabinetAddedToMediaTable = new Dictionary<string, string>(); | ||
| 83 | |||
| 84 | this.SetCabbingThreadCount(); | ||
| 85 | |||
| 86 | // Send Binder object to Facilitate NewCabNamesCallBack Callback | ||
| 87 | CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); | ||
| 88 | |||
| 89 | // Supply Compile MediaTemplate Attributes to Cabinet Builder | ||
| 90 | int MaximumCabinetSizeForLargeFileSplitting; | ||
| 91 | int MaximumUncompressedMediaSize; | ||
| 92 | this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize); | ||
| 93 | cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting; | ||
| 94 | cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize; | ||
| 95 | |||
| 96 | foreach (var entry in this.FileRowsByCabinet) | ||
| 97 | { | ||
| 98 | MediaRow mediaRow = entry.Key; | ||
| 99 | IEnumerable<FileFacade> files = entry.Value; | ||
| 100 | CompressionLevel compressionLevel = this.DefaultCompressionLevel; | ||
| 101 | |||
| 102 | WixMediaRow wixMediaRow = null; | ||
| 103 | string mediaLayoutFolder = null; | ||
| 104 | |||
| 105 | if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) | ||
| 106 | { | ||
| 107 | mediaLayoutFolder = wixMediaRow.Layout; | ||
| 108 | |||
| 109 | if (wixMediaRow.CompressionLevel.HasValue) | ||
| 110 | { | ||
| 111 | compressionLevel = wixMediaRow.CompressionLevel.Value; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); | ||
| 116 | |||
| 117 | CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers); | ||
| 118 | if (null != cabinetWorkItem) | ||
| 119 | { | ||
| 120 | cabinetBuilder.Enqueue(cabinetWorkItem); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | // stop processing if an error previously occurred | ||
| 125 | if (Messaging.Instance.EncounteredError) | ||
| 126 | { | ||
| 127 | return; | ||
| 128 | } | ||
| 129 | |||
| 130 | // create queued cabinets with multiple threads | ||
| 131 | cabinetBuilder.CreateQueuedCabinets(); | ||
| 132 | if (Messaging.Instance.EncounteredError) | ||
| 133 | { | ||
| 134 | return; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | /// <summary> | ||
| 139 | /// Sets the thead count to the number of processors if the current thread count is set to 0. | ||
| 140 | /// </summary> | ||
| 141 | /// <remarks>The thread count value must be greater than 0 otherwise and exception will be thrown.</remarks> | ||
| 142 | private void SetCabbingThreadCount() | ||
| 143 | { | ||
| 144 | // default the number of cabbing threads to the number of processors if it wasn't specified | ||
| 145 | if (0 == this.CabbingThreadCount) | ||
| 146 | { | ||
| 147 | string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); | ||
| 148 | |||
| 149 | try | ||
| 150 | { | ||
| 151 | if (null != numberOfProcessors) | ||
| 152 | { | ||
| 153 | this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat); | ||
| 154 | |||
| 155 | if (0 >= this.CabbingThreadCount) | ||
| 156 | { | ||
| 157 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | else // default to 1 if the environment variable is not set | ||
| 161 | { | ||
| 162 | this.CabbingThreadCount = 1; | ||
| 163 | } | ||
| 164 | |||
| 165 | Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); | ||
| 166 | } | ||
| 167 | catch (ArgumentException) | ||
| 168 | { | ||
| 169 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
| 170 | } | ||
| 171 | catch (FormatException) | ||
| 172 | { | ||
| 173 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
| 174 | } | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | |||
| 179 | /// <summary> | ||
| 180 | /// Creates a work item to create a cabinet. | ||
| 181 | /// </summary> | ||
| 182 | /// <param name="output">Output for the current database.</param> | ||
| 183 | /// <param name="cabinetDir">Directory to create cabinet in.</param> | ||
| 184 | /// <param name="mediaRow">MediaRow containing information about the cabinet.</param> | ||
| 185 | /// <param name="fileFacades">Collection of files in this cabinet.</param> | ||
| 186 | /// <param name="fileTransfers">Array of files to be transfered.</param> | ||
| 187 | /// <returns>created CabinetWorkItem object</returns> | ||
| 188 | private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers) | ||
| 189 | { | ||
| 190 | CabinetWorkItem cabinetWorkItem = null; | ||
| 191 | string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet); | ||
| 192 | |||
| 193 | // check for an empty cabinet | ||
| 194 | if (!fileFacades.Any()) | ||
| 195 | { | ||
| 196 | string cabinetName = mediaRow.Cabinet; | ||
| 197 | |||
| 198 | // remove the leading '#' from the embedded cabinet name to make the warning easier to understand | ||
| 199 | if (cabinetName.StartsWith("#", StringComparison.Ordinal)) | ||
| 200 | { | ||
| 201 | cabinetName = cabinetName.Substring(1); | ||
| 202 | } | ||
| 203 | |||
| 204 | // If building a patch, remind them to run -p for torch. | ||
| 205 | if (OutputType.Patch == output.Type) | ||
| 206 | { | ||
| 207 | Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true)); | ||
| 208 | } | ||
| 209 | else | ||
| 210 | { | ||
| 211 | Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName)); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | var cabinetResolver = new CabinetResolver(this.CabCachePath, this.BackendExtensions); | ||
| 216 | |||
| 217 | ResolvedCabinet resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades); | ||
| 218 | |||
| 219 | // create a cabinet work item if it's not being skipped | ||
| 220 | if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) | ||
| 221 | { | ||
| 222 | int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet). | ||
| 223 | |||
| 224 | cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/); | ||
| 225 | } | ||
| 226 | else // reuse the cabinet from the cabinet cache. | ||
| 227 | { | ||
| 228 | Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path)); | ||
| 229 | |||
| 230 | try | ||
| 231 | { | ||
| 232 | // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The | ||
| 233 | // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that | ||
| 234 | // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from | ||
| 235 | // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) | ||
| 236 | // causing the project to look like it perpetually needs a rebuild until all of the reused | ||
| 237 | // cabinets get newer timestamps. | ||
| 238 | File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now); | ||
| 239 | } | ||
| 240 | catch (Exception e) | ||
| 241 | { | ||
| 242 | Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message)); | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
| 247 | { | ||
| 248 | Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]); | ||
| 249 | |||
| 250 | Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers); | ||
| 251 | streamRow[0] = mediaRow.Cabinet.Substring(1); | ||
| 252 | streamRow[1] = resolvedCabinet.Path; | ||
| 253 | } | ||
| 254 | else | ||
| 255 | { | ||
| 256 | string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet); | ||
| 257 | FileTransfer transfer; | ||
| 258 | if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer)) | ||
| 259 | { | ||
| 260 | transfer.Built = true; | ||
| 261 | fileTransfers.Add(transfer); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | return cabinetWorkItem; | ||
| 266 | } | ||
| 267 | |||
| 268 | //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades) | ||
| 269 | //{ | ||
| 270 | // ResolvedCabinet resolved = null; | ||
| 271 | |||
| 272 | // List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); | ||
| 273 | |||
| 274 | // foreach (var extension in this.BackendExtensions) | ||
| 275 | // { | ||
| 276 | // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); | ||
| 277 | // if (null != resolved) | ||
| 278 | // { | ||
| 279 | // break; | ||
| 280 | // } | ||
| 281 | // } | ||
| 282 | |||
| 283 | // return resolved; | ||
| 284 | //} | ||
| 285 | |||
| 286 | /// <summary> | ||
| 287 | /// Delegate for Cabinet Split Callback | ||
| 288 | /// </summary> | ||
| 289 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] | ||
| 290 | internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); | ||
| 291 | |||
| 292 | /// <summary> | ||
| 293 | /// Call back to Add File Transfer for new Cab and add new Cab to Media table | ||
| 294 | /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe | ||
| 295 | /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored | ||
| 296 | /// </summary> | ||
| 297 | /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param> | ||
| 298 | /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param> | ||
| 299 | /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param> | ||
| 300 | internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) | ||
| 301 | { | ||
| 302 | // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads | ||
| 303 | Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); | ||
| 304 | try | ||
| 305 | { | ||
| 306 | if (!mutex.WaitOne(0, false)) // Check if you can get the lock | ||
| 307 | { | ||
| 308 | // Cound not get the Lock | ||
| 309 | Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel()); | ||
| 310 | mutex.WaitOne(); // Wait on other thread | ||
| 311 | } | ||
| 312 | |||
| 313 | string firstCabinetName = firstCabName + ".cab"; | ||
| 314 | string newCabinetName = newCabName; | ||
| 315 | bool transferAdded = false; // Used for Error Handling | ||
| 316 | |||
| 317 | // Create File Transfer for new Cabinet using transfer of Base Cabinet | ||
| 318 | foreach (FileTransfer transfer in this.FileTransfers) | ||
| 319 | { | ||
| 320 | if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) | ||
| 321 | { | ||
| 322 | string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); | ||
| 323 | string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); | ||
| 324 | |||
| 325 | FileTransfer newTransfer; | ||
| 326 | if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer)) | ||
| 327 | { | ||
| 328 | newTransfer.Built = true; | ||
| 329 | this.fileTransfers.Add(newTransfer); | ||
| 330 | transferAdded = true; | ||
| 331 | break; | ||
| 332 | } | ||
| 333 | } | ||
| 334 | } | ||
| 335 | |||
| 336 | // Check if File Transfer was added | ||
| 337 | if (!transferAdded) | ||
| 338 | { | ||
| 339 | throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); | ||
| 340 | } | ||
| 341 | |||
| 342 | // Add the new Cabinets to media table using LastSequence of Base Cabinet | ||
| 343 | Table mediaTable = this.Output.Tables["Media"]; | ||
| 344 | Table wixFileTable = this.Output.Tables["WixFile"]; | ||
| 345 | int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain | ||
| 346 | int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain | ||
| 347 | bool lastSplitCabinetFound = false; // Used for Error Handling | ||
| 348 | |||
| 349 | string lastCabinetOfThisSequence = String.Empty; | ||
| 350 | // Get the Value of Last Cabinet Added in this split Sequence from Dictionary | ||
| 351 | if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) | ||
| 352 | { | ||
| 353 | // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence | ||
| 354 | lastCabinetOfThisSequence = firstCabinetName; | ||
| 355 | } | ||
| 356 | |||
| 357 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
| 358 | { | ||
| 359 | // Get details for the Last Cabinet Added in this Split Sequence | ||
| 360 | if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 361 | { | ||
| 362 | lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; | ||
| 363 | diskIDForLastSplitCabAdded = mediaRow.DiskId; | ||
| 364 | lastSplitCabinetFound = true; | ||
| 365 | } | ||
| 366 | |||
| 367 | // Check for Name Collision for the new Cabinet added | ||
| 368 | if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 369 | { | ||
| 370 | // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row | ||
| 371 | throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | // Check if the last Split Cabinet was found in the Media Table | ||
| 376 | if (!lastSplitCabinetFound) | ||
| 377 | { | ||
| 378 | throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); | ||
| 379 | } | ||
| 380 | |||
| 381 | // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort | ||
| 382 | // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with | ||
| 383 | // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction | ||
| 384 | MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); | ||
| 385 | newMediaRow.Cabinet = newCabinetName; | ||
| 386 | newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion | ||
| 387 | newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; | ||
| 388 | |||
| 389 | // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique | ||
| 390 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
| 391 | { | ||
| 392 | // Check if this row comes after inserted row and it is not the new cabinet inserted row | ||
| 393 | if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 394 | { | ||
| 395 | mediaRow.DiskId++; // Increment DiskID | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | // Now Increment DiskID for All files Rows so that they refer to the right Media Row | ||
| 400 | foreach (WixFileRow wixFileRow in wixFileTable.Rows) | ||
| 401 | { | ||
| 402 | // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet | ||
| 403 | // This check will work as we have only one large file in every splitting cabinet | ||
| 404 | // If we want to support splitting cabinet with more large files we need to update this code | ||
| 405 | if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) | ||
| 406 | { | ||
| 407 | wixFileRow.DiskId++; // Increment DiskID | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback | ||
| 412 | this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; | ||
| 413 | |||
| 414 | mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails | ||
| 415 | } | ||
| 416 | finally | ||
| 417 | { | ||
| 418 | // Releasing the Mutex here | ||
| 419 | mutex.ReleaseMutex(); | ||
| 420 | } | ||
| 421 | } | ||
| 422 | |||
| 423 | |||
| 424 | /// <summary> | ||
| 425 | /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides | ||
| 426 | /// </summary> | ||
| 427 | /// <param name="output">Output to generate image for.</param> | ||
| 428 | /// <param name="fileRows">The indexed file rows.</param> | ||
| 429 | private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) | ||
| 430 | { | ||
| 431 | // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size | ||
| 432 | string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); | ||
| 433 | string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); | ||
| 434 | int maxCabSizeForLargeFileInMB = 0; | ||
| 435 | int maxPreCompressedSizeInMB = 0; | ||
| 436 | ulong testOverFlow = 0; | ||
| 437 | |||
| 438 | // Supply Compile MediaTemplate Attributes to Cabinet Builder | ||
| 439 | Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; | ||
| 440 | if (mediaTemplateTable != null) | ||
| 441 | { | ||
| 442 | WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; | ||
| 443 | |||
| 444 | // Get the Value for Max Cab Size for File Splitting | ||
| 445 | try | ||
| 446 | { | ||
| 447 | // Override authored mcslfs value if environment variable is authored. | ||
| 448 | if (!String.IsNullOrEmpty(mcslfsString)) | ||
| 449 | { | ||
| 450 | maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString); | ||
| 451 | } | ||
| 452 | else | ||
| 453 | { | ||
| 454 | maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting; | ||
| 455 | } | ||
| 456 | testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; | ||
| 457 | } | ||
| 458 | catch (FormatException) | ||
| 459 | { | ||
| 460 | throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); | ||
| 461 | } | ||
| 462 | catch (OverflowException) | ||
| 463 | { | ||
| 464 | throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting)); | ||
| 465 | } | ||
| 466 | |||
| 467 | try | ||
| 468 | { | ||
| 469 | // Override authored mums value if environment variable is authored. | ||
| 470 | if (!String.IsNullOrEmpty(mumsString)) | ||
| 471 | { | ||
| 472 | maxPreCompressedSizeInMB = Int32.Parse(mumsString); | ||
| 473 | } | ||
| 474 | else | ||
| 475 | { | ||
| 476 | maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; | ||
| 477 | } | ||
| 478 | testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; | ||
| 479 | } | ||
| 480 | catch (FormatException) | ||
| 481 | { | ||
| 482 | throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); | ||
| 483 | } | ||
| 484 | catch (OverflowException) | ||
| 485 | { | ||
| 486 | throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); | ||
| 487 | } | ||
| 488 | |||
| 489 | maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; | ||
| 490 | maxUncompressedMediaSize = maxPreCompressedSizeInMB; | ||
| 491 | } | ||
| 492 | else | ||
| 493 | { | ||
| 494 | maxCabSizeForLargeFileSplitting = 0; | ||
| 495 | maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; | ||
| 496 | } | ||
| 497 | } | ||
| 498 | } | ||
| 499 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs new file mode 100644 index 00000000..767671b8 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs | |||
| @@ -0,0 +1,87 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using WixToolset.Core.Bind; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Rows; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. | ||
| 15 | /// </summary> | ||
| 16 | internal class CreateDeltaPatchesCommand | ||
| 17 | { | ||
| 18 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 19 | |||
| 20 | public Table WixPatchIdTable { private get; set; } | ||
| 21 | |||
| 22 | public string TempFilesLocation { private get; set; } | ||
| 23 | |||
| 24 | public void Execute() | ||
| 25 | { | ||
| 26 | bool optimizePatchSizeForLargeFiles = false; | ||
| 27 | PatchSymbolFlagsType apiPatchingSymbolFlags = 0; | ||
| 28 | |||
| 29 | if (null != this.WixPatchIdTable) | ||
| 30 | { | ||
| 31 | Row row = this.WixPatchIdTable.Rows[0]; | ||
| 32 | if (null != row) | ||
| 33 | { | ||
| 34 | if (null != row[2]) | ||
| 35 | { | ||
| 36 | optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture)); | ||
| 37 | } | ||
| 38 | |||
| 39 | if (null != row[3]) | ||
| 40 | { | ||
| 41 | apiPatchingSymbolFlags = (PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | foreach (FileFacade facade in this.FileFacades) | ||
| 47 | { | ||
| 48 | if (RowOperation.Modify == facade.File.Operation && | ||
| 49 | 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) | ||
| 50 | { | ||
| 51 | string deltaBase = String.Concat("delta_", facade.File.File); | ||
| 52 | string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf")); | ||
| 53 | string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd")); | ||
| 54 | |||
| 55 | bool retainRangeWarning = false; | ||
| 56 | |||
| 57 | if (PatchAPI.PatchInterop.CreateDelta( | ||
| 58 | deltaFile, | ||
| 59 | facade.WixFile.Source, | ||
| 60 | facade.DeltaPatchFile.Symbols, | ||
| 61 | facade.DeltaPatchFile.RetainOffsets, | ||
| 62 | new[] { facade.WixFile.PreviousSource }, | ||
| 63 | facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }), | ||
| 64 | facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }), | ||
| 65 | facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }), | ||
| 66 | facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }), | ||
| 67 | facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }), | ||
| 68 | apiPatchingSymbolFlags, | ||
| 69 | optimizePatchSizeForLargeFiles, | ||
| 70 | out retainRangeWarning)) | ||
| 71 | { | ||
| 72 | PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile); | ||
| 73 | |||
| 74 | facade.WixFile.Source = deltaFile; | ||
| 75 | facade.WixFile.DeltaPatchHeaderSource = headerFile; | ||
| 76 | } | ||
| 77 | |||
| 78 | if (retainRangeWarning) | ||
| 79 | { | ||
| 80 | // TODO: get patch family to add to warning message for PatchWiz parity. | ||
| 81 | Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File)); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs new file mode 100644 index 00000000..aef130b0 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs | |||
| @@ -0,0 +1,68 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Rows; | ||
| 9 | |||
| 10 | internal class CreateSpecialPropertiesCommand | ||
| 11 | { | ||
| 12 | public Table PropertyTable { private get; set; } | ||
| 13 | |||
| 14 | public Table WixPropertyTable { private get; set; } | ||
| 15 | |||
| 16 | public void Execute() | ||
| 17 | { | ||
| 18 | // Create the special properties. | ||
| 19 | if (null != this.WixPropertyTable) | ||
| 20 | { | ||
| 21 | // Create lists of the properties that contribute to the special lists of properties. | ||
| 22 | SortedSet<string> adminProperties = new SortedSet<string>(); | ||
| 23 | SortedSet<string> secureProperties = new SortedSet<string>(); | ||
| 24 | SortedSet<string> hiddenProperties = new SortedSet<string>(); | ||
| 25 | |||
| 26 | foreach (WixPropertyRow wixPropertyRow in this.WixPropertyTable.Rows) | ||
| 27 | { | ||
| 28 | if (wixPropertyRow.Admin) | ||
| 29 | { | ||
| 30 | adminProperties.Add(wixPropertyRow.Id); | ||
| 31 | } | ||
| 32 | |||
| 33 | if (wixPropertyRow.Hidden) | ||
| 34 | { | ||
| 35 | hiddenProperties.Add(wixPropertyRow.Id); | ||
| 36 | } | ||
| 37 | |||
| 38 | if (wixPropertyRow.Secure) | ||
| 39 | { | ||
| 40 | secureProperties.Add(wixPropertyRow.Id); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | Table propertyTable = this.PropertyTable; | ||
| 45 | if (0 < adminProperties.Count) | ||
| 46 | { | ||
| 47 | PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); | ||
| 48 | row.Property = "AdminProperties"; | ||
| 49 | row.Value = String.Join(";", adminProperties); | ||
| 50 | } | ||
| 51 | |||
| 52 | if (0 < secureProperties.Count) | ||
| 53 | { | ||
| 54 | PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); | ||
| 55 | row.Property = "SecureCustomProperties"; | ||
| 56 | row.Value = String.Join(";", secureProperties); | ||
| 57 | } | ||
| 58 | |||
| 59 | if (0 < hiddenProperties.Count) | ||
| 60 | { | ||
| 61 | PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); | ||
| 62 | row.Property = "MsiHiddenProperties"; | ||
| 63 | row.Value = String.Join(";", hiddenProperties); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs new file mode 100644 index 00000000..ae76037d --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs | |||
| @@ -0,0 +1,226 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Runtime.InteropServices; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | using WixToolset.MergeMod; | ||
| 15 | using WixToolset.Msi; | ||
| 16 | using WixToolset.Core.Native; | ||
| 17 | using WixToolset.Core.Bind; | ||
| 18 | using WixToolset.Core.Cab; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Retrieve files information and extract them from merge modules. | ||
| 22 | /// </summary> | ||
| 23 | internal class ExtractMergeModuleFilesCommand | ||
| 24 | { | ||
| 25 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 26 | |||
| 27 | public Table FileTable { private get; set; } | ||
| 28 | |||
| 29 | public Table WixFileTable { private get; set; } | ||
| 30 | |||
| 31 | public Table WixMergeTable { private get; set; } | ||
| 32 | |||
| 33 | public int OutputInstallerVersion { private get; set; } | ||
| 34 | |||
| 35 | public bool SuppressLayout { private get; set; } | ||
| 36 | |||
| 37 | public string TempFilesLocation { private get; set; } | ||
| 38 | |||
| 39 | public IEnumerable<FileFacade> MergeModulesFileFacades { get; private set; } | ||
| 40 | |||
| 41 | public void Execute() | ||
| 42 | { | ||
| 43 | List<FileFacade> mergeModulesFileFacades = new List<FileFacade>(); | ||
| 44 | |||
| 45 | IMsmMerge2 merge = MsmInterop.GetMsmMerge(); | ||
| 46 | |||
| 47 | // Index all of the file rows to be able to detect collisions with files in the Merge Modules. | ||
| 48 | // It may seem a bit expensive to build up this index solely for the purpose of checking collisions | ||
| 49 | // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out | ||
| 50 | // there are other cases where we need all the file rows indexed, however they are not common cases. | ||
| 51 | // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let | ||
| 52 | // this case be slightly more expensive because the cost of maintaining an indexed file row collection | ||
| 53 | // is a lot more costly for the common cases. | ||
| 54 | Dictionary<string, FileFacade> indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal); | ||
| 55 | |||
| 56 | foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows) | ||
| 57 | { | ||
| 58 | bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); | ||
| 59 | |||
| 60 | // If the module has files and creating layout | ||
| 61 | if (containsFiles && !this.SuppressLayout) | ||
| 62 | { | ||
| 63 | this.ExtractFilesFromMergeModule(merge, wixMergeRow); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | this.MergeModulesFileFacades = mergeModulesFileFacades; | ||
| 68 | } | ||
| 69 | |||
| 70 | private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades) | ||
| 71 | { | ||
| 72 | bool containsFiles = false; | ||
| 73 | |||
| 74 | try | ||
| 75 | { | ||
| 76 | // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. | ||
| 77 | using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) | ||
| 78 | { | ||
| 79 | if (db.TableExists("File") && db.TableExists("Component")) | ||
| 80 | { | ||
| 81 | Dictionary<string, FileFacade> uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase); | ||
| 82 | |||
| 83 | using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) | ||
| 84 | { | ||
| 85 | // add each file row from the merge module into the file row collection (check for errors along the way) | ||
| 86 | while (true) | ||
| 87 | { | ||
| 88 | using (Record record = view.Fetch()) | ||
| 89 | { | ||
| 90 | if (null == record) | ||
| 91 | { | ||
| 92 | break; | ||
| 93 | } | ||
| 94 | |||
| 95 | // NOTE: this is very tricky - the merge module file rows are not added to the | ||
| 96 | // file table because they should not be created via idt import. Instead, these | ||
| 97 | // rows are created by merging in the actual modules. | ||
| 98 | FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); | ||
| 99 | fileRow.File = record[1]; | ||
| 100 | fileRow.Compressed = wixMergeRow.FileCompression; | ||
| 101 | |||
| 102 | WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); | ||
| 103 | wixFileRow.Directory = record[2]; | ||
| 104 | wixFileRow.DiskId = wixMergeRow.DiskId; | ||
| 105 | wixFileRow.PatchGroup = -1; | ||
| 106 | wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]); | ||
| 107 | |||
| 108 | FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow); | ||
| 109 | |||
| 110 | FileFacade collidingFacade; | ||
| 111 | |||
| 112 | // If case-sensitive collision with another merge module or a user-authored file identifier. | ||
| 113 | if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) | ||
| 114 | { | ||
| 115 | Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File)); | ||
| 116 | } | ||
| 117 | else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module | ||
| 118 | { | ||
| 119 | Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File)); | ||
| 120 | } | ||
| 121 | else // no collision | ||
| 122 | { | ||
| 123 | mergeModulesFileFacades.Add(mergeModuleFileFacade); | ||
| 124 | |||
| 125 | // Keep updating the indexes as new rows are added. | ||
| 126 | indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); | ||
| 127 | uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); | ||
| 128 | } | ||
| 129 | |||
| 130 | containsFiles = true; | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | // Get the summary information to detect the Schema | ||
| 137 | using (SummaryInformation summaryInformation = new SummaryInformation(db)) | ||
| 138 | { | ||
| 139 | string moduleInstallerVersionString = summaryInformation.GetProperty(14); | ||
| 140 | |||
| 141 | try | ||
| 142 | { | ||
| 143 | int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); | ||
| 144 | if (moduleInstallerVersion > this.OutputInstallerVersion) | ||
| 145 | { | ||
| 146 | Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion)); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | catch (FormatException) | ||
| 150 | { | ||
| 151 | throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | catch (FileNotFoundException) | ||
| 157 | { | ||
| 158 | throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); | ||
| 159 | } | ||
| 160 | catch (Win32Exception) | ||
| 161 | { | ||
| 162 | throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile)); | ||
| 163 | } | ||
| 164 | |||
| 165 | return containsFiles; | ||
| 166 | } | ||
| 167 | |||
| 168 | private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow) | ||
| 169 | { | ||
| 170 | bool moduleOpen = false; | ||
| 171 | short mergeLanguage; | ||
| 172 | |||
| 173 | try | ||
| 174 | { | ||
| 175 | mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); | ||
| 176 | } | ||
| 177 | catch (System.FormatException) | ||
| 178 | { | ||
| 179 | Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); | ||
| 180 | return; | ||
| 181 | } | ||
| 182 | |||
| 183 | try | ||
| 184 | { | ||
| 185 | merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); | ||
| 186 | moduleOpen = true; | ||
| 187 | |||
| 188 | string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat); | ||
| 189 | |||
| 190 | // extract the module cabinet, then explode all of the files to a temp directory | ||
| 191 | string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab"); | ||
| 192 | merge.ExtractCAB(moduleCabPath); | ||
| 193 | |||
| 194 | string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId); | ||
| 195 | Directory.CreateDirectory(mergeIdPath); | ||
| 196 | |||
| 197 | using (var extractCab = new WixExtractCab()) | ||
| 198 | { | ||
| 199 | try | ||
| 200 | { | ||
| 201 | extractCab.Extract(moduleCabPath, mergeIdPath); | ||
| 202 | } | ||
| 203 | catch (FileNotFoundException) | ||
| 204 | { | ||
| 205 | throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); | ||
| 206 | } | ||
| 207 | catch | ||
| 208 | { | ||
| 209 | throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
| 213 | catch (COMException ce) | ||
| 214 | { | ||
| 215 | throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); | ||
| 216 | } | ||
| 217 | finally | ||
| 218 | { | ||
| 219 | if (moduleOpen) | ||
| 220 | { | ||
| 221 | merge.CloseModule(); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs new file mode 100644 index 00000000..26d254f2 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs | |||
| @@ -0,0 +1,332 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Text; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Extensibility; | ||
| 13 | using WixToolset.Msi; | ||
| 14 | using WixToolset.Core.Native; | ||
| 15 | |||
| 16 | internal class GenerateDatabaseCommand | ||
| 17 | { | ||
| 18 | public int Codepage { private get; set; } | ||
| 19 | |||
| 20 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
| 21 | |||
| 22 | /// <summary> | ||
| 23 | /// Whether to keep columns added in a transform. | ||
| 24 | /// </summary> | ||
| 25 | public bool KeepAddedColumns { private get; set; } | ||
| 26 | |||
| 27 | public Output Output { private get; set; } | ||
| 28 | |||
| 29 | public string OutputPath { private get; set; } | ||
| 30 | |||
| 31 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 32 | |||
| 33 | public string TempFilesLocation { private get; set; } | ||
| 34 | |||
| 35 | /// <summary> | ||
| 36 | /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files. | ||
| 37 | /// </summary> | ||
| 38 | public bool SuppressAddingValidationRows { private get; set; } | ||
| 39 | |||
| 40 | public bool UseSubDirectory { private get; set; } | ||
| 41 | |||
| 42 | public void Execute() | ||
| 43 | { | ||
| 44 | // Add the _Validation rows. | ||
| 45 | if (!this.SuppressAddingValidationRows) | ||
| 46 | { | ||
| 47 | Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); | ||
| 48 | |||
| 49 | foreach (Table table in this.Output.Tables) | ||
| 50 | { | ||
| 51 | if (!table.Definition.Unreal) | ||
| 52 | { | ||
| 53 | // Add the validation rows for this table. | ||
| 54 | table.Definition.AddValidationRows(validationTable); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | // Set the base directory. | ||
| 60 | string baseDirectory = this.TempFilesLocation; | ||
| 61 | |||
| 62 | if (this.UseSubDirectory) | ||
| 63 | { | ||
| 64 | string filename = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
| 65 | baseDirectory = Path.Combine(baseDirectory, filename); | ||
| 66 | |||
| 67 | // make sure the directory exists | ||
| 68 | Directory.CreateDirectory(baseDirectory); | ||
| 69 | } | ||
| 70 | |||
| 71 | try | ||
| 72 | { | ||
| 73 | OpenDatabase type = OpenDatabase.CreateDirect; | ||
| 74 | |||
| 75 | // set special flag for patch files | ||
| 76 | if (OutputType.Patch == this.Output.Type) | ||
| 77 | { | ||
| 78 | type |= OpenDatabase.OpenPatchFile; | ||
| 79 | } | ||
| 80 | |||
| 81 | #if DEBUG | ||
| 82 | Console.WriteLine("Opening database at: {0}", this.OutputPath); | ||
| 83 | #endif | ||
| 84 | |||
| 85 | using (Database db = new Database(this.OutputPath, type)) | ||
| 86 | { | ||
| 87 | // Localize the codepage if a value was specified directly. | ||
| 88 | if (-1 != this.Codepage) | ||
| 89 | { | ||
| 90 | this.Output.Codepage = this.Codepage; | ||
| 91 | } | ||
| 92 | |||
| 93 | // if we're not using the default codepage, import a new one into our | ||
| 94 | // database before we add any tables (or the tables would be added | ||
| 95 | // with the wrong codepage). | ||
| 96 | if (0 != this.Output.Codepage) | ||
| 97 | { | ||
| 98 | this.SetDatabaseCodepage(db, this.Output.Codepage); | ||
| 99 | } | ||
| 100 | |||
| 101 | foreach (Table table in this.Output.Tables) | ||
| 102 | { | ||
| 103 | Table importTable = table; | ||
| 104 | bool hasBinaryColumn = false; | ||
| 105 | |||
| 106 | // Skip all unreal tables other than _Streams. | ||
| 107 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
| 108 | { | ||
| 109 | continue; | ||
| 110 | } | ||
| 111 | |||
| 112 | // Do not put the _Validation table in patches, it is not needed. | ||
| 113 | if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name) | ||
| 114 | { | ||
| 115 | continue; | ||
| 116 | } | ||
| 117 | |||
| 118 | // The only way to import binary data is to copy it to a local subdirectory first. | ||
| 119 | // To avoid this extra copying and perf hit, import an empty table with the same | ||
| 120 | // definition and later import the binary data from source using records. | ||
| 121 | foreach (ColumnDefinition columnDefinition in table.Definition.Columns) | ||
| 122 | { | ||
| 123 | if (ColumnType.Object == columnDefinition.Type) | ||
| 124 | { | ||
| 125 | importTable = new Table(table.Section, table.Definition); | ||
| 126 | hasBinaryColumn = true; | ||
| 127 | break; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | // Create the table via IDT import. | ||
| 132 | if ("_Streams" != importTable.Name) | ||
| 133 | { | ||
| 134 | try | ||
| 135 | { | ||
| 136 | db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns); | ||
| 137 | } | ||
| 138 | catch (WixInvalidIdtException) | ||
| 139 | { | ||
| 140 | // If ValidateRows finds anything it doesn't like, it throws | ||
| 141 | importTable.ValidateRows(); | ||
| 142 | |||
| 143 | // Otherwise we rethrow the InvalidIdt | ||
| 144 | throw; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | // insert the rows via SQL query if this table contains object fields | ||
| 149 | if (hasBinaryColumn) | ||
| 150 | { | ||
| 151 | StringBuilder query = new StringBuilder("SELECT "); | ||
| 152 | |||
| 153 | // Build the query for the view. | ||
| 154 | bool firstColumn = true; | ||
| 155 | foreach (ColumnDefinition columnDefinition in table.Definition.Columns) | ||
| 156 | { | ||
| 157 | if (!firstColumn) | ||
| 158 | { | ||
| 159 | query.Append(","); | ||
| 160 | } | ||
| 161 | |||
| 162 | query.AppendFormat(" `{0}`", columnDefinition.Name); | ||
| 163 | firstColumn = false; | ||
| 164 | } | ||
| 165 | query.AppendFormat(" FROM `{0}`", table.Name); | ||
| 166 | |||
| 167 | using (View tableView = db.OpenExecuteView(query.ToString())) | ||
| 168 | { | ||
| 169 | // Import each row containing a stream | ||
| 170 | foreach (Row row in table.Rows) | ||
| 171 | { | ||
| 172 | using (Record record = new Record(table.Definition.Columns.Count)) | ||
| 173 | { | ||
| 174 | StringBuilder streamName = new StringBuilder(); | ||
| 175 | bool needStream = false; | ||
| 176 | |||
| 177 | // the _Streams table doesn't prepend the table name (or a period) | ||
| 178 | if ("_Streams" != table.Name) | ||
| 179 | { | ||
| 180 | streamName.Append(table.Name); | ||
| 181 | } | ||
| 182 | |||
| 183 | for (int i = 0; i < table.Definition.Columns.Count; i++) | ||
| 184 | { | ||
| 185 | ColumnDefinition columnDefinition = table.Definition.Columns[i]; | ||
| 186 | |||
| 187 | switch (columnDefinition.Type) | ||
| 188 | { | ||
| 189 | case ColumnType.Localized: | ||
| 190 | case ColumnType.Preserved: | ||
| 191 | case ColumnType.String: | ||
| 192 | if (columnDefinition.PrimaryKey) | ||
| 193 | { | ||
| 194 | if (0 < streamName.Length) | ||
| 195 | { | ||
| 196 | streamName.Append("."); | ||
| 197 | } | ||
| 198 | streamName.Append((string)row[i]); | ||
| 199 | } | ||
| 200 | |||
| 201 | record.SetString(i + 1, (string)row[i]); | ||
| 202 | break; | ||
| 203 | case ColumnType.Number: | ||
| 204 | record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture)); | ||
| 205 | break; | ||
| 206 | case ColumnType.Object: | ||
| 207 | if (null != row[i]) | ||
| 208 | { | ||
| 209 | needStream = true; | ||
| 210 | try | ||
| 211 | { | ||
| 212 | record.SetStream(i + 1, (string)row[i]); | ||
| 213 | } | ||
| 214 | catch (Win32Exception e) | ||
| 215 | { | ||
| 216 | if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME | ||
| 217 | { | ||
| 218 | throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i])); | ||
| 219 | } | ||
| 220 | else | ||
| 221 | { | ||
| 222 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | break; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | // stream names are created by concatenating the name of the table with the values | ||
| 231 | // of the primary key (delimited by periods) | ||
| 232 | // check for a stream name that is more than 62 characters long (the maximum allowed length) | ||
| 233 | if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) | ||
| 234 | { | ||
| 235 | Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); | ||
| 236 | } | ||
| 237 | else // add the row to the database | ||
| 238 | { | ||
| 239 | tableView.Modify(ModifyView.Assign, record); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | // Remove rows from the _Streams table for wixpdbs. | ||
| 246 | if ("_Streams" == table.Name) | ||
| 247 | { | ||
| 248 | table.Rows.Clear(); | ||
| 249 | } | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | // Insert substorages (usually transforms inside a patch or instance transforms in a package). | ||
| 254 | if (0 < this.Output.SubStorages.Count) | ||
| 255 | { | ||
| 256 | using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) | ||
| 257 | { | ||
| 258 | foreach (SubStorage subStorage in this.Output.SubStorages) | ||
| 259 | { | ||
| 260 | string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); | ||
| 261 | |||
| 262 | // Bind the transform. | ||
| 263 | this.BindTransform(subStorage.Data, transformFile); | ||
| 264 | |||
| 265 | if (Messaging.Instance.EncounteredError) | ||
| 266 | { | ||
| 267 | continue; | ||
| 268 | } | ||
| 269 | |||
| 270 | // add the storage | ||
| 271 | using (Record record = new Record(2)) | ||
| 272 | { | ||
| 273 | record.SetString(1, subStorage.Name); | ||
| 274 | record.SetStream(2, transformFile); | ||
| 275 | storagesView.Modify(ModifyView.Assign, record); | ||
| 276 | } | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | // We're good, commit the changes to the new database. | ||
| 282 | db.Commit(); | ||
| 283 | } | ||
| 284 | } | ||
| 285 | catch (IOException) | ||
| 286 | { | ||
| 287 | // TODO: this error message doesn't seem specific enough | ||
| 288 | throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | private void BindTransform(Output transform, string outputPath) | ||
| 293 | { | ||
| 294 | BindTransformCommand command = new BindTransformCommand(); | ||
| 295 | command.Extensions = this.Extensions; | ||
| 296 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 297 | command.Transform = transform; | ||
| 298 | command.OutputPath = outputPath; | ||
| 299 | command.TableDefinitions = this.TableDefinitions; | ||
| 300 | command.Execute(); | ||
| 301 | } | ||
| 302 | |||
| 303 | /// <summary> | ||
| 304 | /// Sets the codepage of a database. | ||
| 305 | /// </summary> | ||
| 306 | /// <param name="db">Database to set codepage into.</param> | ||
| 307 | /// <param name="output">Output with the codepage for the database.</param> | ||
| 308 | private void SetDatabaseCodepage(Database db, int codepage) | ||
| 309 | { | ||
| 310 | // write out the _ForceCodepage IDT file | ||
| 311 | string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"); | ||
| 312 | using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) | ||
| 313 | { | ||
| 314 | idtFile.WriteLine(); // dummy column name record | ||
| 315 | idtFile.WriteLine(); // dummy column definition record | ||
| 316 | idtFile.Write(codepage); | ||
| 317 | idtFile.WriteLine("\t_ForceCodepage"); | ||
| 318 | } | ||
| 319 | |||
| 320 | // try to import the table into the MSI | ||
| 321 | try | ||
| 322 | { | ||
| 323 | db.Import(idtPath); | ||
| 324 | } | ||
| 325 | catch (WixInvalidIdtException) | ||
| 326 | { | ||
| 327 | // the IDT should be valid, so an invalid code page was given | ||
| 328 | throw new WixException(WixErrors.IllegalCodepage(codepage)); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs new file mode 100644 index 00000000..caf8b7a7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs | |||
| @@ -0,0 +1,149 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.Bind; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Rows; | ||
| 12 | |||
| 13 | internal class GetFileFacadesCommand | ||
| 14 | { | ||
| 15 | public Table FileTable { private get; set; } | ||
| 16 | |||
| 17 | public Table WixFileTable { private get; set; } | ||
| 18 | |||
| 19 | public Table WixDeltaPatchFileTable { private get; set; } | ||
| 20 | |||
| 21 | public Table WixDeltaPatchSymbolPathsTable { private get; set; } | ||
| 22 | |||
| 23 | public List<FileFacade> FileFacades { get; private set; } | ||
| 24 | |||
| 25 | public void Execute() | ||
| 26 | { | ||
| 27 | List<FileFacade> facades = new List<FileFacade>(this.FileTable.Rows.Count); | ||
| 28 | |||
| 29 | RowDictionary<WixFileRow> wixFiles = new RowDictionary<WixFileRow>(this.WixFileTable); | ||
| 30 | RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles = new RowDictionary<WixDeltaPatchFileRow>(this.WixDeltaPatchFileTable); | ||
| 31 | |||
| 32 | foreach (FileRow file in this.FileTable.Rows) | ||
| 33 | { | ||
| 34 | WixDeltaPatchFileRow deltaPatchFile = null; | ||
| 35 | |||
| 36 | deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile); | ||
| 37 | |||
| 38 | facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile)); | ||
| 39 | } | ||
| 40 | |||
| 41 | if (null != this.WixDeltaPatchSymbolPathsTable) | ||
| 42 | { | ||
| 43 | this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); | ||
| 44 | } | ||
| 45 | |||
| 46 | this.FileFacades = facades; | ||
| 47 | } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. | ||
| 51 | /// </summary> | ||
| 52 | public RowDictionary<WixDeltaPatchFileRow> ResolveDeltaPatchSymbolPaths(RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles, IEnumerable<FileFacade> facades) | ||
| 53 | { | ||
| 54 | ILookup<string, FileFacade> filesByComponent = null; | ||
| 55 | ILookup<string, FileFacade> filesByDirectory = null; | ||
| 56 | ILookup<string, FileFacade> filesByDiskId = null; | ||
| 57 | |||
| 58 | foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs<WixDeltaPatchSymbolPathsRow>().OrderBy(r => r.Type)) | ||
| 59 | { | ||
| 60 | switch (row.Type) | ||
| 61 | { | ||
| 62 | case SymbolPathType.File: | ||
| 63 | this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]); | ||
| 64 | break; | ||
| 65 | |||
| 66 | case SymbolPathType.Component: | ||
| 67 | if (null == filesByComponent) | ||
| 68 | { | ||
| 69 | filesByComponent = facades.ToLookup(f => f.File.Component); | ||
| 70 | } | ||
| 71 | |||
| 72 | foreach (FileFacade facade in filesByComponent[row.Id]) | ||
| 73 | { | ||
| 74 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
| 75 | } | ||
| 76 | break; | ||
| 77 | |||
| 78 | case SymbolPathType.Directory: | ||
| 79 | if (null == filesByDirectory) | ||
| 80 | { | ||
| 81 | filesByDirectory = facades.ToLookup(f => f.WixFile.Directory); | ||
| 82 | } | ||
| 83 | |||
| 84 | foreach (FileFacade facade in filesByDirectory[row.Id]) | ||
| 85 | { | ||
| 86 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
| 87 | } | ||
| 88 | break; | ||
| 89 | |||
| 90 | case SymbolPathType.Media: | ||
| 91 | if (null == filesByDiskId) | ||
| 92 | { | ||
| 93 | filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture)); | ||
| 94 | } | ||
| 95 | |||
| 96 | foreach (FileFacade facade in filesByDiskId[row.Id]) | ||
| 97 | { | ||
| 98 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
| 99 | } | ||
| 100 | break; | ||
| 101 | |||
| 102 | case SymbolPathType.Product: | ||
| 103 | foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values) | ||
| 104 | { | ||
| 105 | this.MergeSymbolPaths(row, fileRow); | ||
| 106 | } | ||
| 107 | break; | ||
| 108 | |||
| 109 | default: | ||
| 110 | // error | ||
| 111 | break; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | return deltaPatchFiles; | ||
| 116 | } | ||
| 117 | |||
| 118 | /// <summary> | ||
| 119 | /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row. | ||
| 120 | /// </summary> | ||
| 121 | /// <param name="row">Row from the WixPatchSymbolsPaths table.</param> | ||
| 122 | /// <param name="file">FileRow into which to set symbol information.</param> | ||
| 123 | /// <comment>This includes PreviousData as well.</comment> | ||
| 124 | private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file) | ||
| 125 | { | ||
| 126 | if (null == file.Symbols) | ||
| 127 | { | ||
| 128 | file.Symbols = row.SymbolPaths; | ||
| 129 | } | ||
| 130 | else | ||
| 131 | { | ||
| 132 | file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths); | ||
| 133 | } | ||
| 134 | |||
| 135 | Field field = row.Fields[2]; | ||
| 136 | if (null != field.PreviousData) | ||
| 137 | { | ||
| 138 | if (null == file.PreviousSymbols) | ||
| 139 | { | ||
| 140 | file.PreviousSymbols = field.PreviousData; | ||
| 141 | } | ||
| 142 | else | ||
| 143 | { | ||
| 144 | file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs new file mode 100644 index 00000000..624cbb43 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs | |||
| @@ -0,0 +1,351 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Collections.Specialized; | ||
| 8 | using System.ComponentModel; | ||
| 9 | using System.Diagnostics; | ||
| 10 | using System.Globalization; | ||
| 11 | using System.IO; | ||
| 12 | using System.Linq; | ||
| 13 | using System.Runtime.InteropServices; | ||
| 14 | using System.Text; | ||
| 15 | using System.Xml; | ||
| 16 | using System.Xml.XPath; | ||
| 17 | using WixToolset.Clr.Interop; | ||
| 18 | using WixToolset.Data; | ||
| 19 | using WixToolset.Data.Rows; | ||
| 20 | using WixToolset.MergeMod; | ||
| 21 | using WixToolset.Msi; | ||
| 22 | using WixToolset.Core.Native; | ||
| 23 | using WixToolset.Core.Bind; | ||
| 24 | |||
| 25 | /// <summary> | ||
| 26 | /// Update file information. | ||
| 27 | /// </summary> | ||
| 28 | internal class MergeModulesCommand | ||
| 29 | { | ||
| 30 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 31 | |||
| 32 | public Output Output { private get; set; } | ||
| 33 | |||
| 34 | public string OutputPath { private get; set; } | ||
| 35 | |||
| 36 | public IEnumerable<string> SuppressedTableNames { private get; set; } | ||
| 37 | |||
| 38 | public string TempFilesLocation { private get; set; } | ||
| 39 | |||
| 40 | public void Execute() | ||
| 41 | { | ||
| 42 | Debug.Assert(OutputType.Product == this.Output.Type); | ||
| 43 | |||
| 44 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
| 45 | Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"]; | ||
| 46 | |||
| 47 | // check for merge rows to see if there is any work to do | ||
| 48 | if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count) | ||
| 49 | { | ||
| 50 | return; | ||
| 51 | } | ||
| 52 | |||
| 53 | IMsmMerge2 merge = null; | ||
| 54 | bool commit = true; | ||
| 55 | bool logOpen = false; | ||
| 56 | bool databaseOpen = false; | ||
| 57 | string logPath = null; | ||
| 58 | try | ||
| 59 | { | ||
| 60 | merge = MsmInterop.GetMsmMerge(); | ||
| 61 | |||
| 62 | logPath = Path.Combine(this.TempFilesLocation, "merge.log"); | ||
| 63 | merge.OpenLog(logPath); | ||
| 64 | logOpen = true; | ||
| 65 | |||
| 66 | merge.OpenDatabase(this.OutputPath); | ||
| 67 | databaseOpen = true; | ||
| 68 | |||
| 69 | // process all the merge rows | ||
| 70 | foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows) | ||
| 71 | { | ||
| 72 | bool moduleOpen = false; | ||
| 73 | |||
| 74 | try | ||
| 75 | { | ||
| 76 | short mergeLanguage; | ||
| 77 | |||
| 78 | try | ||
| 79 | { | ||
| 80 | mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); | ||
| 81 | } | ||
| 82 | catch (System.FormatException) | ||
| 83 | { | ||
| 84 | Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); | ||
| 85 | continue; | ||
| 86 | } | ||
| 87 | |||
| 88 | Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); | ||
| 89 | merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); | ||
| 90 | moduleOpen = true; | ||
| 91 | |||
| 92 | // If there is merge configuration data, create a callback object to contain it all. | ||
| 93 | ConfigurationCallback callback = null; | ||
| 94 | if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) | ||
| 95 | { | ||
| 96 | callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); | ||
| 97 | } | ||
| 98 | |||
| 99 | // merge the module into the database that's being built | ||
| 100 | Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile)); | ||
| 101 | merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback); | ||
| 102 | |||
| 103 | // connect any non-primary features | ||
| 104 | if (null != wixFeatureModulesTable) | ||
| 105 | { | ||
| 106 | foreach (Row row in wixFeatureModulesTable.Rows) | ||
| 107 | { | ||
| 108 | if (wixMergeRow.Id == (string)row[1]) | ||
| 109 | { | ||
| 110 | Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0])); | ||
| 111 | merge.Connect((string)row[0]); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | catch (COMException) | ||
| 117 | { | ||
| 118 | commit = false; | ||
| 119 | } | ||
| 120 | finally | ||
| 121 | { | ||
| 122 | IMsmErrors mergeErrors = merge.Errors; | ||
| 123 | |||
| 124 | // display all the errors encountered during the merge operations for this module | ||
| 125 | for (int i = 1; i <= mergeErrors.Count; i++) | ||
| 126 | { | ||
| 127 | IMsmError mergeError = mergeErrors[i]; | ||
| 128 | StringBuilder databaseKeys = new StringBuilder(); | ||
| 129 | StringBuilder moduleKeys = new StringBuilder(); | ||
| 130 | |||
| 131 | // build a string of the database keys | ||
| 132 | for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++) | ||
| 133 | { | ||
| 134 | if (1 != j) | ||
| 135 | { | ||
| 136 | databaseKeys.Append(';'); | ||
| 137 | } | ||
| 138 | databaseKeys.Append(mergeError.DatabaseKeys[j]); | ||
| 139 | } | ||
| 140 | |||
| 141 | // build a string of the module keys | ||
| 142 | for (int j = 1; j <= mergeError.ModuleKeys.Count; j++) | ||
| 143 | { | ||
| 144 | if (1 != j) | ||
| 145 | { | ||
| 146 | moduleKeys.Append(';'); | ||
| 147 | } | ||
| 148 | moduleKeys.Append(mergeError.ModuleKeys[j]); | ||
| 149 | } | ||
| 150 | |||
| 151 | // display the merge error based on the msm error type | ||
| 152 | switch (mergeError.Type) | ||
| 153 | { | ||
| 154 | case MsmErrorType.msmErrorExclusion: | ||
| 155 | Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); | ||
| 156 | break; | ||
| 157 | case MsmErrorType.msmErrorFeatureRequired: | ||
| 158 | Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); | ||
| 159 | break; | ||
| 160 | case MsmErrorType.msmErrorLanguageFailed: | ||
| 161 | Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); | ||
| 162 | break; | ||
| 163 | case MsmErrorType.msmErrorLanguageUnsupported: | ||
| 164 | Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); | ||
| 165 | break; | ||
| 166 | case MsmErrorType.msmErrorResequenceMerge: | ||
| 167 | Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); | ||
| 168 | break; | ||
| 169 | case MsmErrorType.msmErrorTableMerge: | ||
| 170 | if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table | ||
| 171 | { | ||
| 172 | Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); | ||
| 173 | } | ||
| 174 | break; | ||
| 175 | case MsmErrorType.msmErrorPlatformMismatch: | ||
| 176 | Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); | ||
| 177 | break; | ||
| 178 | default: | ||
| 179 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); | ||
| 180 | break; | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | if (0 >= mergeErrors.Count && !commit) | ||
| 185 | { | ||
| 186 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); | ||
| 187 | } | ||
| 188 | |||
| 189 | if (moduleOpen) | ||
| 190 | { | ||
| 191 | merge.CloseModule(); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | finally | ||
| 197 | { | ||
| 198 | if (databaseOpen) | ||
| 199 | { | ||
| 200 | merge.CloseDatabase(commit); | ||
| 201 | } | ||
| 202 | |||
| 203 | if (logOpen) | ||
| 204 | { | ||
| 205 | merge.CloseLog(); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | // stop processing if an error previously occurred | ||
| 210 | if (Messaging.Instance.EncounteredError) | ||
| 211 | { | ||
| 212 | return; | ||
| 213 | } | ||
| 214 | |||
| 215 | using (Database db = new Database(this.OutputPath, OpenDatabase.Direct)) | ||
| 216 | { | ||
| 217 | Table suppressActionTable = this.Output.Tables["WixSuppressAction"]; | ||
| 218 | |||
| 219 | // suppress individual actions | ||
| 220 | if (null != suppressActionTable) | ||
| 221 | { | ||
| 222 | foreach (Row row in suppressActionTable.Rows) | ||
| 223 | { | ||
| 224 | if (db.TableExists((string)row[0])) | ||
| 225 | { | ||
| 226 | string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); | ||
| 227 | |||
| 228 | using (View view = db.OpenExecuteView(query)) | ||
| 229 | { | ||
| 230 | using (Record record = view.Fetch()) | ||
| 231 | { | ||
| 232 | if (null != record) | ||
| 233 | { | ||
| 234 | Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); | ||
| 235 | view.Modify(ModifyView.Delete, record); | ||
| 236 | } | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | // query for merge module actions in suppressed sequences and drop them | ||
| 244 | foreach (string tableName in this.SuppressedTableNames) | ||
| 245 | { | ||
| 246 | if (!db.TableExists(tableName)) | ||
| 247 | { | ||
| 248 | continue; | ||
| 249 | } | ||
| 250 | |||
| 251 | using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) | ||
| 252 | { | ||
| 253 | while (true) | ||
| 254 | { | ||
| 255 | using (Record resultRecord = view.Fetch()) | ||
| 256 | { | ||
| 257 | if (null == resultRecord) | ||
| 258 | { | ||
| 259 | break; | ||
| 260 | } | ||
| 261 | |||
| 262 | Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | // drop suppressed sequences | ||
| 268 | using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) | ||
| 269 | { | ||
| 270 | } | ||
| 271 | |||
| 272 | // delete the validation rows | ||
| 273 | using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) | ||
| 274 | { | ||
| 275 | using (Record record = new Record(1)) | ||
| 276 | { | ||
| 277 | record.SetString(1, tableName); | ||
| 278 | view.Execute(record); | ||
| 279 | } | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | // now update the Attributes column for the files from the Merge Modules | ||
| 284 | Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); | ||
| 285 | using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) | ||
| 286 | { | ||
| 287 | foreach (FileFacade file in this.FileFacades) | ||
| 288 | { | ||
| 289 | if (!file.FromModule) | ||
| 290 | { | ||
| 291 | continue; | ||
| 292 | } | ||
| 293 | |||
| 294 | using (Record record = new Record(1)) | ||
| 295 | { | ||
| 296 | record.SetString(1, file.File.File); | ||
| 297 | view.Execute(record); | ||
| 298 | } | ||
| 299 | |||
| 300 | using (Record recordUpdate = view.Fetch()) | ||
| 301 | { | ||
| 302 | if (null == recordUpdate) | ||
| 303 | { | ||
| 304 | throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); | ||
| 305 | } | ||
| 306 | |||
| 307 | recordUpdate.SetInteger(1, file.File.Sequence); | ||
| 308 | |||
| 309 | // update the file attributes to match the compression specified | ||
| 310 | // on the Merge element or on the Package element | ||
| 311 | int attributes = 0; | ||
| 312 | |||
| 313 | // get the current value if its not null | ||
| 314 | if (!recordUpdate.IsNull(2)) | ||
| 315 | { | ||
| 316 | attributes = recordUpdate.GetInteger(2); | ||
| 317 | } | ||
| 318 | |||
| 319 | if (YesNoType.Yes == file.File.Compressed) | ||
| 320 | { | ||
| 321 | // these are mutually exclusive | ||
| 322 | attributes |= MsiInterop.MsidbFileAttributesCompressed; | ||
| 323 | attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 324 | } | ||
| 325 | else if (YesNoType.No == file.File.Compressed) | ||
| 326 | { | ||
| 327 | // these are mutually exclusive | ||
| 328 | attributes |= MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 329 | attributes &= ~MsiInterop.MsidbFileAttributesCompressed; | ||
| 330 | } | ||
| 331 | else // not specified | ||
| 332 | { | ||
| 333 | Debug.Assert(YesNoType.NotSet == file.File.Compressed); | ||
| 334 | |||
| 335 | // clear any compression bits | ||
| 336 | attributes &= ~MsiInterop.MsidbFileAttributesCompressed; | ||
| 337 | attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 338 | } | ||
| 339 | |||
| 340 | recordUpdate.SetInteger(2, attributes); | ||
| 341 | |||
| 342 | view.Modify(ModifyView.Update, recordUpdate); | ||
| 343 | } | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | db.Commit(); | ||
| 348 | } | ||
| 349 | } | ||
| 350 | } | ||
| 351 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs new file mode 100644 index 00000000..b3c09b9e --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs | |||
| @@ -0,0 +1,118 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.IO; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Rows; | ||
| 11 | using WixToolset.Msi; | ||
| 12 | using WixToolset.Core.Native; | ||
| 13 | using WixToolset.Bind; | ||
| 14 | using WixToolset.Core.Bind; | ||
| 15 | using WixToolset.Data.Bind; | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// Defines the file transfers necessary to layout the uncompressed files. | ||
| 19 | /// </summary> | ||
| 20 | internal class ProcessUncompressedFilesCommand | ||
| 21 | { | ||
| 22 | public string DatabasePath { private get; set; } | ||
| 23 | |||
| 24 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 25 | |||
| 26 | public RowDictionary<MediaRow> MediaRows { private get; set; } | ||
| 27 | |||
| 28 | public string LayoutDirectory { private get; set; } | ||
| 29 | |||
| 30 | public bool Compressed { private get; set; } | ||
| 31 | |||
| 32 | public bool LongNamesInImage { private get; set; } | ||
| 33 | |||
| 34 | public Func<MediaRow, string, string, string> ResolveMedia { private get; set; } | ||
| 35 | |||
| 36 | public Table WixMediaTable { private get; set; } | ||
| 37 | |||
| 38 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
| 39 | |||
| 40 | public void Execute() | ||
| 41 | { | ||
| 42 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
| 43 | |||
| 44 | Hashtable directories = new Hashtable(); | ||
| 45 | |||
| 46 | RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable); | ||
| 47 | |||
| 48 | using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) | ||
| 49 | { | ||
| 50 | using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) | ||
| 51 | { | ||
| 52 | while (true) | ||
| 53 | { | ||
| 54 | using (Record directoryRecord = directoryView.Fetch()) | ||
| 55 | { | ||
| 56 | if (null == directoryRecord) | ||
| 57 | { | ||
| 58 | break; | ||
| 59 | } | ||
| 60 | |||
| 61 | string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage); | ||
| 62 | |||
| 63 | directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) | ||
| 69 | { | ||
| 70 | using (Record fileQueryRecord = new Record(1)) | ||
| 71 | { | ||
| 72 | // for each file in the array of uncompressed files | ||
| 73 | foreach (FileFacade facade in this.FileFacades) | ||
| 74 | { | ||
| 75 | MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId); | ||
| 76 | string relativeFileLayoutPath = null; | ||
| 77 | |||
| 78 | WixMediaRow wixMediaRow = null; | ||
| 79 | string mediaLayoutFolder = null; | ||
| 80 | |||
| 81 | if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) | ||
| 82 | { | ||
| 83 | mediaLayoutFolder = wixMediaRow.Layout; | ||
| 84 | } | ||
| 85 | |||
| 86 | string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); | ||
| 87 | |||
| 88 | // setup up the query record and find the appropriate file in the | ||
| 89 | // previously executed file view | ||
| 90 | fileQueryRecord[1] = facade.File.File; | ||
| 91 | fileView.Execute(fileQueryRecord); | ||
| 92 | |||
| 93 | using (Record fileRecord = fileView.Fetch()) | ||
| 94 | { | ||
| 95 | if (null == fileRecord) | ||
| 96 | { | ||
| 97 | throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File)); | ||
| 98 | } | ||
| 99 | |||
| 100 | relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); | ||
| 101 | } | ||
| 102 | |||
| 103 | // finally put together the base media layout path and the relative file layout path | ||
| 104 | string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); | ||
| 105 | FileTransfer transfer; | ||
| 106 | if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer)) | ||
| 107 | { | ||
| 108 | fileTransfers.Add(transfer); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | this.FileTransfers = fileTransfers; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs new file mode 100644 index 00000000..7da32206 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs | |||
| @@ -0,0 +1,80 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Rows; | ||
| 9 | |||
| 10 | internal class UpdateControlTextCommand | ||
| 11 | { | ||
| 12 | public Table BBControlTable { private get; set; } | ||
| 13 | |||
| 14 | public Table WixBBControlTable { private get; set; } | ||
| 15 | |||
| 16 | public Table ControlTable { private get; set; } | ||
| 17 | |||
| 18 | public Table WixControlTable { private get; set; } | ||
| 19 | |||
| 20 | public void Execute() | ||
| 21 | { | ||
| 22 | if (null != this.WixBBControlTable) | ||
| 23 | { | ||
| 24 | RowDictionary<BBControlRow> bbControlRows = new RowDictionary<BBControlRow>(this.BBControlTable); | ||
| 25 | foreach (Row wixRow in this.WixBBControlTable.Rows) | ||
| 26 | { | ||
| 27 | BBControlRow bbControlRow = bbControlRows.Get(wixRow.GetPrimaryKey()); | ||
| 28 | bbControlRow.Text = this.ReadTextFile(bbControlRow.SourceLineNumbers, wixRow.FieldAsString(2)); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | if (null != this.WixControlTable) | ||
| 33 | { | ||
| 34 | RowDictionary<ControlRow> controlRows = new RowDictionary<ControlRow>(this.ControlTable); | ||
| 35 | foreach (Row wixRow in this.WixControlTable.Rows) | ||
| 36 | { | ||
| 37 | ControlRow controlRow = controlRows.Get(wixRow.GetPrimaryKey()); | ||
| 38 | controlRow.Text = this.ReadTextFile(controlRow.SourceLineNumbers, wixRow.FieldAsString(2)); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// Reads a text file and returns the contents. | ||
| 45 | /// </summary> | ||
| 46 | /// <param name="sourceLineNumbers">Source line numbers for row from source.</param> | ||
| 47 | /// <param name="source">Source path to file to read.</param> | ||
| 48 | /// <returns>Text string read from file.</returns> | ||
| 49 | private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source) | ||
| 50 | { | ||
| 51 | string text = null; | ||
| 52 | |||
| 53 | try | ||
| 54 | { | ||
| 55 | using (StreamReader reader = new StreamReader(source)) | ||
| 56 | { | ||
| 57 | text = reader.ReadToEnd(); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | catch (DirectoryNotFoundException e) | ||
| 61 | { | ||
| 62 | Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); | ||
| 63 | } | ||
| 64 | catch (FileNotFoundException e) | ||
| 65 | { | ||
| 66 | Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); | ||
| 67 | } | ||
| 68 | catch (IOException e) | ||
| 69 | { | ||
| 70 | Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); | ||
| 71 | } | ||
| 72 | catch (NotSupportedException) | ||
| 73 | { | ||
| 74 | Messaging.Instance.OnMessage(WixErrors.FileNotFound(sourceLineNumbers, source)); | ||
| 75 | } | ||
| 76 | |||
| 77 | return text; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs new file mode 100644 index 00000000..cd9444ee --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs | |||
| @@ -0,0 +1,533 @@ | |||
| 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.WindowsInstaller.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Collections.Specialized; | ||
| 8 | using System.ComponentModel; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.IO; | ||
| 11 | using System.Linq; | ||
| 12 | using System.Xml; | ||
| 13 | using System.Xml.XPath; | ||
| 14 | using WixToolset.Clr.Interop; | ||
| 15 | using WixToolset.Core.Bind; | ||
| 16 | using WixToolset.Data; | ||
| 17 | using WixToolset.Data.Rows; | ||
| 18 | using WixToolset.Msi; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Update file information. | ||
| 22 | /// </summary> | ||
| 23 | internal class UpdateFileFacadesCommand | ||
| 24 | { | ||
| 25 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 26 | |||
| 27 | public IEnumerable<FileFacade> UpdateFileFacades { private get; set; } | ||
| 28 | |||
| 29 | public string ModularizationGuid { private get; set; } | ||
| 30 | |||
| 31 | public Output Output { private get; set; } | ||
| 32 | |||
| 33 | public bool OverwriteHash { private get; set; } | ||
| 34 | |||
| 35 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 36 | |||
| 37 | public IDictionary<string, string> VariableCache { private get; set; } | ||
| 38 | |||
| 39 | public void Execute() | ||
| 40 | { | ||
| 41 | foreach (FileFacade file in this.UpdateFileFacades) | ||
| 42 | { | ||
| 43 | this.UpdateFileFacade(file); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | private void UpdateFileFacade(FileFacade file) | ||
| 48 | { | ||
| 49 | FileInfo fileInfo = null; | ||
| 50 | try | ||
| 51 | { | ||
| 52 | fileInfo = new FileInfo(file.WixFile.Source); | ||
| 53 | } | ||
| 54 | catch (ArgumentException) | ||
| 55 | { | ||
| 56 | Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
| 57 | return; | ||
| 58 | } | ||
| 59 | catch (PathTooLongException) | ||
| 60 | { | ||
| 61 | Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
| 62 | return; | ||
| 63 | } | ||
| 64 | catch (NotSupportedException) | ||
| 65 | { | ||
| 66 | Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
| 67 | return; | ||
| 68 | } | ||
| 69 | |||
| 70 | if (!fileInfo.Exists) | ||
| 71 | { | ||
| 72 | Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source)); | ||
| 73 | return; | ||
| 74 | } | ||
| 75 | |||
| 76 | using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) | ||
| 77 | { | ||
| 78 | if (Int32.MaxValue < fileStream.Length) | ||
| 79 | { | ||
| 80 | throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
| 81 | } | ||
| 82 | |||
| 83 | file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); | ||
| 84 | } | ||
| 85 | |||
| 86 | string version = null; | ||
| 87 | string language = null; | ||
| 88 | try | ||
| 89 | { | ||
| 90 | Installer.GetFileVersion(fileInfo.FullName, out version, out language); | ||
| 91 | } | ||
| 92 | catch (Win32Exception e) | ||
| 93 | { | ||
| 94 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | ||
| 95 | { | ||
| 96 | throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); | ||
| 97 | } | ||
| 98 | else | ||
| 99 | { | ||
| 100 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. | ||
| 105 | if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table | ||
| 106 | { | ||
| 107 | if (!this.OverwriteHash) | ||
| 108 | { | ||
| 109 | // not overwriting hash, so don't do the rest of these options. | ||
| 110 | } | ||
| 111 | else if (null != file.File.Version) | ||
| 112 | { | ||
| 113 | // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks | ||
| 114 | // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up. | ||
| 115 | // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing | ||
| 116 | // all the file rows) for a relatively uncommon situation. Let's not do that. | ||
| 117 | // | ||
| 118 | // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version | ||
| 119 | // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. | ||
| 120 | if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) | ||
| 121 | { | ||
| 122 | Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File)); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | else | ||
| 126 | { | ||
| 127 | if (null != file.File.Language) | ||
| 128 | { | ||
| 129 | Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); | ||
| 130 | } | ||
| 131 | |||
| 132 | int[] hash; | ||
| 133 | try | ||
| 134 | { | ||
| 135 | Installer.GetFileHash(fileInfo.FullName, 0, out hash); | ||
| 136 | } | ||
| 137 | catch (Win32Exception e) | ||
| 138 | { | ||
| 139 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | ||
| 140 | { | ||
| 141 | throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); | ||
| 142 | } | ||
| 143 | else | ||
| 144 | { | ||
| 145 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | if (null == file.Hash) | ||
| 150 | { | ||
| 151 | Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]); | ||
| 152 | file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers); | ||
| 153 | } | ||
| 154 | |||
| 155 | file.Hash[0] = file.File.File; | ||
| 156 | file.Hash[1] = 0; | ||
| 157 | file.Hash[2] = hash[0]; | ||
| 158 | file.Hash[3] = hash[1]; | ||
| 159 | file.Hash[4] = hash[2]; | ||
| 160 | file.Hash[5] = hash[3]; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | else // update the file row with the version and language information. | ||
| 164 | { | ||
| 165 | // If no version was provided by the user, use the version from the file itself. | ||
| 166 | // This is the most common case. | ||
| 167 | if (String.IsNullOrEmpty(file.File.Version)) | ||
| 168 | { | ||
| 169 | file.File.Version = version; | ||
| 170 | } | ||
| 171 | else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below. | ||
| 172 | { | ||
| 173 | // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching | ||
| 174 | // the version value). We didn't find it so, we will override the default version they provided with the actual | ||
| 175 | // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match | ||
| 176 | // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case | ||
| 177 | // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more | ||
| 178 | // CPU intensive search to save on the memory intensive index that wouldn't be used much. | ||
| 179 | // | ||
| 180 | // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. | ||
| 181 | // That's typically even more rare than companion files so again, no index, just search. | ||
| 182 | file.File.Version = version; | ||
| 183 | } | ||
| 184 | |||
| 185 | if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language)) | ||
| 186 | { | ||
| 187 | Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); | ||
| 188 | } | ||
| 189 | else // override the default provided by the user (usually nothing) with the actual language from the file itself. | ||
| 190 | { | ||
| 191 | file.File.Language = language; | ||
| 192 | } | ||
| 193 | |||
| 194 | // Populate the binder variables for this file information if requested. | ||
| 195 | if (null != this.VariableCache) | ||
| 196 | { | ||
| 197 | if (!String.IsNullOrEmpty(file.File.Version)) | ||
| 198 | { | ||
| 199 | string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)); | ||
| 200 | this.VariableCache[key] = file.File.Version; | ||
| 201 | } | ||
| 202 | |||
| 203 | if (!String.IsNullOrEmpty(file.File.Language)) | ||
| 204 | { | ||
| 205 | string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", Common.Demodularize(this.Output.Type, ModularizationGuid, file.File.File)); | ||
| 206 | this.VariableCache[key] = file.File.Language; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | // If this is a CLR assembly, load the assembly and get the assembly name information | ||
| 212 | if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType) | ||
| 213 | { | ||
| 214 | bool targetNetfx1 = false; | ||
| 215 | StringDictionary assemblyNameValues = new StringDictionary(); | ||
| 216 | |||
| 217 | ClrInterop.IReferenceIdentity referenceIdentity = null; | ||
| 218 | Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid; | ||
| 219 | uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity); | ||
| 220 | if (0 == result && null != referenceIdentity) | ||
| 221 | { | ||
| 222 | string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion"); | ||
| 223 | if (null != imageRuntimeVersion) | ||
| 224 | { | ||
| 225 | targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase); | ||
| 226 | } | ||
| 227 | |||
| 228 | string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral"; | ||
| 229 | assemblyNameValues.Add("Culture", culture); | ||
| 230 | |||
| 231 | string name = referenceIdentity.GetAttribute(null, "Name"); | ||
| 232 | if (null != name) | ||
| 233 | { | ||
| 234 | assemblyNameValues.Add("Name", name); | ||
| 235 | } | ||
| 236 | |||
| 237 | string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); | ||
| 238 | if (null != processorArchitecture) | ||
| 239 | { | ||
| 240 | assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); | ||
| 241 | } | ||
| 242 | |||
| 243 | string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); | ||
| 244 | if (null != publicKeyToken) | ||
| 245 | { | ||
| 246 | bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase)); | ||
| 247 | |||
| 248 | // Managed code expects "null" instead of "neutral", and | ||
| 249 | // this won't be installed to the GAC since it's not signed anyway. | ||
| 250 | assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant()); | ||
| 251 | assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken); | ||
| 252 | } | ||
| 253 | else if (file.WixFile.AssemblyApplication == null) | ||
| 254 | { | ||
| 255 | throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component)); | ||
| 256 | } | ||
| 257 | |||
| 258 | string assemblyVersion = referenceIdentity.GetAttribute(null, "Version"); | ||
| 259 | if (null != version) | ||
| 260 | { | ||
| 261 | assemblyNameValues.Add("Version", assemblyVersion); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | else | ||
| 265 | { | ||
| 266 | Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result))); | ||
| 267 | return; | ||
| 268 | } | ||
| 269 | |||
| 270 | Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
| 271 | if (assemblyNameValues.ContainsKey("name")) | ||
| 272 | { | ||
| 273 | this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]); | ||
| 274 | } | ||
| 275 | |||
| 276 | if (!String.IsNullOrEmpty(version)) | ||
| 277 | { | ||
| 278 | this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version); | ||
| 279 | } | ||
| 280 | |||
| 281 | if (assemblyNameValues.ContainsKey("version")) | ||
| 282 | { | ||
| 283 | string assemblyVersion = assemblyNameValues["version"]; | ||
| 284 | |||
| 285 | if (!targetNetfx1) | ||
| 286 | { | ||
| 287 | // There is a bug in v1 fusion that requires the assembly's "version" attribute | ||
| 288 | // to be equal to or longer than the "fileVersion" in length when its present; | ||
| 289 | // the workaround is to prepend zeroes to the last version number in the assembly | ||
| 290 | // version. | ||
| 291 | if (null != version && version.Length > assemblyVersion.Length) | ||
| 292 | { | ||
| 293 | string padding = new string('0', version.Length - assemblyVersion.Length); | ||
| 294 | string[] assemblyVersionNumbers = assemblyVersion.Split('.'); | ||
| 295 | |||
| 296 | if (assemblyVersionNumbers.Length > 0) | ||
| 297 | { | ||
| 298 | assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); | ||
| 299 | assemblyVersion = String.Join(".", assemblyVersionNumbers); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion); | ||
| 305 | } | ||
| 306 | |||
| 307 | if (assemblyNameValues.ContainsKey("culture")) | ||
| 308 | { | ||
| 309 | this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]); | ||
| 310 | } | ||
| 311 | |||
| 312 | if (assemblyNameValues.ContainsKey("publicKeyToken")) | ||
| 313 | { | ||
| 314 | this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]); | ||
| 315 | } | ||
| 316 | |||
| 317 | if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) | ||
| 318 | { | ||
| 319 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); | ||
| 320 | } | ||
| 321 | |||
| 322 | if (assemblyNameValues.ContainsKey("processorArchitecture")) | ||
| 323 | { | ||
| 324 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]); | ||
| 325 | } | ||
| 326 | |||
| 327 | // add the assembly name to the information cache | ||
| 328 | if (null != this.VariableCache) | ||
| 329 | { | ||
| 330 | string fileId = Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File); | ||
| 331 | string key = String.Concat("assemblyfullname.", fileId); | ||
| 332 | string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); | ||
| 333 | if (assemblyNameValues.ContainsKey("processorArchitecture")) | ||
| 334 | { | ||
| 335 | assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]); | ||
| 336 | } | ||
| 337 | |||
| 338 | this.VariableCache[key] = assemblyName; | ||
| 339 | |||
| 340 | // Add entries with the preserved case publicKeyToken | ||
| 341 | string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId); | ||
| 342 | this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]); | ||
| 343 | |||
| 344 | string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId); | ||
| 345 | this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"]; | ||
| 346 | } | ||
| 347 | } | ||
| 348 | else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType) | ||
| 349 | { | ||
| 350 | // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through | ||
| 351 | // all files like this. Even though this is a rare case it looks like we might be able to index the | ||
| 352 | // file earlier. | ||
| 353 | FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal)); | ||
| 354 | if (null == fileManifest) | ||
| 355 | { | ||
| 356 | Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest)); | ||
| 357 | } | ||
| 358 | |||
| 359 | string win32Type = null; | ||
| 360 | string win32Name = null; | ||
| 361 | string win32Version = null; | ||
| 362 | string win32ProcessorArchitecture = null; | ||
| 363 | string win32PublicKeyToken = null; | ||
| 364 | |||
| 365 | // loading the dom is expensive we want more performant APIs than the DOM | ||
| 366 | // Navigator is cheaper than dom. Perhaps there is a cheaper API still. | ||
| 367 | try | ||
| 368 | { | ||
| 369 | XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source); | ||
| 370 | XPathNavigator nav = doc.CreateNavigator(); | ||
| 371 | nav.MoveToRoot(); | ||
| 372 | |||
| 373 | // this assumes a particular schema for a win32 manifest and does not | ||
| 374 | // provide error checking if the file does not conform to schema. | ||
| 375 | // The fallback case here is that nothing is added to the MsiAssemblyName | ||
| 376 | // table for an out of tolerance Win32 manifest. Perhaps warnings needed. | ||
| 377 | if (nav.MoveToFirstChild()) | ||
| 378 | { | ||
| 379 | while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") | ||
| 380 | { | ||
| 381 | nav.MoveToNext(); | ||
| 382 | } | ||
| 383 | |||
| 384 | if (nav.MoveToFirstChild()) | ||
| 385 | { | ||
| 386 | bool hasNextSibling = true; | ||
| 387 | while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) | ||
| 388 | { | ||
| 389 | hasNextSibling = nav.MoveToNext(); | ||
| 390 | } | ||
| 391 | if (!hasNextSibling) | ||
| 392 | { | ||
| 393 | Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source)); | ||
| 394 | return; | ||
| 395 | } | ||
| 396 | |||
| 397 | if (nav.MoveToAttribute("type", String.Empty)) | ||
| 398 | { | ||
| 399 | win32Type = nav.Value; | ||
| 400 | nav.MoveToParent(); | ||
| 401 | } | ||
| 402 | |||
| 403 | if (nav.MoveToAttribute("name", String.Empty)) | ||
| 404 | { | ||
| 405 | win32Name = nav.Value; | ||
| 406 | nav.MoveToParent(); | ||
| 407 | } | ||
| 408 | |||
| 409 | if (nav.MoveToAttribute("version", String.Empty)) | ||
| 410 | { | ||
| 411 | win32Version = nav.Value; | ||
| 412 | nav.MoveToParent(); | ||
| 413 | } | ||
| 414 | |||
| 415 | if (nav.MoveToAttribute("processorArchitecture", String.Empty)) | ||
| 416 | { | ||
| 417 | win32ProcessorArchitecture = nav.Value; | ||
| 418 | nav.MoveToParent(); | ||
| 419 | } | ||
| 420 | |||
| 421 | if (nav.MoveToAttribute("publicKeyToken", String.Empty)) | ||
| 422 | { | ||
| 423 | win32PublicKeyToken = nav.Value; | ||
| 424 | nav.MoveToParent(); | ||
| 425 | } | ||
| 426 | } | ||
| 427 | } | ||
| 428 | } | ||
| 429 | catch (FileNotFoundException fe) | ||
| 430 | { | ||
| 431 | Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest")); | ||
| 432 | } | ||
| 433 | catch (XmlException xe) | ||
| 434 | { | ||
| 435 | Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message)); | ||
| 436 | } | ||
| 437 | |||
| 438 | Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
| 439 | if (!String.IsNullOrEmpty(win32Name)) | ||
| 440 | { | ||
| 441 | this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name); | ||
| 442 | } | ||
| 443 | |||
| 444 | if (!String.IsNullOrEmpty(win32Version)) | ||
| 445 | { | ||
| 446 | this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version); | ||
| 447 | } | ||
| 448 | |||
| 449 | if (!String.IsNullOrEmpty(win32Type)) | ||
| 450 | { | ||
| 451 | this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type); | ||
| 452 | } | ||
| 453 | |||
| 454 | if (!String.IsNullOrEmpty(win32ProcessorArchitecture)) | ||
| 455 | { | ||
| 456 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture); | ||
| 457 | } | ||
| 458 | |||
| 459 | if (!String.IsNullOrEmpty(win32PublicKeyToken)) | ||
| 460 | { | ||
| 461 | this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken); | ||
| 462 | } | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | /// <summary> | ||
| 467 | /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise | ||
| 468 | /// create a new row. | ||
| 469 | /// </summary> | ||
| 470 | /// <param name="assemblyNameTable">MsiAssemblyName table.</param> | ||
| 471 | /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param> | ||
| 472 | /// <param name="name">MsiAssemblyName name.</param> | ||
| 473 | /// <param name="value">MsiAssemblyName value.</param> | ||
| 474 | private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value) | ||
| 475 | { | ||
| 476 | // check for null value (this can occur when grabbing the file version from an assembly without one) | ||
| 477 | if (String.IsNullOrEmpty(value)) | ||
| 478 | { | ||
| 479 | Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name)); | ||
| 480 | } | ||
| 481 | else | ||
| 482 | { | ||
| 483 | Row assemblyNameRow = null; | ||
| 484 | |||
| 485 | // override directly authored value | ||
| 486 | foreach (Row row in assemblyNameTable.Rows) | ||
| 487 | { | ||
| 488 | if ((string)row[0] == file.File.Component && (string)row[1] == name) | ||
| 489 | { | ||
| 490 | assemblyNameRow = row; | ||
| 491 | break; | ||
| 492 | } | ||
| 493 | } | ||
| 494 | |||
| 495 | // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. | ||
| 496 | if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType && | ||
| 497 | String.IsNullOrEmpty(file.WixFile.AssemblyApplication) && | ||
| 498 | !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase)) | ||
| 499 | { | ||
| 500 | Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value)); | ||
| 501 | } | ||
| 502 | |||
| 503 | if (null == assemblyNameRow) | ||
| 504 | { | ||
| 505 | assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers); | ||
| 506 | assemblyNameRow[0] = file.File.Component; | ||
| 507 | assemblyNameRow[1] = name; | ||
| 508 | assemblyNameRow[2] = value; | ||
| 509 | |||
| 510 | // put the MsiAssemblyName row in the same section as the related File row | ||
| 511 | assemblyNameRow.SectionId = file.File.SectionId; | ||
| 512 | |||
| 513 | if (null == file.AssemblyNames) | ||
| 514 | { | ||
| 515 | file.AssemblyNames = new List<Row>(); | ||
| 516 | } | ||
| 517 | |||
| 518 | file.AssemblyNames.Add(assemblyNameRow); | ||
| 519 | } | ||
| 520 | else | ||
| 521 | { | ||
| 522 | assemblyNameRow[2] = value; | ||
| 523 | } | ||
| 524 | |||
| 525 | if (this.VariableCache != null) | ||
| 526 | { | ||
| 527 | string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant(); | ||
| 528 | this.VariableCache[key] = (string)assemblyNameRow[2]; | ||
| 529 | } | ||
| 530 | } | ||
| 531 | } | ||
| 532 | } | ||
| 533 | } | ||
