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/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/Bind')
47 files changed, 377 insertions, 10061 deletions
diff --git a/src/WixToolset.Core/Bind/BindBundleCommand.cs b/src/WixToolset.Core/Bind/BindBundleCommand.cs deleted file mode 100644 index 7ea0c830..00000000 --- a/src/WixToolset.Core/Bind/BindBundleCommand.cs +++ /dev/null | |||
| @@ -1,905 +0,0 @@ | |||
| 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.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Reflection; | ||
| 12 | using WixToolset.Bind.Bundles; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Rows; | ||
| 15 | using WixToolset.Extensibility; | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// Binds a this.bundle. | ||
| 19 | /// </summary> | ||
| 20 | internal class BindBundleCommand : ICommand | ||
| 21 | { | ||
| 22 | public CompressionLevel DefaultCompressionLevel { private get; set; } | ||
| 23 | |||
| 24 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
| 25 | |||
| 26 | public BinderFileManagerCore FileManagerCore { private get; set; } | ||
| 27 | |||
| 28 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | ||
| 29 | |||
| 30 | public Output Output { private get; set; } | ||
| 31 | |||
| 32 | public string OutputPath { private get; set; } | ||
| 33 | |||
| 34 | public string PdbFile { private get; set; } | ||
| 35 | |||
| 36 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 37 | |||
| 38 | public string TempFilesLocation { private get; set; } | ||
| 39 | |||
| 40 | public WixVariableResolver WixVariableResolver { private get; set; } | ||
| 41 | |||
| 42 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
| 43 | |||
| 44 | public IEnumerable<string> ContentFilePaths { get; private set; } | ||
| 45 | |||
| 46 | public void Execute() | ||
| 47 | { | ||
| 48 | this.FileTransfers = Enumerable.Empty<FileTransfer>(); | ||
| 49 | this.ContentFilePaths = Enumerable.Empty<string>(); | ||
| 50 | |||
| 51 | // First look for data we expect to find... Chain, WixGroups, etc. | ||
| 52 | |||
| 53 | // We shouldn't really get past the linker phase if there are | ||
| 54 | // no group items... that means that there's no UX, no Chain, | ||
| 55 | // *and* no Containers! | ||
| 56 | Table chainPackageTable = this.GetRequiredTable("WixBundlePackage"); | ||
| 57 | |||
| 58 | Table wixGroupTable = this.GetRequiredTable("WixGroup"); | ||
| 59 | |||
| 60 | // Ensure there is one and only one row in the WixBundle table. | ||
| 61 | // The compiler and linker behavior should have colluded to get | ||
| 62 | // this behavior. | ||
| 63 | WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle"); | ||
| 64 | |||
| 65 | bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user. | ||
| 66 | |||
| 67 | // Ensure there is one and only one row in the WixBootstrapperApplication table. | ||
| 68 | // The compiler and linker behavior should have colluded to get | ||
| 69 | // this behavior. | ||
| 70 | Row baRow = this.GetSingleRowTable("WixBootstrapperApplication"); | ||
| 71 | |||
| 72 | // Ensure there is one and only one row in the WixChain table. | ||
| 73 | // The compiler and linker behavior should have colluded to get | ||
| 74 | // this behavior. | ||
| 75 | WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain"); | ||
| 76 | |||
| 77 | if (Messaging.Instance.EncounteredError) | ||
| 78 | { | ||
| 79 | return; | ||
| 80 | } | ||
| 81 | |||
| 82 | // Localize fields, resolve wix variables, and resolve file paths. | ||
| 83 | ExtractEmbeddedFiles filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); | ||
| 84 | |||
| 85 | IEnumerable<DelayedField> delayedFields; | ||
| 86 | { | ||
| 87 | ResolveFieldsCommand command = new ResolveFieldsCommand(); | ||
| 88 | command.Tables = this.Output.Tables; | ||
| 89 | command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 90 | command.FileManagerCore = this.FileManagerCore; | ||
| 91 | command.FileManagers = this.FileManagers; | ||
| 92 | command.SupportDelayedResolution = true; | ||
| 93 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 94 | command.WixVariableResolver = this.WixVariableResolver; | ||
| 95 | command.Execute(); | ||
| 96 | |||
| 97 | delayedFields = command.DelayedFields; | ||
| 98 | } | ||
| 99 | |||
| 100 | if (Messaging.Instance.EncounteredError) | ||
| 101 | { | ||
| 102 | return; | ||
| 103 | } | ||
| 104 | |||
| 105 | // If there are any fields to resolve later, create the cache to populate during bind. | ||
| 106 | IDictionary<string, string> variableCache = null; | ||
| 107 | if (delayedFields.Any()) | ||
| 108 | { | ||
| 109 | variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); | ||
| 110 | } | ||
| 111 | |||
| 112 | // TODO: Although the WixSearch tables are defined in the Util extension, | ||
| 113 | // the Bundle Binder has to know all about them. We hope to revisit all | ||
| 114 | // of this in the 4.0 timeframe. | ||
| 115 | IEnumerable<WixSearchInfo> orderedSearches = this.OrderSearches(); | ||
| 116 | |||
| 117 | // Extract files that come from cabinet files (this does not extract files from merge modules). | ||
| 118 | { | ||
| 119 | ExtractEmbeddedFilesCommand extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand(); | ||
| 120 | extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 121 | extractEmbeddedFilesCommand.Execute(); | ||
| 122 | } | ||
| 123 | |||
| 124 | // Get the explicit payloads. | ||
| 125 | RowDictionary<WixBundlePayloadRow> payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]); | ||
| 126 | |||
| 127 | // Update explicitly authored payloads with their parent package and container (as appropriate) | ||
| 128 | // to make it easier to gather the payloads later. | ||
| 129 | foreach (WixGroupRow row in wixGroupTable.RowsAs<WixGroupRow>()) | ||
| 130 | { | ||
| 131 | if (ComplexReferenceChildType.Payload == row.ChildType) | ||
| 132 | { | ||
| 133 | WixBundlePayloadRow payload = payloads.Get(row.ChildId); | ||
| 134 | |||
| 135 | if (ComplexReferenceParentType.Package == row.ParentType) | ||
| 136 | { | ||
| 137 | Debug.Assert(String.IsNullOrEmpty(payload.Package)); | ||
| 138 | payload.Package = row.ParentId; | ||
| 139 | } | ||
| 140 | else if (ComplexReferenceParentType.Container == row.ParentType) | ||
| 141 | { | ||
| 142 | Debug.Assert(String.IsNullOrEmpty(payload.Container)); | ||
| 143 | payload.Container = row.ParentId; | ||
| 144 | } | ||
| 145 | else if (ComplexReferenceParentType.Layout == row.ParentType) | ||
| 146 | { | ||
| 147 | payload.LayoutOnly = true; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
| 153 | string layoutDirectory = Path.GetDirectoryName(this.OutputPath); | ||
| 154 | |||
| 155 | // Process the explicitly authored payloads. | ||
| 156 | ISet<string> processedPayloads; | ||
| 157 | { | ||
| 158 | ProcessPayloadsCommand command = new ProcessPayloadsCommand(); | ||
| 159 | command.Payloads = payloads.Values; | ||
| 160 | command.DefaultPackaging = bundleRow.DefaultPackagingType; | ||
| 161 | command.LayoutDirectory = layoutDirectory; | ||
| 162 | command.Execute(); | ||
| 163 | |||
| 164 | fileTransfers.AddRange(command.FileTransfers); | ||
| 165 | |||
| 166 | processedPayloads = new HashSet<string>(payloads.Keys); | ||
| 167 | } | ||
| 168 | |||
| 169 | IDictionary<string, PackageFacade> facades; | ||
| 170 | { | ||
| 171 | GetPackageFacadesCommand command = new GetPackageFacadesCommand(); | ||
| 172 | command.PackageTable = chainPackageTable; | ||
| 173 | command.ExePackageTable = this.Output.Tables["WixBundleExePackage"]; | ||
| 174 | command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"]; | ||
| 175 | command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"]; | ||
| 176 | command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"]; | ||
| 177 | command.Execute(); | ||
| 178 | |||
| 179 | facades = command.PackageFacades; | ||
| 180 | } | ||
| 181 | |||
| 182 | // Process each package facade. Note this is likely to add payloads and other rows to tables so | ||
| 183 | // note that any indexes created above may be out of date now. | ||
| 184 | foreach (PackageFacade facade in facades.Values) | ||
| 185 | { | ||
| 186 | switch (facade.Package.Type) | ||
| 187 | { | ||
| 188 | case WixBundlePackageType.Exe: | ||
| 189 | { | ||
| 190 | ProcessExePackageCommand command = new ProcessExePackageCommand(); | ||
| 191 | command.AuthoredPayloads = payloads; | ||
| 192 | command.Facade = facade; | ||
| 193 | command.Execute(); | ||
| 194 | |||
| 195 | // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer); | ||
| 196 | } | ||
| 197 | break; | ||
| 198 | |||
| 199 | case WixBundlePackageType.Msi: | ||
| 200 | { | ||
| 201 | ProcessMsiPackageCommand command = new ProcessMsiPackageCommand(); | ||
| 202 | command.AuthoredPayloads = payloads; | ||
| 203 | command.Facade = facade; | ||
| 204 | command.FileManager = this.FileManagers.First(); | ||
| 205 | command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]); | ||
| 206 | command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]); | ||
| 207 | command.PayloadTable = this.Output.Tables["WixBundlePayload"]; | ||
| 208 | command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]); | ||
| 209 | command.Execute(); | ||
| 210 | |||
| 211 | if (null != variableCache) | ||
| 212 | { | ||
| 213 | variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString()); | ||
| 214 | |||
| 215 | if (null != facade.MsiPackage.Manufacturer) | ||
| 216 | { | ||
| 217 | variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer); | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | } | ||
| 222 | break; | ||
| 223 | |||
| 224 | case WixBundlePackageType.Msp: | ||
| 225 | { | ||
| 226 | ProcessMspPackageCommand command = new ProcessMspPackageCommand(); | ||
| 227 | command.AuthoredPayloads = payloads; | ||
| 228 | command.Facade = facade; | ||
| 229 | command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); | ||
| 230 | command.Execute(); | ||
| 231 | } | ||
| 232 | break; | ||
| 233 | |||
| 234 | case WixBundlePackageType.Msu: | ||
| 235 | { | ||
| 236 | ProcessMsuPackageCommand command = new ProcessMsuPackageCommand(); | ||
| 237 | command.Facade = facade; | ||
| 238 | command.Execute(); | ||
| 239 | } | ||
| 240 | break; | ||
| 241 | } | ||
| 242 | |||
| 243 | if (null != variableCache) | ||
| 244 | { | ||
| 245 | BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) | ||
| 250 | // are present. | ||
| 251 | payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]); | ||
| 252 | |||
| 253 | // Process the payloads that were added by processing the packages. | ||
| 254 | { | ||
| 255 | ProcessPayloadsCommand command = new ProcessPayloadsCommand(); | ||
| 256 | command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList(); | ||
| 257 | command.DefaultPackaging = bundleRow.DefaultPackagingType; | ||
| 258 | command.LayoutDirectory = layoutDirectory; | ||
| 259 | command.Execute(); | ||
| 260 | |||
| 261 | fileTransfers.AddRange(command.FileTransfers); | ||
| 262 | |||
| 263 | processedPayloads = null; | ||
| 264 | } | ||
| 265 | |||
| 266 | // Set the package metadata from the payloads now that we have the complete payload information. | ||
| 267 | ILookup<string, WixBundlePayloadRow> payloadsByPackage = payloads.Values.ToLookup(p => p.Package); | ||
| 268 | |||
| 269 | { | ||
| 270 | foreach (PackageFacade facade in facades.Values) | ||
| 271 | { | ||
| 272 | facade.Package.Size = 0; | ||
| 273 | |||
| 274 | IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[facade.Package.WixChainItemId]; | ||
| 275 | |||
| 276 | foreach (WixBundlePayloadRow payload in packagePayloads) | ||
| 277 | { | ||
| 278 | facade.Package.Size += payload.FileSize; | ||
| 279 | } | ||
| 280 | |||
| 281 | if (!facade.Package.InstallSize.HasValue) | ||
| 282 | { | ||
| 283 | facade.Package.InstallSize = facade.Package.Size; | ||
| 284 | |||
| 285 | } | ||
| 286 | |||
| 287 | WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload]; | ||
| 288 | |||
| 289 | if (String.IsNullOrEmpty(facade.Package.Description)) | ||
| 290 | { | ||
| 291 | facade.Package.Description = packagePayload.Description; | ||
| 292 | } | ||
| 293 | |||
| 294 | if (String.IsNullOrEmpty(facade.Package.DisplayName)) | ||
| 295 | { | ||
| 296 | facade.Package.DisplayName = packagePayload.DisplayName; | ||
| 297 | } | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | |||
| 302 | // Give the UX payloads their embedded IDs... | ||
| 303 | int uxPayloadIndex = 0; | ||
| 304 | { | ||
| 305 | foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container)) | ||
| 306 | { | ||
| 307 | // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even | ||
| 308 | // downloaded. The current engine requires the UX to be fully present before any downloading starts, | ||
| 309 | // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads | ||
| 310 | // into the temporary UX directory correctly, so we don't allow external either. | ||
| 311 | if (PackagingType.Embedded != payload.Packaging) | ||
| 312 | { | ||
| 313 | Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName)); | ||
| 314 | payload.Packaging = PackagingType.Embedded; | ||
| 315 | } | ||
| 316 | |||
| 317 | payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); | ||
| 318 | ++uxPayloadIndex; | ||
| 319 | } | ||
| 320 | |||
| 321 | if (0 == uxPayloadIndex) | ||
| 322 | { | ||
| 323 | // If we didn't get any UX payloads, it's an error! | ||
| 324 | throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication")); | ||
| 325 | } | ||
| 326 | |||
| 327 | // Give the embedded payloads without an embedded id yet an embedded id. | ||
| 328 | int payloadIndex = 0; | ||
| 329 | foreach (WixBundlePayloadRow payload in payloads.Values) | ||
| 330 | { | ||
| 331 | Debug.Assert(PackagingType.Unknown != payload.Packaging); | ||
| 332 | |||
| 333 | if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) | ||
| 334 | { | ||
| 335 | payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex); | ||
| 336 | ++payloadIndex; | ||
| 337 | } | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | // Determine patches to automatically slipstream. | ||
| 342 | { | ||
| 343 | AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand(); | ||
| 344 | command.PackageFacades = facades.Values; | ||
| 345 | command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]); | ||
| 346 | command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); | ||
| 347 | command.Execute(); | ||
| 348 | } | ||
| 349 | |||
| 350 | // If catalog files exist, non-embedded payloads should validate with the catalogs. | ||
| 351 | IEnumerable<WixBundleCatalogRow> catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs<WixBundleCatalogRow>(); | ||
| 352 | |||
| 353 | if (catalogs.Any()) | ||
| 354 | { | ||
| 355 | VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand(); | ||
| 356 | command.Catalogs = catalogs; | ||
| 357 | command.Payloads = payloads.Values; | ||
| 358 | command.Execute(); | ||
| 359 | } | ||
| 360 | |||
| 361 | if (Messaging.Instance.EncounteredError) | ||
| 362 | { | ||
| 363 | return; | ||
| 364 | } | ||
| 365 | |||
| 366 | IEnumerable<PackageFacade> orderedFacades; | ||
| 367 | IEnumerable<WixBundleRollbackBoundaryRow> boundaries; | ||
| 368 | { | ||
| 369 | OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand(); | ||
| 370 | command.Boundaries = new RowDictionary<WixBundleRollbackBoundaryRow>(this.Output.Tables["WixBundleRollbackBoundary"]); | ||
| 371 | command.PackageFacades = facades; | ||
| 372 | command.WixGroupTable = wixGroupTable; | ||
| 373 | command.Execute(); | ||
| 374 | |||
| 375 | orderedFacades = command.OrderedPackageFacades; | ||
| 376 | boundaries = command.UsedRollbackBoundaries; | ||
| 377 | } | ||
| 378 | |||
| 379 | // Resolve any delayed fields before generating the manifest. | ||
| 380 | if (delayedFields.Any()) | ||
| 381 | { | ||
| 382 | ResolveDelayedFieldsCommand resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(); | ||
| 383 | resolveDelayedFieldsCommand.OutputType = this.Output.Type; | ||
| 384 | resolveDelayedFieldsCommand.DelayedFields = delayedFields; | ||
| 385 | resolveDelayedFieldsCommand.ModularizationGuid = null; | ||
| 386 | resolveDelayedFieldsCommand.VariableCache = variableCache; | ||
| 387 | resolveDelayedFieldsCommand.Execute(); | ||
| 388 | } | ||
| 389 | |||
| 390 | // Set the overridable bundle provider key. | ||
| 391 | this.SetBundleProviderKey(this.Output, bundleRow); | ||
| 392 | |||
| 393 | // Import or generate dependency providers for packages in the manifest. | ||
| 394 | this.ProcessDependencyProviders(this.Output, facades); | ||
| 395 | |||
| 396 | // Update the bundle per-machine/per-user scope based on the chained packages. | ||
| 397 | this.ResolveBundleInstallScope(bundleRow, orderedFacades); | ||
| 398 | |||
| 399 | // Generate the core-defined BA manifest tables... | ||
| 400 | { | ||
| 401 | CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand(); | ||
| 402 | command.BundleRow = bundleRow; | ||
| 403 | command.ChainPackages = orderedFacades; | ||
| 404 | command.LastUXPayloadIndex = uxPayloadIndex; | ||
| 405 | command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>(); | ||
| 406 | command.Output = this.Output; | ||
| 407 | command.Payloads = payloads; | ||
| 408 | command.TableDefinitions = this.TableDefinitions; | ||
| 409 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 410 | command.Execute(); | ||
| 411 | |||
| 412 | WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; | ||
| 413 | payloads.Add(baManifestPayload); | ||
| 414 | } | ||
| 415 | |||
| 416 | foreach (BinderExtension extension in this.Extensions) | ||
| 417 | { | ||
| 418 | extension.Finish(Output); | ||
| 419 | } | ||
| 420 | |||
| 421 | // Create all the containers except the UX container first so the manifest (that goes in the UX container) | ||
| 422 | // can contain all size and hash information about the non-UX containers. | ||
| 423 | RowDictionary<WixBundleContainerRow> containers = new RowDictionary<WixBundleContainerRow>(this.Output.Tables["WixBundleContainer"]); | ||
| 424 | |||
| 425 | ILookup<string, WixBundlePayloadRow> payloadsByContainer = payloads.Values.ToLookup(p => p.Container); | ||
| 426 | |||
| 427 | int attachedContainerIndex = 1; // count starts at one because UX container is "0". | ||
| 428 | |||
| 429 | IEnumerable<WixBundlePayloadRow> uxContainerPayloads = Enumerable.Empty<WixBundlePayloadRow>(); | ||
| 430 | |||
| 431 | foreach (WixBundleContainerRow container in containers.Values) | ||
| 432 | { | ||
| 433 | IEnumerable<WixBundlePayloadRow> containerPayloads = payloadsByContainer[container.Id]; | ||
| 434 | |||
| 435 | if (!containerPayloads.Any()) | ||
| 436 | { | ||
| 437 | if (container.Id != Compiler.BurnDefaultAttachedContainerId) | ||
| 438 | { | ||
| 439 | // TODO: display warning that we're ignoring container that ended up with no paylods in it. | ||
| 440 | } | ||
| 441 | } | ||
| 442 | else if (Compiler.BurnUXContainerId == container.Id) | ||
| 443 | { | ||
| 444 | container.WorkingPath = Path.Combine(this.TempFilesLocation, container.Name); | ||
| 445 | container.AttachedContainerIndex = 0; | ||
| 446 | |||
| 447 | // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first | ||
| 448 | // in the list since that is the Payload that Burn attempts to load. | ||
| 449 | List<WixBundlePayloadRow> uxPayloads = new List<WixBundlePayloadRow>(); | ||
| 450 | |||
| 451 | string baPayloadId = baRow.FieldAsString(0); | ||
| 452 | |||
| 453 | foreach (WixBundlePayloadRow uxPayload in containerPayloads) | ||
| 454 | { | ||
| 455 | if (uxPayload.Id == baPayloadId) | ||
| 456 | { | ||
| 457 | uxPayloads.Insert(0, uxPayload); | ||
| 458 | } | ||
| 459 | else | ||
| 460 | { | ||
| 461 | uxPayloads.Add(uxPayload); | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | uxContainerPayloads = uxPayloads; | ||
| 466 | } | ||
| 467 | else | ||
| 468 | { | ||
| 469 | container.WorkingPath = Path.Combine(this.TempFilesLocation, container.Name); | ||
| 470 | |||
| 471 | // Add detached containers to the list of file transfers. | ||
| 472 | if (ContainerType.Detached == container.Type) | ||
| 473 | { | ||
| 474 | FileTransfer transfer; | ||
| 475 | if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer)) | ||
| 476 | { | ||
| 477 | transfer.Built = true; | ||
| 478 | fileTransfers.Add(transfer); | ||
| 479 | } | ||
| 480 | } | ||
| 481 | else // update the attached container index. | ||
| 482 | { | ||
| 483 | Debug.Assert(ContainerType.Attached == container.Type); | ||
| 484 | |||
| 485 | container.AttachedContainerIndex = attachedContainerIndex; | ||
| 486 | ++attachedContainerIndex; | ||
| 487 | } | ||
| 488 | |||
| 489 | this.CreateContainer(container, containerPayloads, null); | ||
| 490 | } | ||
| 491 | } | ||
| 492 | |||
| 493 | // Create the bundle manifest then UX container. | ||
| 494 | string manifestPath = Path.Combine(this.TempFilesLocation, "bundle-manifest.xml"); | ||
| 495 | { | ||
| 496 | CreateBurnManifestCommand command = new CreateBurnManifestCommand(); | ||
| 497 | command.FileManagers = this.FileManagers; | ||
| 498 | command.Output = this.Output; | ||
| 499 | |||
| 500 | command.BundleInfo = bundleRow; | ||
| 501 | command.Chain = chainRow; | ||
| 502 | command.Containers = containers; | ||
| 503 | command.Catalogs = catalogs; | ||
| 504 | command.ExecutableName = Path.GetFileName(this.OutputPath); | ||
| 505 | command.OrderedPackages = orderedFacades; | ||
| 506 | command.OutputPath = manifestPath; | ||
| 507 | command.RollbackBoundaries = boundaries; | ||
| 508 | command.OrderedSearches = orderedSearches; | ||
| 509 | command.Payloads = payloads; | ||
| 510 | command.UXContainerPayloads = uxContainerPayloads; | ||
| 511 | command.Execute(); | ||
| 512 | } | ||
| 513 | |||
| 514 | WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId]; | ||
| 515 | this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath); | ||
| 516 | |||
| 517 | // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note | ||
| 518 | // that today, the x64 Burn uses the x86 stub. | ||
| 519 | string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString(); | ||
| 520 | |||
| 521 | string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); | ||
| 522 | string bundleTempPath = Path.Combine(this.TempFilesLocation, Path.GetFileName(this.OutputPath)); | ||
| 523 | |||
| 524 | Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile)); | ||
| 525 | |||
| 526 | string bundleFilename = Path.GetFileName(this.OutputPath); | ||
| 527 | if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) | ||
| 528 | { | ||
| 529 | Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename)); | ||
| 530 | } | ||
| 531 | |||
| 532 | FileTransfer bundleTransfer; | ||
| 533 | if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer)) | ||
| 534 | { | ||
| 535 | bundleTransfer.Built = true; | ||
| 536 | fileTransfers.Add(bundleTransfer); | ||
| 537 | } | ||
| 538 | |||
| 539 | File.Copy(stubFile, bundleTempPath, true); | ||
| 540 | File.SetAttributes(bundleTempPath, FileAttributes.Normal); | ||
| 541 | |||
| 542 | this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow); | ||
| 543 | |||
| 544 | // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers | ||
| 545 | // if they should be attached. | ||
| 546 | using (BurnWriter writer = BurnWriter.Open(bundleTempPath)) | ||
| 547 | { | ||
| 548 | FileInfo burnStubFile = new FileInfo(bundleTempPath); | ||
| 549 | writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId); | ||
| 550 | |||
| 551 | // Always attach the UX container first | ||
| 552 | writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX); | ||
| 553 | |||
| 554 | // Now append all other attached containers | ||
| 555 | foreach (WixBundleContainerRow container in containers.Values) | ||
| 556 | { | ||
| 557 | if (ContainerType.Attached == container.Type) | ||
| 558 | { | ||
| 559 | // The container was only created if it had payloads. | ||
| 560 | if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) | ||
| 561 | { | ||
| 562 | writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); | ||
| 563 | } | ||
| 564 | } | ||
| 565 | } | ||
| 566 | } | ||
| 567 | |||
| 568 | if (null != this.PdbFile) | ||
| 569 | { | ||
| 570 | Pdb pdb = new Pdb(); | ||
| 571 | pdb.Output = Output; | ||
| 572 | pdb.Save(this.PdbFile); | ||
| 573 | } | ||
| 574 | |||
| 575 | this.FileTransfers = fileTransfers; | ||
| 576 | this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); | ||
| 577 | } | ||
| 578 | |||
| 579 | private Table GetRequiredTable(string tableName) | ||
| 580 | { | ||
| 581 | Table table = this.Output.Tables[tableName]; | ||
| 582 | if (null == table || 0 == table.Rows.Count) | ||
| 583 | { | ||
| 584 | throw new WixException(WixErrors.MissingBundleInformation(tableName)); | ||
| 585 | } | ||
| 586 | |||
| 587 | return table; | ||
| 588 | } | ||
| 589 | |||
| 590 | private Row GetSingleRowTable(string tableName) | ||
| 591 | { | ||
| 592 | Table table = this.Output.Tables[tableName]; | ||
| 593 | if (null == table || 1 != table.Rows.Count) | ||
| 594 | { | ||
| 595 | throw new WixException(WixErrors.MissingBundleInformation(tableName)); | ||
| 596 | } | ||
| 597 | |||
| 598 | return table.Rows[0]; | ||
| 599 | } | ||
| 600 | |||
| 601 | private List<WixSearchInfo> OrderSearches() | ||
| 602 | { | ||
| 603 | Dictionary<string, WixSearchInfo> allSearches = new Dictionary<string, WixSearchInfo>(); | ||
| 604 | Table wixFileSearchTable = this.Output.Tables["WixFileSearch"]; | ||
| 605 | if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count) | ||
| 606 | { | ||
| 607 | foreach (Row row in wixFileSearchTable.Rows) | ||
| 608 | { | ||
| 609 | WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row); | ||
| 610 | allSearches.Add(fileSearchInfo.Id, fileSearchInfo); | ||
| 611 | } | ||
| 612 | } | ||
| 613 | |||
| 614 | Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"]; | ||
| 615 | if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count) | ||
| 616 | { | ||
| 617 | foreach (Row row in wixRegistrySearchTable.Rows) | ||
| 618 | { | ||
| 619 | WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row); | ||
| 620 | allSearches.Add(registrySearchInfo.Id, registrySearchInfo); | ||
| 621 | } | ||
| 622 | } | ||
| 623 | |||
| 624 | Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"]; | ||
| 625 | if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count) | ||
| 626 | { | ||
| 627 | foreach (Row row in wixComponentSearchTable.Rows) | ||
| 628 | { | ||
| 629 | WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row); | ||
| 630 | allSearches.Add(componentSearchInfo.Id, componentSearchInfo); | ||
| 631 | } | ||
| 632 | } | ||
| 633 | |||
| 634 | Table wixProductSearchTable = this.Output.Tables["WixProductSearch"]; | ||
| 635 | if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count) | ||
| 636 | { | ||
| 637 | foreach (Row row in wixProductSearchTable.Rows) | ||
| 638 | { | ||
| 639 | WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row); | ||
| 640 | allSearches.Add(productSearchInfo.Id, productSearchInfo); | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | // Merge in the variable/condition info and get the canonical ordering for | ||
| 645 | // the searches. | ||
| 646 | List<WixSearchInfo> orderedSearches = new List<WixSearchInfo>(); | ||
| 647 | Table wixSearchTable = this.Output.Tables["WixSearch"]; | ||
| 648 | if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count) | ||
| 649 | { | ||
| 650 | orderedSearches.Capacity = wixSearchTable.Rows.Count; | ||
| 651 | foreach (Row row in wixSearchTable.Rows) | ||
| 652 | { | ||
| 653 | WixSearchInfo searchInfo = allSearches[(string)row[0]]; | ||
| 654 | searchInfo.AddWixSearchRowInfo(row); | ||
| 655 | orderedSearches.Add(searchInfo); | ||
| 656 | } | ||
| 657 | } | ||
| 658 | |||
| 659 | return orderedSearches; | ||
| 660 | } | ||
| 661 | |||
| 662 | /// <summary> | ||
| 663 | /// Populates the variable cache with specific package properties. | ||
| 664 | /// </summary> | ||
| 665 | /// <param name="package">The package with properties to cache.</param> | ||
| 666 | /// <param name="variableCache">The property cache.</param> | ||
| 667 | private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary<string, string> variableCache) | ||
| 668 | { | ||
| 669 | string id = package.WixChainItemId; | ||
| 670 | |||
| 671 | variableCache.Add(String.Concat("packageDescription.", id), package.Description); | ||
| 672 | //variableCache.Add(String.Concat("packageLanguage.", id), package.Language); | ||
| 673 | //variableCache.Add(String.Concat("packageManufacturer.", id), package.Manufacturer); | ||
| 674 | variableCache.Add(String.Concat("packageName.", id), package.DisplayName); | ||
| 675 | variableCache.Add(String.Concat("packageVersion.", id), package.Version); | ||
| 676 | } | ||
| 677 | |||
| 678 | private void CreateContainer(WixBundleContainerRow container, IEnumerable<WixBundlePayloadRow> containerPayloads, string manifestFile) | ||
| 679 | { | ||
| 680 | CreateContainerCommand command = new CreateContainerCommand(); | ||
| 681 | command.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
| 682 | command.Payloads = containerPayloads; | ||
| 683 | command.ManifestFile = manifestFile; | ||
| 684 | command.OutputPath = container.WorkingPath; | ||
| 685 | command.Execute(); | ||
| 686 | |||
| 687 | container.Hash = command.Hash; | ||
| 688 | container.Size = command.Size; | ||
| 689 | } | ||
| 690 | |||
| 691 | private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable<PackageFacade> facades) | ||
| 692 | { | ||
| 693 | foreach (PackageFacade facade in facades) | ||
| 694 | { | ||
| 695 | if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine) | ||
| 696 | { | ||
| 697 | Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); | ||
| 698 | |||
| 699 | bundleInfo.PerMachine = false; | ||
| 700 | break; | ||
| 701 | } | ||
| 702 | } | ||
| 703 | |||
| 704 | foreach (PackageFacade facade in facades) | ||
| 705 | { | ||
| 706 | // Update package scope from bundle scope if default. | ||
| 707 | if (YesNoDefaultType.Default == facade.Package.PerMachine) | ||
| 708 | { | ||
| 709 | facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; | ||
| 710 | } | ||
| 711 | |||
| 712 | // We will only register packages in the same scope as the bundle. Warn if any packages with providers | ||
| 713 | // are in a different scope and not permanent (permanents typically don't need a ref-count). | ||
| 714 | if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count) | ||
| 715 | { | ||
| 716 | Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); | ||
| 717 | } | ||
| 718 | } | ||
| 719 | } | ||
| 720 | |||
| 721 | private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo) | ||
| 722 | { | ||
| 723 | WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection(); | ||
| 724 | WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033); | ||
| 725 | |||
| 726 | version.Load(bundleTempPath); | ||
| 727 | resources.Add(version); | ||
| 728 | |||
| 729 | // Ensure the bundle info provides a full four part version. | ||
| 730 | Version fourPartVersion = new Version(bundleInfo.Version); | ||
| 731 | int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; | ||
| 732 | int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; | ||
| 733 | int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; | ||
| 734 | int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; | ||
| 735 | |||
| 736 | if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) | ||
| 737 | { | ||
| 738 | throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version)); | ||
| 739 | } | ||
| 740 | |||
| 741 | fourPartVersion = new Version(major, minor, build, revision); | ||
| 742 | version.FileVersion = fourPartVersion; | ||
| 743 | version.ProductVersion = fourPartVersion; | ||
| 744 | |||
| 745 | WixToolset.Dtf.Resources.VersionStringTable strings = version[1033]; | ||
| 746 | strings["LegalCopyright"] = bundleInfo.Copyright; | ||
| 747 | strings["OriginalFilename"] = Path.GetFileName(outputPath); | ||
| 748 | strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. | ||
| 749 | strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. | ||
| 750 | |||
| 751 | if (!String.IsNullOrEmpty(bundleInfo.Name)) | ||
| 752 | { | ||
| 753 | strings["ProductName"] = bundleInfo.Name; | ||
| 754 | strings["FileDescription"] = bundleInfo.Name; | ||
| 755 | } | ||
| 756 | |||
| 757 | if (!String.IsNullOrEmpty(bundleInfo.Publisher)) | ||
| 758 | { | ||
| 759 | strings["CompanyName"] = bundleInfo.Publisher; | ||
| 760 | } | ||
| 761 | else | ||
| 762 | { | ||
| 763 | strings["CompanyName"] = String.Empty; | ||
| 764 | } | ||
| 765 | |||
| 766 | if (!String.IsNullOrEmpty(bundleInfo.IconPath)) | ||
| 767 | { | ||
| 768 | Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033); | ||
| 769 | iconGroup.ReadFromFile(bundleInfo.IconPath); | ||
| 770 | resources.Add(iconGroup); | ||
| 771 | |||
| 772 | foreach (Dtf.Resources.Resource icon in iconGroup.Icons) | ||
| 773 | { | ||
| 774 | resources.Add(icon); | ||
| 775 | } | ||
| 776 | } | ||
| 777 | |||
| 778 | if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath)) | ||
| 779 | { | ||
| 780 | Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033); | ||
| 781 | bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath); | ||
| 782 | resources.Add(bitmap); | ||
| 783 | } | ||
| 784 | |||
| 785 | resources.Save(bundleTempPath); | ||
| 786 | } | ||
| 787 | |||
| 788 | #region DependencyExtension | ||
| 789 | /// <summary> | ||
| 790 | /// Imports authored dependency providers for each package in the manifest, | ||
| 791 | /// and generates dependency providers for certain package types that do not | ||
| 792 | /// have a provider defined. | ||
| 793 | /// </summary> | ||
| 794 | /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param> | ||
| 795 | /// <param name="facades">An indexed collection of chained packages.</param> | ||
| 796 | private void ProcessDependencyProviders(Output bundle, IDictionary<string, PackageFacade> facades) | ||
| 797 | { | ||
| 798 | // First import any authored dependencies. These may merge with imported provides from MSI packages. | ||
| 799 | Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; | ||
| 800 | if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) | ||
| 801 | { | ||
| 802 | // Add package information for each dependency provider authored into the manifest. | ||
| 803 | foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) | ||
| 804 | { | ||
| 805 | string packageId = (string)wixDependencyProviderRow[1]; | ||
| 806 | |||
| 807 | PackageFacade facade = null; | ||
| 808 | if (facades.TryGetValue(packageId, out facade)) | ||
| 809 | { | ||
| 810 | ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow); | ||
| 811 | |||
| 812 | if (String.IsNullOrEmpty(dependency.Key)) | ||
| 813 | { | ||
| 814 | switch (facade.Package.Type) | ||
| 815 | { | ||
| 816 | // The WixDependencyExtension allows an empty Key for MSIs and MSPs. | ||
| 817 | case WixBundlePackageType.Msi: | ||
| 818 | dependency.Key = facade.MsiPackage.ProductCode; | ||
| 819 | break; | ||
| 820 | case WixBundlePackageType.Msp: | ||
| 821 | dependency.Key = facade.MspPackage.PatchCode; | ||
| 822 | break; | ||
| 823 | } | ||
| 824 | } | ||
| 825 | |||
| 826 | if (String.IsNullOrEmpty(dependency.Version)) | ||
| 827 | { | ||
| 828 | dependency.Version = facade.Package.Version; | ||
| 829 | } | ||
| 830 | |||
| 831 | // If the version is still missing, a version could not be harvested from the package and was not authored. | ||
| 832 | if (String.IsNullOrEmpty(dependency.Version)) | ||
| 833 | { | ||
| 834 | Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId)); | ||
| 835 | } | ||
| 836 | |||
| 837 | if (String.IsNullOrEmpty(dependency.DisplayName)) | ||
| 838 | { | ||
| 839 | dependency.DisplayName = facade.Package.DisplayName; | ||
| 840 | } | ||
| 841 | |||
| 842 | if (!facade.Provides.Merge(dependency)) | ||
| 843 | { | ||
| 844 | Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); | ||
| 845 | } | ||
| 846 | } | ||
| 847 | } | ||
| 848 | } | ||
| 849 | |||
| 850 | // Generate providers for MSI packages that still do not have providers. | ||
| 851 | foreach (PackageFacade facade in facades.Values) | ||
| 852 | { | ||
| 853 | string key = null; | ||
| 854 | |||
| 855 | if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count) | ||
| 856 | { | ||
| 857 | key = facade.MsiPackage.ProductCode; | ||
| 858 | } | ||
| 859 | else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count) | ||
| 860 | { | ||
| 861 | key = facade.MspPackage.PatchCode; | ||
| 862 | } | ||
| 863 | |||
| 864 | if (!String.IsNullOrEmpty(key)) | ||
| 865 | { | ||
| 866 | ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0); | ||
| 867 | |||
| 868 | if (!facade.Provides.Merge(dependency)) | ||
| 869 | { | ||
| 870 | Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); | ||
| 871 | } | ||
| 872 | } | ||
| 873 | } | ||
| 874 | } | ||
| 875 | |||
| 876 | /// <summary> | ||
| 877 | /// Sets the provider key for the bundle. | ||
| 878 | /// </summary> | ||
| 879 | /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param> | ||
| 880 | /// <param name="bundleInfo">The <see cref="BundleInfo"/> containing the provider key and other information for the bundle.</param> | ||
| 881 | private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo) | ||
| 882 | { | ||
| 883 | // From DependencyCommon.cs in the WixDependencyExtension. | ||
| 884 | const int ProvidesAttributesBundle = 0x10000; | ||
| 885 | |||
| 886 | Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; | ||
| 887 | if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) | ||
| 888 | { | ||
| 889 | // Search the WixDependencyProvider table for the single bundle provider key. | ||
| 890 | foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) | ||
| 891 | { | ||
| 892 | object attributes = wixDependencyProviderRow[5]; | ||
| 893 | if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes)) | ||
| 894 | { | ||
| 895 | bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2]; | ||
| 896 | break; | ||
| 897 | } | ||
| 898 | } | ||
| 899 | } | ||
| 900 | |||
| 901 | // Defaults to the bundle ID as the provider key. | ||
| 902 | } | ||
| 903 | #endregion | ||
| 904 | } | ||
| 905 | } | ||
diff --git a/src/WixToolset.Core/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core/Bind/BindDatabaseCommand.cs deleted file mode 100644 index 93af2e9a..00000000 --- a/src/WixToolset.Core/Bind/BindDatabaseCommand.cs +++ /dev/null | |||
| @@ -1,1311 +0,0 @@ | |||
| 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.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.Databases; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Rows; | ||
| 15 | using WixToolset.Extensibility; | ||
| 16 | using WixToolset.Msi; | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// Binds a databse. | ||
| 20 | /// </summary> | ||
| 21 | internal class BindDatabaseCommand : ICommand | ||
| 22 | { | ||
| 23 | // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. | ||
| 24 | private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); | ||
| 25 | |||
| 26 | public int Codepage { private get; set; } | ||
| 27 | |||
| 28 | public int CabbingThreadCount { private get; set; } | ||
| 29 | |||
| 30 | public CompressionLevel DefaultCompressionLevel { private get; set; } | ||
| 31 | |||
| 32 | public bool DeltaBinaryPatch { get; set; } | ||
| 33 | |||
| 34 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
| 35 | |||
| 36 | public BinderFileManagerCore FileManagerCore { private get; set; } | ||
| 37 | |||
| 38 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | ||
| 39 | |||
| 40 | public IEnumerable<InspectorExtension> InspectorExtensions { private get; set; } | ||
| 41 | |||
| 42 | public Localizer Localizer { private get; set; } | ||
| 43 | |||
| 44 | public string PdbFile { private get; set; } | ||
| 45 | |||
| 46 | public Output Output { private get; set; } | ||
| 47 | |||
| 48 | public string OutputPath { private get; set; } | ||
| 49 | |||
| 50 | public bool SuppressAddingValidationRows { private get; set; } | ||
| 51 | |||
| 52 | public bool SuppressLayout { private get; set; } | ||
| 53 | |||
| 54 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 55 | |||
| 56 | public string TempFilesLocation { private get; set; } | ||
| 57 | |||
| 58 | public Validator Validator { private get; set; } | ||
| 59 | |||
| 60 | public WixVariableResolver WixVariableResolver { private get; set; } | ||
| 61 | |||
| 62 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
| 63 | |||
| 64 | public IEnumerable<string> ContentFilePaths { get; private set; } | ||
| 65 | |||
| 66 | public void Execute() | ||
| 67 | { | ||
| 68 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
| 69 | |||
| 70 | HashSet<string> suppressedTableNames = new HashSet<string>(); | ||
| 71 | |||
| 72 | // Localize fields, resolve wix variables, and resolve file paths. | ||
| 73 | ExtractEmbeddedFiles filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); | ||
| 74 | |||
| 75 | IEnumerable<DelayedField> delayedFields; | ||
| 76 | { | ||
| 77 | ResolveFieldsCommand command = new ResolveFieldsCommand(); | ||
| 78 | command.Tables = this.Output.Tables; | ||
| 79 | command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 80 | command.FileManagerCore = this.FileManagerCore; | ||
| 81 | command.FileManagers = this.FileManagers; | ||
| 82 | command.SupportDelayedResolution = true; | ||
| 83 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 84 | command.WixVariableResolver = this.WixVariableResolver; | ||
| 85 | command.Execute(); | ||
| 86 | |||
| 87 | delayedFields = command.DelayedFields; | ||
| 88 | } | ||
| 89 | |||
| 90 | if (OutputType.Patch == this.Output.Type) | ||
| 91 | { | ||
| 92 | foreach (SubStorage transform in this.Output.SubStorages) | ||
| 93 | { | ||
| 94 | ResolveFieldsCommand command = new ResolveFieldsCommand(); | ||
| 95 | command.Tables = transform.Data.Tables; | ||
| 96 | command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 97 | command.FileManagerCore = this.FileManagerCore; | ||
| 98 | command.FileManagers = this.FileManagers; | ||
| 99 | command.SupportDelayedResolution = false; | ||
| 100 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 101 | command.WixVariableResolver = this.WixVariableResolver; | ||
| 102 | command.Execute(); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | // If there are any fields to resolve later, create the cache to populate during bind. | ||
| 107 | IDictionary<string, string> variableCache = null; | ||
| 108 | if (delayedFields.Any()) | ||
| 109 | { | ||
| 110 | variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); | ||
| 111 | } | ||
| 112 | |||
| 113 | this.LocalizeUI(this.Output.Tables); | ||
| 114 | |||
| 115 | // Process the summary information table before the other tables. | ||
| 116 | bool compressed; | ||
| 117 | bool longNames; | ||
| 118 | int installerVersion; | ||
| 119 | string modularizationGuid; | ||
| 120 | { | ||
| 121 | BindSummaryInfoCommand command = new BindSummaryInfoCommand(); | ||
| 122 | command.Output = this.Output; | ||
| 123 | command.Execute(); | ||
| 124 | |||
| 125 | compressed = command.Compressed; | ||
| 126 | longNames = command.LongNames; | ||
| 127 | installerVersion = command.InstallerVersion; | ||
| 128 | modularizationGuid = command.ModularizationGuid; | ||
| 129 | } | ||
| 130 | |||
| 131 | // Stop processing if an error previously occurred. | ||
| 132 | if (Messaging.Instance.EncounteredError) | ||
| 133 | { | ||
| 134 | return; | ||
| 135 | } | ||
| 136 | |||
| 137 | // Modularize identifiers and add tables with real streams to the import tables. | ||
| 138 | if (OutputType.Module == this.Output.Type) | ||
| 139 | { | ||
| 140 | // Gather all the suppress modularization identifiers | ||
| 141 | HashSet<string> suppressModularizationIdentifiers = null; | ||
| 142 | Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"]; | ||
| 143 | if (null != wixSuppressModularizationTable) | ||
| 144 | { | ||
| 145 | suppressModularizationIdentifiers = new HashSet<string>(wixSuppressModularizationTable.Rows.Select(row => (string)row[0])); | ||
| 146 | } | ||
| 147 | |||
| 148 | foreach (Table table in this.Output.Tables) | ||
| 149 | { | ||
| 150 | table.Modularize(modularizationGuid, suppressModularizationIdentifiers); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | // This must occur after all variables and source paths have been resolved and after modularization. | ||
| 155 | List<FileFacade> fileFacades; | ||
| 156 | { | ||
| 157 | GetFileFacadesCommand command = new GetFileFacadesCommand(); | ||
| 158 | command.FileTable = this.Output.Tables["File"]; | ||
| 159 | command.WixFileTable = this.Output.Tables["WixFile"]; | ||
| 160 | command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"]; | ||
| 161 | command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"]; | ||
| 162 | command.Execute(); | ||
| 163 | |||
| 164 | fileFacades = command.FileFacades; | ||
| 165 | } | ||
| 166 | |||
| 167 | ////if (OutputType.Patch == this.Output.Type) | ||
| 168 | ////{ | ||
| 169 | //// foreach (SubStorage substorage in this.Output.SubStorages) | ||
| 170 | //// { | ||
| 171 | //// Output transform = substorage.Data; | ||
| 172 | |||
| 173 | //// ResolveFieldsCommand command = new ResolveFieldsCommand(); | ||
| 174 | //// command.Tables = transform.Tables; | ||
| 175 | //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 176 | //// command.FileManagerCore = this.FileManagerCore; | ||
| 177 | //// command.FileManagers = this.FileManagers; | ||
| 178 | //// command.SupportDelayedResolution = false; | ||
| 179 | //// command.TempFilesLocation = this.TempFilesLocation; | ||
| 180 | //// command.WixVariableResolver = this.WixVariableResolver; | ||
| 181 | //// command.Execute(); | ||
| 182 | |||
| 183 | //// this.MergeUnrealTables(transform.Tables); | ||
| 184 | //// } | ||
| 185 | ////} | ||
| 186 | |||
| 187 | { | ||
| 188 | CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand(); | ||
| 189 | command.PropertyTable = this.Output.Tables["Property"]; | ||
| 190 | command.WixPropertyTable = this.Output.Tables["WixProperty"]; | ||
| 191 | command.Execute(); | ||
| 192 | } | ||
| 193 | |||
| 194 | if (Messaging.Instance.EncounteredError) | ||
| 195 | { | ||
| 196 | return; | ||
| 197 | } | ||
| 198 | |||
| 199 | // Add binder variables for all properties. | ||
| 200 | Table propertyTable = this.Output.Tables["Property"]; | ||
| 201 | if (null != propertyTable) | ||
| 202 | { | ||
| 203 | foreach (PropertyRow propertyRow in propertyTable.Rows) | ||
| 204 | { | ||
| 205 | // Set the ProductCode if it is to be generated. | ||
| 206 | if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal)) | ||
| 207 | { | ||
| 208 | propertyRow.Value = Common.GenerateGuid(); | ||
| 209 | |||
| 210 | // Update the target ProductCode in any instance transforms. | ||
| 211 | foreach (SubStorage subStorage in this.Output.SubStorages) | ||
| 212 | { | ||
| 213 | Output subStorageOutput = subStorage.Data; | ||
| 214 | if (OutputType.Transform != subStorageOutput.Type) | ||
| 215 | { | ||
| 216 | continue; | ||
| 217 | } | ||
| 218 | |||
| 219 | Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; | ||
| 220 | foreach (Row row in instanceSummaryInformationTable.Rows) | ||
| 221 | { | ||
| 222 | if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) | ||
| 223 | { | ||
| 224 | row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); | ||
| 225 | break; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | // Add the property name and value to the variableCache. | ||
| 232 | if (null != variableCache) | ||
| 233 | { | ||
| 234 | string key = String.Concat("property.", Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property)); | ||
| 235 | variableCache[key] = propertyRow.Value; | ||
| 236 | } | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | // Extract files that come from cabinet files (this does not extract files from merge modules). | ||
| 241 | { | ||
| 242 | ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand(); | ||
| 243 | command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 244 | command.Execute(); | ||
| 245 | } | ||
| 246 | |||
| 247 | if (OutputType.Product == this.Output.Type) | ||
| 248 | { | ||
| 249 | // Retrieve files and their information from merge modules. | ||
| 250 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
| 251 | |||
| 252 | if (null != wixMergeTable) | ||
| 253 | { | ||
| 254 | ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand(); | ||
| 255 | command.FileFacades = fileFacades; | ||
| 256 | command.FileTable = this.Output.Tables["File"]; | ||
| 257 | command.WixFileTable = this.Output.Tables["WixFile"]; | ||
| 258 | command.WixMergeTable = wixMergeTable; | ||
| 259 | command.OutputInstallerVersion = installerVersion; | ||
| 260 | command.SuppressLayout = this.SuppressLayout; | ||
| 261 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 262 | command.Execute(); | ||
| 263 | |||
| 264 | fileFacades.AddRange(command.MergeModulesFileFacades); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | else if (OutputType.Patch == this.Output.Type) | ||
| 268 | { | ||
| 269 | // Merge transform data into the output object. | ||
| 270 | IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output); | ||
| 271 | |||
| 272 | fileFacades.AddRange(filesFromTransform); | ||
| 273 | } | ||
| 274 | |||
| 275 | // stop processing if an error previously occurred | ||
| 276 | if (Messaging.Instance.EncounteredError) | ||
| 277 | { | ||
| 278 | return; | ||
| 279 | } | ||
| 280 | |||
| 281 | Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation()); | ||
| 282 | |||
| 283 | // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table). | ||
| 284 | { | ||
| 285 | UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); | ||
| 286 | command.FileFacades = fileFacades; | ||
| 287 | command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule); | ||
| 288 | command.ModularizationGuid = modularizationGuid; | ||
| 289 | command.Output = this.Output; | ||
| 290 | command.OverwriteHash = true; | ||
| 291 | command.TableDefinitions = this.TableDefinitions; | ||
| 292 | command.VariableCache = variableCache; | ||
| 293 | command.Execute(); | ||
| 294 | } | ||
| 295 | |||
| 296 | // Set generated component guids. | ||
| 297 | this.SetComponentGuids(this.Output); | ||
| 298 | |||
| 299 | // With the Component Guids set now we can create instance transforms. | ||
| 300 | this.CreateInstanceTransforms(this.Output); | ||
| 301 | |||
| 302 | this.ValidateComponentGuids(this.Output); | ||
| 303 | |||
| 304 | this.UpdateControlText(this.Output); | ||
| 305 | |||
| 306 | if (delayedFields.Any()) | ||
| 307 | { | ||
| 308 | ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand(); | ||
| 309 | command.OutputType = this.Output.Type; | ||
| 310 | command.DelayedFields = delayedFields; | ||
| 311 | command.ModularizationGuid = null; | ||
| 312 | command.VariableCache = variableCache; | ||
| 313 | command.Execute(); | ||
| 314 | } | ||
| 315 | |||
| 316 | // Assign files to media. | ||
| 317 | RowDictionary<MediaRow> assignedMediaRows; | ||
| 318 | Dictionary<MediaRow, IEnumerable<FileFacade>> filesByCabinetMedia; | ||
| 319 | IEnumerable<FileFacade> uncompressedFiles; | ||
| 320 | { | ||
| 321 | AssignMediaCommand command = new AssignMediaCommand(); | ||
| 322 | command.FilesCompressed = compressed; | ||
| 323 | command.FileFacades = fileFacades; | ||
| 324 | command.Output = this.Output; | ||
| 325 | command.TableDefinitions = this.TableDefinitions; | ||
| 326 | command.Execute(); | ||
| 327 | |||
| 328 | assignedMediaRows = command.MediaRows; | ||
| 329 | filesByCabinetMedia = command.FileFacadesByCabinetMedia; | ||
| 330 | uncompressedFiles = command.UncompressedFileFacades; | ||
| 331 | } | ||
| 332 | |||
| 333 | // Update file sequence. | ||
| 334 | this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows); | ||
| 335 | |||
| 336 | // stop processing if an error previously occurred | ||
| 337 | if (Messaging.Instance.EncounteredError) | ||
| 338 | { | ||
| 339 | return; | ||
| 340 | } | ||
| 341 | |||
| 342 | // Extended binder extensions can be called now that fields are resolved. | ||
| 343 | { | ||
| 344 | Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]); | ||
| 345 | |||
| 346 | foreach (BinderExtension extension in this.Extensions) | ||
| 347 | { | ||
| 348 | extension.AfterResolvedFields(this.Output); | ||
| 349 | } | ||
| 350 | |||
| 351 | List<FileFacade> updatedFileFacades = new List<FileFacade>(); | ||
| 352 | |||
| 353 | foreach (Row updatedFile in updatedFiles.Rows) | ||
| 354 | { | ||
| 355 | string updatedId = updatedFile.FieldAsString(0); | ||
| 356 | |||
| 357 | FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId)); | ||
| 358 | |||
| 359 | updatedFileFacades.Add(updatedFacade); | ||
| 360 | } | ||
| 361 | |||
| 362 | if (updatedFileFacades.Any()) | ||
| 363 | { | ||
| 364 | UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); | ||
| 365 | command.FileFacades = fileFacades; | ||
| 366 | command.UpdateFileFacades = updatedFileFacades; | ||
| 367 | command.ModularizationGuid = modularizationGuid; | ||
| 368 | command.Output = this.Output; | ||
| 369 | command.OverwriteHash = true; | ||
| 370 | command.TableDefinitions = this.TableDefinitions; | ||
| 371 | command.VariableCache = variableCache; | ||
| 372 | command.Execute(); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | // stop processing if an error previously occurred | ||
| 377 | if (Messaging.Instance.EncounteredError) | ||
| 378 | { | ||
| 379 | return; | ||
| 380 | } | ||
| 381 | |||
| 382 | Directory.CreateDirectory(this.TempFilesLocation); | ||
| 383 | |||
| 384 | if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch) | ||
| 385 | { | ||
| 386 | CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand(); | ||
| 387 | command.FileFacades = fileFacades; | ||
| 388 | command.WixPatchIdTable = this.Output.Tables["WixPatchId"]; | ||
| 389 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 390 | command.Execute(); | ||
| 391 | } | ||
| 392 | |||
| 393 | // create cabinet files and process uncompressed files | ||
| 394 | string layoutDirectory = Path.GetDirectoryName(this.OutputPath); | ||
| 395 | if (!this.SuppressLayout || OutputType.Module == this.Output.Type) | ||
| 396 | { | ||
| 397 | Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles()); | ||
| 398 | |||
| 399 | CreateCabinetsCommand command = new CreateCabinetsCommand(); | ||
| 400 | command.CabbingThreadCount = this.CabbingThreadCount; | ||
| 401 | command.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
| 402 | command.Output = this.Output; | ||
| 403 | command.FileManagers = this.FileManagers; | ||
| 404 | command.LayoutDirectory = layoutDirectory; | ||
| 405 | command.Compressed = compressed; | ||
| 406 | command.FileRowsByCabinet = filesByCabinetMedia; | ||
| 407 | command.ResolveMedia = this.ResolveMedia; | ||
| 408 | command.TableDefinitions = this.TableDefinitions; | ||
| 409 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 410 | command.WixMediaTable = this.Output.Tables["WixMedia"]; | ||
| 411 | command.Execute(); | ||
| 412 | |||
| 413 | fileTransfers.AddRange(command.FileTransfers); | ||
| 414 | } | ||
| 415 | |||
| 416 | if (OutputType.Patch == this.Output.Type) | ||
| 417 | { | ||
| 418 | // copy output data back into the transforms | ||
| 419 | this.CopyToTransformData(this.Output); | ||
| 420 | } | ||
| 421 | |||
| 422 | // stop processing if an error previously occurred | ||
| 423 | if (Messaging.Instance.EncounteredError) | ||
| 424 | { | ||
| 425 | return; | ||
| 426 | } | ||
| 427 | |||
| 428 | // add back suppressed tables which must be present prior to merging in modules | ||
| 429 | if (OutputType.Product == this.Output.Type) | ||
| 430 | { | ||
| 431 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
| 432 | |||
| 433 | if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count) | ||
| 434 | { | ||
| 435 | foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) | ||
| 436 | { | ||
| 437 | string sequenceTableName = sequence.ToString(); | ||
| 438 | Table sequenceTable = this.Output.Tables[sequenceTableName]; | ||
| 439 | |||
| 440 | if (null == sequenceTable) | ||
| 441 | { | ||
| 442 | sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); | ||
| 443 | } | ||
| 444 | |||
| 445 | if (0 == sequenceTable.Rows.Count) | ||
| 446 | { | ||
| 447 | suppressedTableNames.Add(sequenceTableName); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | foreach (BinderExtension extension in this.Extensions) | ||
| 454 | { | ||
| 455 | extension.Finish(this.Output); | ||
| 456 | } | ||
| 457 | |||
| 458 | // generate database file | ||
| 459 | Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase()); | ||
| 460 | string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(this.OutputPath)); | ||
| 461 | this.GenerateDatabase(this.Output, tempDatabaseFile, false, false); | ||
| 462 | |||
| 463 | FileTransfer transfer; | ||
| 464 | if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future | ||
| 465 | { | ||
| 466 | transfer.Built = true; | ||
| 467 | fileTransfers.Add(transfer); | ||
| 468 | } | ||
| 469 | |||
| 470 | // stop processing if an error previously occurred | ||
| 471 | if (Messaging.Instance.EncounteredError) | ||
| 472 | { | ||
| 473 | return; | ||
| 474 | } | ||
| 475 | |||
| 476 | // Output the output to a file | ||
| 477 | Pdb pdb = new Pdb(); | ||
| 478 | pdb.Output = this.Output; | ||
| 479 | if (!String.IsNullOrEmpty(this.PdbFile)) | ||
| 480 | { | ||
| 481 | pdb.Save(this.PdbFile); | ||
| 482 | } | ||
| 483 | |||
| 484 | // Merge modules. | ||
| 485 | if (OutputType.Product == this.Output.Type) | ||
| 486 | { | ||
| 487 | Messaging.Instance.OnMessage(WixVerboses.MergingModules()); | ||
| 488 | |||
| 489 | MergeModulesCommand command = new MergeModulesCommand(); | ||
| 490 | command.FileFacades = fileFacades; | ||
| 491 | command.Output = this.Output; | ||
| 492 | command.OutputPath = tempDatabaseFile; | ||
| 493 | command.SuppressedTableNames = suppressedTableNames; | ||
| 494 | command.Execute(); | ||
| 495 | |||
| 496 | // stop processing if an error previously occurred | ||
| 497 | if (Messaging.Instance.EncounteredError) | ||
| 498 | { | ||
| 499 | return; | ||
| 500 | } | ||
| 501 | } | ||
| 502 | |||
| 503 | // inspect the MSI prior to running ICEs | ||
| 504 | InspectorCore inspectorCore = new InspectorCore(); | ||
| 505 | foreach (InspectorExtension inspectorExtension in this.InspectorExtensions) | ||
| 506 | { | ||
| 507 | inspectorExtension.Core = inspectorCore; | ||
| 508 | inspectorExtension.InspectDatabase(tempDatabaseFile, pdb); | ||
| 509 | |||
| 510 | inspectorExtension.Core = null; // reset. | ||
| 511 | } | ||
| 512 | |||
| 513 | if (Messaging.Instance.EncounteredError) | ||
| 514 | { | ||
| 515 | return; | ||
| 516 | } | ||
| 517 | |||
| 518 | // validate the output if there is an MSI validator | ||
| 519 | if (null != this.Validator) | ||
| 520 | { | ||
| 521 | Stopwatch stopwatch = Stopwatch.StartNew(); | ||
| 522 | |||
| 523 | // set the output file for source line information | ||
| 524 | this.Validator.Output = this.Output; | ||
| 525 | |||
| 526 | Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase()); | ||
| 527 | |||
| 528 | this.Validator.Validate(tempDatabaseFile); | ||
| 529 | |||
| 530 | stopwatch.Stop(); | ||
| 531 | Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); | ||
| 532 | |||
| 533 | // Stop processing if an error occurred. | ||
| 534 | if (Messaging.Instance.EncounteredError) | ||
| 535 | { | ||
| 536 | return; | ||
| 537 | } | ||
| 538 | } | ||
| 539 | |||
| 540 | // Process uncompressed files. | ||
| 541 | if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) | ||
| 542 | { | ||
| 543 | ProcessUncompressedFilesCommand command = new ProcessUncompressedFilesCommand(); | ||
| 544 | command.Compressed = compressed; | ||
| 545 | command.FileFacades = uncompressedFiles; | ||
| 546 | command.LayoutDirectory = layoutDirectory; | ||
| 547 | command.LongNamesInImage = longNames; | ||
| 548 | command.MediaRows = assignedMediaRows; | ||
| 549 | command.ResolveMedia = this.ResolveMedia; | ||
| 550 | command.DatabasePath = tempDatabaseFile; | ||
| 551 | command.WixMediaTable = this.Output.Tables["WixMedia"]; | ||
| 552 | command.Execute(); | ||
| 553 | |||
| 554 | fileTransfers.AddRange(command.FileTransfers); | ||
| 555 | } | ||
| 556 | |||
| 557 | this.FileTransfers = fileTransfers; | ||
| 558 | this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList(); | ||
| 559 | } | ||
| 560 | |||
| 561 | /// <summary> | ||
| 562 | /// Localize dialogs and controls. | ||
| 563 | /// </summary> | ||
| 564 | /// <param name="tables">The tables to localize.</param> | ||
| 565 | private void LocalizeUI(TableIndexedCollection tables) | ||
| 566 | { | ||
| 567 | Table dialogTable = tables["Dialog"]; | ||
| 568 | if (null != dialogTable) | ||
| 569 | { | ||
| 570 | foreach (Row row in dialogTable.Rows) | ||
| 571 | { | ||
| 572 | string dialog = (string)row[0]; | ||
| 573 | LocalizedControl localizedControl = this.Localizer.GetLocalizedControl(dialog, null); | ||
| 574 | if (null != localizedControl) | ||
| 575 | { | ||
| 576 | if (CompilerConstants.IntegerNotSet != localizedControl.X) | ||
| 577 | { | ||
| 578 | row[1] = localizedControl.X; | ||
| 579 | } | ||
| 580 | |||
| 581 | if (CompilerConstants.IntegerNotSet != localizedControl.Y) | ||
| 582 | { | ||
| 583 | row[2] = localizedControl.Y; | ||
| 584 | } | ||
| 585 | |||
| 586 | if (CompilerConstants.IntegerNotSet != localizedControl.Width) | ||
| 587 | { | ||
| 588 | row[3] = localizedControl.Width; | ||
| 589 | } | ||
| 590 | |||
| 591 | if (CompilerConstants.IntegerNotSet != localizedControl.Height) | ||
| 592 | { | ||
| 593 | row[4] = localizedControl.Height; | ||
| 594 | } | ||
| 595 | |||
| 596 | row[5] = (int)row[5] | localizedControl.Attributes; | ||
| 597 | |||
| 598 | if (!String.IsNullOrEmpty(localizedControl.Text)) | ||
| 599 | { | ||
| 600 | row[6] = localizedControl.Text; | ||
| 601 | } | ||
| 602 | } | ||
| 603 | } | ||
| 604 | } | ||
| 605 | |||
| 606 | Table controlTable = tables["Control"]; | ||
| 607 | if (null != controlTable) | ||
| 608 | { | ||
| 609 | foreach (Row row in controlTable.Rows) | ||
| 610 | { | ||
| 611 | string dialog = (string)row[0]; | ||
| 612 | string control = (string)row[1]; | ||
| 613 | LocalizedControl localizedControl = this.Localizer.GetLocalizedControl(dialog, control); | ||
| 614 | if (null != localizedControl) | ||
| 615 | { | ||
| 616 | if (CompilerConstants.IntegerNotSet != localizedControl.X) | ||
| 617 | { | ||
| 618 | row[3] = localizedControl.X.ToString(); | ||
| 619 | } | ||
| 620 | |||
| 621 | if (CompilerConstants.IntegerNotSet != localizedControl.Y) | ||
| 622 | { | ||
| 623 | row[4] = localizedControl.Y.ToString(); | ||
| 624 | } | ||
| 625 | |||
| 626 | if (CompilerConstants.IntegerNotSet != localizedControl.Width) | ||
| 627 | { | ||
| 628 | row[5] = localizedControl.Width.ToString(); | ||
| 629 | } | ||
| 630 | |||
| 631 | if (CompilerConstants.IntegerNotSet != localizedControl.Height) | ||
| 632 | { | ||
| 633 | row[6] = localizedControl.Height.ToString(); | ||
| 634 | } | ||
| 635 | |||
| 636 | row[7] = (int)row[7] | localizedControl.Attributes; | ||
| 637 | |||
| 638 | if (!String.IsNullOrEmpty(localizedControl.Text)) | ||
| 639 | { | ||
| 640 | row[9] = localizedControl.Text; | ||
| 641 | } | ||
| 642 | } | ||
| 643 | } | ||
| 644 | } | ||
| 645 | } | ||
| 646 | |||
| 647 | /// <summary> | ||
| 648 | /// Copy file data between transform substorages and the patch output object | ||
| 649 | /// </summary> | ||
| 650 | /// <param name="output">The output to bind.</param> | ||
| 651 | /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param> | ||
| 652 | private IEnumerable<FileFacade> CopyFromTransformData(Output output) | ||
| 653 | { | ||
| 654 | CopyTransformDataCommand command = new CopyTransformDataCommand(); | ||
| 655 | command.CopyOutFileRows = true; | ||
| 656 | command.FileManagerCore = this.FileManagerCore; | ||
| 657 | command.FileManagers = this.FileManagers; | ||
| 658 | command.Output = output; | ||
| 659 | command.TableDefinitions = this.TableDefinitions; | ||
| 660 | command.Execute(); | ||
| 661 | |||
| 662 | return command.FileFacades; | ||
| 663 | } | ||
| 664 | |||
| 665 | /// <summary> | ||
| 666 | /// Copy file data between transform substorages and the patch output object | ||
| 667 | /// </summary> | ||
| 668 | /// <param name="output">The output to bind.</param> | ||
| 669 | /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param> | ||
| 670 | private void CopyToTransformData(Output output) | ||
| 671 | { | ||
| 672 | CopyTransformDataCommand command = new CopyTransformDataCommand(); | ||
| 673 | command.CopyOutFileRows = false; | ||
| 674 | command.FileManagerCore = this.FileManagerCore; | ||
| 675 | command.FileManagers = this.FileManagers; | ||
| 676 | command.Output = output; | ||
| 677 | command.TableDefinitions = this.TableDefinitions; | ||
| 678 | command.Execute(); | ||
| 679 | } | ||
| 680 | |||
| 681 | /// <summary> | ||
| 682 | /// Takes an id, and demodularizes it (if possible). | ||
| 683 | /// </summary> | ||
| 684 | /// <remarks> | ||
| 685 | /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id. | ||
| 686 | /// </remarks> | ||
| 687 | /// <param name="outputType">The type of the output to bind.</param> | ||
| 688 | /// <param name="modularizationGuid">The modularization GUID.</param> | ||
| 689 | /// <param name="id">The id to demodularize.</param> | ||
| 690 | /// <returns>The demodularized id.</returns> | ||
| 691 | internal static string Demodularize(OutputType outputType, string modularizationGuid, string id) | ||
| 692 | { | ||
| 693 | if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal)) | ||
| 694 | { | ||
| 695 | id = id.Substring(0, id.Length - 37); | ||
| 696 | } | ||
| 697 | |||
| 698 | return id; | ||
| 699 | } | ||
| 700 | |||
| 701 | private void UpdateMediaSequences(OutputType outputType, IEnumerable<FileFacade> fileFacades, RowDictionary<MediaRow> mediaRows) | ||
| 702 | { | ||
| 703 | // Calculate sequence numbers and media disk id layout for all file media information objects. | ||
| 704 | if (OutputType.Module == outputType) | ||
| 705 | { | ||
| 706 | int lastSequence = 0; | ||
| 707 | 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. | ||
| 708 | { | ||
| 709 | facade.File.Sequence = ++lastSequence; | ||
| 710 | } | ||
| 711 | } | ||
| 712 | else | ||
| 713 | { | ||
| 714 | int lastSequence = 0; | ||
| 715 | MediaRow mediaRow = null; | ||
| 716 | Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>(); | ||
| 717 | |||
| 718 | // sequence the non-patch-added files | ||
| 719 | 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. | ||
| 720 | { | ||
| 721 | if (null == mediaRow) | ||
| 722 | { | ||
| 723 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
| 724 | if (OutputType.Patch == outputType) | ||
| 725 | { | ||
| 726 | // patch Media cannot start at zero | ||
| 727 | lastSequence = mediaRow.LastSequence; | ||
| 728 | } | ||
| 729 | } | ||
| 730 | else if (mediaRow.DiskId != facade.WixFile.DiskId) | ||
| 731 | { | ||
| 732 | mediaRow.LastSequence = lastSequence; | ||
| 733 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
| 734 | } | ||
| 735 | |||
| 736 | if (0 < facade.WixFile.PatchGroup) | ||
| 737 | { | ||
| 738 | List<FileFacade> patchGroup = patchGroups[facade.WixFile.PatchGroup]; | ||
| 739 | |||
| 740 | if (null == patchGroup) | ||
| 741 | { | ||
| 742 | patchGroup = new List<FileFacade>(); | ||
| 743 | patchGroups.Add(facade.WixFile.PatchGroup, patchGroup); | ||
| 744 | } | ||
| 745 | |||
| 746 | patchGroup.Add(facade); | ||
| 747 | } | ||
| 748 | else | ||
| 749 | { | ||
| 750 | facade.File.Sequence = ++lastSequence; | ||
| 751 | } | ||
| 752 | } | ||
| 753 | |||
| 754 | if (null != mediaRow) | ||
| 755 | { | ||
| 756 | mediaRow.LastSequence = lastSequence; | ||
| 757 | mediaRow = null; | ||
| 758 | } | ||
| 759 | |||
| 760 | // sequence the patch-added files | ||
| 761 | foreach (List<FileFacade> patchGroup in patchGroups.Values) | ||
| 762 | { | ||
| 763 | foreach (FileFacade facade in patchGroup) | ||
| 764 | { | ||
| 765 | if (null == mediaRow) | ||
| 766 | { | ||
| 767 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
| 768 | } | ||
| 769 | else if (mediaRow.DiskId != facade.WixFile.DiskId) | ||
| 770 | { | ||
| 771 | mediaRow.LastSequence = lastSequence; | ||
| 772 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
| 773 | } | ||
| 774 | |||
| 775 | facade.File.Sequence = ++lastSequence; | ||
| 776 | } | ||
| 777 | } | ||
| 778 | |||
| 779 | if (null != mediaRow) | ||
| 780 | { | ||
| 781 | mediaRow.LastSequence = lastSequence; | ||
| 782 | } | ||
| 783 | } | ||
| 784 | } | ||
| 785 | |||
| 786 | /// <summary> | ||
| 787 | /// Set the guids for components with generatable guids. | ||
| 788 | /// </summary> | ||
| 789 | /// <param name="output">Internal representation of the database to operate on.</param> | ||
| 790 | private void SetComponentGuids(Output output) | ||
| 791 | { | ||
| 792 | Table componentTable = output.Tables["Component"]; | ||
| 793 | if (null != componentTable) | ||
| 794 | { | ||
| 795 | Hashtable registryKeyRows = null; | ||
| 796 | Hashtable directories = null; | ||
| 797 | Hashtable componentIdGenSeeds = null; | ||
| 798 | Dictionary<string, List<FileRow>> fileRows = null; | ||
| 799 | |||
| 800 | // find components with generatable guids | ||
| 801 | foreach (ComponentRow componentRow in componentTable.Rows) | ||
| 802 | { | ||
| 803 | // component guid will be generated | ||
| 804 | if ("*" == componentRow.Guid) | ||
| 805 | { | ||
| 806 | if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath) | ||
| 807 | { | ||
| 808 | Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers)); | ||
| 809 | } | ||
| 810 | else if (componentRow.IsRegistryKeyPath) | ||
| 811 | { | ||
| 812 | if (null == registryKeyRows) | ||
| 813 | { | ||
| 814 | Table registryTable = output.Tables["Registry"]; | ||
| 815 | |||
| 816 | registryKeyRows = new Hashtable(registryTable.Rows.Count); | ||
| 817 | |||
| 818 | foreach (Row registryRow in registryTable.Rows) | ||
| 819 | { | ||
| 820 | registryKeyRows.Add((string)registryRow[0], registryRow); | ||
| 821 | } | ||
| 822 | } | ||
| 823 | |||
| 824 | Row foundRow = registryKeyRows[componentRow.KeyPath] as Row; | ||
| 825 | |||
| 826 | string bitness = componentRow.Is64Bit ? "64" : String.Empty; | ||
| 827 | if (null != foundRow) | ||
| 828 | { | ||
| 829 | string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]); | ||
| 830 | componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant(); | ||
| 831 | } | ||
| 832 | } | ||
| 833 | else // must be a File KeyPath | ||
| 834 | { | ||
| 835 | // if the directory table hasn't been loaded into an indexed hash | ||
| 836 | // of directory ids to target names do that now. | ||
| 837 | if (null == directories) | ||
| 838 | { | ||
| 839 | Table directoryTable = output.Tables["Directory"]; | ||
| 840 | |||
| 841 | int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0; | ||
| 842 | |||
| 843 | directories = new Hashtable(numDirectoryTableRows); | ||
| 844 | |||
| 845 | // get the target paths for all directories | ||
| 846 | if (null != directoryTable) | ||
| 847 | { | ||
| 848 | foreach (Row row in directoryTable.Rows) | ||
| 849 | { | ||
| 850 | // if the directory Id already exists, we will skip it here since | ||
| 851 | // checking for duplicate primary keys is done later when importing tables | ||
| 852 | // into database | ||
| 853 | if (directories.ContainsKey(row[0])) | ||
| 854 | { | ||
| 855 | continue; | ||
| 856 | } | ||
| 857 | |||
| 858 | string targetName = Installer.GetName((string)row[2], false, true); | ||
| 859 | directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName)); | ||
| 860 | } | ||
| 861 | } | ||
| 862 | } | ||
| 863 | |||
| 864 | // if the component id generation seeds have not been indexed | ||
| 865 | // from the WixDirectory table do that now. | ||
| 866 | if (null == componentIdGenSeeds) | ||
| 867 | { | ||
| 868 | Table wixDirectoryTable = output.Tables["WixDirectory"]; | ||
| 869 | |||
| 870 | int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0; | ||
| 871 | |||
| 872 | componentIdGenSeeds = new Hashtable(numWixDirectoryRows); | ||
| 873 | |||
| 874 | // if there are any WixDirectory rows, build up the Component Guid | ||
| 875 | // generation seeds indexed by Directory/@Id. | ||
| 876 | if (null != wixDirectoryTable) | ||
| 877 | { | ||
| 878 | foreach (Row row in wixDirectoryTable.Rows) | ||
| 879 | { | ||
| 880 | componentIdGenSeeds.Add(row[0], (string)row[1]); | ||
| 881 | } | ||
| 882 | } | ||
| 883 | } | ||
| 884 | |||
| 885 | // if the file rows have not been indexed by File.Component yet | ||
| 886 | // then do that now | ||
| 887 | if (null == fileRows) | ||
| 888 | { | ||
| 889 | Table fileTable = output.Tables["File"]; | ||
| 890 | |||
| 891 | int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0; | ||
| 892 | |||
| 893 | fileRows = new Dictionary<string, List<FileRow>>(numFileRows); | ||
| 894 | |||
| 895 | if (null != fileTable) | ||
| 896 | { | ||
| 897 | foreach (FileRow file in fileTable.Rows) | ||
| 898 | { | ||
| 899 | List<FileRow> files; | ||
| 900 | if (!fileRows.TryGetValue(file.Component, out files)) | ||
| 901 | { | ||
| 902 | files = new List<FileRow>(); | ||
| 903 | fileRows.Add(file.Component, files); | ||
| 904 | } | ||
| 905 | |||
| 906 | files.Add(file); | ||
| 907 | } | ||
| 908 | } | ||
| 909 | } | ||
| 910 | |||
| 911 | // validate component meets all the conditions to have a generated guid | ||
| 912 | List<FileRow> currentComponentFiles = fileRows[componentRow.Component]; | ||
| 913 | int numFilesInComponent = currentComponentFiles.Count; | ||
| 914 | string path = null; | ||
| 915 | |||
| 916 | foreach (FileRow fileRow in currentComponentFiles) | ||
| 917 | { | ||
| 918 | if (fileRow.File == componentRow.KeyPath) | ||
| 919 | { | ||
| 920 | // calculate the key file's canonical target path | ||
| 921 | string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true); | ||
| 922 | string fileName = Installer.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture); | ||
| 923 | path = Path.Combine(directoryPath, fileName); | ||
| 924 | |||
| 925 | // find paths that are not canonicalized | ||
| 926 | if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || | ||
| 927 | path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || | ||
| 928 | path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || | ||
| 929 | path.StartsWith("TARGETDIR", StringComparison.Ordinal) || | ||
| 930 | path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || | ||
| 931 | path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) | ||
| 932 | { | ||
| 933 | Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path)); | ||
| 934 | } | ||
| 935 | |||
| 936 | // if component has more than one file, the key path must be versioned | ||
| 937 | if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) | ||
| 938 | { | ||
| 939 | Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers)); | ||
| 940 | } | ||
| 941 | } | ||
| 942 | else | ||
| 943 | { | ||
| 944 | // not a key path, so it must be an unversioned file if component has more than one file | ||
| 945 | if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) | ||
| 946 | { | ||
| 947 | Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers)); | ||
| 948 | } | ||
| 949 | } | ||
| 950 | } | ||
| 951 | |||
| 952 | // if the rules were followed, reward with a generated guid | ||
| 953 | if (!Messaging.Instance.EncounteredError) | ||
| 954 | { | ||
| 955 | componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant(); | ||
| 956 | } | ||
| 957 | } | ||
| 958 | } | ||
| 959 | } | ||
| 960 | } | ||
| 961 | } | ||
| 962 | |||
| 963 | /// <summary> | ||
| 964 | /// Creates instance transform substorages in the output. | ||
| 965 | /// </summary> | ||
| 966 | /// <param name="output">Output containing instance transform definitions.</param> | ||
| 967 | private void CreateInstanceTransforms(Output output) | ||
| 968 | { | ||
| 969 | // Create and add substorages for instance transforms. | ||
| 970 | Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"]; | ||
| 971 | if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count) | ||
| 972 | { | ||
| 973 | string targetProductCode = null; | ||
| 974 | string targetUpgradeCode = null; | ||
| 975 | string targetProductVersion = null; | ||
| 976 | |||
| 977 | Table targetSummaryInformationTable = output.Tables["_SummaryInformation"]; | ||
| 978 | Table targetPropertyTable = output.Tables["Property"]; | ||
| 979 | |||
| 980 | // Get the data from target database | ||
| 981 | foreach (Row propertyRow in targetPropertyTable.Rows) | ||
| 982 | { | ||
| 983 | if ("ProductCode" == (string)propertyRow[0]) | ||
| 984 | { | ||
| 985 | targetProductCode = (string)propertyRow[1]; | ||
| 986 | } | ||
| 987 | else if ("ProductVersion" == (string)propertyRow[0]) | ||
| 988 | { | ||
| 989 | targetProductVersion = (string)propertyRow[1]; | ||
| 990 | } | ||
| 991 | else if ("UpgradeCode" == (string)propertyRow[0]) | ||
| 992 | { | ||
| 993 | targetUpgradeCode = (string)propertyRow[1]; | ||
| 994 | } | ||
| 995 | } | ||
| 996 | |||
| 997 | // Index the Instance Component Rows. | ||
| 998 | Dictionary<string, ComponentRow> instanceComponentGuids = new Dictionary<string, ComponentRow>(); | ||
| 999 | Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"]; | ||
| 1000 | if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count) | ||
| 1001 | { | ||
| 1002 | foreach (Row row in targetInstanceComponentTable.Rows) | ||
| 1003 | { | ||
| 1004 | // Build up all the instances, we'll get the Components rows from the real Component table. | ||
| 1005 | instanceComponentGuids.Add((string)row[0], null); | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | Table targetComponentTable = output.Tables["Component"]; | ||
| 1009 | foreach (ComponentRow componentRow in targetComponentTable.Rows) | ||
| 1010 | { | ||
| 1011 | string component = (string)componentRow[0]; | ||
| 1012 | if (instanceComponentGuids.ContainsKey(component)) | ||
| 1013 | { | ||
| 1014 | instanceComponentGuids[component] = componentRow; | ||
| 1015 | } | ||
| 1016 | } | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | // Generate the instance transforms | ||
| 1020 | foreach (Row instanceRow in wixInstanceTransformsTable.Rows) | ||
| 1021 | { | ||
| 1022 | string instanceId = (string)instanceRow[0]; | ||
| 1023 | |||
| 1024 | Output instanceTransform = new Output(instanceRow.SourceLineNumbers); | ||
| 1025 | instanceTransform.Type = OutputType.Transform; | ||
| 1026 | instanceTransform.Codepage = output.Codepage; | ||
| 1027 | |||
| 1028 | Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 1029 | string targetPlatformAndLanguage = null; | ||
| 1030 | |||
| 1031 | foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows) | ||
| 1032 | { | ||
| 1033 | if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE | ||
| 1034 | { | ||
| 1035 | targetPlatformAndLanguage = (string)summaryInformationRow[1]; | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | // Copy the row's data to the transform. | ||
| 1039 | Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
| 1040 | copyOfSummaryRow[0] = summaryInformationRow[0]; | ||
| 1041 | copyOfSummaryRow[1] = summaryInformationRow[1]; | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | // Modify the appropriate properties. | ||
| 1045 | Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); | ||
| 1046 | |||
| 1047 | // Change the ProductCode property | ||
| 1048 | string productCode = (string)instanceRow[2]; | ||
| 1049 | if ("*" == productCode) | ||
| 1050 | { | ||
| 1051 | productCode = Common.GenerateGuid(); | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
| 1055 | productCodeRow.Operation = RowOperation.Modify; | ||
| 1056 | productCodeRow.Fields[1].Modified = true; | ||
| 1057 | productCodeRow[0] = "ProductCode"; | ||
| 1058 | productCodeRow[1] = productCode; | ||
| 1059 | |||
| 1060 | // Change the instance property | ||
| 1061 | Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
| 1062 | instanceIdRow.Operation = RowOperation.Modify; | ||
| 1063 | instanceIdRow.Fields[1].Modified = true; | ||
| 1064 | instanceIdRow[0] = (string)instanceRow[1]; | ||
| 1065 | instanceIdRow[1] = instanceId; | ||
| 1066 | |||
| 1067 | if (null != instanceRow[3]) | ||
| 1068 | { | ||
| 1069 | // Change the ProductName property | ||
| 1070 | Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
| 1071 | productNameRow.Operation = RowOperation.Modify; | ||
| 1072 | productNameRow.Fields[1].Modified = true; | ||
| 1073 | productNameRow[0] = "ProductName"; | ||
| 1074 | productNameRow[1] = (string)instanceRow[3]; | ||
| 1075 | } | ||
| 1076 | |||
| 1077 | if (null != instanceRow[4]) | ||
| 1078 | { | ||
| 1079 | // Change the UpgradeCode property | ||
| 1080 | Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
| 1081 | upgradeCodeRow.Operation = RowOperation.Modify; | ||
| 1082 | upgradeCodeRow.Fields[1].Modified = true; | ||
| 1083 | upgradeCodeRow[0] = "UpgradeCode"; | ||
| 1084 | upgradeCodeRow[1] = instanceRow[4]; | ||
| 1085 | |||
| 1086 | // Change the Upgrade table | ||
| 1087 | Table targetUpgradeTable = output.Tables["Upgrade"]; | ||
| 1088 | if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) | ||
| 1089 | { | ||
| 1090 | string upgradeId = (string)instanceRow[4]; | ||
| 1091 | Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); | ||
| 1092 | foreach (Row row in targetUpgradeTable.Rows) | ||
| 1093 | { | ||
| 1094 | // In case they are upgrading other codes to this new product, leave the ones that don't match the | ||
| 1095 | // Product.UpgradeCode intact. | ||
| 1096 | if (targetUpgradeCode == (string)row[0]) | ||
| 1097 | { | ||
| 1098 | Row upgradeRow = upgradeTable.CreateRow(null); | ||
| 1099 | upgradeRow.Operation = RowOperation.Add; | ||
| 1100 | upgradeRow.Fields[0].Modified = true; | ||
| 1101 | // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. | ||
| 1102 | // upgradeRow.Fields[0].PreviousData = (string)row[0]; | ||
| 1103 | |||
| 1104 | // Inserting a new Upgrade record with the updated UpgradeCode | ||
| 1105 | upgradeRow[0] = upgradeId; | ||
| 1106 | upgradeRow[1] = row[1]; | ||
| 1107 | upgradeRow[2] = row[2]; | ||
| 1108 | upgradeRow[3] = row[3]; | ||
| 1109 | upgradeRow[4] = row[4]; | ||
| 1110 | upgradeRow[5] = row[5]; | ||
| 1111 | upgradeRow[6] = row[6]; | ||
| 1112 | |||
| 1113 | // Delete the old row | ||
| 1114 | Row upgradeRemoveRow = upgradeTable.CreateRow(null); | ||
| 1115 | upgradeRemoveRow.Operation = RowOperation.Delete; | ||
| 1116 | upgradeRemoveRow[0] = row[0]; | ||
| 1117 | upgradeRemoveRow[1] = row[1]; | ||
| 1118 | upgradeRemoveRow[2] = row[2]; | ||
| 1119 | upgradeRemoveRow[3] = row[3]; | ||
| 1120 | upgradeRemoveRow[4] = row[4]; | ||
| 1121 | upgradeRemoveRow[5] = row[5]; | ||
| 1122 | upgradeRemoveRow[6] = row[6]; | ||
| 1123 | } | ||
| 1124 | } | ||
| 1125 | } | ||
| 1126 | } | ||
| 1127 | |||
| 1128 | // If there are instance Components generate new GUIDs for them. | ||
| 1129 | if (0 < instanceComponentGuids.Count) | ||
| 1130 | { | ||
| 1131 | Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); | ||
| 1132 | foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values) | ||
| 1133 | { | ||
| 1134 | string guid = targetComponentRow.Guid; | ||
| 1135 | if (!String.IsNullOrEmpty(guid)) | ||
| 1136 | { | ||
| 1137 | Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); | ||
| 1138 | instanceComponentRow.Operation = RowOperation.Modify; | ||
| 1139 | instanceComponentRow.Fields[1].Modified = true; | ||
| 1140 | instanceComponentRow[0] = targetComponentRow[0]; | ||
| 1141 | instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture); | ||
| 1142 | instanceComponentRow[2] = targetComponentRow[2]; | ||
| 1143 | instanceComponentRow[3] = targetComponentRow[3]; | ||
| 1144 | instanceComponentRow[4] = targetComponentRow[4]; | ||
| 1145 | instanceComponentRow[5] = targetComponentRow[5]; | ||
| 1146 | } | ||
| 1147 | } | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | // Update the summary information | ||
| 1151 | Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count); | ||
| 1152 | foreach (Row row in instanceSummaryInformationTable.Rows) | ||
| 1153 | { | ||
| 1154 | summaryRows[row[0]] = row; | ||
| 1155 | |||
| 1156 | if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
| 1157 | { | ||
| 1158 | row[1] = targetPlatformAndLanguage; | ||
| 1159 | } | ||
| 1160 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
| 1161 | { | ||
| 1162 | row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); | ||
| 1163 | } | ||
| 1164 | else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) | ||
| 1165 | { | ||
| 1166 | row[1] = 0; | ||
| 1167 | } | ||
| 1168 | else if ((int)SummaryInformation.Transform.Security == (int)row[0]) | ||
| 1169 | { | ||
| 1170 | row[1] = "4"; | ||
| 1171 | } | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) | ||
| 1175 | { | ||
| 1176 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
| 1177 | summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; | ||
| 1178 | summaryRow[1] = targetPlatformAndLanguage; | ||
| 1179 | } | ||
| 1180 | else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) | ||
| 1181 | { | ||
| 1182 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
| 1183 | summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
| 1184 | summaryRow[1] = "0"; | ||
| 1185 | } | ||
| 1186 | else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) | ||
| 1187 | { | ||
| 1188 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
| 1189 | summaryRow[0] = (int)SummaryInformation.Transform.Security; | ||
| 1190 | summaryRow[1] = "4"; | ||
| 1191 | } | ||
| 1192 | |||
| 1193 | output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); | ||
| 1194 | } | ||
| 1195 | } | ||
| 1196 | } | ||
| 1197 | |||
| 1198 | /// <summary> | ||
| 1199 | /// Validate that there are no duplicate GUIDs in the output. | ||
| 1200 | /// </summary> | ||
| 1201 | /// <remarks> | ||
| 1202 | /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a | ||
| 1203 | /// warning, as the conditions might be mutually exclusive. | ||
| 1204 | /// </remarks> | ||
| 1205 | private void ValidateComponentGuids(Output output) | ||
| 1206 | { | ||
| 1207 | Table componentTable = output.Tables["Component"]; | ||
| 1208 | if (null != componentTable) | ||
| 1209 | { | ||
| 1210 | Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count); | ||
| 1211 | |||
| 1212 | foreach (ComponentRow row in componentTable.Rows) | ||
| 1213 | { | ||
| 1214 | // we don't care about unmanaged components and if there's a * GUID remaining, | ||
| 1215 | // there's already an error that prevented it from being replaced with a real GUID. | ||
| 1216 | if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid) | ||
| 1217 | { | ||
| 1218 | bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition); | ||
| 1219 | bool allComponentsHaveConditions = thisComponentHasCondition; | ||
| 1220 | |||
| 1221 | if (componentGuidConditions.ContainsKey(row.Guid)) | ||
| 1222 | { | ||
| 1223 | allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition; | ||
| 1224 | |||
| 1225 | if (allComponentsHaveConditions) | ||
| 1226 | { | ||
| 1227 | Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid)); | ||
| 1228 | } | ||
| 1229 | else | ||
| 1230 | { | ||
| 1231 | Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid)); | ||
| 1232 | } | ||
| 1233 | } | ||
| 1234 | |||
| 1235 | componentGuidConditions[row.Guid] = allComponentsHaveConditions; | ||
| 1236 | } | ||
| 1237 | } | ||
| 1238 | } | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | /// <summary> | ||
| 1242 | /// Update Control and BBControl text by reading from files when necessary. | ||
| 1243 | /// </summary> | ||
| 1244 | /// <param name="output">Internal representation of the msi database to operate upon.</param> | ||
| 1245 | private void UpdateControlText(Output output) | ||
| 1246 | { | ||
| 1247 | UpdateControlTextCommand command = new UpdateControlTextCommand(); | ||
| 1248 | command.BBControlTable = output.Tables["BBControl"]; | ||
| 1249 | command.WixBBControlTable = output.Tables["WixBBControl"]; | ||
| 1250 | command.ControlTable = output.Tables["Control"]; | ||
| 1251 | command.WixControlTable = output.Tables["WixControl"]; | ||
| 1252 | command.Execute(); | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory) | ||
| 1256 | { | ||
| 1257 | string layout = null; | ||
| 1258 | |||
| 1259 | foreach (IBinderFileManager fileManager in this.FileManagers) | ||
| 1260 | { | ||
| 1261 | layout = fileManager.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory); | ||
| 1262 | if (!String.IsNullOrEmpty(layout)) | ||
| 1263 | { | ||
| 1264 | break; | ||
| 1265 | } | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | // If no binder file manager resolved the layout, do the default behavior. | ||
| 1269 | if (String.IsNullOrEmpty(layout)) | ||
| 1270 | { | ||
| 1271 | if (String.IsNullOrEmpty(mediaLayoutDirectory)) | ||
| 1272 | { | ||
| 1273 | layout = layoutDirectory; | ||
| 1274 | } | ||
| 1275 | else if (Path.IsPathRooted(mediaLayoutDirectory)) | ||
| 1276 | { | ||
| 1277 | layout = mediaLayoutDirectory; | ||
| 1278 | } | ||
| 1279 | else | ||
| 1280 | { | ||
| 1281 | layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); | ||
| 1282 | } | ||
| 1283 | } | ||
| 1284 | |||
| 1285 | return layout; | ||
| 1286 | } | ||
| 1287 | |||
| 1288 | /// <summary> | ||
| 1289 | /// Creates the MSI/MSM/PCP database. | ||
| 1290 | /// </summary> | ||
| 1291 | /// <param name="output">Output to create database for.</param> | ||
| 1292 | /// <param name="databaseFile">The database file to create.</param> | ||
| 1293 | /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param> | ||
| 1294 | /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param> | ||
| 1295 | private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) | ||
| 1296 | { | ||
| 1297 | GenerateDatabaseCommand command = new GenerateDatabaseCommand(); | ||
| 1298 | command.Extensions = this.Extensions; | ||
| 1299 | command.FileManagers = this.FileManagers; | ||
| 1300 | command.Output = output; | ||
| 1301 | command.OutputPath = databaseFile; | ||
| 1302 | command.KeepAddedColumns = keepAddedColumns; | ||
| 1303 | command.UseSubDirectory = useSubdirectory; | ||
| 1304 | command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; | ||
| 1305 | command.TableDefinitions = this.TableDefinitions; | ||
| 1306 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 1307 | command.Codepage = this.Codepage; | ||
| 1308 | command.Execute(); | ||
| 1309 | } | ||
| 1310 | } | ||
| 1311 | } | ||
diff --git a/src/WixToolset.Core/Bind/BindTransformCommand.cs b/src/WixToolset.Core/Bind/BindTransformCommand.cs deleted file mode 100644 index e909f191..00000000 --- a/src/WixToolset.Core/Bind/BindTransformCommand.cs +++ /dev/null | |||
| @@ -1,473 +0,0 @@ | |||
| 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.Bind | ||
| 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 : ICommand | ||
| 15 | { | ||
| 16 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
| 17 | |||
| 18 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | ||
| 19 | |||
| 20 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 21 | |||
| 22 | public string TempFilesLocation { private get; set; } | ||
| 23 | |||
| 24 | public Output Transform { private get; set; } | ||
| 25 | |||
| 26 | public string OutputPath { private get; set; } | ||
| 27 | |||
| 28 | public void Execute() | ||
| 29 | { | ||
| 30 | int transformFlags = 0; | ||
| 31 | |||
| 32 | Output targetOutput = new Output(null); | ||
| 33 | Output updatedOutput = new Output(null); | ||
| 34 | |||
| 35 | // TODO: handle added columns | ||
| 36 | |||
| 37 | // to generate a localized transform, both the target and updated | ||
| 38 | // databases need to have the same code page. the only reason to | ||
| 39 | // set different code pages is to support localized primary key | ||
| 40 | // columns, but that would only support deleting rows. if this | ||
| 41 | // becomes necessary, define a PreviousCodepage property on the | ||
| 42 | // Output class and persist this throughout transform generation. | ||
| 43 | targetOutput.Codepage = this.Transform.Codepage; | ||
| 44 | updatedOutput.Codepage = this.Transform.Codepage; | ||
| 45 | |||
| 46 | // remove certain Property rows which will be populated from summary information values | ||
| 47 | string targetUpgradeCode = null; | ||
| 48 | string updatedUpgradeCode = null; | ||
| 49 | |||
| 50 | Table propertyTable = this.Transform.Tables["Property"]; | ||
| 51 | if (null != propertyTable) | ||
| 52 | { | ||
| 53 | for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) | ||
| 54 | { | ||
| 55 | Row row = propertyTable.Rows[i]; | ||
| 56 | |||
| 57 | if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) | ||
| 58 | { | ||
| 59 | propertyTable.Rows.RemoveAt(i); | ||
| 60 | |||
| 61 | if ("UpgradeCode" == (string)row[0]) | ||
| 62 | { | ||
| 63 | updatedUpgradeCode = (string)row[1]; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 70 | Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 71 | Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
| 72 | Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
| 73 | |||
| 74 | // process special summary information values | ||
| 75 | foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows) | ||
| 76 | { | ||
| 77 | if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) | ||
| 78 | { | ||
| 79 | // convert from a web name if provided | ||
| 80 | string codePage = (string)row.Fields[1].Data; | ||
| 81 | if (null == codePage) | ||
| 82 | { | ||
| 83 | codePage = "0"; | ||
| 84 | } | ||
| 85 | else | ||
| 86 | { | ||
| 87 | codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); | ||
| 88 | } | ||
| 89 | |||
| 90 | string previousCodePage = (string)row.Fields[1].PreviousData; | ||
| 91 | if (null == previousCodePage) | ||
| 92 | { | ||
| 93 | previousCodePage = "0"; | ||
| 94 | } | ||
| 95 | else | ||
| 96 | { | ||
| 97 | previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); | ||
| 98 | } | ||
| 99 | |||
| 100 | Row targetCodePageRow = targetSummaryInfo.CreateRow(null); | ||
| 101 | targetCodePageRow[0] = 1; // PID_CODEPAGE | ||
| 102 | targetCodePageRow[1] = previousCodePage; | ||
| 103 | |||
| 104 | Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null); | ||
| 105 | updatedCodePageRow[0] = 1; // PID_CODEPAGE | ||
| 106 | updatedCodePageRow[1] = codePage; | ||
| 107 | } | ||
| 108 | else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] || | ||
| 109 | (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
| 110 | { | ||
| 111 | // the target language | ||
| 112 | string[] propertyData = ((string)row[1]).Split(';'); | ||
| 113 | string lang = 2 == propertyData.Length ? propertyData[1] : "0"; | ||
| 114 | |||
| 115 | Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo; | ||
| 116 | Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable; | ||
| 117 | |||
| 118 | Row productLanguageRow = tempPropertyTable.CreateRow(null); | ||
| 119 | productLanguageRow[0] = "ProductLanguage"; | ||
| 120 | productLanguageRow[1] = lang; | ||
| 121 | |||
| 122 | // set the platform;language on the MSI to be generated | ||
| 123 | Row templateRow = tempSummaryInfo.CreateRow(null); | ||
| 124 | templateRow[0] = 7; // PID_TEMPLATE | ||
| 125 | templateRow[1] = (string)row[1]; | ||
| 126 | } | ||
| 127 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
| 128 | { | ||
| 129 | string[] propertyData = ((string)row[1]).Split(';'); | ||
| 130 | |||
| 131 | Row targetProductCodeRow = targetPropertyTable.CreateRow(null); | ||
| 132 | targetProductCodeRow[0] = "ProductCode"; | ||
| 133 | targetProductCodeRow[1] = propertyData[0].Substring(0, 38); | ||
| 134 | |||
| 135 | Row targetProductVersionRow = targetPropertyTable.CreateRow(null); | ||
| 136 | targetProductVersionRow[0] = "ProductVersion"; | ||
| 137 | targetProductVersionRow[1] = propertyData[0].Substring(38); | ||
| 138 | |||
| 139 | Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null); | ||
| 140 | updatedProductCodeRow[0] = "ProductCode"; | ||
| 141 | updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); | ||
| 142 | |||
| 143 | Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null); | ||
| 144 | updatedProductVersionRow[0] = "ProductVersion"; | ||
| 145 | updatedProductVersionRow[1] = propertyData[1].Substring(38); | ||
| 146 | |||
| 147 | // UpgradeCode is optional and may not exists in the target | ||
| 148 | // or upgraded databases, so do not include a null-valued | ||
| 149 | // UpgradeCode property. | ||
| 150 | |||
| 151 | targetUpgradeCode = propertyData[2]; | ||
| 152 | if (!String.IsNullOrEmpty(targetUpgradeCode)) | ||
| 153 | { | ||
| 154 | Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); | ||
| 155 | targetUpgradeCodeRow[0] = "UpgradeCode"; | ||
| 156 | targetUpgradeCodeRow[1] = targetUpgradeCode; | ||
| 157 | |||
| 158 | // If the target UpgradeCode is specified, an updated | ||
| 159 | // UpgradeCode is required. | ||
| 160 | if (String.IsNullOrEmpty(updatedUpgradeCode)) | ||
| 161 | { | ||
| 162 | updatedUpgradeCode = targetUpgradeCode; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | if (!String.IsNullOrEmpty(updatedUpgradeCode)) | ||
| 167 | { | ||
| 168 | Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); | ||
| 169 | updatedUpgradeCodeRow[0] = "UpgradeCode"; | ||
| 170 | updatedUpgradeCodeRow[1] = updatedUpgradeCode; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) | ||
| 174 | { | ||
| 175 | transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); | ||
| 176 | } | ||
| 177 | else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0]) | ||
| 178 | { | ||
| 179 | // PID_LASTPRINTED should be null for transforms | ||
| 180 | row.Operation = RowOperation.None; | ||
| 181 | } | ||
| 182 | else | ||
| 183 | { | ||
| 184 | // add everything else as is | ||
| 185 | Row targetRow = targetSummaryInfo.CreateRow(null); | ||
| 186 | targetRow[0] = row[0]; | ||
| 187 | targetRow[1] = row[1]; | ||
| 188 | |||
| 189 | Row updatedRow = updatedSummaryInfo.CreateRow(null); | ||
| 190 | updatedRow[0] = row[0]; | ||
| 191 | updatedRow[1] = row[1]; | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | // Validate that both databases have an UpgradeCode if the | ||
| 196 | // authoring transform will validate the UpgradeCode; otherwise, | ||
| 197 | // MsiCreateTransformSummaryinfo() will fail with 1620. | ||
| 198 | if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && | ||
| 199 | (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) | ||
| 200 | { | ||
| 201 | Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired()); | ||
| 202 | } | ||
| 203 | |||
| 204 | string emptyFile = null; | ||
| 205 | |||
| 206 | foreach (Table table in this.Transform.Tables) | ||
| 207 | { | ||
| 208 | // Ignore unreal tables when building transforms except the _Stream table. | ||
| 209 | // These tables are ignored when generating the database so there is no reason | ||
| 210 | // to process them here. | ||
| 211 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
| 212 | { | ||
| 213 | continue; | ||
| 214 | } | ||
| 215 | |||
| 216 | // process table operations | ||
| 217 | switch (table.Operation) | ||
| 218 | { | ||
| 219 | case TableOperation.Add: | ||
| 220 | updatedOutput.EnsureTable(table.Definition); | ||
| 221 | break; | ||
| 222 | case TableOperation.Drop: | ||
| 223 | targetOutput.EnsureTable(table.Definition); | ||
| 224 | continue; | ||
| 225 | default: | ||
| 226 | targetOutput.EnsureTable(table.Definition); | ||
| 227 | updatedOutput.EnsureTable(table.Definition); | ||
| 228 | break; | ||
| 229 | } | ||
| 230 | |||
| 231 | // process row operations | ||
| 232 | foreach (Row row in table.Rows) | ||
| 233 | { | ||
| 234 | switch (row.Operation) | ||
| 235 | { | ||
| 236 | case RowOperation.Add: | ||
| 237 | Table updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
| 238 | updatedTable.Rows.Add(row); | ||
| 239 | continue; | ||
| 240 | case RowOperation.Delete: | ||
| 241 | Table targetTable = targetOutput.EnsureTable(table.Definition); | ||
| 242 | targetTable.Rows.Add(row); | ||
| 243 | |||
| 244 | // fill-in non-primary key values | ||
| 245 | foreach (Field field in row.Fields) | ||
| 246 | { | ||
| 247 | if (!field.Column.PrimaryKey) | ||
| 248 | { | ||
| 249 | if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) | ||
| 250 | { | ||
| 251 | field.Data = field.Column.MinValue; | ||
| 252 | } | ||
| 253 | else if (ColumnType.Object == field.Column.Type) | ||
| 254 | { | ||
| 255 | if (null == emptyFile) | ||
| 256 | { | ||
| 257 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
| 258 | } | ||
| 259 | |||
| 260 | field.Data = emptyFile; | ||
| 261 | } | ||
| 262 | else | ||
| 263 | { | ||
| 264 | field.Data = "0"; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | } | ||
| 268 | continue; | ||
| 269 | } | ||
| 270 | |||
| 271 | // Assure that the file table's sequence is populated | ||
| 272 | if ("File" == table.Name) | ||
| 273 | { | ||
| 274 | foreach (Row fileRow in table.Rows) | ||
| 275 | { | ||
| 276 | if (null == fileRow[7]) | ||
| 277 | { | ||
| 278 | if (RowOperation.Add == fileRow.Operation) | ||
| 279 | { | ||
| 280 | Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); | ||
| 281 | break; | ||
| 282 | } | ||
| 283 | |||
| 284 | // Set to 1 to prevent invalid IDT file from being generated | ||
| 285 | fileRow[7] = 1; | ||
| 286 | } | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | // process modified and unmodified rows | ||
| 291 | bool modifiedRow = false; | ||
| 292 | Row targetRow = new Row(null, table.Definition); | ||
| 293 | Row updatedRow = row; | ||
| 294 | for (int i = 0; i < row.Fields.Length; i++) | ||
| 295 | { | ||
| 296 | Field updatedField = row.Fields[i]; | ||
| 297 | |||
| 298 | if (updatedField.Modified) | ||
| 299 | { | ||
| 300 | // set a different value in the target row to ensure this value will be modified during transform generation | ||
| 301 | if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) | ||
| 302 | { | ||
| 303 | if (null == updatedField.Data || 1 != (int)updatedField.Data) | ||
| 304 | { | ||
| 305 | targetRow[i] = 1; | ||
| 306 | } | ||
| 307 | else | ||
| 308 | { | ||
| 309 | targetRow[i] = 2; | ||
| 310 | } | ||
| 311 | } | ||
| 312 | else if (ColumnType.Object == updatedField.Column.Type) | ||
| 313 | { | ||
| 314 | if (null == emptyFile) | ||
| 315 | { | ||
| 316 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
| 317 | } | ||
| 318 | |||
| 319 | targetRow[i] = emptyFile; | ||
| 320 | } | ||
| 321 | else | ||
| 322 | { | ||
| 323 | if ("0" != (string)updatedField.Data) | ||
| 324 | { | ||
| 325 | targetRow[i] = "0"; | ||
| 326 | } | ||
| 327 | else | ||
| 328 | { | ||
| 329 | targetRow[i] = "1"; | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | modifiedRow = true; | ||
| 334 | } | ||
| 335 | else if (ColumnType.Object == updatedField.Column.Type) | ||
| 336 | { | ||
| 337 | ObjectField objectField = (ObjectField)updatedField; | ||
| 338 | |||
| 339 | // create an empty file for comparing against | ||
| 340 | if (null == objectField.PreviousData) | ||
| 341 | { | ||
| 342 | if (null == emptyFile) | ||
| 343 | { | ||
| 344 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
| 345 | } | ||
| 346 | |||
| 347 | targetRow[i] = emptyFile; | ||
| 348 | modifiedRow = true; | ||
| 349 | } | ||
| 350 | else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data)) | ||
| 351 | { | ||
| 352 | targetRow[i] = objectField.PreviousData; | ||
| 353 | modifiedRow = true; | ||
| 354 | } | ||
| 355 | } | ||
| 356 | else // unmodified | ||
| 357 | { | ||
| 358 | if (null != updatedField.Data) | ||
| 359 | { | ||
| 360 | targetRow[i] = updatedField.Data; | ||
| 361 | } | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | // modified rows and certain special rows go in the target and updated msi databases | ||
| 366 | if (modifiedRow || | ||
| 367 | ("Property" == table.Name && | ||
| 368 | ("ProductCode" == (string)row[0] || | ||
| 369 | "ProductLanguage" == (string)row[0] || | ||
| 370 | "ProductVersion" == (string)row[0] || | ||
| 371 | "UpgradeCode" == (string)row[0]))) | ||
| 372 | { | ||
| 373 | Table targetTable = targetOutput.EnsureTable(table.Definition); | ||
| 374 | targetTable.Rows.Add(targetRow); | ||
| 375 | |||
| 376 | Table updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
| 377 | updatedTable.Rows.Add(updatedRow); | ||
| 378 | } | ||
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | foreach (BinderExtension extension in this.Extensions) | ||
| 383 | { | ||
| 384 | extension.Finish(this.Transform); | ||
| 385 | } | ||
| 386 | |||
| 387 | // Any errors encountered up to this point can cause errors during generation. | ||
| 388 | if (Messaging.Instance.EncounteredError) | ||
| 389 | { | ||
| 390 | return; | ||
| 391 | } | ||
| 392 | |||
| 393 | string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
| 394 | string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi")); | ||
| 395 | string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi")); | ||
| 396 | |||
| 397 | try | ||
| 398 | { | ||
| 399 | if (!String.IsNullOrEmpty(emptyFile)) | ||
| 400 | { | ||
| 401 | using (FileStream fileStream = File.Create(emptyFile)) | ||
| 402 | { | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | this.GenerateDatabase(targetOutput, targetDatabaseFile, false); | ||
| 407 | this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true); | ||
| 408 | |||
| 409 | // make sure the directory exists | ||
| 410 | Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); | ||
| 411 | |||
| 412 | // create the transform file | ||
| 413 | using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) | ||
| 414 | { | ||
| 415 | using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) | ||
| 416 | { | ||
| 417 | if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) | ||
| 418 | { | ||
| 419 | updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); | ||
| 420 | } | ||
| 421 | else | ||
| 422 | { | ||
| 423 | Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); | ||
| 424 | } | ||
| 425 | } | ||
| 426 | } | ||
| 427 | } | ||
| 428 | finally | ||
| 429 | { | ||
| 430 | if (!String.IsNullOrEmpty(emptyFile)) | ||
| 431 | { | ||
| 432 | File.Delete(emptyFile); | ||
| 433 | } | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | private bool CompareFiles(string targetFile, string updatedFile) | ||
| 438 | { | ||
| 439 | bool? compared = null; | ||
| 440 | foreach (IBinderFileManager fileManager in this.FileManagers) | ||
| 441 | { | ||
| 442 | compared = fileManager.CompareFiles(targetFile, updatedFile); | ||
| 443 | if (compared.HasValue) | ||
| 444 | { | ||
| 445 | break; | ||
| 446 | } | ||
| 447 | } | ||
| 448 | |||
| 449 | if (!compared.HasValue) | ||
| 450 | { | ||
| 451 | throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. | ||
| 452 | } | ||
| 453 | |||
| 454 | return compared.Value; | ||
| 455 | } | ||
| 456 | |||
| 457 | private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns) | ||
| 458 | { | ||
| 459 | GenerateDatabaseCommand command = new GenerateDatabaseCommand(); | ||
| 460 | command.Codepage = output.Codepage; | ||
| 461 | command.Extensions = this.Extensions; | ||
| 462 | command.FileManagers = this.FileManagers; | ||
| 463 | command.KeepAddedColumns = keepAddedColumns; | ||
| 464 | command.Output = output; | ||
| 465 | command.OutputPath = outputPath; | ||
| 466 | command.TableDefinitions = this.TableDefinitions; | ||
| 467 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 468 | command.SuppressAddingValidationRows = true; | ||
| 469 | command.UseSubDirectory = true; | ||
| 470 | command.Execute(); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs deleted file mode 100644 index eb02a983..00000000 --- a/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs +++ /dev/null | |||
| @@ -1,112 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Rows; | ||
| 11 | |||
| 12 | internal class AutomaticallySlipstreamPatchesCommand : ICommand | ||
| 13 | { | ||
| 14 | public IEnumerable<PackageFacade> PackageFacades { private get; set; } | ||
| 15 | |||
| 16 | public Table WixBundlePatchTargetCodeTable { private get; set; } | ||
| 17 | |||
| 18 | public Table SlipstreamMspTable { private get; set; } | ||
| 19 | |||
| 20 | public void Execute() | ||
| 21 | { | ||
| 22 | List<WixBundleMsiPackageRow> msiPackages = new List<WixBundleMsiPackageRow>(); | ||
| 23 | Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsProductCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>(); | ||
| 24 | Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsUpgradeCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>(); | ||
| 25 | |||
| 26 | foreach (PackageFacade facade in this.PackageFacades) | ||
| 27 | { | ||
| 28 | if (WixBundlePackageType.Msi == facade.Package.Type) | ||
| 29 | { | ||
| 30 | // Keep track of all MSI packages. | ||
| 31 | msiPackages.Add(facade.MsiPackage); | ||
| 32 | } | ||
| 33 | else if (WixBundlePackageType.Msp == facade.Package.Type && facade.MspPackage.Slipstream) | ||
| 34 | { | ||
| 35 | IEnumerable<WixBundlePatchTargetCodeRow> patchTargetCodeRows = this.WixBundlePatchTargetCodeTable.RowsAs<WixBundlePatchTargetCodeRow>().Where(r => r.MspPackageId == facade.Package.WixChainItemId); | ||
| 36 | |||
| 37 | // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. | ||
| 38 | foreach (WixBundlePatchTargetCodeRow row in patchTargetCodeRows) | ||
| 39 | { | ||
| 40 | if (row.TargetsProductCode) | ||
| 41 | { | ||
| 42 | List<WixBundlePatchTargetCodeRow> rows; | ||
| 43 | if (!targetsProductCode.TryGetValue(row.TargetCode, out rows)) | ||
| 44 | { | ||
| 45 | rows = new List<WixBundlePatchTargetCodeRow>(); | ||
| 46 | targetsProductCode.Add(row.TargetCode, rows); | ||
| 47 | } | ||
| 48 | |||
| 49 | rows.Add(row); | ||
| 50 | } | ||
| 51 | else if (row.TargetsUpgradeCode) | ||
| 52 | { | ||
| 53 | List<WixBundlePatchTargetCodeRow> rows; | ||
| 54 | if (!targetsUpgradeCode.TryGetValue(row.TargetCode, out rows)) | ||
| 55 | { | ||
| 56 | rows = new List<WixBundlePatchTargetCodeRow>(); | ||
| 57 | targetsUpgradeCode.Add(row.TargetCode, rows); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | RowIndexedList<Row> slipstreamMspRows = new RowIndexedList<Row>(SlipstreamMspTable); | ||
| 65 | |||
| 66 | // Loop through the MSI and slipstream patches targeting it. | ||
| 67 | foreach (WixBundleMsiPackageRow msi in msiPackages) | ||
| 68 | { | ||
| 69 | List<WixBundlePatchTargetCodeRow> rows; | ||
| 70 | if (targetsProductCode.TryGetValue(msi.ProductCode, out rows)) | ||
| 71 | { | ||
| 72 | foreach (WixBundlePatchTargetCodeRow row in rows) | ||
| 73 | { | ||
| 74 | Debug.Assert(row.TargetsProductCode); | ||
| 75 | Debug.Assert(!row.TargetsUpgradeCode); | ||
| 76 | |||
| 77 | Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); | ||
| 78 | slipstreamMspRow[0] = msi.ChainPackageId; | ||
| 79 | slipstreamMspRow[1] = row.MspPackageId; | ||
| 80 | |||
| 81 | if (slipstreamMspRows.TryAdd(slipstreamMspRow)) | ||
| 82 | { | ||
| 83 | SlipstreamMspTable.Rows.Add(slipstreamMspRow); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | rows = null; | ||
| 88 | } | ||
| 89 | |||
| 90 | if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out rows)) | ||
| 91 | { | ||
| 92 | foreach (WixBundlePatchTargetCodeRow row in rows) | ||
| 93 | { | ||
| 94 | Debug.Assert(!row.TargetsProductCode); | ||
| 95 | Debug.Assert(row.TargetsUpgradeCode); | ||
| 96 | |||
| 97 | Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); | ||
| 98 | slipstreamMspRow[0] = msi.ChainPackageId; | ||
| 99 | slipstreamMspRow[1] = row.MspPackageId; | ||
| 100 | |||
| 101 | if (slipstreamMspRows.TryAdd(slipstreamMspRow)) | ||
| 102 | { | ||
| 103 | SlipstreamMspTable.Rows.Add(slipstreamMspRow); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | rows = null; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs b/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs deleted file mode 100644 index 8cb07791..00000000 --- a/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs +++ /dev/null | |||
| @@ -1,378 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Common functionality for Burn PE Writer & Reader for the WiX toolset. | ||
| 12 | /// </summary> | ||
| 13 | /// <remarks>This class encapsulates common functionality related to | ||
| 14 | /// bundled/chained setup packages.</remarks> | ||
| 15 | /// <example> | ||
| 16 | /// </example> | ||
| 17 | internal abstract class BurnCommon : IDisposable | ||
| 18 | { | ||
| 19 | public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; | ||
| 20 | public const string BurnUXContainerEmbeddedIdFormat = "u{0}"; | ||
| 21 | public const string BurnUXContainerPayloadIdFormat = "p{0}"; | ||
| 22 | public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; | ||
| 23 | |||
| 24 | // See WinNT.h for details about the PE format, including the | ||
| 25 | // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, | ||
| 26 | // IMAGE_FILE_HEADER, etc. | ||
| 27 | protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64; | ||
| 28 | protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0; | ||
| 29 | protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60; | ||
| 30 | |||
| 31 | protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20) | ||
| 32 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0; | ||
| 33 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6; | ||
| 34 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20; | ||
| 35 | |||
| 36 | protected const UInt32 IMAGE_OPTIONAL_OFFSET_CHECKSUM = 4 * 16; // checksum is 16 DWORDs into IMAGE_OPTIONAL_HEADER which is right after the IMAGE_NT_HEADER. | ||
| 37 | protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY)); | ||
| 38 | |||
| 39 | protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40; | ||
| 40 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0; | ||
| 41 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8; | ||
| 42 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16; | ||
| 43 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20; | ||
| 44 | |||
| 45 | protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs. | ||
| 46 | protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4; | ||
| 47 | protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; | ||
| 48 | |||
| 49 | protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D; | ||
| 50 | protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550; | ||
| 51 | protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword. | ||
| 52 | |||
| 53 | // The ".wixburn" section contains: | ||
| 54 | // 0- 3: magic number | ||
| 55 | // 4- 7: version | ||
| 56 | // 8-23: bundle GUID | ||
| 57 | // 24-27: engine (stub) size | ||
| 58 | // 28-31: original checksum | ||
| 59 | // 32-35: original signature offset | ||
| 60 | // 36-39: original signature size | ||
| 61 | // 40-43: container type (1 = CAB) | ||
| 62 | // 44-47: container count | ||
| 63 | // 48-51: byte count of manifest + UX container | ||
| 64 | // 52-55: byte count of attached container | ||
| 65 | protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0; | ||
| 66 | protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4; | ||
| 67 | protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8; | ||
| 68 | protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24; | ||
| 69 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28; | ||
| 70 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32; | ||
| 71 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36; | ||
| 72 | protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40; | ||
| 73 | protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44; | ||
| 74 | protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48; | ||
| 75 | protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52; | ||
| 76 | protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD) | ||
| 77 | |||
| 78 | protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300; | ||
| 79 | protected const UInt32 BURN_SECTION_VERSION = 0x00000002; | ||
| 80 | |||
| 81 | protected string fileExe; | ||
| 82 | protected UInt32 peOffset = UInt32.MaxValue; | ||
| 83 | protected UInt16 sections = UInt16.MaxValue; | ||
| 84 | protected UInt32 firstSectionOffset = UInt32.MaxValue; | ||
| 85 | protected UInt32 checksumOffset; | ||
| 86 | protected UInt32 certificateTableSignatureOffset; | ||
| 87 | protected UInt32 certificateTableSignatureSize; | ||
| 88 | protected UInt32 wixburnDataOffset = UInt32.MaxValue; | ||
| 89 | |||
| 90 | // TODO: does this enum exist in another form somewhere? | ||
| 91 | /// <summary> | ||
| 92 | /// The types of attached containers that BurnWriter supports. | ||
| 93 | /// </summary> | ||
| 94 | public enum Container | ||
| 95 | { | ||
| 96 | Nothing = 0, | ||
| 97 | UX, | ||
| 98 | Attached | ||
| 99 | } | ||
| 100 | |||
| 101 | /// <summary> | ||
| 102 | /// Creates a BurnCommon for re-writing a PE file. | ||
| 103 | /// </summary> | ||
| 104 | /// <param name="fileExe">File to modify in-place.</param> | ||
| 105 | /// <param name="bundleGuid">GUID for the bundle.</param> | ||
| 106 | public BurnCommon(string fileExe) | ||
| 107 | { | ||
| 108 | this.fileExe = fileExe; | ||
| 109 | } | ||
| 110 | |||
| 111 | public UInt32 Checksum { get; protected set; } | ||
| 112 | public UInt32 SignatureOffset { get; protected set; } | ||
| 113 | public UInt32 SignatureSize { get; protected set; } | ||
| 114 | public UInt32 Version { get; protected set; } | ||
| 115 | public UInt32 StubSize { get; protected set; } | ||
| 116 | public UInt32 OriginalChecksum { get; protected set; } | ||
| 117 | public UInt32 OriginalSignatureOffset { get; protected set; } | ||
| 118 | public UInt32 OriginalSignatureSize { get; protected set; } | ||
| 119 | public UInt32 EngineSize { get; protected set; } | ||
| 120 | public UInt32 ContainerCount { get; protected set; } | ||
| 121 | public UInt32 UXAddress { get; protected set; } | ||
| 122 | public UInt32 UXSize { get; protected set; } | ||
| 123 | public UInt32 AttachedContainerAddress { get; protected set; } | ||
| 124 | public UInt32 AttachedContainerSize { get; protected set; } | ||
| 125 | |||
| 126 | public void Dispose() | ||
| 127 | { | ||
| 128 | Dispose(true); | ||
| 129 | |||
| 130 | GC.SuppressFinalize(this); | ||
| 131 | } | ||
| 132 | |||
| 133 | /// <summary> | ||
| 134 | /// Copies one stream to another. | ||
| 135 | /// </summary> | ||
| 136 | /// <param name="input">Input stream.</param> | ||
| 137 | /// <param name="output">Output stream.</param> | ||
| 138 | /// <param name="size">Optional count of bytes to copy. 0 indicates whole input stream from current should be copied.</param> | ||
| 139 | protected static int CopyStream(Stream input, Stream output, int size) | ||
| 140 | { | ||
| 141 | byte[] bytes = new byte[4096]; | ||
| 142 | int total = 0; | ||
| 143 | int read = 0; | ||
| 144 | do | ||
| 145 | { | ||
| 146 | read = Math.Min(bytes.Length, size - total); | ||
| 147 | read = input.Read(bytes, 0, read); | ||
| 148 | if (0 == read) | ||
| 149 | { | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | |||
| 153 | output.Write(bytes, 0, read); | ||
| 154 | total += read; | ||
| 155 | } while (0 == size || total < size); | ||
| 156 | |||
| 157 | return total; | ||
| 158 | } | ||
| 159 | |||
| 160 | /// <summary> | ||
| 161 | /// Initialize the common information about a Burn engine. | ||
| 162 | /// </summary> | ||
| 163 | /// <param name="reader">Binary reader open against a Burn engine.</param> | ||
| 164 | /// <returns>True if initialized.</returns> | ||
| 165 | protected bool Initialize(BinaryReader reader) | ||
| 166 | { | ||
| 167 | if (!GetWixburnSectionInfo(reader)) | ||
| 168 | { | ||
| 169 | return false; | ||
| 170 | } | ||
| 171 | |||
| 172 | reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin); | ||
| 173 | byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE); | ||
| 174 | UInt32 uint32 = 0; | ||
| 175 | |||
| 176 | uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC); | ||
| 177 | if (BURN_SECTION_MAGIC != uint32) | ||
| 178 | { | ||
| 179 | Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe)); | ||
| 180 | return false; | ||
| 181 | } | ||
| 182 | |||
| 183 | this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); | ||
| 184 | if (BURN_SECTION_VERSION != this.Version) | ||
| 185 | { | ||
| 186 | Messaging.Instance.OnMessage(WixErrors.BundleTooNew(this.fileExe, this.Version)); | ||
| 187 | return false; | ||
| 188 | } | ||
| 189 | |||
| 190 | uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now | ||
| 191 | if (1 != uint32) | ||
| 192 | { | ||
| 193 | Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe)); | ||
| 194 | return false; | ||
| 195 | } | ||
| 196 | |||
| 197 | this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE); | ||
| 198 | this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM); | ||
| 199 | this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET); | ||
| 200 | this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE); | ||
| 201 | |||
| 202 | this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT); | ||
| 203 | this.UXAddress = this.StubSize; | ||
| 204 | this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE); | ||
| 205 | |||
| 206 | // If there is an original signature use that to determine the engine size. | ||
| 207 | if (0 < this.OriginalSignatureOffset) | ||
| 208 | { | ||
| 209 | this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize; | ||
| 210 | } | ||
| 211 | else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature. | ||
| 212 | { | ||
| 213 | this.EngineSize = this.SignatureOffset + this.SignatureSize; | ||
| 214 | } | ||
| 215 | else // just use the stub and UX container as the size of the engine. | ||
| 216 | { | ||
| 217 | this.EngineSize = this.StubSize + this.UXSize; | ||
| 218 | } | ||
| 219 | |||
| 220 | this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0; | ||
| 221 | this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0; | ||
| 222 | |||
| 223 | return true; | ||
| 224 | } | ||
| 225 | |||
| 226 | protected virtual void Dispose(bool disposing) | ||
| 227 | { | ||
| 228 | } | ||
| 229 | |||
| 230 | /// <summary> | ||
| 231 | /// Finds the ".wixburn" section in the current exe. | ||
| 232 | /// </summary> | ||
| 233 | /// <returns>true if the ".wixburn" section is successfully found; false otherwise</returns> | ||
| 234 | private bool GetWixburnSectionInfo(BinaryReader reader) | ||
| 235 | { | ||
| 236 | if (UInt32.MaxValue == this.wixburnDataOffset) | ||
| 237 | { | ||
| 238 | if (!EnsureNTHeader(reader)) | ||
| 239 | { | ||
| 240 | return false; | ||
| 241 | } | ||
| 242 | |||
| 243 | UInt32 wixburnSectionOffset = UInt32.MaxValue; | ||
| 244 | byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE]; | ||
| 245 | |||
| 246 | reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin); | ||
| 247 | for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex) | ||
| 248 | { | ||
| 249 | reader.Read(bytes, 0, bytes.Length); | ||
| 250 | |||
| 251 | if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME)) | ||
| 252 | { | ||
| 253 | wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex); | ||
| 254 | break; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | if (UInt32.MaxValue == wixburnSectionOffset) | ||
| 259 | { | ||
| 260 | Messaging.Instance.OnMessage(WixErrors.StubMissingWixburnSection(this.fileExe)); | ||
| 261 | return false; | ||
| 262 | } | ||
| 263 | |||
| 264 | // we need 56 bytes for the manifest header, which is always going to fit in | ||
| 265 | // the smallest alignment (512 bytes), but just to be paranoid... | ||
| 266 | if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA)) | ||
| 267 | { | ||
| 268 | Messaging.Instance.OnMessage(WixErrors.StubWixburnSectionTooSmall(this.fileExe)); | ||
| 269 | return false; | ||
| 270 | } | ||
| 271 | |||
| 272 | this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA); | ||
| 273 | } | ||
| 274 | |||
| 275 | return true; | ||
| 276 | } | ||
| 277 | |||
| 278 | /// <summary> | ||
| 279 | /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe. | ||
| 280 | /// </summary> | ||
| 281 | /// <returns>true if the exe is a Windows executable; false otherwise</returns> | ||
| 282 | private bool EnsureNTHeader(BinaryReader reader) | ||
| 283 | { | ||
| 284 | if (UInt32.MaxValue == this.firstSectionOffset) | ||
| 285 | { | ||
| 286 | if (!EnsureDosHeader(reader)) | ||
| 287 | { | ||
| 288 | return false; | ||
| 289 | } | ||
| 290 | |||
| 291 | reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin); | ||
| 292 | byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE); | ||
| 293 | |||
| 294 | // Verify the NT signature... | ||
| 295 | if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE)) | ||
| 296 | { | ||
| 297 | Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe)); | ||
| 298 | return false; | ||
| 299 | } | ||
| 300 | |||
| 301 | ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER); | ||
| 302 | |||
| 303 | this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS); | ||
| 304 | this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader; | ||
| 305 | |||
| 306 | this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM; | ||
| 307 | this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE; | ||
| 308 | this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset. | ||
| 309 | |||
| 310 | bytes = reader.ReadBytes(sizeOptionalHeader); | ||
| 311 | this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM); | ||
| 312 | this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE); | ||
| 313 | this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4); | ||
| 314 | } | ||
| 315 | |||
| 316 | return true; | ||
| 317 | } | ||
| 318 | |||
| 319 | /// <summary> | ||
| 320 | /// Checks for a valid DOS header in the current exe. | ||
| 321 | /// </summary> | ||
| 322 | /// <returns>true if the exe starts with a DOS stub; false otherwise</returns> | ||
| 323 | private bool EnsureDosHeader(BinaryReader reader) | ||
| 324 | { | ||
| 325 | if (UInt32.MaxValue == this.peOffset) | ||
| 326 | { | ||
| 327 | byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE); | ||
| 328 | |||
| 329 | // Verify the DOS 'MZ' signature. | ||
| 330 | if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC)) | ||
| 331 | { | ||
| 332 | Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe)); | ||
| 333 | return false; | ||
| 334 | } | ||
| 335 | |||
| 336 | this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER); | ||
| 337 | } | ||
| 338 | |||
| 339 | return true; | ||
| 340 | } | ||
| 341 | |||
| 342 | /// <summary> | ||
| 343 | /// Reads a UInt16 value in little-endian format from an offset in an array of bytes. | ||
| 344 | /// </summary> | ||
| 345 | /// <param name="bytes">Array from which to read.</param> | ||
| 346 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 347 | /// <returns>value at offset</returns> | ||
| 348 | private static UInt16 ReadUInt16(byte[] bytes, UInt32 offset) | ||
| 349 | { | ||
| 350 | Debug.Assert(offset + 2 <= bytes.Length); | ||
| 351 | return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8)); | ||
| 352 | } | ||
| 353 | |||
| 354 | /// <summary> | ||
| 355 | /// Reads a UInt32 value in little-endian format from an offset in an array of bytes. | ||
| 356 | /// </summary> | ||
| 357 | /// <param name="bytes">Array from which to read.</param> | ||
| 358 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 359 | /// <returns>value at offset</returns> | ||
| 360 | private static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) | ||
| 361 | { | ||
| 362 | Debug.Assert(offset + 4 <= bytes.Length); | ||
| 363 | return (UInt32)(bytes[offset] + (bytes[offset + 1] << 8) + (bytes[offset + 2] << 16) + (bytes[offset + 3] << 24)); | ||
| 364 | } | ||
| 365 | |||
| 366 | /// <summary> | ||
| 367 | /// Reads a UInt64 value in little-endian format from an offset in an array of bytes. | ||
| 368 | /// </summary> | ||
| 369 | /// <param name="bytes">Array from which to read.</param> | ||
| 370 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 371 | /// <returns>value at offset</returns> | ||
| 372 | private static UInt64 ReadUInt64(byte[] bytes, UInt32 offset) | ||
| 373 | { | ||
| 374 | Debug.Assert(offset + 8 <= bytes.Length); | ||
| 375 | return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)(BurnCommon.ReadUInt32(bytes, offset + 4)) << 32); | ||
| 376 | } | ||
| 377 | } | ||
| 378 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnReader.cs b/src/WixToolset.Core/Bind/Bundles/BurnReader.cs deleted file mode 100644 index f6d7a197..00000000 --- a/src/WixToolset.Core/Bind/Bundles/BurnReader.cs +++ /dev/null | |||
| @@ -1,210 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.IO; | ||
| 9 | using System.Xml; | ||
| 10 | using WixToolset.Cab; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Burn PE reader for the WiX toolset. | ||
| 14 | /// </summary> | ||
| 15 | /// <remarks>This class encapsulates reading from a stub EXE with containers attached | ||
| 16 | /// for dissecting bundled/chained setup packages.</remarks> | ||
| 17 | /// <example> | ||
| 18 | /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid)) | ||
| 19 | /// { | ||
| 20 | /// reader.ExtractUXContainer(file1, tempFolder); | ||
| 21 | /// } | ||
| 22 | /// </example> | ||
| 23 | internal class BurnReader : BurnCommon | ||
| 24 | { | ||
| 25 | private bool disposed; | ||
| 26 | |||
| 27 | private bool invalidBundle; | ||
| 28 | private BinaryReader binaryReader; | ||
| 29 | private List<DictionaryEntry> attachedContainerPayloadNames; | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Creates a BurnReader for reading a PE file. | ||
| 33 | /// </summary> | ||
| 34 | /// <param name="fileExe">File to read.</param> | ||
| 35 | private BurnReader(string fileExe) | ||
| 36 | : base(fileExe) | ||
| 37 | { | ||
| 38 | this.attachedContainerPayloadNames = new List<DictionaryEntry>(); | ||
| 39 | } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets the underlying stream. | ||
| 43 | /// </summary> | ||
| 44 | public Stream Stream | ||
| 45 | { | ||
| 46 | get | ||
| 47 | { | ||
| 48 | return (null != this.binaryReader) ? this.binaryReader.BaseStream : null; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | /// <summary> | ||
| 53 | /// Opens a Burn reader. | ||
| 54 | /// </summary> | ||
| 55 | /// <param name="fileExe">Path to file.</param> | ||
| 56 | /// <returns>Burn reader.</returns> | ||
| 57 | public static BurnReader Open(string fileExe) | ||
| 58 | { | ||
| 59 | BurnReader reader = new BurnReader(fileExe); | ||
| 60 | |||
| 61 | reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); | ||
| 62 | if (!reader.Initialize(reader.binaryReader)) | ||
| 63 | { | ||
| 64 | reader.invalidBundle = true; | ||
| 65 | } | ||
| 66 | |||
| 67 | return reader; | ||
| 68 | } | ||
| 69 | |||
| 70 | /// <summary> | ||
| 71 | /// Gets the UX container from the exe and extracts its contents to the output directory. | ||
| 72 | /// </summary> | ||
| 73 | /// <param name="outputDirectory">Directory to write extracted files to.</param> | ||
| 74 | /// <returns>True if successful, false otherwise</returns> | ||
| 75 | public bool ExtractUXContainer(string outputDirectory, string tempDirectory) | ||
| 76 | { | ||
| 77 | // No UX container to extract | ||
| 78 | if (this.UXAddress == 0 || this.UXSize == 0) | ||
| 79 | { | ||
| 80 | return false; | ||
| 81 | } | ||
| 82 | |||
| 83 | if (this.invalidBundle) | ||
| 84 | { | ||
| 85 | return false; | ||
| 86 | } | ||
| 87 | |||
| 88 | Directory.CreateDirectory(outputDirectory); | ||
| 89 | string tempCabPath = Path.Combine(tempDirectory, "ux.cab"); | ||
| 90 | string manifestOriginalPath = Path.Combine(outputDirectory, "0"); | ||
| 91 | string manifestPath = Path.Combine(outputDirectory, "manifest.xml"); | ||
| 92 | |||
| 93 | this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); | ||
| 94 | using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) | ||
| 95 | { | ||
| 96 | BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize); | ||
| 97 | } | ||
| 98 | |||
| 99 | using (WixExtractCab extract = new WixExtractCab()) | ||
| 100 | { | ||
| 101 | extract.Extract(tempCabPath, outputDirectory); | ||
| 102 | } | ||
| 103 | |||
| 104 | Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); | ||
| 105 | File.Delete(manifestPath); | ||
| 106 | File.Move(manifestOriginalPath, manifestPath); | ||
| 107 | |||
| 108 | XmlDocument document = new XmlDocument(); | ||
| 109 | document.Load(manifestPath); | ||
| 110 | XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable); | ||
| 111 | namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace); | ||
| 112 | XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager); | ||
| 113 | XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager); | ||
| 114 | |||
| 115 | foreach (XmlNode uxPayload in uxPayloads) | ||
| 116 | { | ||
| 117 | XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath"); | ||
| 118 | XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath"); | ||
| 119 | |||
| 120 | string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); | ||
| 121 | string destinationPath = Path.Combine(outputDirectory, filePathNode.Value); | ||
| 122 | |||
| 123 | Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); | ||
| 124 | File.Delete(destinationPath); | ||
| 125 | File.Move(sourcePath, destinationPath); | ||
| 126 | } | ||
| 127 | |||
| 128 | foreach (XmlNode payload in payloads) | ||
| 129 | { | ||
| 130 | XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath"); | ||
| 131 | XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath"); | ||
| 132 | XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging"); | ||
| 133 | |||
| 134 | string sourcePath = sourcePathNode.Value; | ||
| 135 | string destinationPath = filePathNode.Value; | ||
| 136 | string packaging = packagingNode.Value; | ||
| 137 | |||
| 138 | if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase)) | ||
| 139 | { | ||
| 140 | this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | return true; | ||
| 145 | } | ||
| 146 | |||
| 147 | /// <summary> | ||
| 148 | /// Gets the attached container from the exe and extracts its contents to the output directory. | ||
| 149 | /// </summary> | ||
| 150 | /// <param name="outputDirectory">Directory to write extracted files to.</param> | ||
| 151 | /// <returns>True if successful, false otherwise</returns> | ||
| 152 | public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory) | ||
| 153 | { | ||
| 154 | // No attached container to extract | ||
| 155 | if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0) | ||
| 156 | { | ||
| 157 | return false; | ||
| 158 | } | ||
| 159 | |||
| 160 | if (this.invalidBundle) | ||
| 161 | { | ||
| 162 | return false; | ||
| 163 | } | ||
| 164 | |||
| 165 | Directory.CreateDirectory(outputDirectory); | ||
| 166 | string tempCabPath = Path.Combine(tempDirectory, "attached.cab"); | ||
| 167 | |||
| 168 | this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin); | ||
| 169 | using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) | ||
| 170 | { | ||
| 171 | BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize); | ||
| 172 | } | ||
| 173 | |||
| 174 | using (WixExtractCab extract = new WixExtractCab()) | ||
| 175 | { | ||
| 176 | extract.Extract(tempCabPath, outputDirectory); | ||
| 177 | } | ||
| 178 | |||
| 179 | foreach (DictionaryEntry entry in this.attachedContainerPayloadNames) | ||
| 180 | { | ||
| 181 | string sourcePath = Path.Combine(outputDirectory, (string)entry.Key); | ||
| 182 | string destinationPath = Path.Combine(outputDirectory, (string)entry.Value); | ||
| 183 | |||
| 184 | Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); | ||
| 185 | File.Delete(destinationPath); | ||
| 186 | File.Move(sourcePath, destinationPath); | ||
| 187 | } | ||
| 188 | |||
| 189 | return true; | ||
| 190 | } | ||
| 191 | |||
| 192 | /// <summary> | ||
| 193 | /// Dispose object. | ||
| 194 | /// </summary> | ||
| 195 | /// <param name="disposing">True when releasing managed objects.</param> | ||
| 196 | protected override void Dispose(bool disposing) | ||
| 197 | { | ||
| 198 | if (!this.disposed) | ||
| 199 | { | ||
| 200 | if (disposing && this.binaryReader != null) | ||
| 201 | { | ||
| 202 | this.binaryReader.Close(); | ||
| 203 | this.binaryReader = null; | ||
| 204 | } | ||
| 205 | |||
| 206 | this.disposed = true; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | } | ||
| 210 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs b/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs deleted file mode 100644 index bc0baf46..00000000 --- a/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs +++ /dev/null | |||
| @@ -1,239 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Burn PE writer for the WiX toolset. | ||
| 12 | /// </summary> | ||
| 13 | /// <remarks>This class encapsulates reading/writing to a stub EXE for | ||
| 14 | /// creating bundled/chained setup packages.</remarks> | ||
| 15 | /// <example> | ||
| 16 | /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid)) | ||
| 17 | /// { | ||
| 18 | /// writer.AppendContainer(file1, BurnWriter.Container.UX); | ||
| 19 | /// writer.AppendContainer(file2, BurnWriter.Container.Attached); | ||
| 20 | /// } | ||
| 21 | /// </example> | ||
| 22 | internal class BurnWriter : BurnCommon | ||
| 23 | { | ||
| 24 | private bool disposed; | ||
| 25 | private bool invalidBundle; | ||
| 26 | private BinaryWriter binaryWriter; | ||
| 27 | |||
| 28 | /// <summary> | ||
| 29 | /// Creates a BurnWriter for re-writing a PE file. | ||
| 30 | /// </summary> | ||
| 31 | /// <param name="fileExe">File to modify in-place.</param> | ||
| 32 | /// <param name="bundleGuid">GUID for the bundle.</param> | ||
| 33 | private BurnWriter(string fileExe) | ||
| 34 | : base(fileExe) | ||
| 35 | { | ||
| 36 | } | ||
| 37 | |||
| 38 | /// <summary> | ||
| 39 | /// Opens a Burn writer. | ||
| 40 | /// </summary> | ||
| 41 | /// <param name="fileExe">Path to file.</param> | ||
| 42 | /// <returns>Burn writer.</returns> | ||
| 43 | public static BurnWriter Open(string fileExe) | ||
| 44 | { | ||
| 45 | BurnWriter writer = new BurnWriter(fileExe); | ||
| 46 | |||
| 47 | using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))) | ||
| 48 | { | ||
| 49 | if (!writer.Initialize(binaryReader)) | ||
| 50 | { | ||
| 51 | writer.invalidBundle = true; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | if (!writer.invalidBundle) | ||
| 56 | { | ||
| 57 | writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete)); | ||
| 58 | } | ||
| 59 | |||
| 60 | return writer; | ||
| 61 | } | ||
| 62 | |||
| 63 | /// <summary> | ||
| 64 | /// Update the ".wixburn" section data. | ||
| 65 | /// </summary> | ||
| 66 | /// <param name="stubSize">Size of the stub engine "burn.exe".</param> | ||
| 67 | /// <param name="bundleId">Unique identifier for this bundle.</param> | ||
| 68 | /// <returns></returns> | ||
| 69 | public bool InitializeBundleSectionData(long stubSize, Guid bundleId) | ||
| 70 | { | ||
| 71 | if (this.invalidBundle) | ||
| 72 | { | ||
| 73 | return false; | ||
| 74 | } | ||
| 75 | |||
| 76 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); | ||
| 77 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); | ||
| 78 | |||
| 79 | Messaging.Instance.OnMessage(WixVerboses.BundleGuid(bundleId.ToString("B"))); | ||
| 80 | this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); | ||
| 81 | this.binaryWriter.Write(bundleId.ToByteArray()); | ||
| 82 | |||
| 83 | this.StubSize = (uint)stubSize; | ||
| 84 | |||
| 85 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize); | ||
| 86 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0); | ||
| 87 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0); | ||
| 88 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0); | ||
| 89 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now. | ||
| 90 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0); | ||
| 91 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0); | ||
| 92 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0); | ||
| 93 | this.binaryWriter.BaseStream.Flush(); | ||
| 94 | |||
| 95 | this.EngineSize = this.StubSize; | ||
| 96 | |||
| 97 | return true; | ||
| 98 | } | ||
| 99 | |||
| 100 | /// <summary> | ||
| 101 | /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. | ||
| 102 | /// </summary> | ||
| 103 | /// <param name="fileContainer">File path to append to the current exe.</param> | ||
| 104 | /// <param name="container">Container section represented by the fileContainer.</param> | ||
| 105 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 106 | public bool AppendContainer(string fileContainer, BurnCommon.Container container) | ||
| 107 | { | ||
| 108 | using (FileStream reader = File.OpenRead(fileContainer)) | ||
| 109 | { | ||
| 110 | return this.AppendContainer(reader, reader.Length, container); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | /// <summary> | ||
| 115 | /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. | ||
| 116 | /// </summary> | ||
| 117 | /// <param name="containerStream">File stream to append to the current exe.</param> | ||
| 118 | /// <param name="containerSize">Size of container to append.</param> | ||
| 119 | /// <param name="container">Container section represented by the fileContainer.</param> | ||
| 120 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 121 | public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container) | ||
| 122 | { | ||
| 123 | UInt32 burnSectionCount = 0; | ||
| 124 | UInt32 burnSectionOffsetSize = 0; | ||
| 125 | |||
| 126 | switch (container) | ||
| 127 | { | ||
| 128 | case Container.UX: | ||
| 129 | burnSectionCount = 1; | ||
| 130 | burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE; | ||
| 131 | // TODO: verify that the size in the section data is 0 or the same size. | ||
| 132 | this.EngineSize += (uint)containerSize; | ||
| 133 | this.UXSize = (uint)containerSize; | ||
| 134 | break; | ||
| 135 | |||
| 136 | case Container.Attached: | ||
| 137 | burnSectionCount = 2; | ||
| 138 | burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE; | ||
| 139 | // TODO: verify that the size in the section data is 0 or the same size. | ||
| 140 | this.AttachedContainerSize = (uint)containerSize; | ||
| 141 | break; | ||
| 142 | |||
| 143 | default: | ||
| 144 | Debug.Assert(false); | ||
| 145 | return false; | ||
| 146 | } | ||
| 147 | |||
| 148 | return AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); | ||
| 149 | } | ||
| 150 | |||
| 151 | public void RememberThenResetSignature() | ||
| 152 | { | ||
| 153 | if (this.invalidBundle) | ||
| 154 | { | ||
| 155 | return; | ||
| 156 | } | ||
| 157 | |||
| 158 | this.OriginalChecksum = this.Checksum; | ||
| 159 | this.OriginalSignatureOffset = this.SignatureOffset; | ||
| 160 | this.OriginalSignatureSize = this.SignatureSize; | ||
| 161 | |||
| 162 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum); | ||
| 163 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset); | ||
| 164 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize); | ||
| 165 | |||
| 166 | this.Checksum = 0; | ||
| 167 | this.SignatureOffset = 0; | ||
| 168 | this.SignatureSize = 0; | ||
| 169 | |||
| 170 | this.WriteToOffset(this.checksumOffset, this.Checksum); | ||
| 171 | this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset); | ||
| 172 | this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize); | ||
| 173 | } | ||
| 174 | |||
| 175 | /// <summary> | ||
| 176 | /// Dispose object. | ||
| 177 | /// </summary> | ||
| 178 | /// <param name="disposing">True when releasing managed objects.</param> | ||
| 179 | protected override void Dispose(bool disposing) | ||
| 180 | { | ||
| 181 | if (!this.disposed) | ||
| 182 | { | ||
| 183 | if (disposing && this.binaryWriter != null) | ||
| 184 | { | ||
| 185 | this.binaryWriter.Close(); | ||
| 186 | this.binaryWriter = null; | ||
| 187 | } | ||
| 188 | |||
| 189 | this.disposed = true; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | /// <summary> | ||
| 194 | /// Appends a container to the exe and updates the ".wixburn" section data to point to it. | ||
| 195 | /// </summary> | ||
| 196 | /// <param name="containerStream">File stream to append to the current exe.</param> | ||
| 197 | /// <param name="burnSectionOffsetSize">Offset of size field for this container in ".wixburn" section data.</param> | ||
| 198 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 199 | private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount) | ||
| 200 | { | ||
| 201 | if (this.invalidBundle) | ||
| 202 | { | ||
| 203 | return false; | ||
| 204 | } | ||
| 205 | |||
| 206 | // Update the ".wixburn" section data | ||
| 207 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount); | ||
| 208 | this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize); | ||
| 209 | |||
| 210 | // Append the container to the end of the existing bits. | ||
| 211 | this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); | ||
| 212 | BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); | ||
| 213 | this.binaryWriter.BaseStream.Flush(); | ||
| 214 | |||
| 215 | return true; | ||
| 216 | } | ||
| 217 | |||
| 218 | /// <summary> | ||
| 219 | /// Writes the value to an offset in the Burn section data. | ||
| 220 | /// </summary> | ||
| 221 | /// <param name="offset">Offset in to the Burn section data.</param> | ||
| 222 | /// <param name="value">Value to write.</param> | ||
| 223 | private void WriteToBurnSectionOffset(uint offset, uint value) | ||
| 224 | { | ||
| 225 | this.WriteToOffset(this.wixburnDataOffset + offset, value); | ||
| 226 | } | ||
| 227 | |||
| 228 | /// <summary> | ||
| 229 | /// Writes the value to an offset in the Burn stub. | ||
| 230 | /// </summary> | ||
| 231 | /// <param name="offset">Offset in to the Burn stub.</param> | ||
| 232 | /// <param name="value">Value to write.</param> | ||
| 233 | private void WriteToOffset(uint offset, uint value) | ||
| 234 | { | ||
| 235 | this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin); | ||
| 236 | this.binaryWriter.Write(value); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs deleted file mode 100644 index 1040b394..00000000 --- a/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ /dev/null | |||
| @@ -1,241 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Text; | ||
| 11 | using System.Xml; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | |||
| 15 | internal class CreateBootstrapperApplicationManifestCommand : ICommand | ||
| 16 | { | ||
| 17 | public WixBundleRow BundleRow { private get; set; } | ||
| 18 | |||
| 19 | public IEnumerable<PackageFacade> ChainPackages { private get; set; } | ||
| 20 | |||
| 21 | public int LastUXPayloadIndex { private get; set; } | ||
| 22 | |||
| 23 | public IEnumerable<WixBundleMsiFeatureRow> MsiFeatures { private get; set; } | ||
| 24 | |||
| 25 | public Output Output { private get; set; } | ||
| 26 | |||
| 27 | public RowDictionary<WixBundlePayloadRow> Payloads { private get; set; } | ||
| 28 | |||
| 29 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 30 | |||
| 31 | public string TempFilesLocation { private get; set; } | ||
| 32 | |||
| 33 | public WixBundlePayloadRow BootstrapperApplicationManifestPayloadRow { get; private set; } | ||
| 34 | |||
| 35 | public void Execute() | ||
| 36 | { | ||
| 37 | this.GenerateBAManifestBundleTables(); | ||
| 38 | |||
| 39 | this.GenerateBAManifestMsiFeatureTables(); | ||
| 40 | |||
| 41 | this.GenerateBAManifestPackageTables(); | ||
| 42 | |||
| 43 | this.GenerateBAManifestPayloadTables(); | ||
| 44 | |||
| 45 | string baManifestPath = Path.Combine(this.TempFilesLocation, "wix-badata.xml"); | ||
| 46 | |||
| 47 | this.CreateBootstrapperApplicationManifest(baManifestPath); | ||
| 48 | |||
| 49 | this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath); | ||
| 50 | } | ||
| 51 | |||
| 52 | private void GenerateBAManifestBundleTables() | ||
| 53 | { | ||
| 54 | Table wixBundlePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleProperties"]); | ||
| 55 | |||
| 56 | Row row = wixBundlePropertiesTable.CreateRow(this.BundleRow.SourceLineNumbers); | ||
| 57 | row[0] = this.BundleRow.Name; | ||
| 58 | row[1] = this.BundleRow.LogPathVariable; | ||
| 59 | row[2] = (YesNoDefaultType.Yes == this.BundleRow.Compressed) ? "yes" : "no"; | ||
| 60 | row[3] = this.BundleRow.BundleId.ToString("B"); | ||
| 61 | row[4] = this.BundleRow.UpgradeCode; | ||
| 62 | row[5] = this.BundleRow.PerMachine ? "yes" : "no"; | ||
| 63 | } | ||
| 64 | |||
| 65 | private void GenerateBAManifestPackageTables() | ||
| 66 | { | ||
| 67 | Table wixPackagePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageProperties"]); | ||
| 68 | |||
| 69 | foreach (PackageFacade package in this.ChainPackages) | ||
| 70 | { | ||
| 71 | WixBundlePayloadRow packagePayload = this.Payloads[package.Package.PackagePayload]; | ||
| 72 | |||
| 73 | Row row = wixPackagePropertiesTable.CreateRow(package.Package.SourceLineNumbers); | ||
| 74 | row[0] = package.Package.WixChainItemId; | ||
| 75 | row[1] = (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"; | ||
| 76 | row[2] = package.Package.DisplayName; | ||
| 77 | row[3] = package.Package.Description; | ||
| 78 | row[4] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // TODO: DownloadSize (compressed) (what does this mean when it's embedded?) | ||
| 79 | row[5] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // Package.Size (uncompressed) | ||
| 80 | row[6] = package.Package.InstallSize.Value.ToString(CultureInfo.InvariantCulture); // InstallSize (required disk space) | ||
| 81 | row[7] = package.Package.Type.ToString(); | ||
| 82 | row[8] = package.Package.Permanent ? "yes" : "no"; | ||
| 83 | row[9] = package.Package.LogPathVariable; | ||
| 84 | row[10] = package.Package.RollbackLogPathVariable; | ||
| 85 | row[11] = (PackagingType.Embedded == packagePayload.Packaging) ? "yes" : "no"; | ||
| 86 | |||
| 87 | if (WixBundlePackageType.Msi == package.Package.Type) | ||
| 88 | { | ||
| 89 | row[12] = package.MsiPackage.DisplayInternalUI ? "yes" : "no"; | ||
| 90 | |||
| 91 | if (!String.IsNullOrEmpty(package.MsiPackage.ProductCode)) | ||
| 92 | { | ||
| 93 | row[13] = package.MsiPackage.ProductCode; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) | ||
| 97 | { | ||
| 98 | row[14] = package.MsiPackage.UpgradeCode; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | else if (WixBundlePackageType.Msp == package.Package.Type) | ||
| 102 | { | ||
| 103 | row[12] = package.MspPackage.DisplayInternalUI ? "yes" : "no"; | ||
| 104 | |||
| 105 | if (!String.IsNullOrEmpty(package.MspPackage.PatchCode)) | ||
| 106 | { | ||
| 107 | row[13] = package.MspPackage.PatchCode; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | if (!String.IsNullOrEmpty(package.Package.Version)) | ||
| 112 | { | ||
| 113 | row[15] = package.Package.Version; | ||
| 114 | } | ||
| 115 | |||
| 116 | if (!String.IsNullOrEmpty(package.Package.InstallCondition)) | ||
| 117 | { | ||
| 118 | row[16] = package.Package.InstallCondition; | ||
| 119 | } | ||
| 120 | |||
| 121 | switch (package.Package.Cache) | ||
| 122 | { | ||
| 123 | case YesNoAlwaysType.No: | ||
| 124 | row[17] = "no"; | ||
| 125 | break; | ||
| 126 | case YesNoAlwaysType.Yes: | ||
| 127 | row[17] = "yes"; | ||
| 128 | break; | ||
| 129 | case YesNoAlwaysType.Always: | ||
| 130 | row[17] = "always"; | ||
| 131 | break; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | private void GenerateBAManifestMsiFeatureTables() | ||
| 137 | { | ||
| 138 | Table wixPackageFeatureInfoTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageFeatureInfo"]); | ||
| 139 | |||
| 140 | foreach (WixBundleMsiFeatureRow feature in this.MsiFeatures) | ||
| 141 | { | ||
| 142 | Row row = wixPackageFeatureInfoTable.CreateRow(feature.SourceLineNumbers); | ||
| 143 | row[0] = feature.ChainPackageId; | ||
| 144 | row[1] = feature.Name; | ||
| 145 | row[2] = Convert.ToString(feature.Size, CultureInfo.InvariantCulture); | ||
| 146 | row[3] = feature.Parent; | ||
| 147 | row[4] = feature.Title; | ||
| 148 | row[5] = feature.Description; | ||
| 149 | row[6] = Convert.ToString(feature.Display, CultureInfo.InvariantCulture); | ||
| 150 | row[7] = Convert.ToString(feature.Level, CultureInfo.InvariantCulture); | ||
| 151 | row[8] = feature.Directory; | ||
| 152 | row[9] = Convert.ToString(feature.Attributes, CultureInfo.InvariantCulture); | ||
| 153 | } | ||
| 154 | |||
| 155 | } | ||
| 156 | |||
| 157 | private void GenerateBAManifestPayloadTables() | ||
| 158 | { | ||
| 159 | Table wixPayloadPropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPayloadProperties"]); | ||
| 160 | |||
| 161 | foreach (WixBundlePayloadRow payload in this.Payloads.Values) | ||
| 162 | { | ||
| 163 | WixPayloadPropertiesRow row = (WixPayloadPropertiesRow)wixPayloadPropertiesTable.CreateRow(payload.SourceLineNumbers); | ||
| 164 | row.Id = payload.Id; | ||
| 165 | row.Package = payload.Package; | ||
| 166 | row.Container = payload.Container; | ||
| 167 | row.Name = payload.Name; | ||
| 168 | row.Size = payload.FileSize.ToString(); | ||
| 169 | row.DownloadUrl = payload.DownloadUrl; | ||
| 170 | row.LayoutOnly = payload.LayoutOnly ? "yes" : "no"; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | private void CreateBootstrapperApplicationManifest(string path) | ||
| 175 | { | ||
| 176 | using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.Unicode)) | ||
| 177 | { | ||
| 178 | writer.Formatting = Formatting.Indented; | ||
| 179 | writer.WriteStartDocument(); | ||
| 180 | writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/2010/BootstrapperApplicationData"); | ||
| 181 | |||
| 182 | foreach (Table table in this.Output.Tables) | ||
| 183 | { | ||
| 184 | if (table.Definition.BootstrapperApplicationData) | ||
| 185 | { | ||
| 186 | // We simply assert that the table (and field) name is valid, because | ||
| 187 | // this is up to the extension developer to get right. An author will | ||
| 188 | // only affect the attribute value, and that will get properly escaped. | ||
| 189 | #if DEBUG | ||
| 190 | Debug.Assert(Common.IsIdentifier(table.Name)); | ||
| 191 | foreach (ColumnDefinition column in table.Definition.Columns) | ||
| 192 | { | ||
| 193 | Debug.Assert(Common.IsIdentifier(column.Name)); | ||
| 194 | } | ||
| 195 | #endif // DEBUG | ||
| 196 | |||
| 197 | foreach (Row row in table.Rows) | ||
| 198 | { | ||
| 199 | writer.WriteStartElement(table.Name); | ||
| 200 | |||
| 201 | foreach (Field field in row.Fields) | ||
| 202 | { | ||
| 203 | if (null != field.Data) | ||
| 204 | { | ||
| 205 | writer.WriteAttributeString(field.Column.Name, field.Data.ToString()); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | writer.WriteEndElement(); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | writer.WriteEndElement(); | ||
| 215 | writer.WriteEndDocument(); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | private WixBundlePayloadRow CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) | ||
| 220 | { | ||
| 221 | Table payloadTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePayload"]); | ||
| 222 | WixBundlePayloadRow row = (WixBundlePayloadRow)payloadTable.CreateRow(this.BundleRow.SourceLineNumbers); | ||
| 223 | row.Id = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); | ||
| 224 | row.Name = "BootstrapperApplicationData.xml"; | ||
| 225 | row.SourceFile = baManifestPath; | ||
| 226 | row.Compressed = YesNoDefaultType.Yes; | ||
| 227 | row.UnresolvedSourceFile = baManifestPath; | ||
| 228 | row.Container = Compiler.BurnUXContainerId; | ||
| 229 | row.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex); | ||
| 230 | row.Packaging = PackagingType.Embedded; | ||
| 231 | |||
| 232 | FileInfo fileInfo = new FileInfo(row.SourceFile); | ||
| 233 | |||
| 234 | row.FileSize = (int)fileInfo.Length; | ||
| 235 | |||
| 236 | row.Hash = Common.GetFileHash(fileInfo.FullName); | ||
| 237 | |||
| 238 | return row; | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs deleted file mode 100644 index 7bc708a3..00000000 --- a/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs +++ /dev/null | |||
| @@ -1,686 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Text; | ||
| 11 | using System.Xml; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | |||
| 16 | internal class CreateBurnManifestCommand : ICommand | ||
| 17 | { | ||
| 18 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | ||
| 19 | |||
| 20 | public Output Output { private get; set; } | ||
| 21 | |||
| 22 | public string ExecutableName { private get; set; } | ||
| 23 | |||
| 24 | public WixBundleRow BundleInfo { private get; set; } | ||
| 25 | |||
| 26 | public WixChainRow Chain { private get; set; } | ||
| 27 | |||
| 28 | public string OutputPath { private get; set; } | ||
| 29 | |||
| 30 | public IEnumerable<WixBundleRollbackBoundaryRow> RollbackBoundaries { private get; set; } | ||
| 31 | |||
| 32 | public IEnumerable<PackageFacade> OrderedPackages { private get; set; } | ||
| 33 | |||
| 34 | public IEnumerable<WixSearchInfo> OrderedSearches { private get; set; } | ||
| 35 | |||
| 36 | public Dictionary<string, WixBundlePayloadRow> Payloads { private get; set; } | ||
| 37 | |||
| 38 | public Dictionary<string, WixBundleContainerRow> Containers { private get; set; } | ||
| 39 | |||
| 40 | public IEnumerable<WixBundlePayloadRow> UXContainerPayloads { private get; set; } | ||
| 41 | |||
| 42 | public IEnumerable<WixBundleCatalogRow> Catalogs { private get; set; } | ||
| 43 | |||
| 44 | public void Execute() | ||
| 45 | { | ||
| 46 | using (XmlTextWriter writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) | ||
| 47 | { | ||
| 48 | writer.WriteStartDocument(); | ||
| 49 | |||
| 50 | writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); | ||
| 51 | |||
| 52 | // Write the condition, if there is one | ||
| 53 | if (null != this.BundleInfo.Condition) | ||
| 54 | { | ||
| 55 | writer.WriteElementString("Condition", this.BundleInfo.Condition); | ||
| 56 | } | ||
| 57 | |||
| 58 | // Write the log element if default logging wasn't disabled. | ||
| 59 | if (!String.IsNullOrEmpty(this.BundleInfo.LogPrefix)) | ||
| 60 | { | ||
| 61 | writer.WriteStartElement("Log"); | ||
| 62 | if (!String.IsNullOrEmpty(this.BundleInfo.LogPathVariable)) | ||
| 63 | { | ||
| 64 | writer.WriteAttributeString("PathVariable", this.BundleInfo.LogPathVariable); | ||
| 65 | } | ||
| 66 | writer.WriteAttributeString("Prefix", this.BundleInfo.LogPrefix); | ||
| 67 | writer.WriteAttributeString("Extension", this.BundleInfo.LogExtension); | ||
| 68 | writer.WriteEndElement(); | ||
| 69 | } | ||
| 70 | |||
| 71 | |||
| 72 | // Get update if specified. | ||
| 73 | WixBundleUpdateRow updateRow = this.Output.Tables["WixBundleUpdate"].RowsAs<WixBundleUpdateRow>().FirstOrDefault(); | ||
| 74 | |||
| 75 | if (null != updateRow) | ||
| 76 | { | ||
| 77 | writer.WriteStartElement("Update"); | ||
| 78 | writer.WriteAttributeString("Location", updateRow.Location); | ||
| 79 | writer.WriteEndElement(); // </Update> | ||
| 80 | } | ||
| 81 | |||
| 82 | // Write the RelatedBundle elements | ||
| 83 | |||
| 84 | // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates | ||
| 85 | // enumeration in the index row list is not used). | ||
| 86 | RowIndexedList<WixRelatedBundleRow> relatedBundles = new RowIndexedList<WixRelatedBundleRow>(this.Output.Tables["WixRelatedBundle"]); | ||
| 87 | |||
| 88 | foreach (WixRelatedBundleRow relatedBundle in relatedBundles) | ||
| 89 | { | ||
| 90 | writer.WriteStartElement("RelatedBundle"); | ||
| 91 | writer.WriteAttributeString("Id", relatedBundle.Id); | ||
| 92 | writer.WriteAttributeString("Action", Convert.ToString(relatedBundle.Action, CultureInfo.InvariantCulture)); | ||
| 93 | writer.WriteEndElement(); | ||
| 94 | } | ||
| 95 | |||
| 96 | // Write the variables | ||
| 97 | IEnumerable<WixBundleVariableRow> variables = this.Output.Tables["WixBundleVariable"].RowsAs<WixBundleVariableRow>(); | ||
| 98 | |||
| 99 | foreach (WixBundleVariableRow variable in variables) | ||
| 100 | { | ||
| 101 | writer.WriteStartElement("Variable"); | ||
| 102 | writer.WriteAttributeString("Id", variable.Id); | ||
| 103 | if (null != variable.Type) | ||
| 104 | { | ||
| 105 | writer.WriteAttributeString("Value", variable.Value); | ||
| 106 | writer.WriteAttributeString("Type", variable.Type); | ||
| 107 | } | ||
| 108 | writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); | ||
| 109 | writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); | ||
| 110 | writer.WriteEndElement(); | ||
| 111 | } | ||
| 112 | |||
| 113 | // Write the searches | ||
| 114 | foreach (WixSearchInfo searchinfo in this.OrderedSearches) | ||
| 115 | { | ||
| 116 | searchinfo.WriteXml(writer); | ||
| 117 | } | ||
| 118 | |||
| 119 | // write the UX element | ||
| 120 | writer.WriteStartElement("UX"); | ||
| 121 | if (!String.IsNullOrEmpty(this.BundleInfo.SplashScreenBitmapPath)) | ||
| 122 | { | ||
| 123 | writer.WriteAttributeString("SplashScreen", "yes"); | ||
| 124 | } | ||
| 125 | |||
| 126 | // write the UX allPayloads... | ||
| 127 | foreach (WixBundlePayloadRow payload in this.UXContainerPayloads) | ||
| 128 | { | ||
| 129 | writer.WriteStartElement("Payload"); | ||
| 130 | this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); | ||
| 131 | writer.WriteEndElement(); | ||
| 132 | } | ||
| 133 | |||
| 134 | writer.WriteEndElement(); // </UX> | ||
| 135 | |||
| 136 | // write the catalog elements | ||
| 137 | if (this.Catalogs.Any()) | ||
| 138 | { | ||
| 139 | foreach (WixBundleCatalogRow catalog in this.Catalogs) | ||
| 140 | { | ||
| 141 | writer.WriteStartElement("Catalog"); | ||
| 142 | writer.WriteAttributeString("Id", catalog.Id); | ||
| 143 | writer.WriteAttributeString("Payload", catalog.Payload); | ||
| 144 | writer.WriteEndElement(); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | foreach (WixBundleContainerRow container in this.Containers.Values) | ||
| 149 | { | ||
| 150 | if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) | ||
| 151 | { | ||
| 152 | writer.WriteStartElement("Container"); | ||
| 153 | this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); | ||
| 154 | writer.WriteEndElement(); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | foreach (WixBundlePayloadRow payload in this.Payloads.Values) | ||
| 159 | { | ||
| 160 | if (PackagingType.Embedded == payload.Packaging && Compiler.BurnUXContainerId != payload.Container) | ||
| 161 | { | ||
| 162 | writer.WriteStartElement("Payload"); | ||
| 163 | this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); | ||
| 164 | writer.WriteEndElement(); | ||
| 165 | } | ||
| 166 | else if (PackagingType.External == payload.Packaging) | ||
| 167 | { | ||
| 168 | writer.WriteStartElement("Payload"); | ||
| 169 | this.WriteBurnManifestPayloadAttributes(writer, payload, false, this.Payloads); | ||
| 170 | writer.WriteEndElement(); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | foreach (WixBundleRollbackBoundaryRow rollbackBoundary in this.RollbackBoundaries) | ||
| 175 | { | ||
| 176 | writer.WriteStartElement("RollbackBoundary"); | ||
| 177 | writer.WriteAttributeString("Id", rollbackBoundary.ChainPackageId); | ||
| 178 | writer.WriteAttributeString("Vital", YesNoType.Yes == rollbackBoundary.Vital ? "yes" : "no"); | ||
| 179 | writer.WriteAttributeString("Transaction", YesNoType.Yes == rollbackBoundary.Transaction ? "yes" : "no"); | ||
| 180 | writer.WriteEndElement(); | ||
| 181 | } | ||
| 182 | |||
| 183 | // Write the registration information... | ||
| 184 | writer.WriteStartElement("Registration"); | ||
| 185 | |||
| 186 | writer.WriteAttributeString("Id", this.BundleInfo.BundleId.ToString("B")); | ||
| 187 | writer.WriteAttributeString("ExecutableName", this.ExecutableName); | ||
| 188 | writer.WriteAttributeString("PerMachine", this.BundleInfo.PerMachine ? "yes" : "no"); | ||
| 189 | writer.WriteAttributeString("Tag", this.BundleInfo.Tag); | ||
| 190 | writer.WriteAttributeString("Version", this.BundleInfo.Version); | ||
| 191 | writer.WriteAttributeString("ProviderKey", this.BundleInfo.ProviderKey); | ||
| 192 | |||
| 193 | writer.WriteStartElement("Arp"); | ||
| 194 | writer.WriteAttributeString("Register", (0 < this.BundleInfo.DisableModify && this.BundleInfo.DisableRemove) ? "no" : "yes"); // do not register if disabled modify and remove. | ||
| 195 | writer.WriteAttributeString("DisplayName", this.BundleInfo.Name); | ||
| 196 | writer.WriteAttributeString("DisplayVersion", this.BundleInfo.Version); | ||
| 197 | |||
| 198 | if (!String.IsNullOrEmpty(this.BundleInfo.Publisher)) | ||
| 199 | { | ||
| 200 | writer.WriteAttributeString("Publisher", this.BundleInfo.Publisher); | ||
| 201 | } | ||
| 202 | |||
| 203 | if (!String.IsNullOrEmpty(this.BundleInfo.HelpLink)) | ||
| 204 | { | ||
| 205 | writer.WriteAttributeString("HelpLink", this.BundleInfo.HelpLink); | ||
| 206 | } | ||
| 207 | |||
| 208 | if (!String.IsNullOrEmpty(this.BundleInfo.HelpTelephone)) | ||
| 209 | { | ||
| 210 | writer.WriteAttributeString("HelpTelephone", this.BundleInfo.HelpTelephone); | ||
| 211 | } | ||
| 212 | |||
| 213 | if (!String.IsNullOrEmpty(this.BundleInfo.AboutUrl)) | ||
| 214 | { | ||
| 215 | writer.WriteAttributeString("AboutUrl", this.BundleInfo.AboutUrl); | ||
| 216 | } | ||
| 217 | |||
| 218 | if (!String.IsNullOrEmpty(this.BundleInfo.UpdateUrl)) | ||
| 219 | { | ||
| 220 | writer.WriteAttributeString("UpdateUrl", this.BundleInfo.UpdateUrl); | ||
| 221 | } | ||
| 222 | |||
| 223 | if (!String.IsNullOrEmpty(this.BundleInfo.ParentName)) | ||
| 224 | { | ||
| 225 | writer.WriteAttributeString("ParentDisplayName", this.BundleInfo.ParentName); | ||
| 226 | } | ||
| 227 | |||
| 228 | if (1 == this.BundleInfo.DisableModify) | ||
| 229 | { | ||
| 230 | writer.WriteAttributeString("DisableModify", "yes"); | ||
| 231 | } | ||
| 232 | else if (2 == this.BundleInfo.DisableModify) | ||
| 233 | { | ||
| 234 | writer.WriteAttributeString("DisableModify", "button"); | ||
| 235 | } | ||
| 236 | |||
| 237 | if (this.BundleInfo.DisableRemove) | ||
| 238 | { | ||
| 239 | writer.WriteAttributeString("DisableRemove", "yes"); | ||
| 240 | } | ||
| 241 | writer.WriteEndElement(); // </Arp> | ||
| 242 | |||
| 243 | // Get update registration if specified. | ||
| 244 | WixUpdateRegistrationRow updateRegistrationInfo = this.Output.Tables["WixUpdateRegistration"].RowsAs<WixUpdateRegistrationRow>().FirstOrDefault(); | ||
| 245 | |||
| 246 | if (null != updateRegistrationInfo) | ||
| 247 | { | ||
| 248 | writer.WriteStartElement("Update"); // <Update> | ||
| 249 | writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer); | ||
| 250 | |||
| 251 | if (!String.IsNullOrEmpty(updateRegistrationInfo.Department)) | ||
| 252 | { | ||
| 253 | writer.WriteAttributeString("Department", updateRegistrationInfo.Department); | ||
| 254 | } | ||
| 255 | |||
| 256 | if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily)) | ||
| 257 | { | ||
| 258 | writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily); | ||
| 259 | } | ||
| 260 | |||
| 261 | writer.WriteAttributeString("Name", updateRegistrationInfo.Name); | ||
| 262 | writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification); | ||
| 263 | writer.WriteEndElement(); // </Update> | ||
| 264 | } | ||
| 265 | |||
| 266 | IEnumerable<Row> bundleTags = this.Output.Tables["WixBundleTag"].RowsAs<Row>(); | ||
| 267 | |||
| 268 | foreach (Row row in bundleTags) | ||
| 269 | { | ||
| 270 | writer.WriteStartElement("SoftwareTag"); | ||
| 271 | writer.WriteAttributeString("Filename", (string)row[0]); | ||
| 272 | writer.WriteAttributeString("Regid", (string)row[1]); | ||
| 273 | writer.WriteCData((string)row[4]); | ||
| 274 | writer.WriteEndElement(); | ||
| 275 | } | ||
| 276 | |||
| 277 | writer.WriteEndElement(); // </Register> | ||
| 278 | |||
| 279 | // write the Chain... | ||
| 280 | writer.WriteStartElement("Chain"); | ||
| 281 | if (this.Chain.DisableRollback) | ||
| 282 | { | ||
| 283 | writer.WriteAttributeString("DisableRollback", "yes"); | ||
| 284 | } | ||
| 285 | |||
| 286 | if (this.Chain.DisableSystemRestore) | ||
| 287 | { | ||
| 288 | writer.WriteAttributeString("DisableSystemRestore", "yes"); | ||
| 289 | } | ||
| 290 | |||
| 291 | if (this.Chain.ParallelCache) | ||
| 292 | { | ||
| 293 | writer.WriteAttributeString("ParallelCache", "yes"); | ||
| 294 | } | ||
| 295 | |||
| 296 | // Index a few tables by package. | ||
| 297 | ILookup<string, WixBundlePatchTargetCodeRow> targetCodesByPatch = this.Output.Tables["WixBundlePatchTargetCode"].RowsAs<WixBundlePatchTargetCodeRow>().ToLookup(r => r.MspPackageId); | ||
| 298 | ILookup<string, WixBundleMsiFeatureRow> msiFeaturesByPackage = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>().ToLookup(r => r.ChainPackageId); | ||
| 299 | ILookup<string, WixBundleMsiPropertyRow> msiPropertiesByPackage = this.Output.Tables["WixBundleMsiProperty"].RowsAs<WixBundleMsiPropertyRow>().ToLookup(r => r.ChainPackageId); | ||
| 300 | ILookup<string, WixBundlePayloadRow> payloadsByPackage = this.Payloads.Values.ToLookup(p => p.Package); | ||
| 301 | ILookup<string, WixBundleRelatedPackageRow> relatedPackagesByPackage = this.Output.Tables["WixBundleRelatedPackage"].RowsAs<WixBundleRelatedPackageRow>().ToLookup(r => r.ChainPackageId); | ||
| 302 | ILookup<string, WixBundleSlipstreamMspRow> slipstreamMspsByPackage = this.Output.Tables["WixBundleSlipstreamMsp"].RowsAs<WixBundleSlipstreamMspRow>().ToLookup(r => r.ChainPackageId); | ||
| 303 | ILookup<string, WixBundlePackageExitCodeRow> exitCodesByPackage = this.Output.Tables["WixBundlePackageExitCode"].RowsAs<WixBundlePackageExitCodeRow>().ToLookup(r => r.ChainPackageId); | ||
| 304 | ILookup<string, WixBundlePackageCommandLineRow> commandLinesByPackage = this.Output.Tables["WixBundlePackageCommandLine"].RowsAs<WixBundlePackageCommandLineRow>().ToLookup(r => r.ChainPackageId); | ||
| 305 | |||
| 306 | // Build up the list of target codes from all the MSPs in the chain. | ||
| 307 | List<WixBundlePatchTargetCodeRow> targetCodes = new List<WixBundlePatchTargetCodeRow>(); | ||
| 308 | |||
| 309 | foreach (PackageFacade package in this.OrderedPackages) | ||
| 310 | { | ||
| 311 | writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.Package.Type)); | ||
| 312 | |||
| 313 | writer.WriteAttributeString("Id", package.Package.WixChainItemId); | ||
| 314 | |||
| 315 | switch (package.Package.Cache) | ||
| 316 | { | ||
| 317 | case YesNoAlwaysType.No: | ||
| 318 | writer.WriteAttributeString("Cache", "no"); | ||
| 319 | break; | ||
| 320 | case YesNoAlwaysType.Yes: | ||
| 321 | writer.WriteAttributeString("Cache", "yes"); | ||
| 322 | break; | ||
| 323 | case YesNoAlwaysType.Always: | ||
| 324 | writer.WriteAttributeString("Cache", "always"); | ||
| 325 | break; | ||
| 326 | } | ||
| 327 | |||
| 328 | writer.WriteAttributeString("CacheId", package.Package.CacheId); | ||
| 329 | writer.WriteAttributeString("InstallSize", Convert.ToString(package.Package.InstallSize)); | ||
| 330 | writer.WriteAttributeString("Size", Convert.ToString(package.Package.Size)); | ||
| 331 | writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.Package.PerMachine ? "yes" : "no"); | ||
| 332 | writer.WriteAttributeString("Permanent", package.Package.Permanent ? "yes" : "no"); | ||
| 333 | writer.WriteAttributeString("Vital", (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"); | ||
| 334 | |||
| 335 | if (null != package.Package.RollbackBoundary) | ||
| 336 | { | ||
| 337 | writer.WriteAttributeString("RollbackBoundaryForward", package.Package.RollbackBoundary); | ||
| 338 | } | ||
| 339 | |||
| 340 | if (!String.IsNullOrEmpty(package.Package.RollbackBoundaryBackward)) | ||
| 341 | { | ||
| 342 | writer.WriteAttributeString("RollbackBoundaryBackward", package.Package.RollbackBoundaryBackward); | ||
| 343 | } | ||
| 344 | |||
| 345 | if (!String.IsNullOrEmpty(package.Package.LogPathVariable)) | ||
| 346 | { | ||
| 347 | writer.WriteAttributeString("LogPathVariable", package.Package.LogPathVariable); | ||
| 348 | } | ||
| 349 | |||
| 350 | if (!String.IsNullOrEmpty(package.Package.RollbackLogPathVariable)) | ||
| 351 | { | ||
| 352 | writer.WriteAttributeString("RollbackLogPathVariable", package.Package.RollbackLogPathVariable); | ||
| 353 | } | ||
| 354 | |||
| 355 | if (!String.IsNullOrEmpty(package.Package.InstallCondition)) | ||
| 356 | { | ||
| 357 | writer.WriteAttributeString("InstallCondition", package.Package.InstallCondition); | ||
| 358 | } | ||
| 359 | |||
| 360 | if (WixBundlePackageType.Exe == package.Package.Type) | ||
| 361 | { | ||
| 362 | writer.WriteAttributeString("DetectCondition", package.ExePackage.DetectCondition); | ||
| 363 | writer.WriteAttributeString("InstallArguments", package.ExePackage.InstallCommand); | ||
| 364 | writer.WriteAttributeString("UninstallArguments", package.ExePackage.UninstallCommand); | ||
| 365 | writer.WriteAttributeString("RepairArguments", package.ExePackage.RepairCommand); | ||
| 366 | writer.WriteAttributeString("Repairable", package.ExePackage.Repairable ? "yes" : "no"); | ||
| 367 | if (!String.IsNullOrEmpty(package.ExePackage.ExeProtocol)) | ||
| 368 | { | ||
| 369 | writer.WriteAttributeString("Protocol", package.ExePackage.ExeProtocol); | ||
| 370 | } | ||
| 371 | } | ||
| 372 | else if (WixBundlePackageType.Msi == package.Package.Type) | ||
| 373 | { | ||
| 374 | writer.WriteAttributeString("ProductCode", package.MsiPackage.ProductCode); | ||
| 375 | writer.WriteAttributeString("Language", package.MsiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); | ||
| 376 | writer.WriteAttributeString("Version", package.MsiPackage.ProductVersion); | ||
| 377 | writer.WriteAttributeString("DisplayInternalUI", package.MsiPackage.DisplayInternalUI ? "yes" : "no"); | ||
| 378 | if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) | ||
| 379 | { | ||
| 380 | writer.WriteAttributeString("UpgradeCode", package.MsiPackage.UpgradeCode); | ||
| 381 | } | ||
| 382 | } | ||
| 383 | else if (WixBundlePackageType.Msp == package.Package.Type) | ||
| 384 | { | ||
| 385 | writer.WriteAttributeString("PatchCode", package.MspPackage.PatchCode); | ||
| 386 | writer.WriteAttributeString("PatchXml", package.MspPackage.PatchXml); | ||
| 387 | writer.WriteAttributeString("DisplayInternalUI", package.MspPackage.DisplayInternalUI ? "yes" : "no"); | ||
| 388 | |||
| 389 | // If there is still a chance that all of our patches will target a narrow set of | ||
| 390 | // product codes, add the patch list to the overall list. | ||
| 391 | if (null != targetCodes) | ||
| 392 | { | ||
| 393 | if (!package.MspPackage.TargetUnspecified) | ||
| 394 | { | ||
| 395 | IEnumerable<WixBundlePatchTargetCodeRow> patchTargetCodes = targetCodesByPatch[package.MspPackage.ChainPackageId]; | ||
| 396 | |||
| 397 | targetCodes.AddRange(patchTargetCodes); | ||
| 398 | } | ||
| 399 | else // we have a patch that targets the world, so throw the whole list away. | ||
| 400 | { | ||
| 401 | targetCodes = null; | ||
| 402 | } | ||
| 403 | } | ||
| 404 | } | ||
| 405 | else if (WixBundlePackageType.Msu == package.Package.Type) | ||
| 406 | { | ||
| 407 | writer.WriteAttributeString("DetectCondition", package.MsuPackage.DetectCondition); | ||
| 408 | writer.WriteAttributeString("KB", package.MsuPackage.MsuKB); | ||
| 409 | } | ||
| 410 | |||
| 411 | IEnumerable<WixBundleMsiFeatureRow> packageMsiFeatures = msiFeaturesByPackage[package.Package.WixChainItemId]; | ||
| 412 | |||
| 413 | foreach (WixBundleMsiFeatureRow feature in packageMsiFeatures) | ||
| 414 | { | ||
| 415 | writer.WriteStartElement("MsiFeature"); | ||
| 416 | writer.WriteAttributeString("Id", feature.Name); | ||
| 417 | writer.WriteEndElement(); | ||
| 418 | } | ||
| 419 | |||
| 420 | IEnumerable<WixBundleMsiPropertyRow> packageMsiProperties = msiPropertiesByPackage[package.Package.WixChainItemId]; | ||
| 421 | |||
| 422 | foreach (WixBundleMsiPropertyRow msiProperty in packageMsiProperties) | ||
| 423 | { | ||
| 424 | writer.WriteStartElement("MsiProperty"); | ||
| 425 | writer.WriteAttributeString("Id", msiProperty.Name); | ||
| 426 | writer.WriteAttributeString("Value", msiProperty.Value); | ||
| 427 | if (!String.IsNullOrEmpty(msiProperty.Condition)) | ||
| 428 | { | ||
| 429 | writer.WriteAttributeString("Condition", msiProperty.Condition); | ||
| 430 | } | ||
| 431 | writer.WriteEndElement(); | ||
| 432 | } | ||
| 433 | |||
| 434 | IEnumerable<WixBundleSlipstreamMspRow> packageSlipstreamMsps = slipstreamMspsByPackage[package.Package.WixChainItemId]; | ||
| 435 | |||
| 436 | foreach (WixBundleSlipstreamMspRow slipstreamMsp in packageSlipstreamMsps) | ||
| 437 | { | ||
| 438 | writer.WriteStartElement("SlipstreamMsp"); | ||
| 439 | writer.WriteAttributeString("Id", slipstreamMsp.MspPackageId); | ||
| 440 | writer.WriteEndElement(); | ||
| 441 | } | ||
| 442 | |||
| 443 | IEnumerable<WixBundlePackageExitCodeRow> packageExitCodes = exitCodesByPackage[package.Package.WixChainItemId]; | ||
| 444 | |||
| 445 | foreach (WixBundlePackageExitCodeRow exitCode in packageExitCodes) | ||
| 446 | { | ||
| 447 | writer.WriteStartElement("ExitCode"); | ||
| 448 | |||
| 449 | if (exitCode.Code.HasValue) | ||
| 450 | { | ||
| 451 | writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture)); | ||
| 452 | } | ||
| 453 | else | ||
| 454 | { | ||
| 455 | writer.WriteAttributeString("Code", "*"); | ||
| 456 | } | ||
| 457 | |||
| 458 | writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture)); | ||
| 459 | writer.WriteEndElement(); | ||
| 460 | } | ||
| 461 | |||
| 462 | IEnumerable<WixBundlePackageCommandLineRow> packageCommandLines = commandLinesByPackage[package.Package.WixChainItemId]; | ||
| 463 | |||
| 464 | foreach (WixBundlePackageCommandLineRow commandLine in packageCommandLines) | ||
| 465 | { | ||
| 466 | writer.WriteStartElement("CommandLine"); | ||
| 467 | writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); | ||
| 468 | writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument); | ||
| 469 | writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument); | ||
| 470 | writer.WriteAttributeString("Condition", commandLine.Condition); | ||
| 471 | writer.WriteEndElement(); | ||
| 472 | } | ||
| 473 | |||
| 474 | // Output the dependency information. | ||
| 475 | foreach (ProvidesDependency dependency in package.Provides) | ||
| 476 | { | ||
| 477 | // TODO: Add to wixpdb as an imported table, or link package wixpdbs to bundle wixpdbs. | ||
| 478 | dependency.WriteXml(writer); | ||
| 479 | } | ||
| 480 | |||
| 481 | IEnumerable<WixBundleRelatedPackageRow> packageRelatedPackages = relatedPackagesByPackage[package.Package.WixChainItemId]; | ||
| 482 | |||
| 483 | foreach (WixBundleRelatedPackageRow related in packageRelatedPackages) | ||
| 484 | { | ||
| 485 | writer.WriteStartElement("RelatedPackage"); | ||
| 486 | writer.WriteAttributeString("Id", related.Id); | ||
| 487 | if (!String.IsNullOrEmpty(related.MinVersion)) | ||
| 488 | { | ||
| 489 | writer.WriteAttributeString("MinVersion", related.MinVersion); | ||
| 490 | writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no"); | ||
| 491 | } | ||
| 492 | if (!String.IsNullOrEmpty(related.MaxVersion)) | ||
| 493 | { | ||
| 494 | writer.WriteAttributeString("MaxVersion", related.MaxVersion); | ||
| 495 | writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no"); | ||
| 496 | } | ||
| 497 | writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); | ||
| 498 | |||
| 499 | string[] relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); | ||
| 500 | |||
| 501 | if (0 < relatedLanguages.Length) | ||
| 502 | { | ||
| 503 | writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no"); | ||
| 504 | foreach (string language in relatedLanguages) | ||
| 505 | { | ||
| 506 | writer.WriteStartElement("Language"); | ||
| 507 | writer.WriteAttributeString("Id", language); | ||
| 508 | writer.WriteEndElement(); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | writer.WriteEndElement(); | ||
| 512 | } | ||
| 513 | |||
| 514 | // Write any contained Payloads with the PackagePayload being first | ||
| 515 | writer.WriteStartElement("PayloadRef"); | ||
| 516 | writer.WriteAttributeString("Id", package.Package.PackagePayload); | ||
| 517 | writer.WriteEndElement(); | ||
| 518 | |||
| 519 | IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[package.Package.WixChainItemId]; | ||
| 520 | |||
| 521 | foreach (WixBundlePayloadRow payload in packagePayloads) | ||
| 522 | { | ||
| 523 | if (payload.Id != package.Package.PackagePayload) | ||
| 524 | { | ||
| 525 | writer.WriteStartElement("PayloadRef"); | ||
| 526 | writer.WriteAttributeString("Id", payload.Id); | ||
| 527 | writer.WriteEndElement(); | ||
| 528 | } | ||
| 529 | } | ||
| 530 | |||
| 531 | writer.WriteEndElement(); // </XxxPackage> | ||
| 532 | } | ||
| 533 | writer.WriteEndElement(); // </Chain> | ||
| 534 | |||
| 535 | if (null != targetCodes) | ||
| 536 | { | ||
| 537 | foreach (WixBundlePatchTargetCodeRow targetCode in targetCodes) | ||
| 538 | { | ||
| 539 | writer.WriteStartElement("PatchTargetCode"); | ||
| 540 | writer.WriteAttributeString("TargetCode", targetCode.TargetCode); | ||
| 541 | writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); | ||
| 542 | writer.WriteEndElement(); | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | // Write the ApprovedExeForElevation elements. | ||
| 547 | IEnumerable<WixApprovedExeForElevationRow> approvedExesForElevation = this.Output.Tables["WixApprovedExeForElevation"].RowsAs<WixApprovedExeForElevationRow>(); | ||
| 548 | |||
| 549 | foreach (WixApprovedExeForElevationRow approvedExeForElevation in approvedExesForElevation) | ||
| 550 | { | ||
| 551 | writer.WriteStartElement("ApprovedExeForElevation"); | ||
| 552 | writer.WriteAttributeString("Id", approvedExeForElevation.Id); | ||
| 553 | writer.WriteAttributeString("Key", approvedExeForElevation.Key); | ||
| 554 | |||
| 555 | if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) | ||
| 556 | { | ||
| 557 | writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName); | ||
| 558 | } | ||
| 559 | |||
| 560 | if (approvedExeForElevation.Win64) | ||
| 561 | { | ||
| 562 | writer.WriteAttributeString("Win64", "yes"); | ||
| 563 | } | ||
| 564 | |||
| 565 | writer.WriteEndElement(); | ||
| 566 | } | ||
| 567 | |||
| 568 | writer.WriteEndDocument(); // </BurnManifest> | ||
| 569 | } | ||
| 570 | } | ||
| 571 | |||
| 572 | private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerRow container) | ||
| 573 | { | ||
| 574 | writer.WriteAttributeString("Id", container.Id); | ||
| 575 | writer.WriteAttributeString("FileSize", container.Size.ToString(CultureInfo.InvariantCulture)); | ||
| 576 | writer.WriteAttributeString("Hash", container.Hash); | ||
| 577 | |||
| 578 | if (ContainerType.Detached == container.Type) | ||
| 579 | { | ||
| 580 | string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id, container.Name); | ||
| 581 | if (!String.IsNullOrEmpty(resolvedUrl)) | ||
| 582 | { | ||
| 583 | writer.WriteAttributeString("DownloadUrl", resolvedUrl); | ||
| 584 | } | ||
| 585 | else if (!String.IsNullOrEmpty(container.DownloadUrl)) | ||
| 586 | { | ||
| 587 | writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); | ||
| 588 | } | ||
| 589 | |||
| 590 | writer.WriteAttributeString("FilePath", container.Name); | ||
| 591 | } | ||
| 592 | else if (ContainerType.Attached == container.Type) | ||
| 593 | { | ||
| 594 | if (!String.IsNullOrEmpty(container.DownloadUrl)) | ||
| 595 | { | ||
| 596 | Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id)); | ||
| 597 | } | ||
| 598 | |||
| 599 | writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. | ||
| 600 | writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.ToString(CultureInfo.InvariantCulture)); | ||
| 601 | writer.WriteAttributeString("Attached", "yes"); | ||
| 602 | writer.WriteAttributeString("Primary", "yes"); | ||
| 603 | } | ||
| 604 | } | ||
| 605 | |||
| 606 | private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadRow payload, bool embeddedOnly, Dictionary<string, WixBundlePayloadRow> allPayloads) | ||
| 607 | { | ||
| 608 | Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging); | ||
| 609 | |||
| 610 | writer.WriteAttributeString("Id", payload.Id); | ||
| 611 | writer.WriteAttributeString("FilePath", payload.Name); | ||
| 612 | writer.WriteAttributeString("FileSize", payload.FileSize.ToString(CultureInfo.InvariantCulture)); | ||
| 613 | writer.WriteAttributeString("Hash", payload.Hash); | ||
| 614 | |||
| 615 | if (payload.LayoutOnly) | ||
| 616 | { | ||
| 617 | writer.WriteAttributeString("LayoutOnly", "yes"); | ||
| 618 | } | ||
| 619 | |||
| 620 | if (!String.IsNullOrEmpty(payload.PublicKey)) | ||
| 621 | { | ||
| 622 | writer.WriteAttributeString("CertificateRootPublicKeyIdentifier", payload.PublicKey); | ||
| 623 | } | ||
| 624 | |||
| 625 | if (!String.IsNullOrEmpty(payload.Thumbprint)) | ||
| 626 | { | ||
| 627 | writer.WriteAttributeString("CertificateRootThumbprint", payload.Thumbprint); | ||
| 628 | } | ||
| 629 | |||
| 630 | switch (payload.Packaging) | ||
| 631 | { | ||
| 632 | case PackagingType.Embedded: // this means it's in a container. | ||
| 633 | if (!String.IsNullOrEmpty(payload.DownloadUrl)) | ||
| 634 | { | ||
| 635 | Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id)); | ||
| 636 | } | ||
| 637 | |||
| 638 | writer.WriteAttributeString("Packaging", "embedded"); | ||
| 639 | writer.WriteAttributeString("SourcePath", payload.EmbeddedId); | ||
| 640 | |||
| 641 | if (Compiler.BurnUXContainerId != payload.Container) | ||
| 642 | { | ||
| 643 | writer.WriteAttributeString("Container", payload.Container); | ||
| 644 | } | ||
| 645 | break; | ||
| 646 | |||
| 647 | case PackagingType.External: | ||
| 648 | string packageId = payload.ParentPackagePayload; | ||
| 649 | string parentUrl = payload.ParentPackagePayload == null ? null : allPayloads[payload.ParentPackagePayload].DownloadUrl; | ||
| 650 | string resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id, payload.Name); | ||
| 651 | if (!String.IsNullOrEmpty(resolvedUrl)) | ||
| 652 | { | ||
| 653 | writer.WriteAttributeString("DownloadUrl", resolvedUrl); | ||
| 654 | } | ||
| 655 | else if (!String.IsNullOrEmpty(payload.DownloadUrl)) | ||
| 656 | { | ||
| 657 | writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); | ||
| 658 | } | ||
| 659 | |||
| 660 | writer.WriteAttributeString("Packaging", "external"); | ||
| 661 | writer.WriteAttributeString("SourcePath", payload.Name); | ||
| 662 | break; | ||
| 663 | } | ||
| 664 | |||
| 665 | if (!String.IsNullOrEmpty(payload.Catalog)) | ||
| 666 | { | ||
| 667 | writer.WriteAttributeString("Catalog", payload.Catalog); | ||
| 668 | } | ||
| 669 | } | ||
| 670 | |||
| 671 | private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) | ||
| 672 | { | ||
| 673 | string resolved = null; | ||
| 674 | foreach (IBinderFileManager fileManager in this.FileManagers) | ||
| 675 | { | ||
| 676 | resolved = fileManager.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); | ||
| 677 | if (!String.IsNullOrEmpty(resolved)) | ||
| 678 | { | ||
| 679 | break; | ||
| 680 | } | ||
| 681 | } | ||
| 682 | |||
| 683 | return resolved; | ||
| 684 | } | ||
| 685 | } | ||
| 686 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs deleted file mode 100644 index 1bf987e3..00000000 --- a/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs +++ /dev/null | |||
| @@ -1,68 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using WixToolset.Cab; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Rows; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Creates cabinet files. | ||
| 16 | /// </summary> | ||
| 17 | internal class CreateContainerCommand : ICommand | ||
| 18 | { | ||
| 19 | public CompressionLevel DefaultCompressionLevel { private get; set; } | ||
| 20 | |||
| 21 | public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } | ||
| 22 | |||
| 23 | public string ManifestFile { private get; set; } | ||
| 24 | |||
| 25 | public string OutputPath { private get; set; } | ||
| 26 | |||
| 27 | public string Hash { get; private set; } | ||
| 28 | |||
| 29 | public long Size { get; private set; } | ||
| 30 | |||
| 31 | public void Execute() | ||
| 32 | { | ||
| 33 | int payloadCount = this.Payloads.Count(); // The number of embedded payloads | ||
| 34 | |||
| 35 | if (!String.IsNullOrEmpty(this.ManifestFile)) | ||
| 36 | { | ||
| 37 | ++payloadCount; | ||
| 38 | } | ||
| 39 | |||
| 40 | using (WixCreateCab cab = new WixCreateCab(Path.GetFileName(this.OutputPath), Path.GetDirectoryName(this.OutputPath), payloadCount, 0, 0, this.DefaultCompressionLevel)) | ||
| 41 | { | ||
| 42 | // If a manifest was provided always add it as "payload 0" to the container. | ||
| 43 | if (!String.IsNullOrEmpty(this.ManifestFile)) | ||
| 44 | { | ||
| 45 | cab.AddFile(this.ManifestFile, "0"); | ||
| 46 | } | ||
| 47 | |||
| 48 | foreach (WixBundlePayloadRow payload in this.Payloads) | ||
| 49 | { | ||
| 50 | Debug.Assert(PackagingType.Embedded == payload.Packaging); | ||
| 51 | |||
| 52 | Messaging.Instance.OnMessage(WixVerboses.LoadingPayload(payload.FullFileName)); | ||
| 53 | |||
| 54 | cab.AddFile(payload.FullFileName, payload.EmbeddedId); | ||
| 55 | } | ||
| 56 | |||
| 57 | cab.Complete(); | ||
| 58 | } | ||
| 59 | |||
| 60 | // Now that the container is created, set the outputs of the command. | ||
| 61 | FileInfo fileInfo = new FileInfo(this.OutputPath); | ||
| 62 | |||
| 63 | this.Hash = Common.GetFileHash(fileInfo.FullName); | ||
| 64 | |||
| 65 | this.Size = fileInfo.Length; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs deleted file mode 100644 index dc19e380..00000000 --- a/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs +++ /dev/null | |||
| @@ -1,62 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Rows; | ||
| 8 | |||
| 9 | internal class GetPackageFacadesCommand : ICommand | ||
| 10 | { | ||
| 11 | public Table PackageTable { private get; set; } | ||
| 12 | |||
| 13 | public Table ExePackageTable { private get; set; } | ||
| 14 | |||
| 15 | public Table MsiPackageTable { private get; set; } | ||
| 16 | |||
| 17 | public Table MspPackageTable { private get; set; } | ||
| 18 | |||
| 19 | public Table MsuPackageTable { private get; set; } | ||
| 20 | |||
| 21 | public IDictionary<string, PackageFacade> PackageFacades { get; private set; } | ||
| 22 | |||
| 23 | public void Execute() | ||
| 24 | { | ||
| 25 | RowDictionary<WixBundleExePackageRow> exePackages = new RowDictionary<WixBundleExePackageRow>(this.ExePackageTable); | ||
| 26 | RowDictionary<WixBundleMsiPackageRow> msiPackages = new RowDictionary<WixBundleMsiPackageRow>(this.MsiPackageTable); | ||
| 27 | RowDictionary<WixBundleMspPackageRow> mspPackages = new RowDictionary<WixBundleMspPackageRow>(this.MspPackageTable); | ||
| 28 | RowDictionary<WixBundleMsuPackageRow> msuPackages = new RowDictionary<WixBundleMsuPackageRow>(this.MsuPackageTable); | ||
| 29 | |||
| 30 | Dictionary<string, PackageFacade> facades = new Dictionary<string, PackageFacade>(this.PackageTable.Rows.Count); | ||
| 31 | |||
| 32 | foreach (WixBundlePackageRow package in this.PackageTable.Rows) | ||
| 33 | { | ||
| 34 | string id = package.WixChainItemId; | ||
| 35 | PackageFacade facade = null; | ||
| 36 | |||
| 37 | switch (package.Type) | ||
| 38 | { | ||
| 39 | case WixBundlePackageType.Exe: | ||
| 40 | facade = new PackageFacade(package, exePackages.Get(id)); | ||
| 41 | break; | ||
| 42 | |||
| 43 | case WixBundlePackageType.Msi: | ||
| 44 | facade = new PackageFacade(package, msiPackages.Get(id)); | ||
| 45 | break; | ||
| 46 | |||
| 47 | case WixBundlePackageType.Msp: | ||
| 48 | facade = new PackageFacade(package, mspPackages.Get(id)); | ||
| 49 | break; | ||
| 50 | |||
| 51 | case WixBundlePackageType.Msu: | ||
| 52 | facade = new PackageFacade(package, msuPackages.Get(id)); | ||
| 53 | break; | ||
| 54 | } | ||
| 55 | |||
| 56 | facades.Add(id, facade); | ||
| 57 | } | ||
| 58 | |||
| 59 | this.PackageFacades = facades; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs deleted file mode 100644 index ac3a301d..00000000 --- a/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs +++ /dev/null | |||
| @@ -1,145 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Rows; | ||
| 9 | |||
| 10 | internal class OrderPackagesAndRollbackBoundariesCommand : ICommand | ||
| 11 | { | ||
| 12 | public Table WixGroupTable { private get; set; } | ||
| 13 | |||
| 14 | public RowDictionary<WixBundleRollbackBoundaryRow> Boundaries { private get; set; } | ||
| 15 | |||
| 16 | public IDictionary<string, PackageFacade> PackageFacades { private get; set; } | ||
| 17 | |||
| 18 | public IEnumerable<PackageFacade> OrderedPackageFacades { get; private set; } | ||
| 19 | |||
| 20 | public IEnumerable<WixBundleRollbackBoundaryRow> UsedRollbackBoundaries { get; private set; } | ||
| 21 | |||
| 22 | public void Execute() | ||
| 23 | { | ||
| 24 | List<PackageFacade> orderedFacades = new List<PackageFacade>(); | ||
| 25 | List<WixBundleRollbackBoundaryRow> usedBoundaries = new List<WixBundleRollbackBoundaryRow>(); | ||
| 26 | |||
| 27 | // Process the chain of packages to add them in the correct order | ||
| 28 | // and assign the forward rollback boundaries as appropriate. Remember | ||
| 29 | // rollback boundaries are authored as elements in the chain which | ||
| 30 | // we re-interpret here to add them as attributes on the next available | ||
| 31 | // package in the chain. Essentially we mark some packages as being | ||
| 32 | // the start of a rollback boundary when installing and repairing. | ||
| 33 | // We handle uninstall (aka: backwards) rollback boundaries after | ||
| 34 | // we get these install/repair (aka: forward) rollback boundaries | ||
| 35 | // defined. | ||
| 36 | WixBundleRollbackBoundaryRow previousRollbackBoundary = null; | ||
| 37 | WixBundleRollbackBoundaryRow lastRollbackBoundary = null; | ||
| 38 | bool boundaryHadX86Package = false; | ||
| 39 | |||
| 40 | foreach (WixGroupRow row in this.WixGroupTable.Rows) | ||
| 41 | { | ||
| 42 | if (ComplexReferenceChildType.Package == row.ChildType && ComplexReferenceParentType.PackageGroup == row.ParentType && "WixChain" == row.ParentId) | ||
| 43 | { | ||
| 44 | PackageFacade facade = null; | ||
| 45 | if (PackageFacades.TryGetValue(row.ChildId, out facade)) | ||
| 46 | { | ||
| 47 | if (null != previousRollbackBoundary) | ||
| 48 | { | ||
| 49 | usedBoundaries.Add(previousRollbackBoundary); | ||
| 50 | facade.Package.RollbackBoundary = previousRollbackBoundary.ChainPackageId; | ||
| 51 | previousRollbackBoundary = null; | ||
| 52 | |||
| 53 | boundaryHadX86Package = (facade.Package.x64 == YesNoType.Yes); | ||
| 54 | } | ||
| 55 | |||
| 56 | // Error if MSI transaction has x86 package preceding x64 packages | ||
| 57 | if ((lastRollbackBoundary != null) && (lastRollbackBoundary.Transaction == YesNoType.Yes) | ||
| 58 | && boundaryHadX86Package | ||
| 59 | && (facade.Package.x64 == YesNoType.Yes)) | ||
| 60 | { | ||
| 61 | Messaging.Instance.OnMessage(WixErrors.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers)); | ||
| 62 | } | ||
| 63 | boundaryHadX86Package = boundaryHadX86Package || (facade.Package.x64 == YesNoType.No); | ||
| 64 | |||
| 65 | orderedFacades.Add(facade); | ||
| 66 | } | ||
| 67 | else // must be a rollback boundary. | ||
| 68 | { | ||
| 69 | // Discard the next rollback boundary if we have a previously defined boundary. | ||
| 70 | WixBundleRollbackBoundaryRow nextRollbackBoundary = Boundaries.Get(row.ChildId); | ||
| 71 | if (null != previousRollbackBoundary) | ||
| 72 | { | ||
| 73 | Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.ChainPackageId)); | ||
| 74 | } | ||
| 75 | else | ||
| 76 | { | ||
| 77 | previousRollbackBoundary = nextRollbackBoundary; | ||
| 78 | lastRollbackBoundary = nextRollbackBoundary; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | if (null != previousRollbackBoundary) | ||
| 85 | { | ||
| 86 | Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.ChainPackageId)); | ||
| 87 | } | ||
| 88 | |||
| 89 | // With the forward rollback boundaries assigned, we can now go | ||
| 90 | // through the packages with rollback boundaries and assign backward | ||
| 91 | // rollback boundaries. Backward rollback boundaries are used when | ||
| 92 | // the chain is going "backwards" which (AFAIK) only happens during | ||
| 93 | // uninstall. | ||
| 94 | // | ||
| 95 | // Consider the scenario with three packages: A, B and C. Packages A | ||
| 96 | // and C are marked as rollback boundary packages and package B is | ||
| 97 | // not. The naive implementation would execute the chain like this | ||
| 98 | // (numbers indicate where rollback boundaries would end up): | ||
| 99 | // install: 1 A B 2 C | ||
| 100 | // uninstall: 2 C B 1 A | ||
| 101 | // | ||
| 102 | // The uninstall chain is wrong, A and B should be grouped together | ||
| 103 | // not C and B. The fix is to label packages with a "backwards" | ||
| 104 | // rollback boundary used during uninstall. The backwards rollback | ||
| 105 | // boundaries are assigned to the package *before* the next rollback | ||
| 106 | // boundary. Using our example from above again, I'll mark the | ||
| 107 | // backwards rollback boundaries prime (aka: with '). | ||
| 108 | // install: 1 A B 1' 2 C 2' | ||
| 109 | // uninstall: 2' C 2 1' B A 1 | ||
| 110 | // | ||
| 111 | // If the marked boundaries are ignored during install you get the | ||
| 112 | // same thing as above (good) and if the non-marked boundaries are | ||
| 113 | // ignored during uninstall then A and B are correctly grouped. | ||
| 114 | // Here's what it looks like without all the markers: | ||
| 115 | // install: 1 A B 2 C | ||
| 116 | // uninstall: 2 C 1 B A | ||
| 117 | // Woot! | ||
| 118 | string previousRollbackBoundaryId = null; | ||
| 119 | PackageFacade previousFacade = null; | ||
| 120 | |||
| 121 | foreach (PackageFacade package in orderedFacades) | ||
| 122 | { | ||
| 123 | if (null != package.Package.RollbackBoundary) | ||
| 124 | { | ||
| 125 | if (null != previousFacade) | ||
| 126 | { | ||
| 127 | previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; | ||
| 128 | } | ||
| 129 | |||
| 130 | previousRollbackBoundaryId = package.Package.RollbackBoundary; | ||
| 131 | } | ||
| 132 | |||
| 133 | previousFacade = package; | ||
| 134 | } | ||
| 135 | |||
| 136 | if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) | ||
| 137 | { | ||
| 138 | previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; | ||
| 139 | } | ||
| 140 | |||
| 141 | this.OrderedPackageFacades = orderedFacades; | ||
| 142 | this.UsedRollbackBoundaries = usedBoundaries; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs b/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs deleted file mode 100644 index f7e6410f..00000000 --- a/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs +++ /dev/null | |||
| @@ -1,58 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using WixToolset.Data.Rows; | ||
| 6 | |||
| 7 | internal class PackageFacade | ||
| 8 | { | ||
| 9 | private PackageFacade(WixBundlePackageRow package) | ||
| 10 | { | ||
| 11 | this.Package = package; | ||
| 12 | this.Provides = new ProvidesDependencyCollection(); | ||
| 13 | } | ||
| 14 | |||
| 15 | public PackageFacade(WixBundlePackageRow package, WixBundleExePackageRow exePackage) | ||
| 16 | : this(package) | ||
| 17 | { | ||
| 18 | this.ExePackage = exePackage; | ||
| 19 | } | ||
| 20 | |||
| 21 | public PackageFacade(WixBundlePackageRow package, WixBundleMsiPackageRow msiPackage) | ||
| 22 | : this(package) | ||
| 23 | { | ||
| 24 | this.MsiPackage = msiPackage; | ||
| 25 | } | ||
| 26 | |||
| 27 | public PackageFacade(WixBundlePackageRow package, WixBundleMspPackageRow mspPackage) | ||
| 28 | : this(package) | ||
| 29 | { | ||
| 30 | this.MspPackage = mspPackage; | ||
| 31 | } | ||
| 32 | |||
| 33 | public PackageFacade(WixBundlePackageRow package, WixBundleMsuPackageRow msuPackage) | ||
| 34 | : this(package) | ||
| 35 | { | ||
| 36 | this.MsuPackage = msuPackage; | ||
| 37 | } | ||
| 38 | |||
| 39 | public WixBundlePackageRow Package { get; private set; } | ||
| 40 | |||
| 41 | public WixBundleExePackageRow ExePackage { get; private set; } | ||
| 42 | |||
| 43 | public WixBundleMsiPackageRow MsiPackage { get; private set; } | ||
| 44 | |||
| 45 | public WixBundleMspPackageRow MspPackage { get; private set; } | ||
| 46 | |||
| 47 | public WixBundleMsuPackageRow MsuPackage { get; private set; } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// The provides dependencies authored and imported for this package. | ||
| 51 | /// </summary> | ||
| 52 | /// <remarks> | ||
| 53 | /// TODO: Eventually this collection should turn into Rows so they are tracked in the PDB but | ||
| 54 | /// the relationship with the extension makes it much trickier to pull off. | ||
| 55 | /// </remarks> | ||
| 56 | public ProvidesDependencyCollection Provides { get; private set; } | ||
| 57 | } | ||
| 58 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs deleted file mode 100644 index a1e7c271..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Rows; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Initializes package state from the Exe contents. | ||
| 11 | /// </summary> | ||
| 12 | internal class ProcessExePackageCommand : ICommand | ||
| 13 | { | ||
| 14 | public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } | ||
| 15 | |||
| 16 | public PackageFacade Facade { private get; set; } | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// Processes the Exe packages to add properties and payloads from the Exe packages. | ||
| 20 | /// </summary> | ||
| 21 | public void Execute() | ||
| 22 | { | ||
| 23 | WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); | ||
| 24 | |||
| 25 | if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) | ||
| 26 | { | ||
| 27 | this.Facade.Package.CacheId = packagePayload.Hash; | ||
| 28 | } | ||
| 29 | |||
| 30 | this.Facade.Package.Version = packagePayload.Version; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs deleted file mode 100644 index f73776c0..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs +++ /dev/null | |||
| @@ -1,560 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 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.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | using WixToolset.Msi; | ||
| 16 | using WixToolset.Core.Native; | ||
| 17 | using Dtf = WixToolset.Dtf.WindowsInstaller; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Initializes package state from the MSI contents. | ||
| 21 | /// </summary> | ||
| 22 | internal class ProcessMsiPackageCommand : ICommand | ||
| 23 | { | ||
| 24 | private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"; | ||
| 25 | |||
| 26 | public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } | ||
| 27 | |||
| 28 | public PackageFacade Facade { private get; set; } | ||
| 29 | |||
| 30 | public IBinderFileManager FileManager { private get; set; } | ||
| 31 | |||
| 32 | public Table MsiFeatureTable { private get; set; } | ||
| 33 | |||
| 34 | public Table MsiPropertyTable { private get; set; } | ||
| 35 | |||
| 36 | public Table PayloadTable { private get; set; } | ||
| 37 | |||
| 38 | public Table RelatedPackageTable { private get; set; } | ||
| 39 | |||
| 40 | /// <summary> | ||
| 41 | /// Processes the MSI packages to add properties and payloads from the MSI packages. | ||
| 42 | /// </summary> | ||
| 43 | public void Execute() | ||
| 44 | { | ||
| 45 | WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); | ||
| 46 | |||
| 47 | string sourcePath = packagePayload.FullFileName; | ||
| 48 | bool longNamesInImage = false; | ||
| 49 | bool compressed = false; | ||
| 50 | bool x64 = false; | ||
| 51 | try | ||
| 52 | { | ||
| 53 | // Read data out of the msi database... | ||
| 54 | using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) | ||
| 55 | { | ||
| 56 | // 1 is the Word Count summary information stream bit that means | ||
| 57 | // the MSI uses short file names when set. We care about long file | ||
| 58 | // names so check when the bit is not set. | ||
| 59 | longNamesInImage = 0 == (sumInfo.WordCount & 1); | ||
| 60 | |||
| 61 | // 2 is the Word Count summary information stream bit that means | ||
| 62 | // files are compressed in the MSI by default when the bit is set. | ||
| 63 | compressed = 2 == (sumInfo.WordCount & 2); | ||
| 64 | |||
| 65 | x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64")); | ||
| 66 | |||
| 67 | // 8 is the Word Count summary information stream bit that means | ||
| 68 | // "Elevated privileges are not required to install this package." | ||
| 69 | // in MSI 4.5 and below, if this bit is 0, elevation is required. | ||
| 70 | this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No; | ||
| 71 | this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No; | ||
| 72 | } | ||
| 73 | |||
| 74 | using (Dtf.Database db = new Dtf.Database(sourcePath)) | ||
| 75 | { | ||
| 76 | this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode"); | ||
| 77 | this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode"); | ||
| 78 | this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer"); | ||
| 79 | this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture); | ||
| 80 | this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion"); | ||
| 81 | |||
| 82 | if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion)) | ||
| 83 | { | ||
| 84 | // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? | ||
| 85 | string version = null; | ||
| 86 | string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.'); | ||
| 87 | int count = versionParts.Length; | ||
| 88 | if (0 < count) | ||
| 89 | { | ||
| 90 | version = versionParts[0]; | ||
| 91 | for (int i = 1; i < 4 && i < count; ++i) | ||
| 92 | { | ||
| 93 | version = String.Concat(version, ".", versionParts[i]); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version)) | ||
| 98 | { | ||
| 99 | Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version)); | ||
| 100 | this.Facade.MsiPackage.ProductVersion = version; | ||
| 101 | } | ||
| 102 | else | ||
| 103 | { | ||
| 104 | Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath)); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) | ||
| 109 | { | ||
| 110 | this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion); | ||
| 111 | } | ||
| 112 | |||
| 113 | if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) | ||
| 114 | { | ||
| 115 | this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName"); | ||
| 116 | } | ||
| 117 | |||
| 118 | if (String.IsNullOrEmpty(this.Facade.Package.Description)) | ||
| 119 | { | ||
| 120 | this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS"); | ||
| 121 | } | ||
| 122 | |||
| 123 | ISet<string> payloadNames = this.GetPayloadTargetNames(); | ||
| 124 | |||
| 125 | ISet<string> msiPropertyNames = this.GetMsiPropertyNames(); | ||
| 126 | |||
| 127 | this.SetPerMachineAppropriately(db, sourcePath); | ||
| 128 | |||
| 129 | // Ensure the MSI package is appropriately marked visible or not. | ||
| 130 | this.SetPackageVisibility(db, msiPropertyNames); | ||
| 131 | |||
| 132 | // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. | ||
| 133 | if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL")) | ||
| 134 | { | ||
| 135 | this.AddMsiProperty("MSIFASTINSTALL", "7"); | ||
| 136 | } | ||
| 137 | |||
| 138 | this.CreateRelatedPackages(db); | ||
| 139 | |||
| 140 | // If feature selection is enabled, represent the Feature table in the manifest. | ||
| 141 | if (this.Facade.MsiPackage.EnableFeatureSelection) | ||
| 142 | { | ||
| 143 | this.CreateMsiFeatures(db); | ||
| 144 | } | ||
| 145 | |||
| 146 | // Add all external cabinets as package payloads. | ||
| 147 | this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames); | ||
| 148 | |||
| 149 | // Add all external files as package payloads and calculate the total install size as the rollup of | ||
| 150 | // File table's sizes. | ||
| 151 | this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); | ||
| 152 | |||
| 153 | // Add all dependency providers from the MSI. | ||
| 154 | this.ImportDependencyProviders(db); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | catch (Dtf.InstallerException e) | ||
| 158 | { | ||
| 159 | Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message)); | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | private ISet<string> GetPayloadTargetNames() | ||
| 164 | { | ||
| 165 | IEnumerable<string> payloadNames = this.PayloadTable.RowsAs<WixBundlePayloadRow>() | ||
| 166 | .Where(r => r.Package == this.Facade.Package.WixChainItemId) | ||
| 167 | .Select(r => r.Name); | ||
| 168 | |||
| 169 | return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase); | ||
| 170 | } | ||
| 171 | |||
| 172 | private ISet<string> GetMsiPropertyNames() | ||
| 173 | { | ||
| 174 | IEnumerable<string> properties = this.MsiPropertyTable.RowsAs<WixBundleMsiPropertyRow>() | ||
| 175 | .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId) | ||
| 176 | .Select(r => r.Name); | ||
| 177 | |||
| 178 | return new HashSet<string>(properties, StringComparer.Ordinal); | ||
| 179 | } | ||
| 180 | |||
| 181 | private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath) | ||
| 182 | { | ||
| 183 | if (this.Facade.MsiPackage.ForcePerMachine) | ||
| 184 | { | ||
| 185 | if (YesNoDefaultType.No == this.Facade.Package.PerMachine) | ||
| 186 | { | ||
| 187 | Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath)); | ||
| 188 | this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. | ||
| 189 | } | ||
| 190 | |||
| 191 | // Force ALLUSERS=1 via the MSI command-line. | ||
| 192 | this.AddMsiProperty("ALLUSERS", "1"); | ||
| 193 | } | ||
| 194 | else | ||
| 195 | { | ||
| 196 | string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS"); | ||
| 197 | |||
| 198 | if (String.IsNullOrEmpty(allusers)) | ||
| 199 | { | ||
| 200 | // Not forced per-machine and no ALLUSERS property, flip back to per-user. | ||
| 201 | if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) | ||
| 202 | { | ||
| 203 | Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath)); | ||
| 204 | this.Facade.Package.PerMachine = YesNoDefaultType.No; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | else if (allusers.Equals("1", StringComparison.Ordinal)) | ||
| 208 | { | ||
| 209 | if (YesNoDefaultType.No == this.Facade.Package.PerMachine) | ||
| 210 | { | ||
| 211 | Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath)); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | else if (allusers.Equals("2", StringComparison.Ordinal)) | ||
| 215 | { | ||
| 216 | Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user")); | ||
| 217 | } | ||
| 218 | else | ||
| 219 | { | ||
| 220 | Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers)); | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | private void SetPackageVisibility(Dtf.Database db, ISet<string> msiPropertyNames) | ||
| 226 | { | ||
| 227 | bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT"); | ||
| 228 | |||
| 229 | if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility. | ||
| 230 | { | ||
| 231 | // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. | ||
| 232 | if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) | ||
| 233 | { | ||
| 234 | this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1"); | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | private void CreateRelatedPackages(Dtf.Database db) | ||
| 240 | { | ||
| 241 | // Represent the Upgrade table as related packages. | ||
| 242 | if (db.Tables.Contains("Upgrade")) | ||
| 243 | { | ||
| 244 | using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) | ||
| 245 | { | ||
| 246 | view.Execute(); | ||
| 247 | while (true) | ||
| 248 | { | ||
| 249 | using (Dtf.Record record = view.Fetch()) | ||
| 250 | { | ||
| 251 | if (null == record) | ||
| 252 | { | ||
| 253 | break; | ||
| 254 | } | ||
| 255 | |||
| 256 | WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers); | ||
| 257 | related.ChainPackageId = this.Facade.Package.WixChainItemId; | ||
| 258 | related.Id = record.GetString(1); | ||
| 259 | related.MinVersion = record.GetString(2); | ||
| 260 | related.MaxVersion = record.GetString(3); | ||
| 261 | related.Languages = record.GetString(4); | ||
| 262 | |||
| 263 | int attributes = record.GetInteger(5); | ||
| 264 | related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect; | ||
| 265 | related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; | ||
| 266 | related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive; | ||
| 267 | related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0; | ||
| 268 | } | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | private void CreateMsiFeatures(Dtf.Database db) | ||
| 275 | { | ||
| 276 | if (db.Tables.Contains("Feature")) | ||
| 277 | { | ||
| 278 | using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) | ||
| 279 | using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) | ||
| 280 | { | ||
| 281 | using (Dtf.Record featureRecord = new Dtf.Record(1)) | ||
| 282 | using (Dtf.Record componentRecord = new Dtf.Record(1)) | ||
| 283 | { | ||
| 284 | using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`")) | ||
| 285 | { | ||
| 286 | allFeaturesView.Execute(); | ||
| 287 | |||
| 288 | while (true) | ||
| 289 | { | ||
| 290 | using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch()) | ||
| 291 | { | ||
| 292 | if (null == allFeaturesResultRecord) | ||
| 293 | { | ||
| 294 | break; | ||
| 295 | } | ||
| 296 | |||
| 297 | string featureName = allFeaturesResultRecord.GetString(1); | ||
| 298 | |||
| 299 | // Calculate the Feature size. | ||
| 300 | featureRecord.SetString(1, featureName); | ||
| 301 | featureView.Execute(featureRecord); | ||
| 302 | |||
| 303 | // Loop over all the components for the feature to calculate the size of the feature. | ||
| 304 | long size = 0; | ||
| 305 | while (true) | ||
| 306 | { | ||
| 307 | using (Dtf.Record componentResultRecord = featureView.Fetch()) | ||
| 308 | { | ||
| 309 | if (null == componentResultRecord) | ||
| 310 | { | ||
| 311 | break; | ||
| 312 | } | ||
| 313 | string component = componentResultRecord.GetString(1); | ||
| 314 | componentRecord.SetString(1, component); | ||
| 315 | componentView.Execute(componentRecord); | ||
| 316 | |||
| 317 | while (true) | ||
| 318 | { | ||
| 319 | using (Dtf.Record fileResultRecord = componentView.Fetch()) | ||
| 320 | { | ||
| 321 | if (null == fileResultRecord) | ||
| 322 | { | ||
| 323 | break; | ||
| 324 | } | ||
| 325 | |||
| 326 | string fileSize = fileResultRecord.GetString(1); | ||
| 327 | size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers); | ||
| 334 | feature.ChainPackageId = this.Facade.Package.WixChainItemId; | ||
| 335 | feature.Name = featureName; | ||
| 336 | feature.Parent = allFeaturesResultRecord.GetString(2); | ||
| 337 | feature.Title = allFeaturesResultRecord.GetString(3); | ||
| 338 | feature.Description = allFeaturesResultRecord.GetString(4); | ||
| 339 | feature.Display = allFeaturesResultRecord.GetInteger(5); | ||
| 340 | feature.Level = allFeaturesResultRecord.GetInteger(6); | ||
| 341 | feature.Directory = allFeaturesResultRecord.GetString(7); | ||
| 342 | feature.Attributes = allFeaturesResultRecord.GetInteger(8); | ||
| 343 | feature.Size = size; | ||
| 344 | } | ||
| 345 | } | ||
| 346 | } | ||
| 347 | } | ||
| 348 | } | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet<string> payloadNames) | ||
| 353 | { | ||
| 354 | if (db.Tables.Contains("Media")) | ||
| 355 | { | ||
| 356 | foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`")) | ||
| 357 | { | ||
| 358 | if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
| 359 | { | ||
| 360 | // If we didn't find the Payload as an existing child of the package, we need to | ||
| 361 | // add it. We expect the file to exist on-disk in the same relative location as | ||
| 362 | // the MSI expects to find it... | ||
| 363 | string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); | ||
| 364 | |||
| 365 | if (!payloadNames.Contains(cabinetName)) | ||
| 366 | { | ||
| 367 | string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet); | ||
| 368 | string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal); | ||
| 369 | |||
| 370 | WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); | ||
| 371 | payload.Id = generatedId; | ||
| 372 | payload.Name = cabinetName; | ||
| 373 | payload.SourceFile = payloadSourceFile; | ||
| 374 | payload.Compressed = packagePayload.Compressed; | ||
| 375 | payload.UnresolvedSourceFile = cabinetName; | ||
| 376 | payload.Package = packagePayload.Package; | ||
| 377 | payload.Container = packagePayload.Container; | ||
| 378 | payload.ContentFile = true; | ||
| 379 | payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; | ||
| 380 | payload.Packaging = packagePayload.Packaging; | ||
| 381 | payload.ParentPackagePayload = packagePayload.Id; | ||
| 382 | } | ||
| 383 | } | ||
| 384 | } | ||
| 385 | } | ||
| 386 | } | ||
| 387 | |||
| 388 | private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames) | ||
| 389 | { | ||
| 390 | long size = 0; | ||
| 391 | |||
| 392 | if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File")) | ||
| 393 | { | ||
| 394 | Hashtable directories = new Hashtable(); | ||
| 395 | |||
| 396 | // Load up the directory hash table so we will be able to resolve source paths | ||
| 397 | // for files in the MSI database. | ||
| 398 | using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) | ||
| 399 | { | ||
| 400 | view.Execute(); | ||
| 401 | while (true) | ||
| 402 | { | ||
| 403 | using (Dtf.Record record = view.Fetch()) | ||
| 404 | { | ||
| 405 | if (null == record) | ||
| 406 | { | ||
| 407 | break; | ||
| 408 | } | ||
| 409 | |||
| 410 | string sourceName = Installer.GetName(record.GetString(3), true, longNamesInImage); | ||
| 411 | directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName)); | ||
| 412 | } | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | // Resolve the source paths to external files and add each file size to the total | ||
| 417 | // install size of the package. | ||
| 418 | using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) | ||
| 419 | { | ||
| 420 | view.Execute(); | ||
| 421 | while (true) | ||
| 422 | { | ||
| 423 | using (Dtf.Record record = view.Fetch()) | ||
| 424 | { | ||
| 425 | if (null == record) | ||
| 426 | { | ||
| 427 | break; | ||
| 428 | } | ||
| 429 | |||
| 430 | // Skip adding the loose files as payloads if it was suppressed. | ||
| 431 | if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration) | ||
| 432 | { | ||
| 433 | // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not | ||
| 434 | // explicitly marked compressed then this is an external file. | ||
| 435 | if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) || | ||
| 436 | (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed))) | ||
| 437 | { | ||
| 438 | string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); | ||
| 439 | string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); | ||
| 440 | |||
| 441 | if (!payloadNames.Contains(name)) | ||
| 442 | { | ||
| 443 | string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2)); | ||
| 444 | string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal); | ||
| 445 | |||
| 446 | WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); | ||
| 447 | payload.Id = generatedId; | ||
| 448 | payload.Name = name; | ||
| 449 | payload.SourceFile = payloadSourceFile; | ||
| 450 | payload.Compressed = packagePayload.Compressed; | ||
| 451 | payload.UnresolvedSourceFile = name; | ||
| 452 | payload.Package = packagePayload.Package; | ||
| 453 | payload.Container = packagePayload.Container; | ||
| 454 | payload.ContentFile = true; | ||
| 455 | payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; | ||
| 456 | payload.Packaging = packagePayload.Packaging; | ||
| 457 | payload.ParentPackagePayload = packagePayload.Id; | ||
| 458 | } | ||
| 459 | } | ||
| 460 | } | ||
| 461 | |||
| 462 | size += record.GetInteger(5); | ||
| 463 | } | ||
| 464 | } | ||
| 465 | } | ||
| 466 | } | ||
| 467 | |||
| 468 | return size; | ||
| 469 | } | ||
| 470 | |||
| 471 | private void AddMsiProperty(string name, string value) | ||
| 472 | { | ||
| 473 | WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers); | ||
| 474 | row.ChainPackageId = this.Facade.Package.WixChainItemId; | ||
| 475 | row.Name = name; | ||
| 476 | row.Value = value; | ||
| 477 | } | ||
| 478 | |||
| 479 | private void ImportDependencyProviders(Dtf.Database db) | ||
| 480 | { | ||
| 481 | if (db.Tables.Contains("WixDependencyProvider")) | ||
| 482 | { | ||
| 483 | string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; | ||
| 484 | |||
| 485 | using (Dtf.View view = db.OpenView(query)) | ||
| 486 | { | ||
| 487 | view.Execute(); | ||
| 488 | while (true) | ||
| 489 | { | ||
| 490 | using (Dtf.Record record = view.Fetch()) | ||
| 491 | { | ||
| 492 | if (null == record) | ||
| 493 | { | ||
| 494 | break; | ||
| 495 | } | ||
| 496 | |||
| 497 | // Import the provider key and attributes. | ||
| 498 | string providerKey = record.GetString(1); | ||
| 499 | string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion; | ||
| 500 | string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName; | ||
| 501 | int attributes = record.GetInteger(4); | ||
| 502 | |||
| 503 | ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes); | ||
| 504 | dependency.Imported = true; | ||
| 505 | |||
| 506 | this.Facade.Provides.Add(dependency); | ||
| 507 | } | ||
| 508 | } | ||
| 509 | } | ||
| 510 | } | ||
| 511 | } | ||
| 512 | |||
| 513 | /// <summary> | ||
| 514 | /// Queries a Windows Installer database for a Property value. | ||
| 515 | /// </summary> | ||
| 516 | /// <param name="db">Database to query.</param> | ||
| 517 | /// <param name="property">Property to examine.</param> | ||
| 518 | /// <returns>String value for result or null if query doesn't match a single result.</returns> | ||
| 519 | private static string GetProperty(Dtf.Database db, string property) | ||
| 520 | { | ||
| 521 | try | ||
| 522 | { | ||
| 523 | return db.ExecuteScalar(PropertyQuery(property)).ToString(); | ||
| 524 | } | ||
| 525 | catch (Dtf.InstallerException) | ||
| 526 | { | ||
| 527 | } | ||
| 528 | |||
| 529 | return null; | ||
| 530 | } | ||
| 531 | |||
| 532 | /// <summary> | ||
| 533 | /// Queries a Windows Installer database to determine if one or more rows exist in the Property table. | ||
| 534 | /// </summary> | ||
| 535 | /// <param name="db">Database to query.</param> | ||
| 536 | /// <param name="property">Property to examine.</param> | ||
| 537 | /// <returns>True if query matches at least one result.</returns> | ||
| 538 | private static bool HasProperty(Dtf.Database db, string property) | ||
| 539 | { | ||
| 540 | try | ||
| 541 | { | ||
| 542 | return 0 < db.ExecuteQuery(PropertyQuery(property)).Count; | ||
| 543 | } | ||
| 544 | catch (Dtf.InstallerException) | ||
| 545 | { | ||
| 546 | } | ||
| 547 | |||
| 548 | return false; | ||
| 549 | } | ||
| 550 | |||
| 551 | private static string PropertyQuery(string property) | ||
| 552 | { | ||
| 553 | // quick sanity check that we'll be creating a valid query... | ||
| 554 | // TODO: Are there any other special characters we should be looking for? | ||
| 555 | Debug.Assert(!property.Contains("'")); | ||
| 556 | |||
| 557 | return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property); | ||
| 558 | } | ||
| 559 | } | ||
| 560 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs deleted file mode 100644 index 24063221..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs +++ /dev/null | |||
| @@ -1,189 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Text; | ||
| 11 | using System.Xml; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | using Dtf = WixToolset.Dtf.WindowsInstaller; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Initializes package state from the Msp contents. | ||
| 18 | /// </summary> | ||
| 19 | internal class ProcessMspPackageCommand : ICommand | ||
| 20 | { | ||
| 21 | private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'"; | ||
| 22 | private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false); | ||
| 23 | |||
| 24 | public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } | ||
| 25 | |||
| 26 | public PackageFacade Facade { private get; set; } | ||
| 27 | |||
| 28 | public Table WixBundlePatchTargetCodeTable { private get; set; } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Processes the Msp packages to add properties and payloads from the Msp packages. | ||
| 32 | /// </summary> | ||
| 33 | public void Execute() | ||
| 34 | { | ||
| 35 | WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); | ||
| 36 | |||
| 37 | string sourcePath = packagePayload.FullFileName; | ||
| 38 | |||
| 39 | try | ||
| 40 | { | ||
| 41 | // Read data out of the msp database... | ||
| 42 | using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) | ||
| 43 | { | ||
| 44 | this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); | ||
| 45 | } | ||
| 46 | |||
| 47 | using (Dtf.Database db = new Dtf.Database(sourcePath)) | ||
| 48 | { | ||
| 49 | if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) | ||
| 50 | { | ||
| 51 | this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName"); | ||
| 52 | } | ||
| 53 | |||
| 54 | if (String.IsNullOrEmpty(this.Facade.Package.Description)) | ||
| 55 | { | ||
| 56 | this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description"); | ||
| 57 | } | ||
| 58 | |||
| 59 | this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName"); | ||
| 60 | } | ||
| 61 | |||
| 62 | this.ProcessPatchXml(packagePayload, sourcePath); | ||
| 63 | } | ||
| 64 | catch (Dtf.InstallerException e) | ||
| 65 | { | ||
| 66 | Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); | ||
| 67 | return; | ||
| 68 | } | ||
| 69 | |||
| 70 | if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) | ||
| 71 | { | ||
| 72 | this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath) | ||
| 77 | { | ||
| 78 | HashSet<string> uniqueTargetCodes = new HashSet<string>(); | ||
| 79 | |||
| 80 | string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath); | ||
| 81 | |||
| 82 | XmlDocument doc = new XmlDocument(); | ||
| 83 | doc.LoadXml(patchXml); | ||
| 84 | |||
| 85 | XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); | ||
| 86 | nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); | ||
| 87 | |||
| 88 | // Determine target ProductCodes and/or UpgradeCodes. | ||
| 89 | foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) | ||
| 90 | { | ||
| 91 | // If this patch targets a product code, this is the best case. | ||
| 92 | XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); | ||
| 93 | WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; | ||
| 94 | |||
| 95 | if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) | ||
| 96 | { | ||
| 97 | attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; | ||
| 98 | } | ||
| 99 | else // maybe targets an upgrade code? | ||
| 100 | { | ||
| 101 | targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr); | ||
| 102 | if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) | ||
| 103 | { | ||
| 104 | attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; | ||
| 105 | } | ||
| 106 | else // this patch targets an unknown number of products | ||
| 107 | { | ||
| 108 | this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | string targetCode = targetCodeElement.InnerText; | ||
| 113 | |||
| 114 | if (uniqueTargetCodes.Add(targetCode)) | ||
| 115 | { | ||
| 116 | WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers); | ||
| 117 | row.MspPackageId = packagePayload.Id; | ||
| 118 | row.TargetCode = targetCode; | ||
| 119 | row.Attributes = attributes; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | // Suppress patch sequence data for improved performance. | ||
| 124 | XmlNode root = doc.DocumentElement; | ||
| 125 | foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) | ||
| 126 | { | ||
| 127 | root.RemoveChild(node); | ||
| 128 | } | ||
| 129 | |||
| 130 | // Save the XML as compact as possible. | ||
| 131 | using (StringWriter writer = new StringWriter()) | ||
| 132 | { | ||
| 133 | XmlWriterSettings settings = new XmlWriterSettings() | ||
| 134 | { | ||
| 135 | Encoding = ProcessMspPackageCommand.XmlOutputEncoding, | ||
| 136 | Indent = false, | ||
| 137 | NewLineChars = string.Empty, | ||
| 138 | NewLineHandling = NewLineHandling.Replace, | ||
| 139 | }; | ||
| 140 | |||
| 141 | using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) | ||
| 142 | { | ||
| 143 | doc.WriteTo(xmlWriter); | ||
| 144 | } | ||
| 145 | |||
| 146 | this.Facade.MspPackage.PatchXml = writer.ToString(); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | /// <summary> | ||
| 151 | /// Queries a Windows Installer patch database for a Property value from the MsiPatchMetadata table. | ||
| 152 | /// </summary> | ||
| 153 | /// <param name="db">Database to query.</param> | ||
| 154 | /// <param name="property">Property to examine.</param> | ||
| 155 | /// <returns>String value for result or null if query doesn't match a single result.</returns> | ||
| 156 | private static string GetPatchMetadataProperty(Dtf.Database db, string property) | ||
| 157 | { | ||
| 158 | try | ||
| 159 | { | ||
| 160 | return db.ExecuteScalar(PatchMetadataPropertyQuery(property)).ToString(); | ||
| 161 | } | ||
| 162 | catch (Dtf.InstallerException) | ||
| 163 | { | ||
| 164 | } | ||
| 165 | |||
| 166 | return null; | ||
| 167 | } | ||
| 168 | |||
| 169 | private static string PatchMetadataPropertyQuery(string property) | ||
| 170 | { | ||
| 171 | // quick sanity check that we'll be creating a valid query... | ||
| 172 | // TODO: Are there any other special characters we should be looking for? | ||
| 173 | Debug.Assert(!property.Contains("'")); | ||
| 174 | |||
| 175 | return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property); | ||
| 176 | } | ||
| 177 | |||
| 178 | private static bool TargetsCode(XmlNode node) | ||
| 179 | { | ||
| 180 | if (null != node) | ||
| 181 | { | ||
| 182 | XmlAttribute attr = node.Attributes["Validate"]; | ||
| 183 | return null != attr && "true".Equals(attr.Value); | ||
| 184 | } | ||
| 185 | |||
| 186 | return false; | ||
| 187 | } | ||
| 188 | } | ||
| 189 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs deleted file mode 100644 index ba59f5f5..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs +++ /dev/null | |||
| @@ -1,30 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Rows; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Processes the Msu packages to add properties and payloads from the Msu packages. | ||
| 11 | /// </summary> | ||
| 12 | internal class ProcessMsuPackageCommand : ICommand | ||
| 13 | { | ||
| 14 | public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } | ||
| 15 | |||
| 16 | public PackageFacade Facade { private get; set; } | ||
| 17 | |||
| 18 | public void Execute() | ||
| 19 | { | ||
| 20 | WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); | ||
| 21 | |||
| 22 | if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) | ||
| 23 | { | ||
| 24 | this.Facade.Package.CacheId = packagePayload.Hash; | ||
| 25 | } | ||
| 26 | |||
| 27 | this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs deleted file mode 100644 index a83a7a4a..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs +++ /dev/null | |||
| @@ -1,159 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.IO; | ||
| 9 | using System.Security.Cryptography; | ||
| 10 | using System.Security.Cryptography.X509Certificates; | ||
| 11 | using System.Text; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | |||
| 15 | internal class ProcessPayloadsCommand : ICommand | ||
| 16 | { | ||
| 17 | private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); | ||
| 18 | |||
| 19 | public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } | ||
| 20 | |||
| 21 | public PackagingType DefaultPackaging { private get; set; } | ||
| 22 | |||
| 23 | public string LayoutDirectory { private get; set; } | ||
| 24 | |||
| 25 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
| 26 | |||
| 27 | public void Execute() | ||
| 28 | { | ||
| 29 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
| 30 | |||
| 31 | foreach (WixBundlePayloadRow payload in this.Payloads) | ||
| 32 | { | ||
| 33 | string normalizedPath = payload.Name.Replace('\\', '/'); | ||
| 34 | if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) | ||
| 35 | { | ||
| 36 | Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name)); | ||
| 37 | } | ||
| 38 | |||
| 39 | // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden | ||
| 40 | // in the .wixlib). | ||
| 41 | ObjectField field = (ObjectField)payload.Fields[2]; | ||
| 42 | payload.ContentFile = !field.EmbeddedFileIndex.HasValue; | ||
| 43 | |||
| 44 | this.UpdatePayloadPackagingType(payload); | ||
| 45 | |||
| 46 | if (String.IsNullOrEmpty(payload.SourceFile)) | ||
| 47 | { | ||
| 48 | // Remote payloads obviously cannot be embedded. | ||
| 49 | Debug.Assert(PackagingType.Embedded != payload.Packaging); | ||
| 50 | } | ||
| 51 | else // not a remote payload so we have a lot more to update. | ||
| 52 | { | ||
| 53 | this.UpdatePayloadFileInformation(payload); | ||
| 54 | |||
| 55 | this.UpdatePayloadVersionInformation(payload); | ||
| 56 | |||
| 57 | // External payloads need to be transfered. | ||
| 58 | if (PackagingType.External == payload.Packaging) | ||
| 59 | { | ||
| 60 | FileTransfer transfer; | ||
| 61 | if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(this.LayoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer)) | ||
| 62 | { | ||
| 63 | fileTransfers.Add(transfer); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | this.FileTransfers = fileTransfers; | ||
| 70 | } | ||
| 71 | |||
| 72 | private void UpdatePayloadPackagingType(WixBundlePayloadRow payload) | ||
| 73 | { | ||
| 74 | if (PackagingType.Unknown == payload.Packaging) | ||
| 75 | { | ||
| 76 | if (YesNoDefaultType.Yes == payload.Compressed) | ||
| 77 | { | ||
| 78 | payload.Packaging = PackagingType.Embedded; | ||
| 79 | } | ||
| 80 | else if (YesNoDefaultType.No == payload.Compressed) | ||
| 81 | { | ||
| 82 | payload.Packaging = PackagingType.External; | ||
| 83 | } | ||
| 84 | else | ||
| 85 | { | ||
| 86 | payload.Packaging = this.DefaultPackaging; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | // Embedded payloads that are not assigned a container already are placed in the default attached | ||
| 91 | // container. | ||
| 92 | if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.Container)) | ||
| 93 | { | ||
| 94 | payload.Container = Compiler.BurnDefaultAttachedContainerId; | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | private void UpdatePayloadFileInformation(WixBundlePayloadRow payload) | ||
| 99 | { | ||
| 100 | FileInfo fileInfo = new FileInfo(payload.SourceFile); | ||
| 101 | |||
| 102 | if (null != fileInfo) | ||
| 103 | { | ||
| 104 | payload.FileSize = (int)fileInfo.Length; | ||
| 105 | |||
| 106 | payload.Hash = Common.GetFileHash(fileInfo.FullName); | ||
| 107 | |||
| 108 | // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation. | ||
| 109 | if (payload.EnableSignatureValidation) | ||
| 110 | { | ||
| 111 | X509Certificate2 certificate = null; | ||
| 112 | try | ||
| 113 | { | ||
| 114 | certificate = new X509Certificate2(fileInfo.FullName); | ||
| 115 | } | ||
| 116 | catch (CryptographicException) // we don't care about non-signed files. | ||
| 117 | { | ||
| 118 | } | ||
| 119 | |||
| 120 | // If there is a certificate, remember its hashed public key identifier and thumbprint. | ||
| 121 | if (null != certificate) | ||
| 122 | { | ||
| 123 | byte[] publicKeyIdentifierHash = new byte[128]; | ||
| 124 | uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length; | ||
| 125 | |||
| 126 | WixToolset.Core.Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize); | ||
| 127 | StringBuilder sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2); | ||
| 128 | for (int i = 0; i < publicKeyIdentifierHashSize; ++i) | ||
| 129 | { | ||
| 130 | sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]); | ||
| 131 | } | ||
| 132 | |||
| 133 | payload.PublicKey = sb.ToString(); | ||
| 134 | payload.Thumbprint = certificate.Thumbprint; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | private void UpdatePayloadVersionInformation(WixBundlePayloadRow payload) | ||
| 141 | { | ||
| 142 | FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(payload.SourceFile); | ||
| 143 | |||
| 144 | if (null != versionInfo) | ||
| 145 | { | ||
| 146 | // Use the fixed version info block for the file since the resource text may not be a dotted quad. | ||
| 147 | Version version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); | ||
| 148 | |||
| 149 | if (ProcessPayloadsCommand.EmptyVersion != version) | ||
| 150 | { | ||
| 151 | payload.Version = version.ToString(); | ||
| 152 | } | ||
| 153 | |||
| 154 | payload.Description = versionInfo.FileDescription; | ||
| 155 | payload.DisplayName = versionInfo.ProductName; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs deleted file mode 100644 index 9c614c26..00000000 --- a/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs +++ /dev/null | |||
| @@ -1,148 +0,0 @@ | |||
| 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.Bind.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Runtime.InteropServices; | ||
| 10 | using System.Text; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Rows; | ||
| 13 | |||
| 14 | internal class VerifyPayloadsWithCatalogCommand : ICommand | ||
| 15 | { | ||
| 16 | public IEnumerable<WixBundleCatalogRow> Catalogs { private get; set; } | ||
| 17 | |||
| 18 | public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } | ||
| 19 | |||
| 20 | public void Execute() | ||
| 21 | { | ||
| 22 | List<CatalogIdWithPath> catalogIdsWithPaths = this.Catalogs | ||
| 23 | .Join(this.Payloads, | ||
| 24 | catalog => catalog.Payload, | ||
| 25 | payload => payload.Id, | ||
| 26 | (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id, FullPath = Path.GetFullPath(payload.SourceFile) }) | ||
| 27 | .ToList(); | ||
| 28 | |||
| 29 | foreach (WixBundlePayloadRow payloadInfo in this.Payloads) | ||
| 30 | { | ||
| 31 | // Payloads that are not embedded should be verfied. | ||
| 32 | if (String.IsNullOrEmpty(payloadInfo.EmbeddedId)) | ||
| 33 | { | ||
| 34 | bool validated = false; | ||
| 35 | |||
| 36 | foreach (CatalogIdWithPath catalog in catalogIdsWithPaths) | ||
| 37 | { | ||
| 38 | if (!validated) | ||
| 39 | { | ||
| 40 | // Get the file hash | ||
| 41 | uint cryptHashSize = 20; | ||
| 42 | byte[] cryptHashBytes = new byte[cryptHashSize]; | ||
| 43 | int error; | ||
| 44 | IntPtr fileHandle = IntPtr.Zero; | ||
| 45 | using (FileStream payloadStream = File.OpenRead(payloadInfo.FullFileName)) | ||
| 46 | { | ||
| 47 | // Get the file handle | ||
| 48 | fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle(); | ||
| 49 | |||
| 50 | // 20 bytes is usually the hash size. Future hashes may be bigger | ||
| 51 | if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) | ||
| 52 | { | ||
| 53 | error = Marshal.GetLastWin32Error(); | ||
| 54 | |||
| 55 | if (VerifyInterop.ErrorInsufficientBuffer == error) | ||
| 56 | { | ||
| 57 | error = 0; | ||
| 58 | cryptHashBytes = new byte[cryptHashSize]; | ||
| 59 | if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) | ||
| 60 | { | ||
| 61 | error = Marshal.GetLastWin32Error(); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | if (0 != error) | ||
| 66 | { | ||
| 67 | Messaging.Instance.OnMessage(WixErrors.CatalogFileHashFailed(payloadInfo.FullFileName, error)); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | VerifyInterop.WinTrustCatalogInfo catalogData = new VerifyInterop.WinTrustCatalogInfo(); | ||
| 73 | VerifyInterop.WinTrustData trustData = new VerifyInterop.WinTrustData(); | ||
| 74 | try | ||
| 75 | { | ||
| 76 | // Create WINTRUST_CATALOG_INFO structure | ||
| 77 | catalogData.cbStruct = (uint)Marshal.SizeOf(catalogData); | ||
| 78 | catalogData.cbCalculatedFileHash = cryptHashSize; | ||
| 79 | catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize); | ||
| 80 | Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize); | ||
| 81 | |||
| 82 | StringBuilder hashString = new StringBuilder(); | ||
| 83 | foreach (byte hashByte in cryptHashBytes) | ||
| 84 | { | ||
| 85 | hashString.Append(hashByte.ToString("X2")); | ||
| 86 | } | ||
| 87 | catalogData.pcwszMemberTag = hashString.ToString(); | ||
| 88 | |||
| 89 | // The file names need to be lower case for older OSes | ||
| 90 | catalogData.pcwszMemberFilePath = payloadInfo.FullFileName.ToLowerInvariant(); | ||
| 91 | catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant(); | ||
| 92 | |||
| 93 | // Create WINTRUST_DATA structure | ||
| 94 | trustData.cbStruct = (uint)Marshal.SizeOf(trustData); | ||
| 95 | trustData.dwUIChoice = VerifyInterop.WTD_UI_NONE; | ||
| 96 | trustData.fdwRevocationChecks = VerifyInterop.WTD_REVOKE_NONE; | ||
| 97 | trustData.dwUnionChoice = VerifyInterop.WTD_CHOICE_CATALOG; | ||
| 98 | trustData.dwStateAction = VerifyInterop.WTD_STATEACTION_VERIFY; | ||
| 99 | trustData.dwProvFlags = VerifyInterop.WTD_REVOCATION_CHECK_NONE; | ||
| 100 | |||
| 101 | // Create the structure pointers for unmanaged | ||
| 102 | trustData.pCatalog = Marshal.AllocCoTaskMem(Marshal.SizeOf(catalogData)); | ||
| 103 | Marshal.StructureToPtr(catalogData, trustData.pCatalog, false); | ||
| 104 | |||
| 105 | // Call WinTrustVerify to validate the file with the catalog | ||
| 106 | IntPtr noWindow = new IntPtr(-1); | ||
| 107 | Guid verifyGuid = new Guid(VerifyInterop.GenericVerify2); | ||
| 108 | long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData); | ||
| 109 | if (0 == verifyResult) | ||
| 110 | { | ||
| 111 | payloadInfo.Catalog = catalog.Id; | ||
| 112 | validated = true; | ||
| 113 | break; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | finally | ||
| 117 | { | ||
| 118 | // Free the structure memory | ||
| 119 | if (IntPtr.Zero != trustData.pCatalog) | ||
| 120 | { | ||
| 121 | Marshal.FreeCoTaskMem(trustData.pCatalog); | ||
| 122 | } | ||
| 123 | |||
| 124 | if (IntPtr.Zero != catalogData.pbCalculatedFileHash) | ||
| 125 | { | ||
| 126 | Marshal.FreeCoTaskMem(catalogData.pbCalculatedFileHash); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | // Error message if the file was not validated by one of the catalogs | ||
| 133 | if (!validated) | ||
| 134 | { | ||
| 135 | Messaging.Instance.OnMessage(WixErrors.CatalogVerificationFailed(payloadInfo.FullFileName)); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | private class CatalogIdWithPath | ||
| 142 | { | ||
| 143 | public string Id { get; set; } | ||
| 144 | |||
| 145 | public string FullPath { get; set; } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs b/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs deleted file mode 100644 index 5e2650e9..00000000 --- a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs +++ /dev/null | |||
| @@ -1,314 +0,0 @@ | |||
| 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.Bind.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.Data.Rows; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. | ||
| 14 | /// </summary> | ||
| 15 | public class AssignMediaCommand : ICommand | ||
| 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/Bind/Databases/BindSummaryInfoCommand.cs b/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs deleted file mode 100644 index 95bd4cf0..00000000 --- a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs +++ /dev/null | |||
| @@ -1,135 +0,0 @@ | |||
| 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.Bind.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 : ICommand | ||
| 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/Bind/Databases/CabinetBuilder.cs b/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs deleted file mode 100644 index 2de6ec25..00000000 --- a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs +++ /dev/null | |||
| @@ -1,176 +0,0 @@ | |||
| 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.Bind.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Threading; | ||
| 10 | using WixToolset.Cab; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Rows; | ||
| 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 | public int MaximumUncompressedMediaSize { get; set; } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Instantiate a new CabinetBuilder. | ||
| 32 | /// </summary> | ||
| 33 | /// <param name="threadCount">number of threads to use</param> | ||
| 34 | /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param> | ||
| 35 | public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress) | ||
| 36 | { | ||
| 37 | if (0 >= threadCount) | ||
| 38 | { | ||
| 39 | throw new ArgumentOutOfRangeException("threadCount"); | ||
| 40 | } | ||
| 41 | |||
| 42 | this.cabinetWorkItems = new Queue(); | ||
| 43 | this.lockObject = new object(); | ||
| 44 | |||
| 45 | this.threadCount = threadCount; | ||
| 46 | |||
| 47 | // Set Address of Binder's callback function for Cabinet Splitting | ||
| 48 | this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; | ||
| 49 | } | ||
| 50 | |||
| 51 | /// <summary> | ||
| 52 | /// Enqueues a CabinetWorkItem to the queue. | ||
| 53 | /// </summary> | ||
| 54 | /// <param name="cabinetWorkItem">cabinet work item</param> | ||
| 55 | public void Enqueue(CabinetWorkItem cabinetWorkItem) | ||
| 56 | { | ||
| 57 | this.cabinetWorkItems.Enqueue(cabinetWorkItem); | ||
| 58 | } | ||
| 59 | |||
| 60 | /// <summary> | ||
| 61 | /// Create the queued cabinets. | ||
| 62 | /// </summary> | ||
| 63 | /// <returns>error message number (zero if no error)</returns> | ||
| 64 | public void CreateQueuedCabinets() | ||
| 65 | { | ||
| 66 | // don't create more threads than the number of cabinets to build | ||
| 67 | if (this.cabinetWorkItems.Count < this.threadCount) | ||
| 68 | { | ||
| 69 | this.threadCount = this.cabinetWorkItems.Count; | ||
| 70 | } | ||
| 71 | |||
| 72 | if (0 < this.threadCount) | ||
| 73 | { | ||
| 74 | Thread[] threads = new Thread[this.threadCount]; | ||
| 75 | |||
| 76 | for (int i = 0; i < threads.Length; i++) | ||
| 77 | { | ||
| 78 | threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); | ||
| 79 | threads[i].Start(); | ||
| 80 | } | ||
| 81 | |||
| 82 | // wait for all threads to finish | ||
| 83 | foreach (Thread thread in threads) | ||
| 84 | { | ||
| 85 | thread.Join(); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | /// <summary> | ||
| 91 | /// This function gets called by multiple threads to do actual work. | ||
| 92 | /// It takes one work item at a time and calls this.CreateCabinet(). | ||
| 93 | /// It does not return until cabinetWorkItems queue is empty | ||
| 94 | /// </summary> | ||
| 95 | private void ProcessWorkItems() | ||
| 96 | { | ||
| 97 | try | ||
| 98 | { | ||
| 99 | while (true) | ||
| 100 | { | ||
| 101 | CabinetWorkItem cabinetWorkItem; | ||
| 102 | |||
| 103 | lock (this.cabinetWorkItems) | ||
| 104 | { | ||
| 105 | // check if there are any more cabinets to create | ||
| 106 | if (0 == this.cabinetWorkItems.Count) | ||
| 107 | { | ||
| 108 | break; | ||
| 109 | } | ||
| 110 | |||
| 111 | cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue(); | ||
| 112 | } | ||
| 113 | |||
| 114 | // create a cabinet | ||
| 115 | this.CreateCabinet(cabinetWorkItem); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | catch (WixException we) | ||
| 119 | { | ||
| 120 | Messaging.Instance.OnMessage(we.Error); | ||
| 121 | } | ||
| 122 | catch (Exception e) | ||
| 123 | { | ||
| 124 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace)); | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | /// <summary> | ||
| 129 | /// Creates a cabinet using the wixcab.dll interop layer. | ||
| 130 | /// </summary> | ||
| 131 | /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param> | ||
| 132 | private void CreateCabinet(CabinetWorkItem cabinetWorkItem) | ||
| 133 | { | ||
| 134 | Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile)); | ||
| 135 | |||
| 136 | int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting | ||
| 137 | ulong maxPreCompressedSizeInBytes = 0; | ||
| 138 | |||
| 139 | if (MaximumCabinetSizeForLargeFileSplitting != 0) | ||
| 140 | { | ||
| 141 | // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize | ||
| 142 | // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file | ||
| 143 | if (1 == cabinetWorkItem.FileFacades.Count()) | ||
| 144 | { | ||
| 145 | // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs | ||
| 146 | // Get the Value for Max Uncompressed Media Size | ||
| 147 | maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024; | ||
| 148 | |||
| 149 | foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row | ||
| 150 | { | ||
| 151 | if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes) | ||
| 152 | { | ||
| 153 | // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting | ||
| 154 | maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | // create the cabinet file | ||
| 161 | string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile); | ||
| 162 | string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile); | ||
| 163 | |||
| 164 | using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel)) | ||
| 165 | { | ||
| 166 | foreach (FileFacade facade in cabinetWorkItem.FileFacades) | ||
| 167 | { | ||
| 168 | cab.AddFile(facade); | ||
| 169 | } | ||
| 170 | |||
| 171 | cab.Complete(newCabNamesCallBackAddress); | ||
| 172 | } | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs b/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs deleted file mode 100644 index 20241bc9..00000000 --- a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs +++ /dev/null | |||
| @@ -1,78 +0,0 @@ | |||
| 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.Bind.Databases | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Rows; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// A cabinet builder work item. | ||
| 11 | /// </summary> | ||
| 12 | internal sealed class CabinetWorkItem | ||
| 13 | { | ||
| 14 | private string cabinetFile; | ||
| 15 | private CompressionLevel compressionLevel; | ||
| 16 | //private BinderFileManager binderFileManager; | ||
| 17 | private int maxThreshold; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Instantiate a new CabinetWorkItem. | ||
| 21 | /// </summary> | ||
| 22 | /// <param name="fileFacades">The collection of files in this cabinet.</param> | ||
| 23 | /// <param name="cabinetFile">The cabinet file.</param> | ||
| 24 | /// <param name="maxThreshold">Maximum threshold for each cabinet.</param> | ||
| 25 | /// <param name="compressionLevel">The compression level of the cabinet.</param> | ||
| 26 | /// <param name="binderFileManager">The binder file manager.</param> | ||
| 27 | public CabinetWorkItem(IEnumerable<FileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/) | ||
| 28 | { | ||
| 29 | this.cabinetFile = cabinetFile; | ||
| 30 | this.compressionLevel = compressionLevel; | ||
| 31 | this.FileFacades = fileFacades; | ||
| 32 | //this.binderFileManager = binderFileManager; | ||
| 33 | this.maxThreshold = maxThreshold; | ||
| 34 | } | ||
| 35 | |||
| 36 | /// <summary> | ||
| 37 | /// Gets the cabinet file. | ||
| 38 | /// </summary> | ||
| 39 | /// <value>The cabinet file.</value> | ||
| 40 | public string CabinetFile | ||
| 41 | { | ||
| 42 | get { return this.cabinetFile; } | ||
| 43 | } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Gets the compression level of the cabinet. | ||
| 47 | /// </summary> | ||
| 48 | /// <value>The compression level of the cabinet.</value> | ||
| 49 | public CompressionLevel CompressionLevel | ||
| 50 | { | ||
| 51 | get { return this.compressionLevel; } | ||
| 52 | } | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Gets the collection of files in this cabinet. | ||
| 56 | /// </summary> | ||
| 57 | /// <value>The collection of files in this cabinet.</value> | ||
| 58 | public IEnumerable<FileFacade> FileFacades { get; private set; } | ||
| 59 | |||
| 60 | /// <summary> | ||
| 61 | /// Gets the binder file manager. | ||
| 62 | /// </summary> | ||
| 63 | /// <value>The binder file manager.</value> | ||
| 64 | //public BinderFileManager BinderFileManager | ||
| 65 | //{ | ||
| 66 | // get { return this.binderFileManager; } | ||
| 67 | //} | ||
| 68 | |||
| 69 | /// <summary> | ||
| 70 | /// Gets the max threshold. | ||
| 71 | /// </summary> | ||
| 72 | /// <value>The maximum threshold for a folder in a cabinet.</value> | ||
| 73 | public int MaxThreshold | ||
| 74 | { | ||
| 75 | get { return this.maxThreshold; } | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs b/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs deleted file mode 100644 index 7cb18e0f..00000000 --- a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs +++ /dev/null | |||
| @@ -1,91 +0,0 @@ | |||
| 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.Bind.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/Bind/Databases/CopyTransformDataCommand.cs b/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs deleted file mode 100644 index af1ab3b0..00000000 --- a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs +++ /dev/null | |||
| @@ -1,606 +0,0 @@ | |||
| 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.Bind.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 | |||
| 13 | internal class CopyTransformDataCommand : ICommand | ||
| 14 | { | ||
| 15 | public bool CopyOutFileRows { private get; set; } | ||
| 16 | |||
| 17 | public BinderFileManagerCore FileManagerCore { private get; set; } | ||
| 18 | |||
| 19 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | ||
| 20 | |||
| 21 | public Output Output { private get; set; } | ||
| 22 | |||
| 23 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 24 | |||
| 25 | public IEnumerable<FileFacade> FileFacades { get; private set; } | ||
| 26 | |||
| 27 | public void Execute() | ||
| 28 | { | ||
| 29 | Debug.Assert(OutputType.Patch != this.Output.Type); | ||
| 30 | |||
| 31 | List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null; | ||
| 32 | |||
| 33 | #if false // TODO: Fix this patching related code to work correctly with FileFacades. | ||
| 34 | bool copyToPatch = (allFileRows != null); | ||
| 35 | bool copyFromPatch = !copyToPatch; | ||
| 36 | |||
| 37 | RowDictionary<MediaRow> patchMediaRows = new RowDictionary<MediaRow>(); | ||
| 38 | |||
| 39 | Dictionary<int, RowDictionary<WixFileRow>> patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>(); | ||
| 40 | |||
| 41 | Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); | ||
| 42 | Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); | ||
| 43 | |||
| 44 | if (copyFromPatch) | ||
| 45 | { | ||
| 46 | // index patch files by diskId+fileId | ||
| 47 | foreach (WixFileRow patchFileRow in patchFileTable.Rows) | ||
| 48 | { | ||
| 49 | int diskId = patchFileRow.DiskId; | ||
| 50 | RowDictionary<WixFileRow> mediaFileRows; | ||
| 51 | if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) | ||
| 52 | { | ||
| 53 | mediaFileRows = new RowDictionary<WixFileRow>(); | ||
| 54 | patchMediaFileRows.Add(diskId, mediaFileRows); | ||
| 55 | } | ||
| 56 | |||
| 57 | mediaFileRows.Add(patchFileRow); | ||
| 58 | } | ||
| 59 | |||
| 60 | Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); | ||
| 61 | patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable); | ||
| 62 | } | ||
| 63 | |||
| 64 | // index paired transforms | ||
| 65 | Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>(); | ||
| 66 | foreach (SubStorage substorage in this.Output.SubStorages) | ||
| 67 | { | ||
| 68 | if (substorage.Name.StartsWith("#")) | ||
| 69 | { | ||
| 70 | pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | try | ||
| 75 | { | ||
| 76 | // copy File bind data into substorages | ||
| 77 | foreach (SubStorage substorage in this.Output.SubStorages) | ||
| 78 | { | ||
| 79 | if (substorage.Name.StartsWith("#")) | ||
| 80 | { | ||
| 81 | // no changes necessary for paired transforms | ||
| 82 | continue; | ||
| 83 | } | ||
| 84 | |||
| 85 | Output mainTransform = substorage.Data; | ||
| 86 | Table mainWixFileTable = mainTransform.Tables["WixFile"]; | ||
| 87 | Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"]; | ||
| 88 | |||
| 89 | this.FileManagerCore.ActiveSubStorage = substorage; | ||
| 90 | |||
| 91 | RowDictionary<WixFileRow> mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable); | ||
| 92 | RowDictionary<Row> mainMsiFileHashIndex = new RowDictionary<Row>(); | ||
| 93 | |||
| 94 | Table mainFileTable = mainTransform.Tables["File"]; | ||
| 95 | Output pairedTransform = (Output)pairedTransforms[substorage.Name]; | ||
| 96 | |||
| 97 | // copy Media.LastSequence and index the MsiFileHash table if it exists. | ||
| 98 | if (copyFromPatch) | ||
| 99 | { | ||
| 100 | Table pairedMediaTable = pairedTransform.Tables["Media"]; | ||
| 101 | foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) | ||
| 102 | { | ||
| 103 | MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); | ||
| 104 | pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; | ||
| 105 | } | ||
| 106 | |||
| 107 | if (null != mainMsiFileHashTable) | ||
| 108 | { | ||
| 109 | mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable); | ||
| 110 | } | ||
| 111 | |||
| 112 | // Validate file row changes for keypath-related issues | ||
| 113 | this.ValidateFileRowChanges(mainTransform); | ||
| 114 | } | ||
| 115 | |||
| 116 | // Index File table of pairedTransform | ||
| 117 | Table pairedFileTable = pairedTransform.Tables["File"]; | ||
| 118 | RowDictionary<FileRow> pairedFileRows = new RowDictionary<FileRow>(pairedFileTable); | ||
| 119 | |||
| 120 | if (null != mainFileTable) | ||
| 121 | { | ||
| 122 | if (copyFromPatch) | ||
| 123 | { | ||
| 124 | // Remove the MsiFileHash table because it will be updated later with the final file hash for each file | ||
| 125 | mainTransform.Tables.Remove("MsiFileHash"); | ||
| 126 | } | ||
| 127 | |||
| 128 | foreach (FileRow mainFileRow in mainFileTable.Rows) | ||
| 129 | { | ||
| 130 | if (RowOperation.Delete == mainFileRow.Operation) | ||
| 131 | { | ||
| 132 | continue; | ||
| 133 | } | ||
| 134 | else if (RowOperation.None == mainFileRow.Operation && !copyToPatch) | ||
| 135 | { | ||
| 136 | continue; | ||
| 137 | } | ||
| 138 | |||
| 139 | WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File); | ||
| 140 | |||
| 141 | if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes. | ||
| 142 | { | ||
| 143 | ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6]; | ||
| 144 | FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File); | ||
| 145 | |||
| 146 | // If the file is new, we always need to add it to the patch. | ||
| 147 | if (mainFileRow.Operation != RowOperation.Add) | ||
| 148 | { | ||
| 149 | // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. | ||
| 150 | if (null == objectField.PreviousData) | ||
| 151 | { | ||
| 152 | if (mainFileRow.Operation == RowOperation.None) | ||
| 153 | { | ||
| 154 | continue; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | else | ||
| 158 | { | ||
| 159 | // TODO: should this entire condition be placed in the binder file manager? | ||
| 160 | if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) && | ||
| 161 | !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) | ||
| 162 | { | ||
| 163 | // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. | ||
| 164 | mainFileRow.Operation = RowOperation.Modify; | ||
| 165 | if (null != pairedFileRow) | ||
| 166 | { | ||
| 167 | // Always patch-added, but never non-compressed. | ||
| 168 | pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; | ||
| 169 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 170 | pairedFileRow.Fields[6].Modified = true; | ||
| 171 | pairedFileRow.Operation = RowOperation.Modify; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | else | ||
| 175 | { | ||
| 176 | // The File is same. We need mark all the attributes as unchanged. | ||
| 177 | mainFileRow.Operation = RowOperation.None; | ||
| 178 | foreach (Field field in mainFileRow.Fields) | ||
| 179 | { | ||
| 180 | field.Modified = false; | ||
| 181 | } | ||
| 182 | |||
| 183 | if (null != pairedFileRow) | ||
| 184 | { | ||
| 185 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded; | ||
| 186 | pairedFileRow.Fields[6].Modified = false; | ||
| 187 | pairedFileRow.Operation = RowOperation.None; | ||
| 188 | } | ||
| 189 | continue; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | else if (null != pairedFileRow) // RowOperation.Add | ||
| 194 | { | ||
| 195 | // Always patch-added, but never non-compressed. | ||
| 196 | pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; | ||
| 197 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 198 | pairedFileRow.Fields[6].Modified = true; | ||
| 199 | pairedFileRow.Operation = RowOperation.Add; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | // index patch files by diskId+fileId | ||
| 204 | int diskId = mainWixFileRow.DiskId; | ||
| 205 | |||
| 206 | RowDictionary<WixFileRow> mediaFileRows; | ||
| 207 | if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) | ||
| 208 | { | ||
| 209 | mediaFileRows = new RowDictionary<WixFileRow>(); | ||
| 210 | patchMediaFileRows.Add(diskId, mediaFileRows); | ||
| 211 | } | ||
| 212 | |||
| 213 | string fileId = mainFileRow.File; | ||
| 214 | WixFileRow patchFileRow = mediaFileRows.Get(fileId); | ||
| 215 | if (copyToPatch) | ||
| 216 | { | ||
| 217 | if (null == patchFileRow) | ||
| 218 | { | ||
| 219 | FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 220 | patchActualFileRow.CopyFrom(mainFileRow); | ||
| 221 | |||
| 222 | patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 223 | patchFileRow.CopyFrom(mainWixFileRow); | ||
| 224 | |||
| 225 | mediaFileRows.Add(patchFileRow); | ||
| 226 | |||
| 227 | allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right? | ||
| 228 | } | ||
| 229 | else | ||
| 230 | { | ||
| 231 | // TODO: confirm the rest of data is identical? | ||
| 232 | |||
| 233 | // make sure Source is same. Otherwise we are silently ignoring a file. | ||
| 234 | if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase)) | ||
| 235 | { | ||
| 236 | Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); | ||
| 237 | } | ||
| 238 | |||
| 239 | // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. | ||
| 240 | patchFileRow.AppendPreviousDataFrom(mainWixFileRow); | ||
| 241 | } | ||
| 242 | } | ||
| 243 | else | ||
| 244 | { | ||
| 245 | // copy data from the patch back to the transform | ||
| 246 | if (null != patchFileRow) | ||
| 247 | { | ||
| 248 | FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId); | ||
| 249 | for (int i = 0; i < patchFileRow.Fields.Length; i++) | ||
| 250 | { | ||
| 251 | string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString(); | ||
| 252 | string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString(); | ||
| 253 | |||
| 254 | if (1 == i) | ||
| 255 | { | ||
| 256 | // File.Component_ changes should not come from the shared file rows | ||
| 257 | // that contain the file information as each individual transform might | ||
| 258 | // have different changes (or no changes at all). | ||
| 259 | } | ||
| 260 | // File.Attributes should not changed for binary deltas | ||
| 261 | else if (6 == i) | ||
| 262 | { | ||
| 263 | if (null != patchFileRow.Patch) | ||
| 264 | { | ||
| 265 | // File.Attribute should not change for binary deltas | ||
| 266 | pairedFileRow.Attributes = mainFileRow.Attributes; | ||
| 267 | mainFileRow.Fields[i].Modified = false; | ||
| 268 | } | ||
| 269 | } | ||
| 270 | // File.Sequence is updated in pairedTransform, not mainTransform | ||
| 271 | else if (7 == i) | ||
| 272 | { | ||
| 273 | // file sequence is updated in Patch table instead of File table for delta patches | ||
| 274 | if (null != patchFileRow.Patch) | ||
| 275 | { | ||
| 276 | pairedFileRow.Fields[i].Modified = false; | ||
| 277 | } | ||
| 278 | else | ||
| 279 | { | ||
| 280 | pairedFileRow[i] = patchFileRow[i]; | ||
| 281 | pairedFileRow.Fields[i].Modified = true; | ||
| 282 | } | ||
| 283 | mainFileRow.Fields[i].Modified = false; | ||
| 284 | } | ||
| 285 | else if (patchValue != mainValue) | ||
| 286 | { | ||
| 287 | mainFileRow[i] = patchFileRow[i]; | ||
| 288 | mainFileRow.Fields[i].Modified = true; | ||
| 289 | if (mainFileRow.Operation == RowOperation.None) | ||
| 290 | { | ||
| 291 | mainFileRow.Operation = RowOperation.Modify; | ||
| 292 | } | ||
| 293 | } | ||
| 294 | } | ||
| 295 | |||
| 296 | // copy MsiFileHash row for this File | ||
| 297 | Row patchHashRow; | ||
| 298 | if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow)) | ||
| 299 | { | ||
| 300 | patchHashRow = patchFileRow.Hash; | ||
| 301 | } | ||
| 302 | |||
| 303 | if (null != patchHashRow) | ||
| 304 | { | ||
| 305 | Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); | ||
| 306 | Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 307 | for (int i = 0; i < patchHashRow.Fields.Length; i++) | ||
| 308 | { | ||
| 309 | mainHashRow[i] = patchHashRow[i]; | ||
| 310 | if (i > 1) | ||
| 311 | { | ||
| 312 | // assume all hash fields have been modified | ||
| 313 | mainHashRow.Fields[i].Modified = true; | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | // assume the MsiFileHash operation follows the File one | ||
| 318 | mainHashRow.Operation = mainFileRow.Operation; | ||
| 319 | } | ||
| 320 | |||
| 321 | // copy MsiAssemblyName rows for this File | ||
| 322 | List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames; | ||
| 323 | if (null != patchAssemblyNameRows) | ||
| 324 | { | ||
| 325 | Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
| 326 | foreach (Row patchAssemblyNameRow in patchAssemblyNameRows) | ||
| 327 | { | ||
| 328 | // Copy if there isn't an identical modified/added row already in the transform. | ||
| 329 | bool foundMatchingModifiedRow = false; | ||
| 330 | foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows) | ||
| 331 | { | ||
| 332 | if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) | ||
| 333 | { | ||
| 334 | foundMatchingModifiedRow = true; | ||
| 335 | break; | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | if (!foundMatchingModifiedRow) | ||
| 340 | { | ||
| 341 | Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 342 | for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++) | ||
| 343 | { | ||
| 344 | mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; | ||
| 345 | } | ||
| 346 | |||
| 347 | // assume value field has been modified | ||
| 348 | mainAssemblyNameRow.Fields[2].Modified = true; | ||
| 349 | mainAssemblyNameRow.Operation = mainFileRow.Operation; | ||
| 350 | } | ||
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 354 | // Add patch header for this file | ||
| 355 | if (null != patchFileRow.Patch) | ||
| 356 | { | ||
| 357 | // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. | ||
| 358 | AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); | ||
| 359 | AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); | ||
| 360 | |||
| 361 | // Add to Patch table | ||
| 362 | Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); | ||
| 363 | if (0 == patchTable.Rows.Count) | ||
| 364 | { | ||
| 365 | patchTable.Operation = TableOperation.Add; | ||
| 366 | } | ||
| 367 | |||
| 368 | Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 369 | patchRow[0] = patchFileRow.File; | ||
| 370 | patchRow[1] = patchFileRow.Sequence; | ||
| 371 | |||
| 372 | FileInfo patchFile = new FileInfo(patchFileRow.Source); | ||
| 373 | patchRow[2] = (int)patchFile.Length; | ||
| 374 | patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; | ||
| 375 | |||
| 376 | string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; | ||
| 377 | if (MsiInterop.MsiMaxStreamNameLength < streamName.Length) | ||
| 378 | { | ||
| 379 | streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); | ||
| 380 | Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); | ||
| 381 | if (0 == patchHeadersTable.Rows.Count) | ||
| 382 | { | ||
| 383 | patchHeadersTable.Operation = TableOperation.Add; | ||
| 384 | } | ||
| 385 | Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 386 | patchHeadersRow[0] = streamName; | ||
| 387 | patchHeadersRow[1] = patchFileRow.Patch; | ||
| 388 | patchRow[5] = streamName; | ||
| 389 | patchHeadersRow.Operation = RowOperation.Add; | ||
| 390 | } | ||
| 391 | else | ||
| 392 | { | ||
| 393 | patchRow[4] = patchFileRow.Patch; | ||
| 394 | } | ||
| 395 | patchRow.Operation = RowOperation.Add; | ||
| 396 | } | ||
| 397 | } | ||
| 398 | else | ||
| 399 | { | ||
| 400 | // TODO: throw because all transform rows should have made it into the patch | ||
| 401 | } | ||
| 402 | } | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | if (copyFromPatch) | ||
| 407 | { | ||
| 408 | this.Output.Tables.Remove("Media"); | ||
| 409 | this.Output.Tables.Remove("File"); | ||
| 410 | this.Output.Tables.Remove("MsiFileHash"); | ||
| 411 | this.Output.Tables.Remove("MsiAssemblyName"); | ||
| 412 | } | ||
| 413 | } | ||
| 414 | } | ||
| 415 | finally | ||
| 416 | { | ||
| 417 | this.FileManagerCore.ActiveSubStorage = null; | ||
| 418 | } | ||
| 419 | #endif | ||
| 420 | this.FileFacades = allFileRows; | ||
| 421 | } | ||
| 422 | |||
| 423 | /// <summary> | ||
| 424 | /// Adds the PatchFiles action to the sequence table if it does not already exist. | ||
| 425 | /// </summary> | ||
| 426 | /// <param name="table">The sequence table to check or modify.</param> | ||
| 427 | /// <param name="mainTransform">The primary authoring transform.</param> | ||
| 428 | /// <param name="pairedTransform">The secondary patch transform.</param> | ||
| 429 | /// <param name="mainFileRow">The file row that contains information about the patched file.</param> | ||
| 430 | private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow) | ||
| 431 | { | ||
| 432 | // Find/add PatchFiles action (also determine sequence for it). | ||
| 433 | // Search mainTransform first, then pairedTransform (pairedTransform overrides). | ||
| 434 | bool hasPatchFilesAction = false; | ||
| 435 | int seqInstallFiles = 0; | ||
| 436 | int seqDuplicateFiles = 0; | ||
| 437 | string tableName = table.ToString(); | ||
| 438 | |||
| 439 | TestSequenceTableForPatchFilesAction( | ||
| 440 | mainTransform.Tables[tableName], | ||
| 441 | ref hasPatchFilesAction, | ||
| 442 | ref seqInstallFiles, | ||
| 443 | ref seqDuplicateFiles); | ||
| 444 | TestSequenceTableForPatchFilesAction( | ||
| 445 | pairedTransform.Tables[tableName], | ||
| 446 | ref hasPatchFilesAction, | ||
| 447 | ref seqInstallFiles, | ||
| 448 | ref seqDuplicateFiles); | ||
| 449 | if (!hasPatchFilesAction) | ||
| 450 | { | ||
| 451 | Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); | ||
| 452 | if (0 == iesTable.Rows.Count) | ||
| 453 | { | ||
| 454 | iesTable.Operation = TableOperation.Add; | ||
| 455 | } | ||
| 456 | |||
| 457 | Row patchAction = iesTable.CreateRow(null); | ||
| 458 | WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"]; | ||
| 459 | int sequence = wixPatchAction.Sequence; | ||
| 460 | // Test for default sequence value's appropriateness | ||
| 461 | if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence)) | ||
| 462 | { | ||
| 463 | if (0 != seqDuplicateFiles) | ||
| 464 | { | ||
| 465 | if (seqDuplicateFiles < seqInstallFiles) | ||
| 466 | { | ||
| 467 | throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); | ||
| 468 | } | ||
| 469 | else | ||
| 470 | { | ||
| 471 | sequence = (seqDuplicateFiles + seqInstallFiles) / 2; | ||
| 472 | if (seqInstallFiles == sequence || seqDuplicateFiles == sequence) | ||
| 473 | { | ||
| 474 | throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); | ||
| 475 | } | ||
| 476 | } | ||
| 477 | } | ||
| 478 | else | ||
| 479 | { | ||
| 480 | sequence = seqInstallFiles + 1; | ||
| 481 | } | ||
| 482 | } | ||
| 483 | patchAction[0] = wixPatchAction.Action; | ||
| 484 | patchAction[1] = wixPatchAction.Condition; | ||
| 485 | patchAction[2] = sequence; | ||
| 486 | patchAction.Operation = RowOperation.Add; | ||
| 487 | } | ||
| 488 | } | ||
| 489 | |||
| 490 | /// <summary> | ||
| 491 | /// Tests sequence table for PatchFiles and associated actions | ||
| 492 | /// </summary> | ||
| 493 | /// <param name="iesTable">The table to test.</param> | ||
| 494 | /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param> | ||
| 495 | /// <param name="seqInstallFiles">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param> | ||
| 496 | /// <param name="seqDuplicateFiles">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param> | ||
| 497 | private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles) | ||
| 498 | { | ||
| 499 | if (null != iesTable) | ||
| 500 | { | ||
| 501 | foreach (Row iesRow in iesTable.Rows) | ||
| 502 | { | ||
| 503 | if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
| 504 | { | ||
| 505 | hasPatchFilesAction = true; | ||
| 506 | } | ||
| 507 | if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
| 508 | { | ||
| 509 | seqInstallFiles = (int)iesRow.Fields[2].Data; | ||
| 510 | } | ||
| 511 | if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
| 512 | { | ||
| 513 | seqDuplicateFiles = (int)iesRow.Fields[2].Data; | ||
| 514 | } | ||
| 515 | } | ||
| 516 | } | ||
| 517 | } | ||
| 518 | |||
| 519 | /// <summary> | ||
| 520 | /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. | ||
| 521 | /// </summary> | ||
| 522 | /// <param name="output">The output to validate.</param> | ||
| 523 | private void ValidateFileRowChanges(Output transform) | ||
| 524 | { | ||
| 525 | Table componentTable = transform.Tables["Component"]; | ||
| 526 | Table fileTable = transform.Tables["File"]; | ||
| 527 | |||
| 528 | // There's no sense validating keypaths if the transform has no component or file table | ||
| 529 | if (componentTable == null || fileTable == null) | ||
| 530 | { | ||
| 531 | return; | ||
| 532 | } | ||
| 533 | |||
| 534 | Dictionary<string, string> componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count); | ||
| 535 | |||
| 536 | // Index the Component table for non-directory & non-registry key paths. | ||
| 537 | foreach (Row row in componentTable.Rows) | ||
| 538 | { | ||
| 539 | if (null != row.Fields[5].Data && | ||
| 540 | 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath)) | ||
| 541 | { | ||
| 542 | componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString()); | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | Dictionary<string, string> componentWithChangedKeyPath = new Dictionary<string, string>(); | ||
| 547 | Dictionary<string, string> componentWithNonKeyPathChanged = new Dictionary<string, string>(); | ||
| 548 | // Verify changes in the file table, now that file diffing has occurred | ||
| 549 | foreach (FileRow row in fileTable.Rows) | ||
| 550 | { | ||
| 551 | string fileId = row.Fields[0].Data.ToString(); | ||
| 552 | string componentId = row.Fields[1].Data.ToString(); | ||
| 553 | |||
| 554 | if (RowOperation.Modify != row.Operation) | ||
| 555 | { | ||
| 556 | continue; | ||
| 557 | } | ||
| 558 | |||
| 559 | // If this file is the keypath of a component | ||
| 560 | if (componentKeyPath.ContainsValue(fileId)) | ||
| 561 | { | ||
| 562 | if (!componentWithChangedKeyPath.ContainsKey(componentId)) | ||
| 563 | { | ||
| 564 | componentWithChangedKeyPath.Add(componentId, fileId); | ||
| 565 | } | ||
| 566 | } | ||
| 567 | else | ||
| 568 | { | ||
| 569 | if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) | ||
| 570 | { | ||
| 571 | componentWithNonKeyPathChanged.Add(componentId, fileId); | ||
| 572 | } | ||
| 573 | } | ||
| 574 | } | ||
| 575 | |||
| 576 | foreach (KeyValuePair<string, string> componentFile in componentWithNonKeyPathChanged) | ||
| 577 | { | ||
| 578 | // Make sure all changes to non keypath files also had a change in the keypath. | ||
| 579 | if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key)) | ||
| 580 | { | ||
| 581 | Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key])); | ||
| 582 | } | ||
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | private bool CompareFiles(string targetFile, string updatedFile) | ||
| 587 | { | ||
| 588 | bool? compared = null; | ||
| 589 | foreach (IBinderFileManager fileManager in this.FileManagers) | ||
| 590 | { | ||
| 591 | compared = fileManager.CompareFiles(targetFile, updatedFile); | ||
| 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/Bind/Databases/CreateCabinetsCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs deleted file mode 100644 index 35c8abb4..00000000 --- a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs +++ /dev/null | |||
| @@ -1,489 +0,0 @@ | |||
| 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.Bind.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.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Creates cabinet files. | ||
| 18 | /// </summary> | ||
| 19 | internal class CreateCabinetsCommand : ICommand | ||
| 20 | { | ||
| 21 | private List<FileTransfer> fileTransfers; | ||
| 22 | |||
| 23 | private FileSplitCabNamesCallback newCabNamesCallBack; | ||
| 24 | |||
| 25 | private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence | ||
| 26 | |||
| 27 | public CreateCabinetsCommand() | ||
| 28 | { | ||
| 29 | this.fileTransfers = new List<FileTransfer>(); | ||
| 30 | |||
| 31 | this.newCabNamesCallBack = NewCabNamesCallBack; | ||
| 32 | } | ||
| 33 | |||
| 34 | /// <summary> | ||
| 35 | /// Sets the number of threads to use for cabinet creation. | ||
| 36 | /// </summary> | ||
| 37 | public int CabbingThreadCount { private get; set; } | ||
| 38 | |||
| 39 | public string TempFilesLocation { private get; set; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Sets the default compression level to use for cabinets | ||
| 43 | /// that don't have their compression level explicitly set. | ||
| 44 | /// </summary> | ||
| 45 | public CompressionLevel DefaultCompressionLevel { private get; set; } | ||
| 46 | |||
| 47 | public Output Output { private get; set; } | ||
| 48 | |||
| 49 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | ||
| 50 | |||
| 51 | public string LayoutDirectory { private get; set; } | ||
| 52 | |||
| 53 | public bool Compressed { private get; set; } | ||
| 54 | |||
| 55 | public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; } | ||
| 56 | |||
| 57 | public Func<MediaRow, string, string, string> ResolveMedia { private get; set; } | ||
| 58 | |||
| 59 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 60 | |||
| 61 | public Table WixMediaTable { private get; set; } | ||
| 62 | |||
| 63 | public IEnumerable<FileTransfer> FileTransfers { get { return this.fileTransfers; } } | ||
| 64 | |||
| 65 | /// <param name="output">Output to generate image for.</param> | ||
| 66 | /// <param name="fileTransfers">Array of files to be transfered.</param> | ||
| 67 | /// <param name="layoutDirectory">The directory in which the image should be layed out.</param> | ||
| 68 | /// <param name="compressed">Flag if source image should be compressed.</param> | ||
| 69 | /// <returns>The uncompressed file rows.</returns> | ||
| 70 | public void Execute() | ||
| 71 | { | ||
| 72 | RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable); | ||
| 73 | |||
| 74 | this.lastCabinetAddedToMediaTable = new Dictionary<string, string>(); | ||
| 75 | |||
| 76 | this.SetCabbingThreadCount(); | ||
| 77 | |||
| 78 | // Send Binder object to Facilitate NewCabNamesCallBack Callback | ||
| 79 | CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); | ||
| 80 | |||
| 81 | // Supply Compile MediaTemplate Attributes to Cabinet Builder | ||
| 82 | int MaximumCabinetSizeForLargeFileSplitting; | ||
| 83 | int MaximumUncompressedMediaSize; | ||
| 84 | this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize); | ||
| 85 | cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting; | ||
| 86 | cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize; | ||
| 87 | |||
| 88 | foreach (var entry in this.FileRowsByCabinet) | ||
| 89 | { | ||
| 90 | MediaRow mediaRow = entry.Key; | ||
| 91 | IEnumerable<FileFacade> files = entry.Value; | ||
| 92 | CompressionLevel compressionLevel = this.DefaultCompressionLevel; | ||
| 93 | |||
| 94 | WixMediaRow wixMediaRow = null; | ||
| 95 | string mediaLayoutFolder = null; | ||
| 96 | |||
| 97 | if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) | ||
| 98 | { | ||
| 99 | mediaLayoutFolder = wixMediaRow.Layout; | ||
| 100 | |||
| 101 | if (wixMediaRow.CompressionLevel.HasValue) | ||
| 102 | { | ||
| 103 | compressionLevel = wixMediaRow.CompressionLevel.Value; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); | ||
| 108 | |||
| 109 | CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers); | ||
| 110 | if (null != cabinetWorkItem) | ||
| 111 | { | ||
| 112 | cabinetBuilder.Enqueue(cabinetWorkItem); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | // stop processing if an error previously occurred | ||
| 117 | if (Messaging.Instance.EncounteredError) | ||
| 118 | { | ||
| 119 | return; | ||
| 120 | } | ||
| 121 | |||
| 122 | // create queued cabinets with multiple threads | ||
| 123 | cabinetBuilder.CreateQueuedCabinets(); | ||
| 124 | if (Messaging.Instance.EncounteredError) | ||
| 125 | { | ||
| 126 | return; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | /// <summary> | ||
| 131 | /// Sets the thead count to the number of processors if the current thread count is set to 0. | ||
| 132 | /// </summary> | ||
| 133 | /// <remarks>The thread count value must be greater than 0 otherwise and exception will be thrown.</remarks> | ||
| 134 | private void SetCabbingThreadCount() | ||
| 135 | { | ||
| 136 | // default the number of cabbing threads to the number of processors if it wasn't specified | ||
| 137 | if (0 == this.CabbingThreadCount) | ||
| 138 | { | ||
| 139 | string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); | ||
| 140 | |||
| 141 | try | ||
| 142 | { | ||
| 143 | if (null != numberOfProcessors) | ||
| 144 | { | ||
| 145 | this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat); | ||
| 146 | |||
| 147 | if (0 >= this.CabbingThreadCount) | ||
| 148 | { | ||
| 149 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | else // default to 1 if the environment variable is not set | ||
| 153 | { | ||
| 154 | this.CabbingThreadCount = 1; | ||
| 155 | } | ||
| 156 | |||
| 157 | Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); | ||
| 158 | } | ||
| 159 | catch (ArgumentException) | ||
| 160 | { | ||
| 161 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
| 162 | } | ||
| 163 | catch (FormatException) | ||
| 164 | { | ||
| 165 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | |||
| 171 | /// <summary> | ||
| 172 | /// Creates a work item to create a cabinet. | ||
| 173 | /// </summary> | ||
| 174 | /// <param name="output">Output for the current database.</param> | ||
| 175 | /// <param name="cabinetDir">Directory to create cabinet in.</param> | ||
| 176 | /// <param name="mediaRow">MediaRow containing information about the cabinet.</param> | ||
| 177 | /// <param name="fileFacades">Collection of files in this cabinet.</param> | ||
| 178 | /// <param name="fileTransfers">Array of files to be transfered.</param> | ||
| 179 | /// <returns>created CabinetWorkItem object</returns> | ||
| 180 | private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers) | ||
| 181 | { | ||
| 182 | CabinetWorkItem cabinetWorkItem = null; | ||
| 183 | string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet); | ||
| 184 | |||
| 185 | // check for an empty cabinet | ||
| 186 | if (!fileFacades.Any()) | ||
| 187 | { | ||
| 188 | string cabinetName = mediaRow.Cabinet; | ||
| 189 | |||
| 190 | // remove the leading '#' from the embedded cabinet name to make the warning easier to understand | ||
| 191 | if (cabinetName.StartsWith("#", StringComparison.Ordinal)) | ||
| 192 | { | ||
| 193 | cabinetName = cabinetName.Substring(1); | ||
| 194 | } | ||
| 195 | |||
| 196 | // If building a patch, remind them to run -p for torch. | ||
| 197 | if (OutputType.Patch == output.Type) | ||
| 198 | { | ||
| 199 | Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true)); | ||
| 200 | } | ||
| 201 | else | ||
| 202 | { | ||
| 203 | Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName)); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | ResolvedCabinet resolvedCabinet = this.ResolveCabinet(tempCabinetFileX, fileFacades); | ||
| 208 | |||
| 209 | // create a cabinet work item if it's not being skipped | ||
| 210 | if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) | ||
| 211 | { | ||
| 212 | int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet). | ||
| 213 | |||
| 214 | cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/); | ||
| 215 | } | ||
| 216 | else // reuse the cabinet from the cabinet cache. | ||
| 217 | { | ||
| 218 | Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path)); | ||
| 219 | |||
| 220 | try | ||
| 221 | { | ||
| 222 | // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The | ||
| 223 | // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that | ||
| 224 | // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from | ||
| 225 | // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) | ||
| 226 | // causing the project to look like it perpetually needs a rebuild until all of the reused | ||
| 227 | // cabinets get newer timestamps. | ||
| 228 | File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now); | ||
| 229 | } | ||
| 230 | catch (Exception e) | ||
| 231 | { | ||
| 232 | Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message)); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
| 237 | { | ||
| 238 | Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]); | ||
| 239 | |||
| 240 | Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers); | ||
| 241 | streamRow[0] = mediaRow.Cabinet.Substring(1); | ||
| 242 | streamRow[1] = resolvedCabinet.Path; | ||
| 243 | } | ||
| 244 | else | ||
| 245 | { | ||
| 246 | string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet); | ||
| 247 | FileTransfer transfer; | ||
| 248 | if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer)) | ||
| 249 | { | ||
| 250 | transfer.Built = true; | ||
| 251 | fileTransfers.Add(transfer); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | return cabinetWorkItem; | ||
| 256 | } | ||
| 257 | |||
| 258 | private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades) | ||
| 259 | { | ||
| 260 | ResolvedCabinet resolved = null; | ||
| 261 | |||
| 262 | List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); | ||
| 263 | |||
| 264 | foreach (IBinderFileManager fileManager in this.FileManagers) | ||
| 265 | { | ||
| 266 | resolved = fileManager.ResolveCabinet(cabinetPath, filesWithPath); | ||
| 267 | if (null != resolved) | ||
| 268 | { | ||
| 269 | break; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | return resolved; | ||
| 274 | } | ||
| 275 | |||
| 276 | /// <summary> | ||
| 277 | /// Delegate for Cabinet Split Callback | ||
| 278 | /// </summary> | ||
| 279 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] | ||
| 280 | internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); | ||
| 281 | |||
| 282 | /// <summary> | ||
| 283 | /// Call back to Add File Transfer for new Cab and add new Cab to Media table | ||
| 284 | /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe | ||
| 285 | /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored | ||
| 286 | /// </summary> | ||
| 287 | /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param> | ||
| 288 | /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param> | ||
| 289 | /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param> | ||
| 290 | internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) | ||
| 291 | { | ||
| 292 | // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads | ||
| 293 | Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); | ||
| 294 | try | ||
| 295 | { | ||
| 296 | if (!mutex.WaitOne(0, false)) // Check if you can get the lock | ||
| 297 | { | ||
| 298 | // Cound not get the Lock | ||
| 299 | Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel()); | ||
| 300 | mutex.WaitOne(); // Wait on other thread | ||
| 301 | } | ||
| 302 | |||
| 303 | string firstCabinetName = firstCabName + ".cab"; | ||
| 304 | string newCabinetName = newCabName; | ||
| 305 | bool transferAdded = false; // Used for Error Handling | ||
| 306 | |||
| 307 | // Create File Transfer for new Cabinet using transfer of Base Cabinet | ||
| 308 | foreach (FileTransfer transfer in this.FileTransfers) | ||
| 309 | { | ||
| 310 | if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) | ||
| 311 | { | ||
| 312 | string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); | ||
| 313 | string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); | ||
| 314 | |||
| 315 | FileTransfer newTransfer; | ||
| 316 | if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer)) | ||
| 317 | { | ||
| 318 | newTransfer.Built = true; | ||
| 319 | this.fileTransfers.Add(newTransfer); | ||
| 320 | transferAdded = true; | ||
| 321 | break; | ||
| 322 | } | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | // Check if File Transfer was added | ||
| 327 | if (!transferAdded) | ||
| 328 | { | ||
| 329 | throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); | ||
| 330 | } | ||
| 331 | |||
| 332 | // Add the new Cabinets to media table using LastSequence of Base Cabinet | ||
| 333 | Table mediaTable = this.Output.Tables["Media"]; | ||
| 334 | Table wixFileTable = this.Output.Tables["WixFile"]; | ||
| 335 | int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain | ||
| 336 | int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain | ||
| 337 | bool lastSplitCabinetFound = false; // Used for Error Handling | ||
| 338 | |||
| 339 | string lastCabinetOfThisSequence = String.Empty; | ||
| 340 | // Get the Value of Last Cabinet Added in this split Sequence from Dictionary | ||
| 341 | if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) | ||
| 342 | { | ||
| 343 | // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence | ||
| 344 | lastCabinetOfThisSequence = firstCabinetName; | ||
| 345 | } | ||
| 346 | |||
| 347 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
| 348 | { | ||
| 349 | // Get details for the Last Cabinet Added in this Split Sequence | ||
| 350 | if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 351 | { | ||
| 352 | lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; | ||
| 353 | diskIDForLastSplitCabAdded = mediaRow.DiskId; | ||
| 354 | lastSplitCabinetFound = true; | ||
| 355 | } | ||
| 356 | |||
| 357 | // Check for Name Collision for the new Cabinet added | ||
| 358 | if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 359 | { | ||
| 360 | // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row | ||
| 361 | throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | // Check if the last Split Cabinet was found in the Media Table | ||
| 366 | if (!lastSplitCabinetFound) | ||
| 367 | { | ||
| 368 | throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); | ||
| 369 | } | ||
| 370 | |||
| 371 | // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort | ||
| 372 | // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with | ||
| 373 | // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction | ||
| 374 | MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); | ||
| 375 | newMediaRow.Cabinet = newCabinetName; | ||
| 376 | newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion | ||
| 377 | newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; | ||
| 378 | |||
| 379 | // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique | ||
| 380 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
| 381 | { | ||
| 382 | // Check if this row comes after inserted row and it is not the new cabinet inserted row | ||
| 383 | if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 384 | { | ||
| 385 | mediaRow.DiskId++; // Increment DiskID | ||
| 386 | } | ||
| 387 | } | ||
| 388 | |||
| 389 | // Now Increment DiskID for All files Rows so that they refer to the right Media Row | ||
| 390 | foreach (WixFileRow wixFileRow in wixFileTable.Rows) | ||
| 391 | { | ||
| 392 | // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet | ||
| 393 | // This check will work as we have only one large file in every splitting cabinet | ||
| 394 | // If we want to support splitting cabinet with more large files we need to update this code | ||
| 395 | if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) | ||
| 396 | { | ||
| 397 | wixFileRow.DiskId++; // Increment DiskID | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback | ||
| 402 | this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; | ||
| 403 | |||
| 404 | mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails | ||
| 405 | } | ||
| 406 | finally | ||
| 407 | { | ||
| 408 | // Releasing the Mutex here | ||
| 409 | mutex.ReleaseMutex(); | ||
| 410 | } | ||
| 411 | } | ||
| 412 | |||
| 413 | |||
| 414 | /// <summary> | ||
| 415 | /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides | ||
| 416 | /// </summary> | ||
| 417 | /// <param name="output">Output to generate image for.</param> | ||
| 418 | /// <param name="fileRows">The indexed file rows.</param> | ||
| 419 | private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) | ||
| 420 | { | ||
| 421 | // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size | ||
| 422 | string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); | ||
| 423 | string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); | ||
| 424 | int maxCabSizeForLargeFileInMB = 0; | ||
| 425 | int maxPreCompressedSizeInMB = 0; | ||
| 426 | ulong testOverFlow = 0; | ||
| 427 | |||
| 428 | // Supply Compile MediaTemplate Attributes to Cabinet Builder | ||
| 429 | Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; | ||
| 430 | if (mediaTemplateTable != null) | ||
| 431 | { | ||
| 432 | WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; | ||
| 433 | |||
| 434 | // Get the Value for Max Cab Size for File Splitting | ||
| 435 | try | ||
| 436 | { | ||
| 437 | // Override authored mcslfs value if environment variable is authored. | ||
| 438 | if (!String.IsNullOrEmpty(mcslfsString)) | ||
| 439 | { | ||
| 440 | maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString); | ||
| 441 | } | ||
| 442 | else | ||
| 443 | { | ||
| 444 | maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting; | ||
| 445 | } | ||
| 446 | testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; | ||
| 447 | } | ||
| 448 | catch (FormatException) | ||
| 449 | { | ||
| 450 | throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); | ||
| 451 | } | ||
| 452 | catch (OverflowException) | ||
| 453 | { | ||
| 454 | throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, CompilerCore.MaxValueOfMaxCabSizeForLargeFileSplitting)); | ||
| 455 | } | ||
| 456 | |||
| 457 | try | ||
| 458 | { | ||
| 459 | // Override authored mums value if environment variable is authored. | ||
| 460 | if (!String.IsNullOrEmpty(mumsString)) | ||
| 461 | { | ||
| 462 | maxPreCompressedSizeInMB = Int32.Parse(mumsString); | ||
| 463 | } | ||
| 464 | else | ||
| 465 | { | ||
| 466 | maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; | ||
| 467 | } | ||
| 468 | testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; | ||
| 469 | } | ||
| 470 | catch (FormatException) | ||
| 471 | { | ||
| 472 | throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); | ||
| 473 | } | ||
| 474 | catch (OverflowException) | ||
| 475 | { | ||
| 476 | throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); | ||
| 477 | } | ||
| 478 | |||
| 479 | maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; | ||
| 480 | maxUncompressedMediaSize = maxPreCompressedSizeInMB; | ||
| 481 | } | ||
| 482 | else | ||
| 483 | { | ||
| 484 | maxCabSizeForLargeFileSplitting = 0; | ||
| 485 | maxUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize; | ||
| 486 | } | ||
| 487 | } | ||
| 488 | } | ||
| 489 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs deleted file mode 100644 index 933a1ea8..00000000 --- a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs +++ /dev/null | |||
| @@ -1,86 +0,0 @@ | |||
| 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.Bind.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.Data.Rows; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. | ||
| 14 | /// </summary> | ||
| 15 | internal class CreateDeltaPatchesCommand : ICommand | ||
| 16 | { | ||
| 17 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 18 | |||
| 19 | public Table WixPatchIdTable { private get; set; } | ||
| 20 | |||
| 21 | public string TempFilesLocation { private get; set; } | ||
| 22 | |||
| 23 | public void Execute() | ||
| 24 | { | ||
| 25 | bool optimizePatchSizeForLargeFiles = false; | ||
| 26 | PatchAPI.PatchInterop.PatchSymbolFlagsType apiPatchingSymbolFlags = 0; | ||
| 27 | |||
| 28 | if (null != this.WixPatchIdTable) | ||
| 29 | { | ||
| 30 | Row row = this.WixPatchIdTable.Rows[0]; | ||
| 31 | if (null != row) | ||
| 32 | { | ||
| 33 | if (null != row[2]) | ||
| 34 | { | ||
| 35 | optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture)); | ||
| 36 | } | ||
| 37 | |||
| 38 | if (null != row[3]) | ||
| 39 | { | ||
| 40 | apiPatchingSymbolFlags = (PatchAPI.PatchInterop.PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | foreach (FileFacade facade in this.FileFacades) | ||
| 46 | { | ||
| 47 | if (RowOperation.Modify == facade.File.Operation && | ||
| 48 | 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) | ||
| 49 | { | ||
| 50 | string deltaBase = String.Concat("delta_", facade.File.File); | ||
| 51 | string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf")); | ||
| 52 | string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd")); | ||
| 53 | |||
| 54 | bool retainRangeWarning = false; | ||
| 55 | |||
| 56 | if (PatchAPI.PatchInterop.CreateDelta( | ||
| 57 | deltaFile, | ||
| 58 | facade.WixFile.Source, | ||
| 59 | facade.DeltaPatchFile.Symbols, | ||
| 60 | facade.DeltaPatchFile.RetainOffsets, | ||
| 61 | new[] { facade.WixFile.PreviousSource }, | ||
| 62 | facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }), | ||
| 63 | facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }), | ||
| 64 | facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }), | ||
| 65 | facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }), | ||
| 66 | facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }), | ||
| 67 | apiPatchingSymbolFlags, | ||
| 68 | optimizePatchSizeForLargeFiles, | ||
| 69 | out retainRangeWarning)) | ||
| 70 | { | ||
| 71 | PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile); | ||
| 72 | |||
| 73 | facade.WixFile.Source = deltaFile; | ||
| 74 | facade.WixFile.DeltaPatchHeaderSource = headerFile; | ||
| 75 | } | ||
| 76 | |||
| 77 | if (retainRangeWarning) | ||
| 78 | { | ||
| 79 | // TODO: get patch family to add to warning message for PatchWiz parity. | ||
| 80 | Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File)); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs deleted file mode 100644 index 5db2768b..00000000 --- a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs +++ /dev/null | |||
| @@ -1,68 +0,0 @@ | |||
| 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.Bind.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Rows; | ||
| 9 | |||
| 10 | internal class CreateSpecialPropertiesCommand : ICommand | ||
| 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/Bind/Databases/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs deleted file mode 100644 index bee1488b..00000000 --- a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs +++ /dev/null | |||
| @@ -1,225 +0,0 @@ | |||
| 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.Bind.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.Cab; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Rows; | ||
| 15 | using WixToolset.MergeMod; | ||
| 16 | using WixToolset.Msi; | ||
| 17 | using WixToolset.Core.Native; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Retrieve files information and extract them from merge modules. | ||
| 21 | /// </summary> | ||
| 22 | internal class ExtractMergeModuleFilesCommand : ICommand | ||
| 23 | { | ||
| 24 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 25 | |||
| 26 | public Table FileTable { private get; set; } | ||
| 27 | |||
| 28 | public Table WixFileTable { private get; set; } | ||
| 29 | |||
| 30 | public Table WixMergeTable { private get; set; } | ||
| 31 | |||
| 32 | public int OutputInstallerVersion { private get; set; } | ||
| 33 | |||
| 34 | public bool SuppressLayout { private get; set; } | ||
| 35 | |||
| 36 | public string TempFilesLocation { private get; set; } | ||
| 37 | |||
| 38 | public IEnumerable<FileFacade> MergeModulesFileFacades { get; private set; } | ||
| 39 | |||
| 40 | public void Execute() | ||
| 41 | { | ||
| 42 | List<FileFacade> mergeModulesFileFacades = new List<FileFacade>(); | ||
| 43 | |||
| 44 | IMsmMerge2 merge = MsmInterop.GetMsmMerge(); | ||
| 45 | |||
| 46 | // Index all of the file rows to be able to detect collisions with files in the Merge Modules. | ||
| 47 | // It may seem a bit expensive to build up this index solely for the purpose of checking collisions | ||
| 48 | // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out | ||
| 49 | // there are other cases where we need all the file rows indexed, however they are not common cases. | ||
| 50 | // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let | ||
| 51 | // this case be slightly more expensive because the cost of maintaining an indexed file row collection | ||
| 52 | // is a lot more costly for the common cases. | ||
| 53 | Dictionary<string, FileFacade> indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal); | ||
| 54 | |||
| 55 | foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows) | ||
| 56 | { | ||
| 57 | bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); | ||
| 58 | |||
| 59 | // If the module has files and creating layout | ||
| 60 | if (containsFiles && !this.SuppressLayout) | ||
| 61 | { | ||
| 62 | this.ExtractFilesFromMergeModule(merge, wixMergeRow); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | this.MergeModulesFileFacades = mergeModulesFileFacades; | ||
| 67 | } | ||
| 68 | |||
| 69 | private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades) | ||
| 70 | { | ||
| 71 | bool containsFiles = false; | ||
| 72 | |||
| 73 | try | ||
| 74 | { | ||
| 75 | // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. | ||
| 76 | using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) | ||
| 77 | { | ||
| 78 | if (db.TableExists("File") && db.TableExists("Component")) | ||
| 79 | { | ||
| 80 | Dictionary<string, FileFacade> uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase); | ||
| 81 | |||
| 82 | using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) | ||
| 83 | { | ||
| 84 | // add each file row from the merge module into the file row collection (check for errors along the way) | ||
| 85 | while (true) | ||
| 86 | { | ||
| 87 | using (Record record = view.Fetch()) | ||
| 88 | { | ||
| 89 | if (null == record) | ||
| 90 | { | ||
| 91 | break; | ||
| 92 | } | ||
| 93 | |||
| 94 | // NOTE: this is very tricky - the merge module file rows are not added to the | ||
| 95 | // file table because they should not be created via idt import. Instead, these | ||
| 96 | // rows are created by merging in the actual modules. | ||
| 97 | FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); | ||
| 98 | fileRow.File = record[1]; | ||
| 99 | fileRow.Compressed = wixMergeRow.FileCompression; | ||
| 100 | |||
| 101 | WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); | ||
| 102 | wixFileRow.Directory = record[2]; | ||
| 103 | wixFileRow.DiskId = wixMergeRow.DiskId; | ||
| 104 | wixFileRow.PatchGroup = -1; | ||
| 105 | wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]); | ||
| 106 | |||
| 107 | FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow); | ||
| 108 | |||
| 109 | FileFacade collidingFacade; | ||
| 110 | |||
| 111 | // If case-sensitive collision with another merge module or a user-authored file identifier. | ||
| 112 | if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) | ||
| 113 | { | ||
| 114 | Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File)); | ||
| 115 | } | ||
| 116 | else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module | ||
| 117 | { | ||
| 118 | Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File)); | ||
| 119 | } | ||
| 120 | else // no collision | ||
| 121 | { | ||
| 122 | mergeModulesFileFacades.Add(mergeModuleFileFacade); | ||
| 123 | |||
| 124 | // Keep updating the indexes as new rows are added. | ||
| 125 | indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); | ||
| 126 | uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); | ||
| 127 | } | ||
| 128 | |||
| 129 | containsFiles = true; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | // Get the summary information to detect the Schema | ||
| 136 | using (SummaryInformation summaryInformation = new SummaryInformation(db)) | ||
| 137 | { | ||
| 138 | string moduleInstallerVersionString = summaryInformation.GetProperty(14); | ||
| 139 | |||
| 140 | try | ||
| 141 | { | ||
| 142 | int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); | ||
| 143 | if (moduleInstallerVersion > this.OutputInstallerVersion) | ||
| 144 | { | ||
| 145 | Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion)); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | catch (FormatException) | ||
| 149 | { | ||
| 150 | throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | catch (FileNotFoundException) | ||
| 156 | { | ||
| 157 | throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); | ||
| 158 | } | ||
| 159 | catch (Win32Exception) | ||
| 160 | { | ||
| 161 | throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile)); | ||
| 162 | } | ||
| 163 | |||
| 164 | return containsFiles; | ||
| 165 | } | ||
| 166 | |||
| 167 | private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow) | ||
| 168 | { | ||
| 169 | bool moduleOpen = false; | ||
| 170 | short mergeLanguage; | ||
| 171 | |||
| 172 | try | ||
| 173 | { | ||
| 174 | mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); | ||
| 175 | } | ||
| 176 | catch (System.FormatException) | ||
| 177 | { | ||
| 178 | Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); | ||
| 179 | return; | ||
| 180 | } | ||
| 181 | |||
| 182 | try | ||
| 183 | { | ||
| 184 | merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); | ||
| 185 | moduleOpen = true; | ||
| 186 | |||
| 187 | string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat); | ||
| 188 | |||
| 189 | // extract the module cabinet, then explode all of the files to a temp directory | ||
| 190 | string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab"); | ||
| 191 | merge.ExtractCAB(moduleCabPath); | ||
| 192 | |||
| 193 | string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId); | ||
| 194 | Directory.CreateDirectory(mergeIdPath); | ||
| 195 | |||
| 196 | using (WixExtractCab extractCab = new WixExtractCab()) | ||
| 197 | { | ||
| 198 | try | ||
| 199 | { | ||
| 200 | extractCab.Extract(moduleCabPath, mergeIdPath); | ||
| 201 | } | ||
| 202 | catch (FileNotFoundException) | ||
| 203 | { | ||
| 204 | throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); | ||
| 205 | } | ||
| 206 | catch | ||
| 207 | { | ||
| 208 | throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
| 212 | catch (COMException ce) | ||
| 213 | { | ||
| 214 | throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); | ||
| 215 | } | ||
| 216 | finally | ||
| 217 | { | ||
| 218 | if (moduleOpen) | ||
| 219 | { | ||
| 220 | merge.CloseModule(); | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs deleted file mode 100644 index b6bcd3af..00000000 --- a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs +++ /dev/null | |||
| @@ -1,148 +0,0 @@ | |||
| 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.Bind.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Rows; | ||
| 11 | |||
| 12 | internal class GetFileFacadesCommand : ICommand | ||
| 13 | { | ||
| 14 | public Table FileTable { private get; set; } | ||
| 15 | |||
| 16 | public Table WixFileTable { private get; set; } | ||
| 17 | |||
| 18 | public Table WixDeltaPatchFileTable { private get; set; } | ||
| 19 | |||
| 20 | public Table WixDeltaPatchSymbolPathsTable { private get; set; } | ||
| 21 | |||
| 22 | public List<FileFacade> FileFacades { get; private set; } | ||
| 23 | |||
| 24 | public void Execute() | ||
| 25 | { | ||
| 26 | List<FileFacade> facades = new List<FileFacade>(this.FileTable.Rows.Count); | ||
| 27 | |||
| 28 | RowDictionary<WixFileRow> wixFiles = new RowDictionary<WixFileRow>(this.WixFileTable); | ||
| 29 | RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles = new RowDictionary<WixDeltaPatchFileRow>(this.WixDeltaPatchFileTable); | ||
| 30 | |||
| 31 | foreach (FileRow file in this.FileTable.Rows) | ||
| 32 | { | ||
| 33 | WixDeltaPatchFileRow deltaPatchFile = null; | ||
| 34 | |||
| 35 | deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile); | ||
| 36 | |||
| 37 | facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile)); | ||
| 38 | } | ||
| 39 | |||
| 40 | if (null != this.WixDeltaPatchSymbolPathsTable) | ||
| 41 | { | ||
| 42 | this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); | ||
| 43 | } | ||
| 44 | |||
| 45 | this.FileFacades = facades; | ||
| 46 | } | ||
| 47 | |||
| 48 | /// <summary> | ||
| 49 | /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. | ||
| 50 | /// </summary> | ||
| 51 | public RowDictionary<WixDeltaPatchFileRow> ResolveDeltaPatchSymbolPaths(RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles, IEnumerable<FileFacade> facades) | ||
| 52 | { | ||
| 53 | ILookup<string, FileFacade> filesByComponent = null; | ||
| 54 | ILookup<string, FileFacade> filesByDirectory = null; | ||
| 55 | ILookup<string, FileFacade> filesByDiskId = null; | ||
| 56 | |||
| 57 | foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs<WixDeltaPatchSymbolPathsRow>().OrderBy(r => r.Type)) | ||
| 58 | { | ||
| 59 | switch (row.Type) | ||
| 60 | { | ||
| 61 | case SymbolPathType.File: | ||
| 62 | this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]); | ||
| 63 | break; | ||
| 64 | |||
| 65 | case SymbolPathType.Component: | ||
| 66 | if (null == filesByComponent) | ||
| 67 | { | ||
| 68 | filesByComponent = facades.ToLookup(f => f.File.Component); | ||
| 69 | } | ||
| 70 | |||
| 71 | foreach (FileFacade facade in filesByComponent[row.Id]) | ||
| 72 | { | ||
| 73 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
| 74 | } | ||
| 75 | break; | ||
| 76 | |||
| 77 | case SymbolPathType.Directory: | ||
| 78 | if (null == filesByDirectory) | ||
| 79 | { | ||
| 80 | filesByDirectory = facades.ToLookup(f => f.WixFile.Directory); | ||
| 81 | } | ||
| 82 | |||
| 83 | foreach (FileFacade facade in filesByDirectory[row.Id]) | ||
| 84 | { | ||
| 85 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
| 86 | } | ||
| 87 | break; | ||
| 88 | |||
| 89 | case SymbolPathType.Media: | ||
| 90 | if (null == filesByDiskId) | ||
| 91 | { | ||
| 92 | filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture)); | ||
| 93 | } | ||
| 94 | |||
| 95 | foreach (FileFacade facade in filesByDiskId[row.Id]) | ||
| 96 | { | ||
| 97 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
| 98 | } | ||
| 99 | break; | ||
| 100 | |||
| 101 | case SymbolPathType.Product: | ||
| 102 | foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values) | ||
| 103 | { | ||
| 104 | this.MergeSymbolPaths(row, fileRow); | ||
| 105 | } | ||
| 106 | break; | ||
| 107 | |||
| 108 | default: | ||
| 109 | // error | ||
| 110 | break; | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | return deltaPatchFiles; | ||
| 115 | } | ||
| 116 | |||
| 117 | /// <summary> | ||
| 118 | /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row. | ||
| 119 | /// </summary> | ||
| 120 | /// <param name="row">Row from the WixPatchSymbolsPaths table.</param> | ||
| 121 | /// <param name="file">FileRow into which to set symbol information.</param> | ||
| 122 | /// <comment>This includes PreviousData as well.</comment> | ||
| 123 | private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file) | ||
| 124 | { | ||
| 125 | if (null == file.Symbols) | ||
| 126 | { | ||
| 127 | file.Symbols = row.SymbolPaths; | ||
| 128 | } | ||
| 129 | else | ||
| 130 | { | ||
| 131 | file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths); | ||
| 132 | } | ||
| 133 | |||
| 134 | Field field = row.Fields[2]; | ||
| 135 | if (null != field.PreviousData) | ||
| 136 | { | ||
| 137 | if (null == file.PreviousSymbols) | ||
| 138 | { | ||
| 139 | file.PreviousSymbols = field.PreviousData; | ||
| 140 | } | ||
| 141 | else | ||
| 142 | { | ||
| 143 | file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs b/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs deleted file mode 100644 index 035ef059..00000000 --- a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs +++ /dev/null | |||
| @@ -1,350 +0,0 @@ | |||
| 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.Bind.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 | |||
| 24 | /// <summary> | ||
| 25 | /// Update file information. | ||
| 26 | /// </summary> | ||
| 27 | internal class MergeModulesCommand : ICommand | ||
| 28 | { | ||
| 29 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 30 | |||
| 31 | public Output Output { private get; set; } | ||
| 32 | |||
| 33 | public string OutputPath { private get; set; } | ||
| 34 | |||
| 35 | public IEnumerable<string> SuppressedTableNames { private get; set; } | ||
| 36 | |||
| 37 | public string TempFilesLocation { private get; set; } | ||
| 38 | |||
| 39 | public void Execute() | ||
| 40 | { | ||
| 41 | Debug.Assert(OutputType.Product == this.Output.Type); | ||
| 42 | |||
| 43 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
| 44 | Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"]; | ||
| 45 | |||
| 46 | // check for merge rows to see if there is any work to do | ||
| 47 | if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count) | ||
| 48 | { | ||
| 49 | return; | ||
| 50 | } | ||
| 51 | |||
| 52 | IMsmMerge2 merge = null; | ||
| 53 | bool commit = true; | ||
| 54 | bool logOpen = false; | ||
| 55 | bool databaseOpen = false; | ||
| 56 | string logPath = null; | ||
| 57 | try | ||
| 58 | { | ||
| 59 | merge = MsmInterop.GetMsmMerge(); | ||
| 60 | |||
| 61 | logPath = Path.Combine(this.TempFilesLocation, "merge.log"); | ||
| 62 | merge.OpenLog(logPath); | ||
| 63 | logOpen = true; | ||
| 64 | |||
| 65 | merge.OpenDatabase(this.OutputPath); | ||
| 66 | databaseOpen = true; | ||
| 67 | |||
| 68 | // process all the merge rows | ||
| 69 | foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows) | ||
| 70 | { | ||
| 71 | bool moduleOpen = false; | ||
| 72 | |||
| 73 | try | ||
| 74 | { | ||
| 75 | short mergeLanguage; | ||
| 76 | |||
| 77 | try | ||
| 78 | { | ||
| 79 | mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); | ||
| 80 | } | ||
| 81 | catch (System.FormatException) | ||
| 82 | { | ||
| 83 | Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); | ||
| 84 | continue; | ||
| 85 | } | ||
| 86 | |||
| 87 | Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); | ||
| 88 | merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); | ||
| 89 | moduleOpen = true; | ||
| 90 | |||
| 91 | // If there is merge configuration data, create a callback object to contain it all. | ||
| 92 | ConfigurationCallback callback = null; | ||
| 93 | if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) | ||
| 94 | { | ||
| 95 | callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); | ||
| 96 | } | ||
| 97 | |||
| 98 | // merge the module into the database that's being built | ||
| 99 | Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile)); | ||
| 100 | merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback); | ||
| 101 | |||
| 102 | // connect any non-primary features | ||
| 103 | if (null != wixFeatureModulesTable) | ||
| 104 | { | ||
| 105 | foreach (Row row in wixFeatureModulesTable.Rows) | ||
| 106 | { | ||
| 107 | if (wixMergeRow.Id == (string)row[1]) | ||
| 108 | { | ||
| 109 | Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0])); | ||
| 110 | merge.Connect((string)row[0]); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | catch (COMException) | ||
| 116 | { | ||
| 117 | commit = false; | ||
| 118 | } | ||
| 119 | finally | ||
| 120 | { | ||
| 121 | IMsmErrors mergeErrors = merge.Errors; | ||
| 122 | |||
| 123 | // display all the errors encountered during the merge operations for this module | ||
| 124 | for (int i = 1; i <= mergeErrors.Count; i++) | ||
| 125 | { | ||
| 126 | IMsmError mergeError = mergeErrors[i]; | ||
| 127 | StringBuilder databaseKeys = new StringBuilder(); | ||
| 128 | StringBuilder moduleKeys = new StringBuilder(); | ||
| 129 | |||
| 130 | // build a string of the database keys | ||
| 131 | for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++) | ||
| 132 | { | ||
| 133 | if (1 != j) | ||
| 134 | { | ||
| 135 | databaseKeys.Append(';'); | ||
| 136 | } | ||
| 137 | databaseKeys.Append(mergeError.DatabaseKeys[j]); | ||
| 138 | } | ||
| 139 | |||
| 140 | // build a string of the module keys | ||
| 141 | for (int j = 1; j <= mergeError.ModuleKeys.Count; j++) | ||
| 142 | { | ||
| 143 | if (1 != j) | ||
| 144 | { | ||
| 145 | moduleKeys.Append(';'); | ||
| 146 | } | ||
| 147 | moduleKeys.Append(mergeError.ModuleKeys[j]); | ||
| 148 | } | ||
| 149 | |||
| 150 | // display the merge error based on the msm error type | ||
| 151 | switch (mergeError.Type) | ||
| 152 | { | ||
| 153 | case MsmErrorType.msmErrorExclusion: | ||
| 154 | Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); | ||
| 155 | break; | ||
| 156 | case MsmErrorType.msmErrorFeatureRequired: | ||
| 157 | Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); | ||
| 158 | break; | ||
| 159 | case MsmErrorType.msmErrorLanguageFailed: | ||
| 160 | Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); | ||
| 161 | break; | ||
| 162 | case MsmErrorType.msmErrorLanguageUnsupported: | ||
| 163 | Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); | ||
| 164 | break; | ||
| 165 | case MsmErrorType.msmErrorResequenceMerge: | ||
| 166 | Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); | ||
| 167 | break; | ||
| 168 | case MsmErrorType.msmErrorTableMerge: | ||
| 169 | if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table | ||
| 170 | { | ||
| 171 | Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); | ||
| 172 | } | ||
| 173 | break; | ||
| 174 | case MsmErrorType.msmErrorPlatformMismatch: | ||
| 175 | Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); | ||
| 176 | break; | ||
| 177 | default: | ||
| 178 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); | ||
| 179 | break; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | if (0 >= mergeErrors.Count && !commit) | ||
| 184 | { | ||
| 185 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); | ||
| 186 | } | ||
| 187 | |||
| 188 | if (moduleOpen) | ||
| 189 | { | ||
| 190 | merge.CloseModule(); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | finally | ||
| 196 | { | ||
| 197 | if (databaseOpen) | ||
| 198 | { | ||
| 199 | merge.CloseDatabase(commit); | ||
| 200 | } | ||
| 201 | |||
| 202 | if (logOpen) | ||
| 203 | { | ||
| 204 | merge.CloseLog(); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | // stop processing if an error previously occurred | ||
| 209 | if (Messaging.Instance.EncounteredError) | ||
| 210 | { | ||
| 211 | return; | ||
| 212 | } | ||
| 213 | |||
| 214 | using (Database db = new Database(this.OutputPath, OpenDatabase.Direct)) | ||
| 215 | { | ||
| 216 | Table suppressActionTable = this.Output.Tables["WixSuppressAction"]; | ||
| 217 | |||
| 218 | // suppress individual actions | ||
| 219 | if (null != suppressActionTable) | ||
| 220 | { | ||
| 221 | foreach (Row row in suppressActionTable.Rows) | ||
| 222 | { | ||
| 223 | if (db.TableExists((string)row[0])) | ||
| 224 | { | ||
| 225 | string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); | ||
| 226 | |||
| 227 | using (View view = db.OpenExecuteView(query)) | ||
| 228 | { | ||
| 229 | using (Record record = view.Fetch()) | ||
| 230 | { | ||
| 231 | if (null != record) | ||
| 232 | { | ||
| 233 | Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); | ||
| 234 | view.Modify(ModifyView.Delete, record); | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | // query for merge module actions in suppressed sequences and drop them | ||
| 243 | foreach (string tableName in this.SuppressedTableNames) | ||
| 244 | { | ||
| 245 | if (!db.TableExists(tableName)) | ||
| 246 | { | ||
| 247 | continue; | ||
| 248 | } | ||
| 249 | |||
| 250 | using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) | ||
| 251 | { | ||
| 252 | while (true) | ||
| 253 | { | ||
| 254 | using (Record resultRecord = view.Fetch()) | ||
| 255 | { | ||
| 256 | if (null == resultRecord) | ||
| 257 | { | ||
| 258 | break; | ||
| 259 | } | ||
| 260 | |||
| 261 | Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | // drop suppressed sequences | ||
| 267 | using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) | ||
| 268 | { | ||
| 269 | } | ||
| 270 | |||
| 271 | // delete the validation rows | ||
| 272 | using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) | ||
| 273 | { | ||
| 274 | using (Record record = new Record(1)) | ||
| 275 | { | ||
| 276 | record.SetString(1, tableName); | ||
| 277 | view.Execute(record); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | // now update the Attributes column for the files from the Merge Modules | ||
| 283 | Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); | ||
| 284 | using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) | ||
| 285 | { | ||
| 286 | foreach (FileFacade file in this.FileFacades) | ||
| 287 | { | ||
| 288 | if (!file.FromModule) | ||
| 289 | { | ||
| 290 | continue; | ||
| 291 | } | ||
| 292 | |||
| 293 | using (Record record = new Record(1)) | ||
| 294 | { | ||
| 295 | record.SetString(1, file.File.File); | ||
| 296 | view.Execute(record); | ||
| 297 | } | ||
| 298 | |||
| 299 | using (Record recordUpdate = view.Fetch()) | ||
| 300 | { | ||
| 301 | if (null == recordUpdate) | ||
| 302 | { | ||
| 303 | throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); | ||
| 304 | } | ||
| 305 | |||
| 306 | recordUpdate.SetInteger(1, file.File.Sequence); | ||
| 307 | |||
| 308 | // update the file attributes to match the compression specified | ||
| 309 | // on the Merge element or on the Package element | ||
| 310 | int attributes = 0; | ||
| 311 | |||
| 312 | // get the current value if its not null | ||
| 313 | if (!recordUpdate.IsNull(2)) | ||
| 314 | { | ||
| 315 | attributes = recordUpdate.GetInteger(2); | ||
| 316 | } | ||
| 317 | |||
| 318 | if (YesNoType.Yes == file.File.Compressed) | ||
| 319 | { | ||
| 320 | // these are mutually exclusive | ||
| 321 | attributes |= MsiInterop.MsidbFileAttributesCompressed; | ||
| 322 | attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 323 | } | ||
| 324 | else if (YesNoType.No == file.File.Compressed) | ||
| 325 | { | ||
| 326 | // these are mutually exclusive | ||
| 327 | attributes |= MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 328 | attributes &= ~MsiInterop.MsidbFileAttributesCompressed; | ||
| 329 | } | ||
| 330 | else // not specified | ||
| 331 | { | ||
| 332 | Debug.Assert(YesNoType.NotSet == file.File.Compressed); | ||
| 333 | |||
| 334 | // clear any compression bits | ||
| 335 | attributes &= ~MsiInterop.MsidbFileAttributesCompressed; | ||
| 336 | attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 337 | } | ||
| 338 | |||
| 339 | recordUpdate.SetInteger(2, attributes); | ||
| 340 | |||
| 341 | view.Modify(ModifyView.Update, recordUpdate); | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } | ||
| 345 | |||
| 346 | db.Commit(); | ||
| 347 | } | ||
| 348 | } | ||
| 349 | } | ||
| 350 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs deleted file mode 100644 index dd7b85b7..00000000 --- a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs +++ /dev/null | |||
| @@ -1,115 +0,0 @@ | |||
| 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.Bind.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 | |||
| 14 | /// <summary> | ||
| 15 | /// Defines the file transfers necessary to layout the uncompressed files. | ||
| 16 | /// </summary> | ||
| 17 | internal class ProcessUncompressedFilesCommand : ICommand | ||
| 18 | { | ||
| 19 | public string DatabasePath { private get; set; } | ||
| 20 | |||
| 21 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 22 | |||
| 23 | public RowDictionary<MediaRow> MediaRows { private get; set; } | ||
| 24 | |||
| 25 | public string LayoutDirectory { private get; set; } | ||
| 26 | |||
| 27 | public bool Compressed { private get; set; } | ||
| 28 | |||
| 29 | public bool LongNamesInImage { private get; set; } | ||
| 30 | |||
| 31 | public Func<MediaRow, string, string, string> ResolveMedia { private get; set; } | ||
| 32 | |||
| 33 | public Table WixMediaTable { private get; set; } | ||
| 34 | |||
| 35 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
| 36 | |||
| 37 | public void Execute() | ||
| 38 | { | ||
| 39 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
| 40 | |||
| 41 | Hashtable directories = new Hashtable(); | ||
| 42 | |||
| 43 | RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable); | ||
| 44 | |||
| 45 | using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) | ||
| 46 | { | ||
| 47 | using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) | ||
| 48 | { | ||
| 49 | while (true) | ||
| 50 | { | ||
| 51 | using (Record directoryRecord = directoryView.Fetch()) | ||
| 52 | { | ||
| 53 | if (null == directoryRecord) | ||
| 54 | { | ||
| 55 | break; | ||
| 56 | } | ||
| 57 | |||
| 58 | string sourceName = Installer.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage); | ||
| 59 | |||
| 60 | directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) | ||
| 66 | { | ||
| 67 | using (Record fileQueryRecord = new Record(1)) | ||
| 68 | { | ||
| 69 | // for each file in the array of uncompressed files | ||
| 70 | foreach (FileFacade facade in this.FileFacades) | ||
| 71 | { | ||
| 72 | MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId); | ||
| 73 | string relativeFileLayoutPath = null; | ||
| 74 | |||
| 75 | WixMediaRow wixMediaRow = null; | ||
| 76 | string mediaLayoutFolder = null; | ||
| 77 | |||
| 78 | if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) | ||
| 79 | { | ||
| 80 | mediaLayoutFolder = wixMediaRow.Layout; | ||
| 81 | } | ||
| 82 | |||
| 83 | string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); | ||
| 84 | |||
| 85 | // setup up the query record and find the appropriate file in the | ||
| 86 | // previously executed file view | ||
| 87 | fileQueryRecord[1] = facade.File.File; | ||
| 88 | fileView.Execute(fileQueryRecord); | ||
| 89 | |||
| 90 | using (Record fileRecord = fileView.Fetch()) | ||
| 91 | { | ||
| 92 | if (null == fileRecord) | ||
| 93 | { | ||
| 94 | throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File)); | ||
| 95 | } | ||
| 96 | |||
| 97 | relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); | ||
| 98 | } | ||
| 99 | |||
| 100 | // finally put together the base media layout path and the relative file layout path | ||
| 101 | string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); | ||
| 102 | FileTransfer transfer; | ||
| 103 | if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer)) | ||
| 104 | { | ||
| 105 | fileTransfers.Add(transfer); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | this.FileTransfers = fileTransfers; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs deleted file mode 100644 index 9e17ee02..00000000 --- a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs +++ /dev/null | |||
| @@ -1,80 +0,0 @@ | |||
| 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.Bind.Databases | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Rows; | ||
| 9 | |||
| 10 | internal class UpdateControlTextCommand : ICommand | ||
| 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/Bind/Databases/UpdateFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs deleted file mode 100644 index 36818afa..00000000 --- a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs +++ /dev/null | |||
| @@ -1,532 +0,0 @@ | |||
| 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.Bind.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.Data; | ||
| 16 | using WixToolset.Data.Rows; | ||
| 17 | using WixToolset.Msi; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Update file information. | ||
| 21 | /// </summary> | ||
| 22 | internal class UpdateFileFacadesCommand : ICommand | ||
| 23 | { | ||
| 24 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
| 25 | |||
| 26 | public IEnumerable<FileFacade> UpdateFileFacades { private get; set; } | ||
| 27 | |||
| 28 | public string ModularizationGuid { private get; set; } | ||
| 29 | |||
| 30 | public Output Output { private get; set; } | ||
| 31 | |||
| 32 | public bool OverwriteHash { private get; set; } | ||
| 33 | |||
| 34 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 35 | |||
| 36 | public IDictionary<string, string> VariableCache { private get; set; } | ||
| 37 | |||
| 38 | public void Execute() | ||
| 39 | { | ||
| 40 | foreach (FileFacade file in this.UpdateFileFacades) | ||
| 41 | { | ||
| 42 | this.UpdateFileFacade(file); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | private void UpdateFileFacade(FileFacade file) | ||
| 47 | { | ||
| 48 | FileInfo fileInfo = null; | ||
| 49 | try | ||
| 50 | { | ||
| 51 | fileInfo = new FileInfo(file.WixFile.Source); | ||
| 52 | } | ||
| 53 | catch (ArgumentException) | ||
| 54 | { | ||
| 55 | Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
| 56 | return; | ||
| 57 | } | ||
| 58 | catch (PathTooLongException) | ||
| 59 | { | ||
| 60 | Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
| 61 | return; | ||
| 62 | } | ||
| 63 | catch (NotSupportedException) | ||
| 64 | { | ||
| 65 | Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
| 66 | return; | ||
| 67 | } | ||
| 68 | |||
| 69 | if (!fileInfo.Exists) | ||
| 70 | { | ||
| 71 | Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source)); | ||
| 72 | return; | ||
| 73 | } | ||
| 74 | |||
| 75 | using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) | ||
| 76 | { | ||
| 77 | if (Int32.MaxValue < fileStream.Length) | ||
| 78 | { | ||
| 79 | throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
| 80 | } | ||
| 81 | |||
| 82 | file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); | ||
| 83 | } | ||
| 84 | |||
| 85 | string version = null; | ||
| 86 | string language = null; | ||
| 87 | try | ||
| 88 | { | ||
| 89 | Installer.GetFileVersion(fileInfo.FullName, out version, out language); | ||
| 90 | } | ||
| 91 | catch (Win32Exception e) | ||
| 92 | { | ||
| 93 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | ||
| 94 | { | ||
| 95 | throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); | ||
| 96 | } | ||
| 97 | else | ||
| 98 | { | ||
| 99 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. | ||
| 104 | if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table | ||
| 105 | { | ||
| 106 | if (!this.OverwriteHash) | ||
| 107 | { | ||
| 108 | // not overwriting hash, so don't do the rest of these options. | ||
| 109 | } | ||
| 110 | else if (null != file.File.Version) | ||
| 111 | { | ||
| 112 | // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks | ||
| 113 | // 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. | ||
| 114 | // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing | ||
| 115 | // all the file rows) for a relatively uncommon situation. Let's not do that. | ||
| 116 | // | ||
| 117 | // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version | ||
| 118 | // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. | ||
| 119 | if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) | ||
| 120 | { | ||
| 121 | Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File)); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | else | ||
| 125 | { | ||
| 126 | if (null != file.File.Language) | ||
| 127 | { | ||
| 128 | Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); | ||
| 129 | } | ||
| 130 | |||
| 131 | int[] hash; | ||
| 132 | try | ||
| 133 | { | ||
| 134 | Installer.GetFileHash(fileInfo.FullName, 0, out hash); | ||
| 135 | } | ||
| 136 | catch (Win32Exception e) | ||
| 137 | { | ||
| 138 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | ||
| 139 | { | ||
| 140 | throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); | ||
| 141 | } | ||
| 142 | else | ||
| 143 | { | ||
| 144 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | if (null == file.Hash) | ||
| 149 | { | ||
| 150 | Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]); | ||
| 151 | file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers); | ||
| 152 | } | ||
| 153 | |||
| 154 | file.Hash[0] = file.File.File; | ||
| 155 | file.Hash[1] = 0; | ||
| 156 | file.Hash[2] = hash[0]; | ||
| 157 | file.Hash[3] = hash[1]; | ||
| 158 | file.Hash[4] = hash[2]; | ||
| 159 | file.Hash[5] = hash[3]; | ||
| 160 | } | ||
| 161 | } | ||
| 162 | else // update the file row with the version and language information. | ||
| 163 | { | ||
| 164 | // If no version was provided by the user, use the version from the file itself. | ||
| 165 | // This is the most common case. | ||
| 166 | if (String.IsNullOrEmpty(file.File.Version)) | ||
| 167 | { | ||
| 168 | file.File.Version = version; | ||
| 169 | } | ||
| 170 | else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below. | ||
| 171 | { | ||
| 172 | // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching | ||
| 173 | // the version value). We didn't find it so, we will override the default version they provided with the actual | ||
| 174 | // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match | ||
| 175 | // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case | ||
| 176 | // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more | ||
| 177 | // CPU intensive search to save on the memory intensive index that wouldn't be used much. | ||
| 178 | // | ||
| 179 | // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. | ||
| 180 | // That's typically even more rare than companion files so again, no index, just search. | ||
| 181 | file.File.Version = version; | ||
| 182 | } | ||
| 183 | |||
| 184 | if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language)) | ||
| 185 | { | ||
| 186 | Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); | ||
| 187 | } | ||
| 188 | else // override the default provided by the user (usually nothing) with the actual language from the file itself. | ||
| 189 | { | ||
| 190 | file.File.Language = language; | ||
| 191 | } | ||
| 192 | |||
| 193 | // Populate the binder variables for this file information if requested. | ||
| 194 | if (null != this.VariableCache) | ||
| 195 | { | ||
| 196 | if (!String.IsNullOrEmpty(file.File.Version)) | ||
| 197 | { | ||
| 198 | string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)); | ||
| 199 | this.VariableCache[key] = file.File.Version; | ||
| 200 | } | ||
| 201 | |||
| 202 | if (!String.IsNullOrEmpty(file.File.Language)) | ||
| 203 | { | ||
| 204 | string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, ModularizationGuid, file.File.File)); | ||
| 205 | this.VariableCache[key] = file.File.Language; | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | // If this is a CLR assembly, load the assembly and get the assembly name information | ||
| 211 | if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType) | ||
| 212 | { | ||
| 213 | bool targetNetfx1 = false; | ||
| 214 | StringDictionary assemblyNameValues = new StringDictionary(); | ||
| 215 | |||
| 216 | ClrInterop.IReferenceIdentity referenceIdentity = null; | ||
| 217 | Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid; | ||
| 218 | uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity); | ||
| 219 | if (0 == result && null != referenceIdentity) | ||
| 220 | { | ||
| 221 | string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion"); | ||
| 222 | if (null != imageRuntimeVersion) | ||
| 223 | { | ||
| 224 | targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase); | ||
| 225 | } | ||
| 226 | |||
| 227 | string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral"; | ||
| 228 | assemblyNameValues.Add("Culture", culture); | ||
| 229 | |||
| 230 | string name = referenceIdentity.GetAttribute(null, "Name"); | ||
| 231 | if (null != name) | ||
| 232 | { | ||
| 233 | assemblyNameValues.Add("Name", name); | ||
| 234 | } | ||
| 235 | |||
| 236 | string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); | ||
| 237 | if (null != processorArchitecture) | ||
| 238 | { | ||
| 239 | assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); | ||
| 240 | } | ||
| 241 | |||
| 242 | string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); | ||
| 243 | if (null != publicKeyToken) | ||
| 244 | { | ||
| 245 | bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase)); | ||
| 246 | |||
| 247 | // Managed code expects "null" instead of "neutral", and | ||
| 248 | // this won't be installed to the GAC since it's not signed anyway. | ||
| 249 | assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant()); | ||
| 250 | assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken); | ||
| 251 | } | ||
| 252 | else if (file.WixFile.AssemblyApplication == null) | ||
| 253 | { | ||
| 254 | throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component)); | ||
| 255 | } | ||
| 256 | |||
| 257 | string assemblyVersion = referenceIdentity.GetAttribute(null, "Version"); | ||
| 258 | if (null != version) | ||
| 259 | { | ||
| 260 | assemblyNameValues.Add("Version", assemblyVersion); | ||
| 261 | } | ||
| 262 | } | ||
| 263 | else | ||
| 264 | { | ||
| 265 | Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result))); | ||
| 266 | return; | ||
| 267 | } | ||
| 268 | |||
| 269 | Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
| 270 | if (assemblyNameValues.ContainsKey("name")) | ||
| 271 | { | ||
| 272 | this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]); | ||
| 273 | } | ||
| 274 | |||
| 275 | if (!String.IsNullOrEmpty(version)) | ||
| 276 | { | ||
| 277 | this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version); | ||
| 278 | } | ||
| 279 | |||
| 280 | if (assemblyNameValues.ContainsKey("version")) | ||
| 281 | { | ||
| 282 | string assemblyVersion = assemblyNameValues["version"]; | ||
| 283 | |||
| 284 | if (!targetNetfx1) | ||
| 285 | { | ||
| 286 | // There is a bug in v1 fusion that requires the assembly's "version" attribute | ||
| 287 | // to be equal to or longer than the "fileVersion" in length when its present; | ||
| 288 | // the workaround is to prepend zeroes to the last version number in the assembly | ||
| 289 | // version. | ||
| 290 | if (null != version && version.Length > assemblyVersion.Length) | ||
| 291 | { | ||
| 292 | string padding = new string('0', version.Length - assemblyVersion.Length); | ||
| 293 | string[] assemblyVersionNumbers = assemblyVersion.Split('.'); | ||
| 294 | |||
| 295 | if (assemblyVersionNumbers.Length > 0) | ||
| 296 | { | ||
| 297 | assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); | ||
| 298 | assemblyVersion = String.Join(".", assemblyVersionNumbers); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion); | ||
| 304 | } | ||
| 305 | |||
| 306 | if (assemblyNameValues.ContainsKey("culture")) | ||
| 307 | { | ||
| 308 | this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]); | ||
| 309 | } | ||
| 310 | |||
| 311 | if (assemblyNameValues.ContainsKey("publicKeyToken")) | ||
| 312 | { | ||
| 313 | this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]); | ||
| 314 | } | ||
| 315 | |||
| 316 | if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) | ||
| 317 | { | ||
| 318 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); | ||
| 319 | } | ||
| 320 | |||
| 321 | if (assemblyNameValues.ContainsKey("processorArchitecture")) | ||
| 322 | { | ||
| 323 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]); | ||
| 324 | } | ||
| 325 | |||
| 326 | // add the assembly name to the information cache | ||
| 327 | if (null != this.VariableCache) | ||
| 328 | { | ||
| 329 | string fileId = BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File); | ||
| 330 | string key = String.Concat("assemblyfullname.", fileId); | ||
| 331 | string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); | ||
| 332 | if (assemblyNameValues.ContainsKey("processorArchitecture")) | ||
| 333 | { | ||
| 334 | assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]); | ||
| 335 | } | ||
| 336 | |||
| 337 | this.VariableCache[key] = assemblyName; | ||
| 338 | |||
| 339 | // Add entries with the preserved case publicKeyToken | ||
| 340 | string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId); | ||
| 341 | this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]); | ||
| 342 | |||
| 343 | string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId); | ||
| 344 | this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"]; | ||
| 345 | } | ||
| 346 | } | ||
| 347 | else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType) | ||
| 348 | { | ||
| 349 | // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through | ||
| 350 | // all files like this. Even though this is a rare case it looks like we might be able to index the | ||
| 351 | // file earlier. | ||
| 352 | FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal)); | ||
| 353 | if (null == fileManifest) | ||
| 354 | { | ||
| 355 | Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest)); | ||
| 356 | } | ||
| 357 | |||
| 358 | string win32Type = null; | ||
| 359 | string win32Name = null; | ||
| 360 | string win32Version = null; | ||
| 361 | string win32ProcessorArchitecture = null; | ||
| 362 | string win32PublicKeyToken = null; | ||
| 363 | |||
| 364 | // loading the dom is expensive we want more performant APIs than the DOM | ||
| 365 | // Navigator is cheaper than dom. Perhaps there is a cheaper API still. | ||
| 366 | try | ||
| 367 | { | ||
| 368 | XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source); | ||
| 369 | XPathNavigator nav = doc.CreateNavigator(); | ||
| 370 | nav.MoveToRoot(); | ||
| 371 | |||
| 372 | // this assumes a particular schema for a win32 manifest and does not | ||
| 373 | // provide error checking if the file does not conform to schema. | ||
| 374 | // The fallback case here is that nothing is added to the MsiAssemblyName | ||
| 375 | // table for an out of tolerance Win32 manifest. Perhaps warnings needed. | ||
| 376 | if (nav.MoveToFirstChild()) | ||
| 377 | { | ||
| 378 | while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") | ||
| 379 | { | ||
| 380 | nav.MoveToNext(); | ||
| 381 | } | ||
| 382 | |||
| 383 | if (nav.MoveToFirstChild()) | ||
| 384 | { | ||
| 385 | bool hasNextSibling = true; | ||
| 386 | while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) | ||
| 387 | { | ||
| 388 | hasNextSibling = nav.MoveToNext(); | ||
| 389 | } | ||
| 390 | if (!hasNextSibling) | ||
| 391 | { | ||
| 392 | Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source)); | ||
| 393 | return; | ||
| 394 | } | ||
| 395 | |||
| 396 | if (nav.MoveToAttribute("type", String.Empty)) | ||
| 397 | { | ||
| 398 | win32Type = nav.Value; | ||
| 399 | nav.MoveToParent(); | ||
| 400 | } | ||
| 401 | |||
| 402 | if (nav.MoveToAttribute("name", String.Empty)) | ||
| 403 | { | ||
| 404 | win32Name = nav.Value; | ||
| 405 | nav.MoveToParent(); | ||
| 406 | } | ||
| 407 | |||
| 408 | if (nav.MoveToAttribute("version", String.Empty)) | ||
| 409 | { | ||
| 410 | win32Version = nav.Value; | ||
| 411 | nav.MoveToParent(); | ||
| 412 | } | ||
| 413 | |||
| 414 | if (nav.MoveToAttribute("processorArchitecture", String.Empty)) | ||
| 415 | { | ||
| 416 | win32ProcessorArchitecture = nav.Value; | ||
| 417 | nav.MoveToParent(); | ||
| 418 | } | ||
| 419 | |||
| 420 | if (nav.MoveToAttribute("publicKeyToken", String.Empty)) | ||
| 421 | { | ||
| 422 | win32PublicKeyToken = nav.Value; | ||
| 423 | nav.MoveToParent(); | ||
| 424 | } | ||
| 425 | } | ||
| 426 | } | ||
| 427 | } | ||
| 428 | catch (FileNotFoundException fe) | ||
| 429 | { | ||
| 430 | Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest")); | ||
| 431 | } | ||
| 432 | catch (XmlException xe) | ||
| 433 | { | ||
| 434 | Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message)); | ||
| 435 | } | ||
| 436 | |||
| 437 | Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
| 438 | if (!String.IsNullOrEmpty(win32Name)) | ||
| 439 | { | ||
| 440 | this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name); | ||
| 441 | } | ||
| 442 | |||
| 443 | if (!String.IsNullOrEmpty(win32Version)) | ||
| 444 | { | ||
| 445 | this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version); | ||
| 446 | } | ||
| 447 | |||
| 448 | if (!String.IsNullOrEmpty(win32Type)) | ||
| 449 | { | ||
| 450 | this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type); | ||
| 451 | } | ||
| 452 | |||
| 453 | if (!String.IsNullOrEmpty(win32ProcessorArchitecture)) | ||
| 454 | { | ||
| 455 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture); | ||
| 456 | } | ||
| 457 | |||
| 458 | if (!String.IsNullOrEmpty(win32PublicKeyToken)) | ||
| 459 | { | ||
| 460 | this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken); | ||
| 461 | } | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | /// <summary> | ||
| 466 | /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise | ||
| 467 | /// create a new row. | ||
| 468 | /// </summary> | ||
| 469 | /// <param name="assemblyNameTable">MsiAssemblyName table.</param> | ||
| 470 | /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param> | ||
| 471 | /// <param name="name">MsiAssemblyName name.</param> | ||
| 472 | /// <param name="value">MsiAssemblyName value.</param> | ||
| 473 | private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value) | ||
| 474 | { | ||
| 475 | // check for null value (this can occur when grabbing the file version from an assembly without one) | ||
| 476 | if (String.IsNullOrEmpty(value)) | ||
| 477 | { | ||
| 478 | Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name)); | ||
| 479 | } | ||
| 480 | else | ||
| 481 | { | ||
| 482 | Row assemblyNameRow = null; | ||
| 483 | |||
| 484 | // override directly authored value | ||
| 485 | foreach (Row row in assemblyNameTable.Rows) | ||
| 486 | { | ||
| 487 | if ((string)row[0] == file.File.Component && (string)row[1] == name) | ||
| 488 | { | ||
| 489 | assemblyNameRow = row; | ||
| 490 | break; | ||
| 491 | } | ||
| 492 | } | ||
| 493 | |||
| 494 | // 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. | ||
| 495 | if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType && | ||
| 496 | String.IsNullOrEmpty(file.WixFile.AssemblyApplication) && | ||
| 497 | !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase)) | ||
| 498 | { | ||
| 499 | Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value)); | ||
| 500 | } | ||
| 501 | |||
| 502 | if (null == assemblyNameRow) | ||
| 503 | { | ||
| 504 | assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers); | ||
| 505 | assemblyNameRow[0] = file.File.Component; | ||
| 506 | assemblyNameRow[1] = name; | ||
| 507 | assemblyNameRow[2] = value; | ||
| 508 | |||
| 509 | // put the MsiAssemblyName row in the same section as the related File row | ||
| 510 | assemblyNameRow.SectionId = file.File.SectionId; | ||
| 511 | |||
| 512 | if (null == file.AssemblyNames) | ||
| 513 | { | ||
| 514 | file.AssemblyNames = new List<Row>(); | ||
| 515 | } | ||
| 516 | |||
| 517 | file.AssemblyNames.Add(assemblyNameRow); | ||
| 518 | } | ||
| 519 | else | ||
| 520 | { | ||
| 521 | assemblyNameRow[2] = value; | ||
| 522 | } | ||
| 523 | |||
| 524 | if (this.VariableCache != null) | ||
| 525 | { | ||
| 526 | string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant(); | ||
| 527 | this.VariableCache[key] = (string)assemblyNameRow[2]; | ||
| 528 | } | ||
| 529 | } | ||
| 530 | } | ||
| 531 | } | ||
| 532 | } | ||
diff --git a/src/WixToolset.Core/Bind/DelayedField.cs b/src/WixToolset.Core/Bind/DelayedField.cs index 181ac3e3..6c56f27c 100644 --- a/src/WixToolset.Core/Bind/DelayedField.cs +++ b/src/WixToolset.Core/Bind/DelayedField.cs | |||
| @@ -1,18 +1,15 @@ | |||
| 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. | 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 | 2 | ||
| 3 | namespace WixToolset.Bind | 3 | namespace WixToolset.Core.Bind |
| 4 | { | 4 | { |
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using System.Text; | ||
| 9 | using WixToolset.Data; | 5 | using WixToolset.Data; |
| 6 | using WixToolset.Extensibility; | ||
| 10 | 7 | ||
| 11 | /// <summary> | 8 | /// <summary> |
| 12 | /// Structure used to hold a row and field that contain binder variables, which need to be resolved | 9 | /// Structure used to hold a row and field that contain binder variables, which need to be resolved |
| 13 | /// later, once the files have been resolved. | 10 | /// later, once the files have been resolved. |
| 14 | /// </summary> | 11 | /// </summary> |
| 15 | internal class DelayedField | 12 | internal class DelayedField : IDelayedField |
| 16 | { | 13 | { |
| 17 | /// <summary> | 14 | /// <summary> |
| 18 | /// Basic constructor for struct | 15 | /// Basic constructor for struct |
| @@ -28,11 +25,11 @@ namespace WixToolset.Bind | |||
| 28 | /// <summary> | 25 | /// <summary> |
| 29 | /// The row containing the field. | 26 | /// The row containing the field. |
| 30 | /// </summary> | 27 | /// </summary> |
| 31 | public Row Row { get; private set; } | 28 | public Row Row { get; } |
| 32 | 29 | ||
| 33 | /// <summary> | 30 | /// <summary> |
| 34 | /// The field needing further resolving. | 31 | /// The field needing further resolving. |
| 35 | /// </summary> | 32 | /// </summary> |
| 36 | public Field Field { get; private set; } | 33 | public Field Field { get; } |
| 37 | } | 34 | } |
| 38 | } | 35 | } |
diff --git a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs new file mode 100644 index 00000000..fc2b43c7 --- /dev/null +++ b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs | |||
| @@ -0,0 +1,16 @@ | |||
| 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.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility; | ||
| 7 | |||
| 8 | internal class ExpectedExtractFile : IExpectedExtractFile | ||
| 9 | { | ||
| 10 | public Uri Uri { get; set; } | ||
| 11 | |||
| 12 | public int EmbeddedFileIndex { get; set; } | ||
| 13 | |||
| 14 | public string OutputPath { get; set; } | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs index 0ecd0096..28fc4817 100644 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | |||
| @@ -1,6 +1,6 @@ | |||
| 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. | 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 | 2 | ||
| 3 | namespace WixToolset.Bind | 3 | namespace WixToolset.Core.Bind |
| 4 | { | 4 | { |
| 5 | using System; | 5 | using System; |
| 6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
| @@ -13,11 +13,11 @@ namespace WixToolset.Bind | |||
| 13 | /// <summary> | 13 | /// <summary> |
| 14 | /// Internal helper class used to extract embedded files. | 14 | /// Internal helper class used to extract embedded files. |
| 15 | /// </summary> | 15 | /// </summary> |
| 16 | internal sealed class ExtractEmbeddedFiles | 16 | internal class ExtractEmbeddedFiles |
| 17 | { | 17 | { |
| 18 | private Dictionary<Uri, SortedList<int, string>> filesWithEmbeddedFiles = new Dictionary<Uri, SortedList<int, string>>(); | 18 | private Dictionary<Uri, SortedList<int, string>> filesWithEmbeddedFiles = new Dictionary<Uri, SortedList<int, string>>(); |
| 19 | 19 | ||
| 20 | public IEnumerable<Uri> Uris { get { return this.filesWithEmbeddedFiles.Keys; } } | 20 | public IEnumerable<Uri> Uris => this.filesWithEmbeddedFiles.Keys; |
| 21 | 21 | ||
| 22 | /// <summary> | 22 | /// <summary> |
| 23 | /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path. | 23 | /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path. |
| @@ -53,15 +53,30 @@ namespace WixToolset.Bind | |||
| 53 | return extractPath; | 53 | return extractPath; |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | public IEnumerable<ExtractFile> GetExtractFilesForUri(Uri uri) | 56 | public IEnumerable<ExpectedExtractFile> GetExpectedEmbeddedFiles() |
| 57 | { | 57 | { |
| 58 | SortedList<int, string> extracts; | 58 | foreach (var uriWithExtracts in filesWithEmbeddedFiles) |
| 59 | if (!filesWithEmbeddedFiles.TryGetValue(uri, out extracts)) | 59 | { |
| 60 | foreach (var extracts in uriWithExtracts.Value) | ||
| 61 | { | ||
| 62 | yield return new ExpectedExtractFile | ||
| 63 | { | ||
| 64 | Uri = uriWithExtracts.Key, | ||
| 65 | EmbeddedFileIndex = extracts.Key, | ||
| 66 | OutputPath = extracts.Value, | ||
| 67 | }; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | public IEnumerable<ExpectedExtractFile> GetExtractFilesForUri(Uri uri) | ||
| 73 | { | ||
| 74 | if (!filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) | ||
| 60 | { | 75 | { |
| 61 | extracts = new SortedList<int, string>(); | 76 | extracts = new SortedList<int, string>(); |
| 62 | } | 77 | } |
| 63 | 78 | ||
| 64 | return extracts.Select(e => new ExtractFile() { EmbeddedFileIndex = e.Key, OutputPath = e.Value }); | 79 | return extracts.Select(e => new ExpectedExtractFile() { Uri = uri, EmbeddedFileIndex = e.Key, OutputPath = e.Value }); |
| 65 | } | 80 | } |
| 66 | 81 | ||
| 67 | private string HashUri(string uri) | 82 | private string HashUri(string uri) |
| @@ -72,12 +87,5 @@ namespace WixToolset.Bind | |||
| 72 | return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); | 87 | return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); |
| 73 | } | 88 | } |
| 74 | } | 89 | } |
| 75 | |||
| 76 | internal struct ExtractFile | ||
| 77 | { | ||
| 78 | public int EmbeddedFileIndex { get; set; } | ||
| 79 | |||
| 80 | public string OutputPath { get; set; } | ||
| 81 | } | ||
| 82 | } | 90 | } |
| 83 | } | 91 | } |
diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs index 68bfd8d7..7de40fb8 100644 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs | |||
| @@ -1,19 +1,26 @@ | |||
| 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. | 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 | 2 | ||
| 3 | namespace WixToolset.Bind | 3 | namespace WixToolset.Core.Bind |
| 4 | { | 4 | { |
| 5 | using System.Collections.Generic; | ||
| 5 | using System.IO; | 6 | using System.IO; |
| 7 | using System.Linq; | ||
| 6 | using System.Reflection; | 8 | using System.Reflection; |
| 7 | using WixToolset.Data; | 9 | using WixToolset.Data; |
| 10 | using WixToolset.Extensibility; | ||
| 8 | 11 | ||
| 9 | internal class ExtractEmbeddedFilesCommand : ICommand | 12 | public class ExtractEmbeddedFilesCommand |
| 10 | { | 13 | { |
| 11 | public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } | 14 | public IEnumerable<IExpectedExtractFile> FilesWithEmbeddedFiles { private get; set; } |
| 12 | 15 | ||
| 13 | public void Execute() | 16 | public void Execute() |
| 14 | { | 17 | { |
| 15 | foreach (var baseUri in this.FilesWithEmbeddedFiles.Uris) | 18 | var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri); |
| 19 | |||
| 20 | foreach (var expectedEmbeddedFileByUri in group) | ||
| 16 | { | 21 | { |
| 22 | var baseUri = expectedEmbeddedFileByUri.Key; | ||
| 23 | |||
| 17 | Stream stream = null; | 24 | Stream stream = null; |
| 18 | try | 25 | try |
| 19 | { | 26 | { |
| @@ -34,18 +41,20 @@ namespace WixToolset.Bind | |||
| 34 | 41 | ||
| 35 | using (FileStructure fs = FileStructure.Read(stream)) | 42 | using (FileStructure fs = FileStructure.Read(stream)) |
| 36 | { | 43 | { |
| 37 | foreach (var embeddedFile in this.FilesWithEmbeddedFiles.GetExtractFilesForUri(baseUri)) | 44 | var uniqueIndicies = new SortedSet<int>(); |
| 45 | |||
| 46 | foreach (var embeddedFile in expectedEmbeddedFileByUri) | ||
| 38 | { | 47 | { |
| 39 | fs.ExtractEmbeddedFile(embeddedFile.EmbeddedFileIndex, embeddedFile.OutputPath); | 48 | if (uniqueIndicies.Add(embeddedFile.EmbeddedFileIndex)) |
| 49 | { | ||
| 50 | fs.ExtractEmbeddedFile(embeddedFile.EmbeddedFileIndex, embeddedFile.OutputPath); | ||
| 51 | } | ||
| 40 | } | 52 | } |
| 41 | } | 53 | } |
| 42 | } | 54 | } |
| 43 | finally | 55 | finally |
| 44 | { | 56 | { |
| 45 | if (null != stream) | 57 | stream?.Close(); |
| 46 | { | ||
| 47 | stream.Close(); | ||
| 48 | } | ||
| 49 | } | 58 | } |
| 50 | } | 59 | } |
| 51 | } | 60 | } |
diff --git a/src/WixToolset.Core/Bind/Databases/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs index 37115c97..aaa6b7d3 100644 --- a/src/WixToolset.Core/Bind/Databases/FileFacade.cs +++ b/src/WixToolset.Core/Bind/FileFacade.cs | |||
| @@ -1,6 +1,6 @@ | |||
| 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. | 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 | 2 | ||
| 3 | namespace WixToolset.Bind.Databases | 3 | namespace WixToolset.Core.Bind |
| 4 | { | 4 | { |
| 5 | using System.Collections.Generic; | 5 | using System.Collections.Generic; |
| 6 | using WixToolset.Data; | 6 | using WixToolset.Data; |
diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs new file mode 100644 index 00000000..8d624e6f --- /dev/null +++ b/src/WixToolset.Core/Bind/FileResolver.cs | |||
| @@ -0,0 +1,231 @@ | |||
| 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.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Runtime.InteropServices; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Bind; | ||
| 12 | using WixToolset.Extensibility; | ||
| 13 | |||
| 14 | internal class FileResolver | ||
| 15 | { | ||
| 16 | private const string BindPathOpenString = "!(bindpath."; | ||
| 17 | |||
| 18 | private FileResolver(IEnumerable<BindPath> bindPaths) | ||
| 19 | { | ||
| 20 | this.BindPaths = (bindPaths ?? Array.Empty<BindPath>()).ToLookup(b => b.Stage); | ||
| 21 | this.RebaseTarget = this.BindPaths[BindStage.Target].Any(); | ||
| 22 | this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any(); | ||
| 23 | } | ||
| 24 | |||
| 25 | public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<IBinderExtension> extensions) : this(bindPaths) | ||
| 26 | { | ||
| 27 | this.BinderExtensions = extensions ?? Array.Empty<IBinderExtension>(); | ||
| 28 | } | ||
| 29 | |||
| 30 | public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<ILibrarianExtension> extensions) : this(bindPaths) | ||
| 31 | { | ||
| 32 | this.LibrarianExtensions = extensions ?? Array.Empty<ILibrarianExtension>(); | ||
| 33 | } | ||
| 34 | |||
| 35 | private ILookup<BindStage, BindPath> BindPaths { get; } | ||
| 36 | |||
| 37 | public bool RebaseTarget { get; } | ||
| 38 | |||
| 39 | public bool RebaseUpdated { get; } | ||
| 40 | |||
| 41 | private IEnumerable<IBinderExtension> BinderExtensions { get; } | ||
| 42 | |||
| 43 | private IEnumerable<ILibrarianExtension> LibrarianExtensions { get; } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Copies a file. | ||
| 47 | /// </summary> | ||
| 48 | /// <param name="source">The file to copy.</param> | ||
| 49 | /// <param name="destination">The destination file.</param> | ||
| 50 | /// <param name="overwrite">true if the destination file can be overwritten; otherwise, false.</param> | ||
| 51 | public bool CopyFile(string source, string destination, bool overwrite) | ||
| 52 | { | ||
| 53 | foreach (var extension in this.BinderExtensions) | ||
| 54 | { | ||
| 55 | if (extension.CopyFile(source, destination, overwrite)) | ||
| 56 | { | ||
| 57 | return true; | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | if (overwrite && File.Exists(destination)) | ||
| 62 | { | ||
| 63 | File.Delete(destination); | ||
| 64 | } | ||
| 65 | |||
| 66 | if (!CreateHardLink(destination, source, IntPtr.Zero)) | ||
| 67 | { | ||
| 68 | #if DEBUG | ||
| 69 | int er = Marshal.GetLastWin32Error(); | ||
| 70 | #endif | ||
| 71 | |||
| 72 | File.Copy(source, destination, overwrite); | ||
| 73 | } | ||
| 74 | |||
| 75 | return true; | ||
| 76 | } | ||
| 77 | |||
| 78 | /// <summary> | ||
| 79 | /// Moves a file. | ||
| 80 | /// </summary> | ||
| 81 | /// <param name="source">The file to move.</param> | ||
| 82 | /// <param name="destination">The destination file.</param> | ||
| 83 | public bool MoveFile(string source, string destination, bool overwrite) | ||
| 84 | { | ||
| 85 | foreach (var extension in this.BinderExtensions) | ||
| 86 | { | ||
| 87 | if (extension.MoveFile(source, destination, overwrite)) | ||
| 88 | { | ||
| 89 | return true; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | if (overwrite && File.Exists(destination)) | ||
| 94 | { | ||
| 95 | File.Delete(destination); | ||
| 96 | } | ||
| 97 | |||
| 98 | var directory = Path.GetDirectoryName(destination); | ||
| 99 | if (!String.IsNullOrEmpty(directory)) | ||
| 100 | { | ||
| 101 | Directory.CreateDirectory(directory); | ||
| 102 | } | ||
| 103 | |||
| 104 | File.Move(source, destination); | ||
| 105 | |||
| 106 | return true; | ||
| 107 | } | ||
| 108 | |||
| 109 | public string Resolve(SourceLineNumber sourceLineNumbers, string table, string path) | ||
| 110 | { | ||
| 111 | foreach (var extension in this.LibrarianExtensions) | ||
| 112 | { | ||
| 113 | var resolved = extension.Resolve(sourceLineNumbers, table, path); | ||
| 114 | |||
| 115 | if (null != resolved) | ||
| 116 | { | ||
| 117 | return resolved; | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | return this.ResolveUsingBindPaths(path, table, sourceLineNumbers, BindStage.Normal); | ||
| 122 | } | ||
| 123 | |||
| 124 | /// <summary> | ||
| 125 | /// Resolves the source path of a file using binder extensions. | ||
| 126 | /// </summary> | ||
| 127 | /// <param name="source">Original source value.</param> | ||
| 128 | /// <param name="type">Optional type of source file being resolved.</param> | ||
| 129 | /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param> | ||
| 130 | /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param> | ||
| 131 | /// <returns>Should return a valid path for the stream to be imported.</returns> | ||
| 132 | public string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) | ||
| 133 | { | ||
| 134 | foreach (var extension in this.BinderExtensions) | ||
| 135 | { | ||
| 136 | var resolved = extension.ResolveFile(source, type, sourceLineNumbers, bindStage); | ||
| 137 | |||
| 138 | if (null != resolved) | ||
| 139 | { | ||
| 140 | return resolved; | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | return this.ResolveUsingBindPaths(source, type, sourceLineNumbers, bindStage); | ||
| 145 | } | ||
| 146 | |||
| 147 | private string ResolveUsingBindPaths(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) | ||
| 148 | { | ||
| 149 | string resolved = null; | ||
| 150 | |||
| 151 | // If the file exists, we're good to go. | ||
| 152 | if (CheckFileExists(source)) | ||
| 153 | { | ||
| 154 | resolved = source; | ||
| 155 | } | ||
| 156 | else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist. | ||
| 157 | { | ||
| 158 | resolved = null; | ||
| 159 | } | ||
| 160 | else // not a rooted path so let's try applying all the different source resolution options. | ||
| 161 | { | ||
| 162 | string bindName = String.Empty; | ||
| 163 | var path = source; | ||
| 164 | string pathWithoutSourceDir = null; | ||
| 165 | |||
| 166 | if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) | ||
| 167 | { | ||
| 168 | int closeParen = source.IndexOf(')', BindPathOpenString.Length); | ||
| 169 | if (-1 != closeParen) | ||
| 170 | { | ||
| 171 | bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); | ||
| 172 | path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace. | ||
| 173 | path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted. | ||
| 174 | } | ||
| 175 | } | ||
| 176 | else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal)) | ||
| 177 | { | ||
| 178 | pathWithoutSourceDir = path.Substring(10); | ||
| 179 | } | ||
| 180 | |||
| 181 | var bindPaths = this.BindPaths[bindStage]; | ||
| 182 | |||
| 183 | foreach (var bindPath in bindPaths) | ||
| 184 | { | ||
| 185 | if (!String.IsNullOrEmpty(pathWithoutSourceDir)) | ||
| 186 | { | ||
| 187 | var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); | ||
| 188 | |||
| 189 | if (CheckFileExists(filePath)) | ||
| 190 | { | ||
| 191 | resolved = filePath; | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | if (String.IsNullOrEmpty(resolved)) | ||
| 196 | { | ||
| 197 | var filePath = Path.Combine(bindPath.Path, path); | ||
| 198 | |||
| 199 | if (CheckFileExists(filePath)) | ||
| 200 | { | ||
| 201 | resolved = filePath; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | if (null == resolved) | ||
| 208 | { | ||
| 209 | throw new WixFileNotFoundException(sourceLineNumbers, source, type); | ||
| 210 | } | ||
| 211 | |||
| 212 | // Didn't find the file. | ||
| 213 | return resolved; | ||
| 214 | } | ||
| 215 | |||
| 216 | private static bool CheckFileExists(string path) | ||
| 217 | { | ||
| 218 | try | ||
| 219 | { | ||
| 220 | return File.Exists(path); | ||
| 221 | } | ||
| 222 | catch (ArgumentException) | ||
| 223 | { | ||
| 224 | throw new WixException(WixErrors.IllegalCharactersInPath(path)); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
| 229 | private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); | ||
| 230 | } | ||
| 231 | } | ||
diff --git a/src/WixToolset.Core/Bind/FileTransfer.cs b/src/WixToolset.Core/Bind/FileTransfer.cs deleted file mode 100644 index 64bbc5f1..00000000 --- a/src/WixToolset.Core/Bind/FileTransfer.cs +++ /dev/null | |||
| @@ -1,113 +0,0 @@ | |||
| 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.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset; | ||
| 8 | using WixToolset.Data; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Structure used for all file transfer information. | ||
| 12 | /// </summary> | ||
| 13 | internal class FileTransfer | ||
| 14 | { | ||
| 15 | /// <summary>Source path to file.</summary> | ||
| 16 | public string Source { get; set; } | ||
| 17 | |||
| 18 | /// <summary>Destination path for file.</summary> | ||
| 19 | public string Destination { get; set; } | ||
| 20 | |||
| 21 | /// <summary>Flag if file should be moved (optimal).</summary> | ||
| 22 | public bool Move { get; set; } | ||
| 23 | |||
| 24 | /// <summary>Optional source line numbers where this file transfer orginated.</summary> | ||
| 25 | public SourceLineNumber SourceLineNumbers { get; set; } | ||
| 26 | |||
| 27 | /// <summary>Optional type of file this transfer is moving or copying.</summary> | ||
| 28 | public string Type { get; set; } | ||
| 29 | |||
| 30 | /// <summary>Indicates whether the file transer was a built by this build or copied from other some build.</summary> | ||
| 31 | internal bool Built { get; set; } | ||
| 32 | |||
| 33 | /// <summary>Set during layout of media when the file transfer when the source and target resolve to the same path.</summary> | ||
| 34 | internal bool Redundant { get; set; } | ||
| 35 | |||
| 36 | /// <summary> | ||
| 37 | /// Prefer the TryCreate() method to create FileTransfer objects. | ||
| 38 | /// </summary> | ||
| 39 | /// <param name="source">Source path to file.</param> | ||
| 40 | /// <param name="destination">Destination path for file.</param> | ||
| 41 | /// <param name="move">File if file should be moved (optimal).</param> | ||
| 42 | /// <param name="type">Optional type of file this transfer is transferring.</param> | ||
| 43 | /// <param name="sourceLineNumbers">Optional source line numbers wher this transfer originated.</param> | ||
| 44 | public FileTransfer(string source, string destination, bool move, string type = null, SourceLineNumber sourceLineNumbers = null) | ||
| 45 | { | ||
| 46 | this.Source = source; | ||
| 47 | this.Destination = destination; | ||
| 48 | this.Move = move; | ||
| 49 | |||
| 50 | this.Type = type; | ||
| 51 | this.SourceLineNumbers = sourceLineNumbers; | ||
| 52 | } | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Creates a file transfer if the source and destination are different. | ||
| 56 | /// </summary> | ||
| 57 | /// <param name="source">Source path to file.</param> | ||
| 58 | /// <param name="destination">Destination path for file.</param> | ||
| 59 | /// <param name="move">File if file should be moved (optimal).</param> | ||
| 60 | /// <param name="type">Optional type of file this transfer is transferring.</param> | ||
| 61 | /// <param name="sourceLineNumbers">Optional source line numbers wher this transfer originated.</param> | ||
| 62 | /// <returns>true if the source and destination are the different, false if no file transfer is created.</returns> | ||
| 63 | public static bool TryCreate(string source, string destination, bool move, string type, SourceLineNumber sourceLineNumbers, out FileTransfer transfer) | ||
| 64 | { | ||
| 65 | string sourceFullPath = GetValidatedFullPath(sourceLineNumbers, source); | ||
| 66 | |||
| 67 | string fileLayoutFullPath = GetValidatedFullPath(sourceLineNumbers, destination); | ||
| 68 | |||
| 69 | // if the current source path (where we know that the file already exists) and the resolved | ||
| 70 | // path as dictated by the Directory table are not the same, then propagate the file. The | ||
| 71 | // image that we create may have already been done by some other process other than the linker, so | ||
| 72 | // there is no reason to copy the files to the resolved source if they are already there. | ||
| 73 | if (String.Equals(sourceFullPath, fileLayoutFullPath, StringComparison.OrdinalIgnoreCase)) | ||
| 74 | { | ||
| 75 | transfer = null; | ||
| 76 | return false; | ||
| 77 | } | ||
| 78 | |||
| 79 | transfer = new FileTransfer(source, destination, move, type, sourceLineNumbers); | ||
| 80 | return true; | ||
| 81 | } | ||
| 82 | |||
| 83 | private static string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path) | ||
| 84 | { | ||
| 85 | string result; | ||
| 86 | |||
| 87 | try | ||
| 88 | { | ||
| 89 | result = Path.GetFullPath(path); | ||
| 90 | |||
| 91 | string filename = Path.GetFileName(result); | ||
| 92 | |||
| 93 | foreach (string reservedName in Common.ReservedFileNames) | ||
| 94 | { | ||
| 95 | if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase)) | ||
| 96 | { | ||
| 97 | throw new WixException(WixErrors.InvalidFileName(sourceLineNumbers, path)); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | catch (System.ArgumentException) | ||
| 102 | { | ||
| 103 | throw new WixException(WixErrors.InvalidFileName(sourceLineNumbers, path)); | ||
| 104 | } | ||
| 105 | catch (System.IO.PathTooLongException) | ||
| 106 | { | ||
| 107 | throw new WixException(WixErrors.PathTooLong(sourceLineNumbers, path)); | ||
| 108 | } | ||
| 109 | |||
| 110 | return result; | ||
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
diff --git a/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs deleted file mode 100644 index fdf1ab32..00000000 --- a/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs +++ /dev/null | |||
| @@ -1,335 +0,0 @@ | |||
| 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.Bind | ||
| 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 : ICommand | ||
| 17 | { | ||
| 18 | public int Codepage { private get; set; } | ||
| 19 | |||
| 20 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
| 21 | |||
| 22 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | ||
| 23 | |||
| 24 | /// <summary> | ||
| 25 | /// Whether to keep columns added in a transform. | ||
| 26 | /// </summary> | ||
| 27 | public bool KeepAddedColumns { private get; set; } | ||
| 28 | |||
| 29 | public Output Output { private get; set; } | ||
| 30 | |||
| 31 | public string OutputPath { private get; set; } | ||
| 32 | |||
| 33 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 34 | |||
| 35 | public string TempFilesLocation { private get; set; } | ||
| 36 | |||
| 37 | /// <summary> | ||
| 38 | /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files. | ||
| 39 | /// </summary> | ||
| 40 | public bool SuppressAddingValidationRows { private get; set; } | ||
| 41 | |||
| 42 | public bool UseSubDirectory { private get; set; } | ||
| 43 | |||
| 44 | public void Execute() | ||
| 45 | { | ||
| 46 | // Add the _Validation rows. | ||
| 47 | if (!this.SuppressAddingValidationRows) | ||
| 48 | { | ||
| 49 | Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); | ||
| 50 | |||
| 51 | foreach (Table table in this.Output.Tables) | ||
| 52 | { | ||
| 53 | if (!table.Definition.Unreal) | ||
| 54 | { | ||
| 55 | // Add the validation rows for this table. | ||
| 56 | table.Definition.AddValidationRows(validationTable); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | // Set the base directory. | ||
| 62 | string baseDirectory = this.TempFilesLocation; | ||
| 63 | |||
| 64 | if (this.UseSubDirectory) | ||
| 65 | { | ||
| 66 | string filename = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
| 67 | baseDirectory = Path.Combine(baseDirectory, filename); | ||
| 68 | |||
| 69 | // make sure the directory exists | ||
| 70 | Directory.CreateDirectory(baseDirectory); | ||
| 71 | } | ||
| 72 | |||
| 73 | try | ||
| 74 | { | ||
| 75 | OpenDatabase type = OpenDatabase.CreateDirect; | ||
| 76 | |||
| 77 | // set special flag for patch files | ||
| 78 | if (OutputType.Patch == this.Output.Type) | ||
| 79 | { | ||
| 80 | type |= OpenDatabase.OpenPatchFile; | ||
| 81 | } | ||
| 82 | |||
| 83 | #if DEBUG | ||
| 84 | Console.WriteLine("Opening database at: {0}", this.OutputPath); | ||
| 85 | #endif | ||
| 86 | |||
| 87 | using (Database db = new Database(this.OutputPath, type)) | ||
| 88 | { | ||
| 89 | // Localize the codepage if a value was specified directly. | ||
| 90 | if (-1 != this.Codepage) | ||
| 91 | { | ||
| 92 | this.Output.Codepage = this.Codepage; | ||
| 93 | } | ||
| 94 | |||
| 95 | // if we're not using the default codepage, import a new one into our | ||
| 96 | // database before we add any tables (or the tables would be added | ||
| 97 | // with the wrong codepage). | ||
| 98 | if (0 != this.Output.Codepage) | ||
| 99 | { | ||
| 100 | this.SetDatabaseCodepage(db, this.Output.Codepage); | ||
| 101 | } | ||
| 102 | |||
| 103 | foreach (Table table in this.Output.Tables) | ||
| 104 | { | ||
| 105 | Table importTable = table; | ||
| 106 | bool hasBinaryColumn = false; | ||
| 107 | |||
| 108 | // Skip all unreal tables other than _Streams. | ||
| 109 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
| 110 | { | ||
| 111 | continue; | ||
| 112 | } | ||
| 113 | |||
| 114 | // Do not put the _Validation table in patches, it is not needed. | ||
| 115 | if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name) | ||
| 116 | { | ||
| 117 | continue; | ||
| 118 | } | ||
| 119 | |||
| 120 | // The only way to import binary data is to copy it to a local subdirectory first. | ||
| 121 | // To avoid this extra copying and perf hit, import an empty table with the same | ||
| 122 | // definition and later import the binary data from source using records. | ||
| 123 | foreach (ColumnDefinition columnDefinition in table.Definition.Columns) | ||
| 124 | { | ||
| 125 | if (ColumnType.Object == columnDefinition.Type) | ||
| 126 | { | ||
| 127 | importTable = new Table(table.Section, table.Definition); | ||
| 128 | hasBinaryColumn = true; | ||
| 129 | break; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | // Create the table via IDT import. | ||
| 134 | if ("_Streams" != importTable.Name) | ||
| 135 | { | ||
| 136 | try | ||
| 137 | { | ||
| 138 | db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns); | ||
| 139 | } | ||
| 140 | catch (WixInvalidIdtException) | ||
| 141 | { | ||
| 142 | // If ValidateRows finds anything it doesn't like, it throws | ||
| 143 | importTable.ValidateRows(); | ||
| 144 | |||
| 145 | // Otherwise we rethrow the InvalidIdt | ||
| 146 | throw; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | // insert the rows via SQL query if this table contains object fields | ||
| 151 | if (hasBinaryColumn) | ||
| 152 | { | ||
| 153 | StringBuilder query = new StringBuilder("SELECT "); | ||
| 154 | |||
| 155 | // Build the query for the view. | ||
| 156 | bool firstColumn = true; | ||
| 157 | foreach (ColumnDefinition columnDefinition in table.Definition.Columns) | ||
| 158 | { | ||
| 159 | if (!firstColumn) | ||
| 160 | { | ||
| 161 | query.Append(","); | ||
| 162 | } | ||
| 163 | |||
| 164 | query.AppendFormat(" `{0}`", columnDefinition.Name); | ||
| 165 | firstColumn = false; | ||
| 166 | } | ||
| 167 | query.AppendFormat(" FROM `{0}`", table.Name); | ||
| 168 | |||
| 169 | using (View tableView = db.OpenExecuteView(query.ToString())) | ||
| 170 | { | ||
| 171 | // Import each row containing a stream | ||
| 172 | foreach (Row row in table.Rows) | ||
| 173 | { | ||
| 174 | using (Record record = new Record(table.Definition.Columns.Count)) | ||
| 175 | { | ||
| 176 | StringBuilder streamName = new StringBuilder(); | ||
| 177 | bool needStream = false; | ||
| 178 | |||
| 179 | // the _Streams table doesn't prepend the table name (or a period) | ||
| 180 | if ("_Streams" != table.Name) | ||
| 181 | { | ||
| 182 | streamName.Append(table.Name); | ||
| 183 | } | ||
| 184 | |||
| 185 | for (int i = 0; i < table.Definition.Columns.Count; i++) | ||
| 186 | { | ||
| 187 | ColumnDefinition columnDefinition = table.Definition.Columns[i]; | ||
| 188 | |||
| 189 | switch (columnDefinition.Type) | ||
| 190 | { | ||
| 191 | case ColumnType.Localized: | ||
| 192 | case ColumnType.Preserved: | ||
| 193 | case ColumnType.String: | ||
| 194 | if (columnDefinition.PrimaryKey) | ||
| 195 | { | ||
| 196 | if (0 < streamName.Length) | ||
| 197 | { | ||
| 198 | streamName.Append("."); | ||
| 199 | } | ||
| 200 | streamName.Append((string)row[i]); | ||
| 201 | } | ||
| 202 | |||
| 203 | record.SetString(i + 1, (string)row[i]); | ||
| 204 | break; | ||
| 205 | case ColumnType.Number: | ||
| 206 | record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture)); | ||
| 207 | break; | ||
| 208 | case ColumnType.Object: | ||
| 209 | if (null != row[i]) | ||
| 210 | { | ||
| 211 | needStream = true; | ||
| 212 | try | ||
| 213 | { | ||
| 214 | record.SetStream(i + 1, (string)row[i]); | ||
| 215 | } | ||
| 216 | catch (Win32Exception e) | ||
| 217 | { | ||
| 218 | if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME | ||
| 219 | { | ||
| 220 | throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i])); | ||
| 221 | } | ||
| 222 | else | ||
| 223 | { | ||
| 224 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
| 228 | break; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | // stream names are created by concatenating the name of the table with the values | ||
| 233 | // of the primary key (delimited by periods) | ||
| 234 | // check for a stream name that is more than 62 characters long (the maximum allowed length) | ||
| 235 | if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) | ||
| 236 | { | ||
| 237 | Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); | ||
| 238 | } | ||
| 239 | else // add the row to the database | ||
| 240 | { | ||
| 241 | tableView.Modify(ModifyView.Assign, record); | ||
| 242 | } | ||
| 243 | } | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | // Remove rows from the _Streams table for wixpdbs. | ||
| 248 | if ("_Streams" == table.Name) | ||
| 249 | { | ||
| 250 | table.Rows.Clear(); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | // Insert substorages (usually transforms inside a patch or instance transforms in a package). | ||
| 256 | if (0 < this.Output.SubStorages.Count) | ||
| 257 | { | ||
| 258 | using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) | ||
| 259 | { | ||
| 260 | foreach (SubStorage subStorage in this.Output.SubStorages) | ||
| 261 | { | ||
| 262 | string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); | ||
| 263 | |||
| 264 | // Bind the transform. | ||
| 265 | this.BindTransform(subStorage.Data, transformFile); | ||
| 266 | |||
| 267 | if (Messaging.Instance.EncounteredError) | ||
| 268 | { | ||
| 269 | continue; | ||
| 270 | } | ||
| 271 | |||
| 272 | // add the storage | ||
| 273 | using (Record record = new Record(2)) | ||
| 274 | { | ||
| 275 | record.SetString(1, subStorage.Name); | ||
| 276 | record.SetStream(2, transformFile); | ||
| 277 | storagesView.Modify(ModifyView.Assign, record); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | // We're good, commit the changes to the new database. | ||
| 284 | db.Commit(); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | catch (IOException) | ||
| 288 | { | ||
| 289 | // TODO: this error message doesn't seem specific enough | ||
| 290 | throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath); | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | private void BindTransform(Output transform, string outputPath) | ||
| 295 | { | ||
| 296 | BindTransformCommand command = new BindTransformCommand(); | ||
| 297 | command.Extensions = this.Extensions; | ||
| 298 | command.FileManagers = this.FileManagers; | ||
| 299 | command.TempFilesLocation = this.TempFilesLocation; | ||
| 300 | command.Transform = transform; | ||
| 301 | command.OutputPath = outputPath; | ||
| 302 | command.TableDefinitions = this.TableDefinitions; | ||
| 303 | command.Execute(); | ||
| 304 | } | ||
| 305 | |||
| 306 | /// <summary> | ||
| 307 | /// Sets the codepage of a database. | ||
| 308 | /// </summary> | ||
| 309 | /// <param name="db">Database to set codepage into.</param> | ||
| 310 | /// <param name="output">Output with the codepage for the database.</param> | ||
| 311 | private void SetDatabaseCodepage(Database db, int codepage) | ||
| 312 | { | ||
| 313 | // write out the _ForceCodepage IDT file | ||
| 314 | string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"); | ||
| 315 | using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) | ||
| 316 | { | ||
| 317 | idtFile.WriteLine(); // dummy column name record | ||
| 318 | idtFile.WriteLine(); // dummy column definition record | ||
| 319 | idtFile.Write(codepage); | ||
| 320 | idtFile.WriteLine("\t_ForceCodepage"); | ||
| 321 | } | ||
| 322 | |||
| 323 | // try to import the table into the MSI | ||
| 324 | try | ||
| 325 | { | ||
| 326 | db.Import(idtPath); | ||
| 327 | } | ||
| 328 | catch (WixInvalidIdtException) | ||
| 329 | { | ||
| 330 | // the IDT should be valid, so an invalid code page was given | ||
| 331 | throw new WixException(WixErrors.IllegalCodepage(codepage)); | ||
| 332 | } | ||
| 333 | } | ||
| 334 | } | ||
| 335 | } | ||
diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs index 4ffe9e82..15365c2a 100644 --- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs | |||
| @@ -1,23 +1,22 @@ | |||
| 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. | 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 | 2 | ||
| 3 | namespace WixToolset.Bind | 3 | namespace WixToolset.Core.Bind |
| 4 | { | 4 | { |
| 5 | using System; | 5 | using System; |
| 6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
| 7 | using System.Globalization; | 7 | using System.Globalization; |
| 8 | using System.Linq; | ||
| 9 | using System.Text; | ||
| 10 | using WixToolset.Data; | 8 | using WixToolset.Data; |
| 9 | using WixToolset.Extensibility; | ||
| 11 | 10 | ||
| 12 | /// <summary> | 11 | /// <summary> |
| 13 | /// Resolves the fields which had variables that needed to be resolved after the file information | 12 | /// Resolves the fields which had variables that needed to be resolved after the file information |
| 14 | /// was loaded. | 13 | /// was loaded. |
| 15 | /// </summary> | 14 | /// </summary> |
| 16 | internal class ResolveDelayedFieldsCommand : ICommand | 15 | public class ResolveDelayedFieldsCommand : ICommand |
| 17 | { | 16 | { |
| 18 | public OutputType OutputType { private get; set;} | 17 | public OutputType OutputType { private get; set;} |
| 19 | 18 | ||
| 20 | public IEnumerable<DelayedField> DelayedFields { private get; set;} | 19 | public IEnumerable<IDelayedField> DelayedFields { private get; set;} |
| 21 | 20 | ||
| 22 | public IDictionary<string, string> VariableCache { private get; set; } | 21 | public IDictionary<string, string> VariableCache { private get; set; } |
| 23 | 22 | ||
| @@ -29,9 +28,9 @@ namespace WixToolset.Bind | |||
| 29 | /// <param name="modularizationGuid">The modularization guid (used in case of a merge module).</param> | 28 | /// <param name="modularizationGuid">The modularization guid (used in case of a merge module).</param> |
| 30 | public void Execute() | 29 | public void Execute() |
| 31 | { | 30 | { |
| 32 | List<DelayedField> deferredFields = new List<DelayedField>(); | 31 | var deferredFields = new List<IDelayedField>(); |
| 33 | 32 | ||
| 34 | foreach (DelayedField delayedField in this.DelayedFields) | 33 | foreach (IDelayedField delayedField in this.DelayedFields) |
| 35 | { | 34 | { |
| 36 | try | 35 | try |
| 37 | { | 36 | { |
| @@ -43,7 +42,7 @@ namespace WixToolset.Bind | |||
| 43 | string value = WixVariableResolver.ResolveDelayedVariables(propertyRow.SourceLineNumbers, (string)delayedField.Field.Data, this.VariableCache); | 42 | string value = WixVariableResolver.ResolveDelayedVariables(propertyRow.SourceLineNumbers, (string)delayedField.Field.Data, this.VariableCache); |
| 44 | 43 | ||
| 45 | // update the variable cache with the new value | 44 | // update the variable cache with the new value |
| 46 | string key = String.Concat("property.", BindDatabaseCommand.Demodularize(this.OutputType, this.ModularizationGuid, (string)propertyRow[0])); | 45 | string key = String.Concat("property.", Common.Demodularize(this.OutputType, this.ModularizationGuid, (string)propertyRow[0])); |
| 47 | this.VariableCache[key] = value; | 46 | this.VariableCache[key] = value; |
| 48 | 47 | ||
| 49 | // update the field data | 48 | // update the field data |
diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs index 4caec9b4..f4f4f9e8 100644 --- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | |||
| @@ -1,29 +1,32 @@ | |||
| 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. | 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 | 2 | ||
| 3 | namespace WixToolset.Bind | 3 | namespace WixToolset.Core.Bind |
| 4 | { | 4 | { |
| 5 | using System.Collections.Generic; | 5 | using System.Collections.Generic; |
| 6 | using WixToolset.Data; | 6 | using WixToolset.Data; |
| 7 | using WixToolset.Data.Bind; | ||
| 7 | using WixToolset.Extensibility; | 8 | using WixToolset.Extensibility; |
| 8 | 9 | ||
| 9 | /// <summary> | 10 | /// <summary> |
| 10 | /// Resolve source fields in the tables included in the output | 11 | /// Resolve source fields in the tables included in the output |
| 11 | /// </summary> | 12 | /// </summary> |
| 12 | internal class ResolveFieldsCommand : ICommand | 13 | internal class ResolveFieldsCommand |
| 13 | { | 14 | { |
| 14 | public TableIndexedCollection Tables { private get; set; } | 15 | public bool BuildingPatch { private get; set; } |
| 15 | 16 | ||
| 16 | public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } | 17 | public IBindVariableResolver BindVariableResolver { private get; set; } |
| 17 | 18 | ||
| 18 | public BinderFileManagerCore FileManagerCore { private get; set; } | 19 | public IEnumerable<BindPath> BindPaths { private get; set; } |
| 19 | 20 | ||
| 20 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | 21 | public IEnumerable<IBinderExtension> Extensions { private get; set; } |
| 21 | 22 | ||
| 22 | public bool SupportDelayedResolution { private get; set; } | 23 | public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } |
| 24 | |||
| 25 | public string IntermediateFolder { private get; set; } | ||
| 23 | 26 | ||
| 24 | public string TempFilesLocation { private get; set; } | 27 | public TableIndexedCollection Tables { private get; set; } |
| 25 | 28 | ||
| 26 | public WixVariableResolver WixVariableResolver { private get; set; } | 29 | public bool SupportDelayedResolution { private get; set; } |
| 27 | 30 | ||
| 28 | public IEnumerable<DelayedField> DelayedFields { get; private set; } | 31 | public IEnumerable<DelayedField> DelayedFields { get; private set; } |
| 29 | 32 | ||
| @@ -31,6 +34,8 @@ namespace WixToolset.Bind | |||
| 31 | { | 34 | { |
| 32 | List<DelayedField> delayedFields = this.SupportDelayedResolution ? new List<DelayedField>() : null; | 35 | List<DelayedField> delayedFields = this.SupportDelayedResolution ? new List<DelayedField>() : null; |
| 33 | 36 | ||
| 37 | var fileResolver = new FileResolver(this.BindPaths, this.Extensions); | ||
| 38 | |||
| 34 | foreach (Table table in this.Tables) | 39 | foreach (Table table in this.Tables) |
| 35 | { | 40 | { |
| 36 | foreach (Row row in table.Rows) | 41 | foreach (Row row in table.Rows) |
| @@ -46,7 +51,7 @@ namespace WixToolset.Bind | |||
| 46 | // resolve localization and wix variables | 51 | // resolve localization and wix variables |
| 47 | if (field.Data is string) | 52 | if (field.Data is string) |
| 48 | { | 53 | { |
| 49 | field.Data = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, field.AsString(), false, ref isDefault, ref delayedResolve); | 54 | field.Data = this.BindVariableResolver.ResolveVariables(row.SourceLineNumbers, field.AsString(), false, out isDefault, out delayedResolve); |
| 50 | if (delayedResolve) | 55 | if (delayedResolve) |
| 51 | { | 56 | { |
| 52 | delayedFields.Add(new DelayedField(row, field)); | 57 | delayedFields.Add(new DelayedField(row, field)); |
| @@ -74,7 +79,7 @@ namespace WixToolset.Bind | |||
| 74 | // File is embedded and path to it was not modified above. | 79 | // File is embedded and path to it was not modified above. |
| 75 | if (objectField.EmbeddedFileIndex.HasValue && isDefault) | 80 | if (objectField.EmbeddedFileIndex.HasValue && isDefault) |
| 76 | { | 81 | { |
| 77 | string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.BaseUri, objectField.EmbeddedFileIndex.Value, this.TempFilesLocation); | 82 | string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.BaseUri, objectField.EmbeddedFileIndex.Value, this.IntermediateFolder); |
| 78 | 83 | ||
| 79 | // Set the path to the embedded file once where it will be extracted. | 84 | // Set the path to the embedded file once where it will be extracted. |
| 80 | objectField.Data = extractPath; | 85 | objectField.Data = extractPath; |
| @@ -83,7 +88,7 @@ namespace WixToolset.Bind | |||
| 83 | { | 88 | { |
| 84 | try | 89 | try |
| 85 | { | 90 | { |
| 86 | if (OutputType.Patch != this.FileManagerCore.Output.Type) // Normal binding for non-Patch scenario such as link (light.exe) | 91 | if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) |
| 87 | { | 92 | { |
| 88 | // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file | 93 | // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file |
| 89 | if (null == objectField.UnresolvedData) | 94 | if (null == objectField.UnresolvedData) |
| @@ -92,12 +97,12 @@ namespace WixToolset.Bind | |||
| 92 | } | 97 | } |
| 93 | 98 | ||
| 94 | // resolve the path to the file | 99 | // resolve the path to the file |
| 95 | objectField.Data = this.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); | 100 | objectField.Data = fileResolver.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); |
| 96 | } | 101 | } |
| 97 | else if (!(this.FileManagerCore.RebaseTarget || this.FileManagerCore.RebaseUpdated)) // Normal binding for Patch Scenario (normal patch, no re-basing logic) | 102 | else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) |
| 98 | { | 103 | { |
| 99 | // resolve the path to the file | 104 | // resolve the path to the file |
| 100 | objectField.Data = this.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); | 105 | objectField.Data = fileResolver.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); |
| 101 | } | 106 | } |
| 102 | else // Re-base binding path scenario caused by pyro.exe -bt -bu | 107 | else // Re-base binding path scenario caused by pyro.exe -bt -bu |
| 103 | { | 108 | { |
| @@ -106,7 +111,7 @@ namespace WixToolset.Bind | |||
| 106 | 111 | ||
| 107 | // if -bu is used in pyro command, this condition holds true and the tool | 112 | // if -bu is used in pyro command, this condition holds true and the tool |
| 108 | // will use pre-resolved source for new wixpdb file | 113 | // will use pre-resolved source for new wixpdb file |
| 109 | if (this.FileManagerCore.RebaseUpdated) | 114 | if (fileResolver.RebaseUpdated) |
| 110 | { | 115 | { |
| 111 | // try to use the unResolved Source if it exists. | 116 | // try to use the unResolved Source if it exists. |
| 112 | // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll | 117 | // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll |
| @@ -117,7 +122,7 @@ namespace WixToolset.Bind | |||
| 117 | } | 122 | } |
| 118 | } | 123 | } |
| 119 | 124 | ||
| 120 | objectField.Data = this.ResolveFile(filePathToResolve, table.Name, row.SourceLineNumbers, BindStage.Updated); | 125 | objectField.Data = fileResolver.ResolveFile(filePathToResolve, table.Name, row.SourceLineNumbers, BindStage.Updated); |
| 121 | } | 126 | } |
| 122 | } | 127 | } |
| 123 | catch (WixFileNotFoundException) | 128 | catch (WixFileNotFoundException) |
| @@ -127,10 +132,10 @@ namespace WixToolset.Bind | |||
| 127 | } | 132 | } |
| 128 | } | 133 | } |
| 129 | 134 | ||
| 130 | isDefault = true; | ||
| 131 | if (null != objectField.PreviousData) | 135 | if (null != objectField.PreviousData) |
| 132 | { | 136 | { |
| 133 | objectField.PreviousData = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, objectField.PreviousData, false, ref isDefault); | 137 | objectField.PreviousData = this.BindVariableResolver.ResolveVariables(row.SourceLineNumbers, objectField.PreviousData, false, out isDefault); |
| 138 | |||
| 134 | if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. | 139 | if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. |
| 135 | { | 140 | { |
| 136 | // file is compressed in a cabinet (and not modified above) | 141 | // file is compressed in a cabinet (and not modified above) |
| @@ -142,7 +147,7 @@ namespace WixToolset.Bind | |||
| 142 | objectField.PreviousBaseUri = objectField.BaseUri; | 147 | objectField.PreviousBaseUri = objectField.BaseUri; |
| 143 | } | 148 | } |
| 144 | 149 | ||
| 145 | string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.TempFilesLocation); | 150 | string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder); |
| 146 | 151 | ||
| 147 | // set the path to the file once its extracted from the cabinet | 152 | // set the path to the file once its extracted from the cabinet |
| 148 | objectField.PreviousData = extractPath; | 153 | objectField.PreviousData = extractPath; |
| @@ -151,14 +156,14 @@ namespace WixToolset.Bind | |||
| 151 | { | 156 | { |
| 152 | try | 157 | try |
| 153 | { | 158 | { |
| 154 | if (!this.FileManagerCore.RebaseTarget && !this.FileManagerCore.RebaseUpdated) | 159 | if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) |
| 155 | { | 160 | { |
| 156 | // resolve the path to the file | 161 | // resolve the path to the file |
| 157 | objectField.PreviousData = this.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Normal); | 162 | objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Normal); |
| 158 | } | 163 | } |
| 159 | else | 164 | else |
| 160 | { | 165 | { |
| 161 | if (this.FileManagerCore.RebaseTarget) | 166 | if (fileResolver.RebaseTarget) |
| 162 | { | 167 | { |
| 163 | // if -bt is used, it come here | 168 | // if -bt is used, it come here |
| 164 | // Try to use the original unresolved source from either target build or update build | 169 | // Try to use the original unresolved source from either target build or update build |
| @@ -172,7 +177,7 @@ namespace WixToolset.Bind | |||
| 172 | } | 177 | } |
| 173 | 178 | ||
| 174 | // resolve the path to the file | 179 | // resolve the path to the file |
| 175 | objectField.PreviousData = this.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Target); | 180 | objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Target); |
| 176 | 181 | ||
| 177 | } | 182 | } |
| 178 | } | 183 | } |
| @@ -192,24 +197,28 @@ namespace WixToolset.Bind | |||
| 192 | this.DelayedFields = delayedFields; | 197 | this.DelayedFields = delayedFields; |
| 193 | } | 198 | } |
| 194 | 199 | ||
| 200 | #if false | ||
| 195 | private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal) | 201 | private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal) |
| 196 | { | 202 | { |
| 197 | string path = null; | 203 | string path = null; |
| 198 | foreach (IBinderFileManager fileManager in this.FileManagers) | 204 | foreach (var extension in this.Extensions) |
| 199 | { | 205 | { |
| 200 | path = fileManager.ResolveFile(source, type, sourceLineNumbers, bindStage); | 206 | path = extension.ResolveFile(source, type, sourceLineNumbers, bindStage); |
| 201 | if (null != path) | 207 | if (null != path) |
| 202 | { | 208 | { |
| 203 | break; | 209 | break; |
| 204 | } | 210 | } |
| 205 | } | 211 | } |
| 206 | 212 | ||
| 207 | if (null == path) | 213 | throw new NotImplementedException(); // need to do default binder stuff |
| 208 | { | 214 | |
| 209 | throw new WixFileNotFoundException(sourceLineNumbers, source, type); | 215 | //if (null == path) |
| 210 | } | 216 | //{ |
| 217 | // throw new WixFileNotFoundException(sourceLineNumbers, source, type); | ||
| 218 | //} | ||
| 211 | 219 | ||
| 212 | return path; | 220 | //return path; |
| 213 | } | 221 | } |
| 222 | #endif | ||
| 214 | } | 223 | } |
| 215 | } | 224 | } |
diff --git a/src/WixToolset.Core/Bind/ResolveResult.cs b/src/WixToolset.Core/Bind/ResolveResult.cs new file mode 100644 index 00000000..13f25054 --- /dev/null +++ b/src/WixToolset.Core/Bind/ResolveResult.cs | |||
| @@ -0,0 +1,14 @@ | |||
| 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.Bind | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Extensibility; | ||
| 7 | |||
| 8 | public class ResolveResult | ||
| 9 | { | ||
| 10 | public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; } | ||
| 11 | |||
| 12 | public IEnumerable<IDelayedField> DelayedFields { get; set; } | ||
| 13 | } | ||
| 14 | } \ No newline at end of file | ||
diff --git a/src/WixToolset.Core/Bind/ResolvedDirectory.cs b/src/WixToolset.Core/Bind/ResolvedDirectory.cs index 6985f95d..fca706d8 100644 --- a/src/WixToolset.Core/Bind/ResolvedDirectory.cs +++ b/src/WixToolset.Core/Bind/ResolvedDirectory.cs | |||
| @@ -5,7 +5,7 @@ namespace WixToolset.Bind | |||
| 5 | /// <summary> | 5 | /// <summary> |
| 6 | /// Structure used for resolved directory information. | 6 | /// Structure used for resolved directory information. |
| 7 | /// </summary> | 7 | /// </summary> |
| 8 | internal struct ResolvedDirectory | 8 | public struct ResolvedDirectory |
| 9 | { | 9 | { |
| 10 | /// <summary>The directory parent.</summary> | 10 | /// <summary>The directory parent.</summary> |
| 11 | public string DirectoryParent; | 11 | public string DirectoryParent; |
diff --git a/src/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/WixToolset.Core/Bind/TransferFilesCommand.cs index 719b8b20..f116569c 100644 --- a/src/WixToolset.Core/Bind/TransferFilesCommand.cs +++ b/src/WixToolset.Core/Bind/TransferFilesCommand.cs | |||
| @@ -1,29 +1,37 @@ | |||
| 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. | 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 | 2 | ||
| 3 | namespace WixToolset.Bind | 3 | namespace WixToolset.Core.Bind |
| 4 | { | 4 | { |
| 5 | using System; | 5 | using System; |
| 6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
| 7 | using System.IO; | 7 | using System.IO; |
| 8 | using System.Security.AccessControl; | 8 | using System.Security.AccessControl; |
| 9 | using WixToolset.Data; | 9 | using WixToolset.Data; |
| 10 | using WixToolset.Data.Bind; | ||
| 10 | using WixToolset.Extensibility; | 11 | using WixToolset.Extensibility; |
| 11 | 12 | ||
| 12 | internal class TransferFilesCommand : ICommand | 13 | internal class TransferFilesCommand |
| 13 | { | 14 | { |
| 14 | public IEnumerable<IBinderFileManager> FileManagers { private get; set; } | 15 | public TransferFilesCommand(IEnumerable<BindPath> bindPaths, IEnumerable<IBinderExtension> extensions, IEnumerable<FileTransfer> fileTransfers, bool suppressAclReset) |
| 16 | { | ||
| 17 | this.FileResolver = new FileResolver(bindPaths, extensions); | ||
| 18 | this.FileTransfers = fileTransfers; | ||
| 19 | this.SuppressAclReset = suppressAclReset; | ||
| 20 | } | ||
| 21 | |||
| 22 | private FileResolver FileResolver { get; } | ||
| 15 | 23 | ||
| 16 | public IEnumerable<FileTransfer> FileTransfers { private get; set; } | 24 | private IEnumerable<FileTransfer> FileTransfers { get; } |
| 17 | 25 | ||
| 18 | public bool SuppressAclReset { private get; set; } | 26 | private bool SuppressAclReset { get; } |
| 19 | 27 | ||
| 20 | public void Execute() | 28 | public void Execute() |
| 21 | { | 29 | { |
| 22 | List<string> destinationFiles = new List<string>(); | 30 | List<string> destinationFiles = new List<string>(); |
| 23 | 31 | ||
| 24 | foreach (FileTransfer fileTransfer in this.FileTransfers) | 32 | foreach (var fileTransfer in this.FileTransfers) |
| 25 | { | 33 | { |
| 26 | string fileSource = this.ResolveFile(fileTransfer.Source, fileTransfer.Type, fileTransfer.SourceLineNumbers, BindStage.Normal); | 34 | string fileSource = this.FileResolver.ResolveFile(fileTransfer.Source, fileTransfer.Type, fileTransfer.SourceLineNumbers, BindStage.Normal); |
| 27 | 35 | ||
| 28 | // If the source and destination are identical, then there's nothing to do here | 36 | // If the source and destination are identical, then there's nothing to do here |
| 29 | if (0 == String.Compare(fileSource, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) | 37 | if (0 == String.Compare(fileSource, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) |
| @@ -165,44 +173,17 @@ namespace WixToolset.Bind | |||
| 165 | } | 173 | } |
| 166 | } | 174 | } |
| 167 | 175 | ||
| 168 | private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) | 176 | private void TransferFile(bool move, string source, string destination) |
| 169 | { | 177 | { |
| 170 | string path = null; | 178 | bool complete = false; |
| 171 | foreach (IBinderFileManager fileManager in this.FileManagers) | ||
| 172 | { | ||
| 173 | path = fileManager.ResolveFile(source, type, sourceLineNumbers, bindStage); | ||
| 174 | if (null != path) | ||
| 175 | { | ||
| 176 | break; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | 179 | ||
| 180 | if (null == path) | 180 | if (move) |
| 181 | { | 181 | { |
| 182 | throw new WixFileNotFoundException(sourceLineNumbers, source, type); | 182 | complete = this.FileResolver.MoveFile(source, destination, true); |
| 183 | } | 183 | } |
| 184 | 184 | else | |
| 185 | return path; | ||
| 186 | } | ||
| 187 | |||
| 188 | private void TransferFile(bool move, string source, string destination) | ||
| 189 | { | ||
| 190 | bool complete = false; | ||
| 191 | foreach (IBinderFileManager fileManager in this.FileManagers) | ||
| 192 | { | 185 | { |
| 193 | if (move) | 186 | complete = this.FileResolver.CopyFile(source, destination, true); |
| 194 | { | ||
| 195 | complete = fileManager.MoveFile(source, destination, true); | ||
| 196 | } | ||
| 197 | else | ||
| 198 | { | ||
| 199 | complete = fileManager.CopyFile(source, destination, true); | ||
| 200 | } | ||
| 201 | |||
| 202 | if (complete) | ||
| 203 | { | ||
| 204 | break; | ||
| 205 | } | ||
| 206 | } | 187 | } |
| 207 | 188 | ||
| 208 | if (!complete) | 189 | if (!complete) |
