aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-02-25 09:58:20 -0800
committerRob Mensching <rob@firegiant.com>2021-02-27 07:47:08 -0800
commit760fb810ba5ecc3c6ce752a9bfa3755f7b7c0f6a (patch)
tree3ad06ffe520f31142e23ce9e752473110d232ec5
parent4536440f8d76346bcd120fe9e1410e428f855ee9 (diff)
downloadwix-760fb810ba5ecc3c6ce752a9bfa3755f7b7c0f6a.tar.gz
wix-760fb810ba5ecc3c6ce752a9bfa3755f7b7c0f6a.tar.bz2
wix-760fb810ba5ecc3c6ce752a9bfa3755f7b7c0f6a.zip
Absorb Tag.wixext into Core as SoftwareTag element
Resolves wixtoolset/issues#5949
-rw-r--r--src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs7
-rw-r--r--src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs1
-rw-r--r--src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs142
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs12
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs12
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs3
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs131
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs2
-rw-r--r--src/WixToolset.Core/Bind/FileFacade.cs2
-rw-r--r--src/WixToolset.Core/CompilerCore.cs1
-rw-r--r--src/WixToolset.Core/CompilerErrors.cs30
-rw-r--r--src/WixToolset.Core/Compiler_2.cs3
-rw-r--r--src/WixToolset.Core/Compiler_Bundle.cs3
-rw-r--r--src/WixToolset.Core/Compiler_Patch.cs3
-rw-r--r--src/WixToolset.Core/Compiler_Tag.cs315
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs100
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs15
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl11
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs10
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs20
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt1
22 files changed, 814 insertions, 11 deletions
diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
index 93620e1b..c9a111c6 100644
--- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
@@ -380,6 +380,13 @@ namespace WixToolset.Core.Burn
380 // Update the bundle per-machine/per-user scope based on the chained packages. 380 // Update the bundle per-machine/per-user scope based on the chained packages.
381 this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades); 381 this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades);
382 382
383 var softwareTags = section.Symbols.OfType<WixBundleTagSymbol>().ToList();
384 if (softwareTags.Any())
385 {
386 var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags);
387 command.Execute();
388 }
389
383 // Give the extension one last hook before generating the output files. 390 // Give the extension one last hook before generating the output files.
384 foreach (var extension in this.BackendExtensions) 391 foreach (var extension in this.BackendExtensions)
385 { 392 {
diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
index c51d380c..36ced6cf 100644
--- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
+++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
@@ -82,6 +82,7 @@ namespace WixToolset.Core.Burn.Bind
82 case SymbolDefinitionType.WixBundleRelatedPackage: 82 case SymbolDefinitionType.WixBundleRelatedPackage:
83 case SymbolDefinitionType.WixBundleRollbackBoundary: 83 case SymbolDefinitionType.WixBundleRollbackBoundary:
84 case SymbolDefinitionType.WixBundleSlipstreamMsp: 84 case SymbolDefinitionType.WixBundleSlipstreamMsp:
85 case SymbolDefinitionType.WixBundleTag:
85 case SymbolDefinitionType.WixBundleUpdate: 86 case SymbolDefinitionType.WixBundleUpdate:
86 case SymbolDefinitionType.WixBundleVariable: 87 case SymbolDefinitionType.WixBundleVariable:
87 case SymbolDefinitionType.WixBuildInfo: 88 case SymbolDefinitionType.WixBuildInfo:
diff --git a/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs
new file mode 100644
index 00000000..8584d2a4
--- /dev/null
+++ b/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs
@@ -0,0 +1,142 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using System.Xml;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Dtf.WindowsInstaller;
14
15 internal class ProcessBundleSoftwareTagsCommand
16 {
17 public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable<WixBundleTagSymbol> softwareTags)
18 {
19 this.Section = section;
20 this.SoftwareTags = softwareTags;
21 }
22
23 private IntermediateSection Section { get; }
24
25 private IEnumerable<WixBundleTagSymbol> SoftwareTags { get; }
26
27 public void Execute()
28 {
29 var bundleInfo = this.Section.Symbols.OfType<WixBundleSymbol>().FirstOrDefault();
30 var bundleId = NormalizeGuid(bundleInfo.BundleId);
31 var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode);
32
33 var uniqueId = String.Concat("wix:bundle/", bundleId);
34 var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode);
35
36 // Try to collect all the software id tags from all the child packages.
37 var containedTags = CollectPackageTags(this.Section);
38
39 foreach (var bundleTag in this.SoftwareTags)
40 {
41 using (var ms = new MemoryStream())
42 {
43 CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags);
44 bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray());
45 }
46 }
47 }
48
49 private static string NormalizeGuid(string guidString)
50 {
51 if (Guid.TryParse(guidString, out var guid))
52 {
53 return guid.ToString("D").ToUpperInvariant();
54 }
55
56 return guidString;
57 }
58
59 private static IEnumerable<SoftwareTag> CollectPackageTags(IntermediateSection section)
60 {
61 var tags = new List<SoftwareTag>();
62
63 var msiPackages = section.Symbols.OfType<WixBundlePackageSymbol>().Where(s => s.Type == WixBundlePackageType.Msi).ToList();
64 if (msiPackages.Any())
65 {
66 var payloadSymbolsById = section.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(s => s.Id.Id);
67
68 foreach (var msiPackage in msiPackages)
69 {
70 var payload = payloadSymbolsById[msiPackage.PayloadRef];
71
72 using (var db = new Database(payload.SourceFile.Path))
73 {
74 using (var view = db.OpenView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`"))
75 {
76 view.Execute();
77 while (true)
78 {
79 using (var record = view.Fetch())
80 {
81 if (null == record)
82 {
83 break;
84 }
85
86 tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) });
87 }
88 }
89 }
90 }
91 }
92 }
93
94 return tags;
95 }
96
97 private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable<SoftwareTag> containedTags)
98 {
99 var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric";
100
101 using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true}))
102 {
103 writer.WriteStartDocument();
104 writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd");
105 writer.WriteAttributeString("tagId", uniqueId);
106 writer.WriteAttributeString("name", name);
107 writer.WriteAttributeString("version", version);
108 writer.WriteAttributeString("versionScheme", versionScheme);
109
110 writer.WriteStartElement("Entity");
111 writer.WriteAttributeString("name", manufacturer);
112 writer.WriteAttributeString("regid", regid);
113 writer.WriteAttributeString("role", "softwareCreator tagCreator");
114 writer.WriteEndElement(); // </Entity>
115
116 if (!String.IsNullOrEmpty(persistendId))
117 {
118 writer.WriteStartElement("Meta");
119 writer.WriteAttributeString("persistentId", persistendId);
120 writer.WriteEndElement(); // </Meta>
121 }
122
123 foreach (var containedTag in containedTags)
124 {
125 writer.WriteStartElement("Link");
126 writer.WriteAttributeString("rel", "component");
127 writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id));
128 writer.WriteEndElement(); // </Link>
129 }
130
131 writer.WriteEndElement(); // </SoftwareIdentity>
132 }
133 }
134
135 private class SoftwareTag
136 {
137 public string Regid { get; set; }
138
139 public string Id { get; set; }
140 }
141 }
142}
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
index 71bc0229..3bc6bf1b 100644
--- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
@@ -295,17 +295,15 @@ namespace WixToolset.Core.Burn.Bundles
295 writer.WriteEndElement(); // </Update> 295 writer.WriteEndElement(); // </Update>
296 } 296 }
297 297
298#if TODO // Handle SWID Tags 298 foreach (var bundleTagSymbol in this.Section.Symbols.OfType<WixBundleTagSymbol>())
299 var bundleTags = this.Output.Tables["WixBundleTag"].RowsAs<Row>();
300 foreach (var row in bundleTags)
301 { 299 {
302 writer.WriteStartElement("SoftwareTag"); 300 writer.WriteStartElement("SoftwareTag");
303 writer.WriteAttributeString("Filename", (string)row[0]); 301 writer.WriteAttributeString("Filename", bundleTagSymbol.Filename);
304 writer.WriteAttributeString("Regid", (string)row[1]); 302 writer.WriteAttributeString("Regid", bundleTagSymbol.Regid);
305 writer.WriteCData((string)row[4]); 303 writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath);
304 writer.WriteCData(bundleTagSymbol.Xml);
306 writer.WriteEndElement(); 305 writer.WriteEndElement();
307 } 306 }
308#endif
309 307
310 writer.WriteEndElement(); // </Register> 308 writer.WriteEndElement(); // </Register>
311 309
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 012c7c4c..25a093fd 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -282,6 +282,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
282 return null; 282 return null;
283 } 283 }
284 284
285 // Process SoftwareTags in MSI packages.
286 if (SectionType.Product == section.Type)
287 {
288 var softwareTags = section.Symbols.OfType<WixProductTagSymbol>().ToList();
289
290 if (softwareTags.Any())
291 {
292 var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder);
293 command.Execute();
294 }
295 }
296
285 // Gather information about files that do not come from merge modules. 297 // Gather information about files that do not come from merge modules.
286 { 298 {
287 var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); 299 var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true);
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
index b52ff434..37383caa 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
@@ -231,6 +231,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
231 case SymbolDefinitionType.WixPatchRef: 231 case SymbolDefinitionType.WixPatchRef:
232 case SymbolDefinitionType.WixPatchTarget: 232 case SymbolDefinitionType.WixPatchTarget:
233 case SymbolDefinitionType.WixProperty: 233 case SymbolDefinitionType.WixProperty:
234 case SymbolDefinitionType.WixProductTag:
234 case SymbolDefinitionType.WixSimpleReference: 235 case SymbolDefinitionType.WixSimpleReference:
235 case SymbolDefinitionType.WixSuppressAction: 236 case SymbolDefinitionType.WixSuppressAction:
236 case SymbolDefinitionType.WixSuppressModularization: 237 case SymbolDefinitionType.WixSuppressModularization:
@@ -456,7 +457,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
456 457
457 private void AddDirectorySymbol(DirectorySymbol symbol) 458 private void AddDirectorySymbol(DirectorySymbol symbol)
458 { 459 {
459 if (String.IsNullOrEmpty(symbol.ShortName) && !symbol.Name.Equals(".") && !symbol.Name.Equals("SourceDir") && !Common.IsValidShortFilename(symbol.Name, false)) 460 if (String.IsNullOrEmpty(symbol.ShortName) && symbol.Name != null && !symbol.Name.Equals(".") && !symbol.Name.Equals("SourceDir") && !Common.IsValidShortFilename(symbol.Name, false))
460 { 461 {
461 symbol.ShortName = CreateShortName(symbol.Name, false, false, "Directory", symbol.ParentDirectoryRef); 462 symbol.ShortName = CreateShortName(symbol.Name, false, false, "Directory", symbol.ParentDirectoryRef);
462 } 463 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs
new file mode 100644
index 00000000..9a068603
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs
@@ -0,0 +1,131 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Xml;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12
13 internal class ProcessPackageSoftwareTagsCommand
14 {
15 public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable<WixProductTagSymbol> softwareTags, string intermediateFolder)
16 {
17 this.Section = section;
18 this.SoftwareTags = softwareTags;
19 this.IntermediateFolder = intermediateFolder;
20 }
21
22 private string IntermediateFolder { get; }
23
24 private IntermediateSection Section { get; }
25
26 private IEnumerable<WixProductTagSymbol> SoftwareTags { get; }
27
28 public void Execute()
29 {
30 string productName = null;
31 string productVersion = null;
32 string manufacturer = null;
33 string upgradeCode = null;
34
35 var summaryInfo = this.Section.Symbols.OfType<SummaryInformationSymbol>().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode);
36 var packageCode = NormalizeGuid(summaryInfo?.Value);
37
38 foreach (var property in this.Section.Symbols.OfType<PropertySymbol>())
39 {
40 switch (property.Id.Id)
41 {
42 case "ProductName":
43 productName = property.Value;
44 break;
45 case "ProductVersion":
46 productVersion = property.Value;
47 break;
48 case "Manufacturer":
49 manufacturer = property.Value;
50 break;
51 case "UpgradeCode":
52 upgradeCode = NormalizeGuid(property.Value);
53 break;
54 }
55 }
56
57 var fileSymbolsById = this.Section.Symbols.OfType<FileSymbol>().Where(f => f.Id != null).ToDictionary(f => f.Id.Id);
58
59 var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag");
60
61 Directory.CreateDirectory(workingFolder);
62
63 foreach (var tagRow in this.SoftwareTags)
64 {
65 if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol))
66 {
67 var uniqueId = String.Concat("msi:package/", packageCode);
68 var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode);
69
70 // Write the tag file.
71 fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) };
72
73 using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create))
74 {
75 CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId);
76 }
77
78 // Ensure the matching "SoftwareIdentificationTag" row exists and
79 // is populated correctly.
80 this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id)
81 {
82 FileRef = fileSymbol.Id.Id,
83 Regid = tagRow.Regid,
84 TagId = uniqueId,
85 PersistentId = persistentId
86 });
87 }
88 }
89 }
90
91 private static string NormalizeGuid(string guidString)
92 {
93 if (Guid.TryParse(guidString, out var guid))
94 {
95 return guid.ToString("D").ToUpperInvariant();
96 }
97
98 return guidString;
99 }
100
101 private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId)
102 {
103 var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric";
104
105 using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true }))
106 {
107 writer.WriteStartDocument();
108 writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd");
109 writer.WriteAttributeString("tagId", uniqueId);
110 writer.WriteAttributeString("name", name);
111 writer.WriteAttributeString("version", version);
112 writer.WriteAttributeString("versionScheme", versionScheme);
113
114 writer.WriteStartElement("Entity");
115 writer.WriteAttributeString("name", manufacturer);
116 writer.WriteAttributeString("regid", regid);
117 writer.WriteAttributeString("role", "softwareCreator tagCreator");
118 writer.WriteEndElement(); // </Entity>
119
120 if (!String.IsNullOrEmpty(persistendId))
121 {
122 writer.WriteStartElement("Meta");
123 writer.WriteAttributeString("persistentId", persistendId);
124 writer.WriteEndElement(); // </Meta>
125 }
126
127 writer.WriteEndElement(); // </SoftwareIdentity>
128 }
129 }
130 }
131}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
index 938627ed..d5bdc797 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
@@ -45,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
45 { 45 {
46 var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id); 46 var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id);
47 47
48 foreach (var file in this.UpdateFileFacades) 48 foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null))
49 { 49 {
50 this.UpdateFileFacade(file, assemblyNameSymbols); 50 this.UpdateFileFacade(file, assemblyNameSymbols);
51 } 51 }
diff --git a/src/WixToolset.Core/Bind/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs
index ec4e9725..9705cd01 100644
--- a/src/WixToolset.Core/Bind/FileFacade.cs
+++ b/src/WixToolset.Core/Bind/FileFacade.cs
@@ -125,7 +125,7 @@ namespace WixToolset.Core.Bind
125 125
126 public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; 126 public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers;
127 127
128 public string SourcePath => this.FileRow == null ? this.FileSymbol.Source.Path : this.FileRow.Source; 128 public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source;
129 129
130 public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; 130 public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed;
131 131
diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs
index 1f6d6329..53e0f3fc 100644
--- a/src/WixToolset.Core/CompilerCore.cs
+++ b/src/WixToolset.Core/CompilerCore.cs
@@ -6,7 +6,6 @@ namespace WixToolset.Core
6 using System.Collections; 6 using System.Collections;
7 using System.Collections.Generic; 7 using System.Collections.Generic;
8 using System.Diagnostics; 8 using System.Diagnostics;
9 using System.Diagnostics.CodeAnalysis;
10 using System.Globalization; 9 using System.Globalization;
11 using System.Reflection; 10 using System.Reflection;
12 using System.Text; 11 using System.Text;
diff --git a/src/WixToolset.Core/CompilerErrors.cs b/src/WixToolset.Core/CompilerErrors.cs
new file mode 100644
index 00000000..da64c376
--- /dev/null
+++ b/src/WixToolset.Core/CompilerErrors.cs
@@ -0,0 +1,30 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6
7 internal static class CompilerErrors
8 {
9 public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name)
10 {
11 return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name);
12 }
13
14 public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid)
15 {
16 return Message(sourceLineNumbers, Ids.ExampleRegid, "Regid '{0}' is a placeholder that must be replaced with an appropriate value for your installation. Use the simplified URI for your organization or project.", regid);
17 }
18
19 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
20 {
21 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
22 }
23
24 public enum Ids
25 {
26 IllegalName = 6601,
27 ExampleRegid = 6602,
28 }
29 }
30}
diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs
index 09d56e49..295392c8 100644
--- a/src/WixToolset.Core/Compiler_2.cs
+++ b/src/WixToolset.Core/Compiler_2.cs
@@ -316,6 +316,9 @@ namespace WixToolset.Core
316 string parentName = null; 316 string parentName = null;
317 this.ParseSFPCatalogElement(child, ref parentName); 317 this.ParseSFPCatalogElement(child, ref parentName);
318 break; 318 break;
319 case "SoftwareTag":
320 this.ParsePackageTagElement(child);
321 break;
319 case "SummaryInformation": 322 case "SummaryInformation":
320 this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); 323 this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet);
321 break; 324 break;
diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs
index 7a386de7..1ee09166 100644
--- a/src/WixToolset.Core/Compiler_Bundle.cs
+++ b/src/WixToolset.Core/Compiler_Bundle.cs
@@ -346,6 +346,9 @@ namespace WixToolset.Core
346 case "SetVariableRef": 346 case "SetVariableRef":
347 this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); 347 this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable);
348 break; 348 break;
349 case "SoftwareTag":
350 this.ParseBundleTagElement(child);
351 break;
349 case "Update": 352 case "Update":
350 this.ParseUpdateElement(child); 353 this.ParseUpdateElement(child);
351 break; 354 break;
diff --git a/src/WixToolset.Core/Compiler_Patch.cs b/src/WixToolset.Core/Compiler_Patch.cs
index 2fb1affb..83737c43 100644
--- a/src/WixToolset.Core/Compiler_Patch.cs
+++ b/src/WixToolset.Core/Compiler_Patch.cs
@@ -410,6 +410,9 @@ namespace WixToolset.Core
410 case "PropertyRef": 410 case "PropertyRef":
411 this.ParsePatchChildRefElement(child, "Property"); 411 this.ParsePatchChildRefElement(child, "Property");
412 break; 412 break;
413 case "SoftwareTagRef":
414 this.ParseTagRefElement(child);
415 break;
413 case "UIRef": 416 case "UIRef":
414 this.ParsePatchChildRefElement(child, "WixUI"); 417 this.ParsePatchChildRefElement(child, "WixUI");
415 break; 418 break;
diff --git a/src/WixToolset.Core/Compiler_Tag.cs b/src/WixToolset.Core/Compiler_Tag.cs
new file mode 100644
index 00000000..2b3523c8
--- /dev/null
+++ b/src/WixToolset.Core/Compiler_Tag.cs
@@ -0,0 +1,315 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Xml.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9
10 /// <summary>
11 /// Compiler of the WiX toolset.
12 /// </summary>
13 internal partial class Compiler : ICompiler
14 {
15 /// <summary>
16 /// Parses a Tag element for Software Id Tag registration under a Bundle element.
17 /// </summary>
18 /// <param name="node">The element to parse.</param>
19 private void ParseBundleTagElement(XElement node)
20 {
21 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
22 string name = null;
23 string regid = null;
24 string installPath = null;
25
26 foreach (var attrib in node.Attributes())
27 {
28 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
29 {
30 switch (attrib.Name.LocalName)
31 {
32 case "Name":
33 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
34 break;
35 case "Regid":
36 regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
37 break;
38 case "InstallDirectory":
39 case "Bitness":
40 this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package"));
41 break;
42 case "InstallPath":
43 installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
44 break;
45 default:
46 this.Core.UnexpectedAttribute(node, attrib);
47 break;
48 }
49 }
50 else
51 {
52 this.Core.ParseExtensionAttribute(node, attrib);
53 }
54 }
55
56 this.Core.ParseForExtensionElements(node);
57
58 if (String.IsNullOrEmpty(name))
59 {
60 name = node.Parent?.Attribute("Name")?.Value;
61
62 if (String.IsNullOrEmpty(name))
63 {
64 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
65 }
66 }
67
68 if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name))
69 {
70 this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name));
71 }
72
73 if (String.IsNullOrEmpty(regid))
74 {
75 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid"));
76 }
77 else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase))
78 {
79 this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid));
80 }
81
82 if (String.IsNullOrEmpty(installPath))
83 {
84 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath"));
85 }
86
87 if (!this.Core.EncounteredError)
88 {
89 this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers)
90 {
91 Filename = String.Concat(name, ".swidtag"),
92 Regid = regid,
93 Name = name,
94 InstallPath = installPath
95 });
96 }
97 }
98
99 /// <summary>
100 /// Parses a Tag element for Software Id Tag registration under a Package element.
101 /// </summary>
102 /// <param name="node">The element to parse.</param>
103 private void ParsePackageTagElement(XElement node)
104 {
105 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
106 Identifier id = null;
107 string name = null;
108 string regid = null;
109 string feature = null;
110 string installDirectory = null;
111 var win64 = this.Context.IsCurrentPlatform64Bit;
112
113 foreach (var attrib in node.Attributes())
114 {
115 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
116 {
117 switch (attrib.Name.LocalName)
118 {
119 case "Id":
120 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
121 break;
122 case "Name":
123 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
124 break;
125 case "Regid":
126 regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
127 break;
128 case "Feature":
129 feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
130 break;
131 case "InstallDirectory":
132 installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
133 break;
134 case "InstallPath":
135 this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle"));
136 break;
137 case "Bitness":
138 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
139 switch (bitnessValue)
140 {
141 case "always32":
142 win64 = false;
143 break;
144 case "always64":
145 win64 = true;
146 break;
147 case "default":
148 case "":
149 break;
150 default:
151 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
152 break;
153 }
154 break;
155 default:
156 this.Core.UnexpectedAttribute(node, attrib);
157 break;
158 }
159 }
160 else
161 {
162 this.Core.ParseExtensionAttribute(node, attrib);
163 }
164 }
165
166 this.Core.ParseForExtensionElements(node);
167
168 if (String.IsNullOrEmpty(name))
169 {
170 name = node.Parent?.Attribute("Name")?.Value;
171
172 if (String.IsNullOrEmpty(name))
173 {
174 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
175 }
176 }
177
178 if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name))
179 {
180 this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name));
181 }
182
183 if (String.IsNullOrEmpty(regid))
184 {
185 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid"));
186 }
187 else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase))
188 {
189 this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid));
190 return;
191 }
192 else if (id == null)
193 {
194 id = this.CreateTagId(regid);
195 }
196
197 if (String.IsNullOrEmpty(installDirectory))
198 {
199 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory"));
200 }
201
202 if (!this.Core.EncounteredError)
203 {
204 var fileName = String.Concat(name, ".swidtag");
205
206 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory);
207 this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id)
208 {
209 Name = "swidtag",
210 ParentDirectoryRef = installDirectory,
211 ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1"
212 });
213
214 this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id)
215 {
216 ComponentId = "*",
217 DirectoryRef = id.Id,
218 KeyPath = id.Id,
219 KeyPathType = ComponentKeyPathType.File,
220 Location = ComponentLocation.LocalOnly,
221 Win64 = win64
222 });
223
224 this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id)
225 {
226 ComponentRef = id.Id,
227 Name = fileName,
228 DiskId = 1,
229 Attributes = FileSymbolAttributes.ReadOnly,
230 });
231
232 if (!String.IsNullOrEmpty(feature))
233 {
234 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature);
235 }
236 else
237 {
238 feature = "WixSwidTag";
239 this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Private, feature))
240 {
241 Title = "ISO/IEC 19770-2",
242 Level = 1,
243 InstallDefault = FeatureInstallDefault.Local,
244 Display = 0,
245 DisallowAdvertise = true,
246 DisallowAbsent = true,
247 });
248 }
249 this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true);
250
251 this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag");
252 this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id)
253 {
254 FileRef = id.Id,
255 Regid = regid,
256 Name = name
257 });
258 }
259 }
260
261 /// <summary>
262 /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element.
263 /// </summary>
264 /// <param name="node">The element to parse.</param>
265 private void ParseTagRefElement(XElement node)
266 {
267 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
268 string regid = null;
269
270 foreach (var attrib in node.Attributes())
271 {
272 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
273 {
274 switch (attrib.Name.LocalName)
275 {
276 case "Regid":
277 regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
278 break;
279 default:
280 this.Core.UnexpectedAttribute(node, attrib);
281 break;
282 }
283 }
284 else
285 {
286 this.Core.ParseExtensionAttribute(node, attrib);
287 }
288 }
289
290 this.Core.ParseForExtensionElements(node);
291
292 if (String.IsNullOrEmpty(regid))
293 {
294 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid"));
295 }
296 else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase))
297 {
298 this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid));
299 }
300
301 if (!this.Core.EncounteredError)
302 {
303 var id = this.CreateTagId(regid);
304
305 this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id)
306 {
307 Table = SymbolDefinitions.Component.Name,
308 PrimaryKeys = id.Id
309 });
310 }
311 }
312
313 private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag");
314 }
315}
diff --git a/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs b/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs
new file mode 100644
index 00000000..15276b18
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs
@@ -0,0 +1,100 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using System.Xml.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using WixToolset.Data;
11 using Xunit;
12
13 public class SoftwareTagFixture
14 {
15 private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn";
16 private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd";
17
18 [Fact]
19 public void CanBuildPackageWithTag()
20 {
21 var folder = TestData.Get(@"TestData\ProductTag");
22 var build = new Builder(folder, null, new[] { folder });
23
24 var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag");
25
26 var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length;
27 var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart);
28 results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd);
29 WixAssert.CompareLineByLine(new[]
30 {
31 "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1",
32 "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2",
33 "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t"
34 }, results.ToArray());
35 }
36
37 [Fact]
38 public void CanBuildBundleWithTag()
39 {
40 var testDataFolder = TestData.Get(@"TestData");
41
42 using (var fs = new DisposableFileSystem())
43 {
44 var baseFolder = fs.GetFolder();
45 var intermediateFolder = Path.Combine(baseFolder, "obj");
46
47 var result = WixRunner.Execute(new[]
48 {
49 "build",
50 Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"),
51 Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"),
52 "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"),
53 "-bindpath", Path.Combine(testDataFolder, "ProductTag"),
54 "-intermediateFolder", Path.Combine(intermediateFolder, "package"),
55 "-o", Path.Combine(baseFolder, "package", @"test.msi")
56 });
57
58 result.AssertSuccess();
59
60 result = WixRunner.Execute(new[]
61 {
62 "build",
63 Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"),
64 "-bindpath", Path.Combine(testDataFolder, "BundleTag"),
65 "-bindpath", Path.Combine(baseFolder, "package"),
66 "-intermediateFolder", intermediateFolder,
67 "-o", Path.Combine(baseFolder, @"bin\test.exe")
68 });
69
70 result.AssertSuccess();
71
72 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe")));
73 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
74
75 using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb")))
76 {
77 var badata = ouput.GetDataStream("wix-burndata.xml");
78 var doc = XDocument.Load(badata);
79
80 var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value;
81
82 var swidTagPath = Path.Combine(baseFolder, "test.swidtag");
83 File.WriteAllText(swidTagPath, swidTag);
84
85 var docTag = XDocument.Load(swidTagPath);
86 var title = docTag.Root.Attribute("name").Value;
87 var version = docTag.Root.Attribute("version").Value;
88 Assert.Equal("~TagTestBundle", title);
89 Assert.Equal("4.3.2.1", version);
90 }
91 }
92 }
93
94 private static void Build(string[] args)
95 {
96 var result = WixRunner.Execute(args)
97 .AssertSuccess();
98 }
99 }
100}
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs
new file mode 100644
index 00000000..f44fb7bc
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs
@@ -0,0 +1,15 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" >
2 <Bundle Name="~TagTestBundle" Version="4.3.2.1" Manufacturer="Example Corporation" UpgradeCode="047730A5-30FE-4A62-A520-DA9381B8226A">
3 <BootstrapperApplication>
4 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
5 </BootstrapperApplication>
6
7 <SoftwareTag Regid="wixtoolset.org" InstallPath="[ProgramFiles6432Folder]\Test\swidtag" />
8
9 <Chain>
10 <MsiPackage SourceFile="test.msi">
11 <MsiProperty Name="TEST" Value="1" />
12 </MsiPackage>
13 </Chain>
14 </Bundle>
15</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll
new file mode 100644
index 00000000..64061ea0
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll
@@ -0,0 +1 @@
This is fakeba.dll. \ No newline at end of file
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl
new file mode 100644
index 00000000..38c12ac1
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs
new file mode 100644
index 00000000..37a2c462
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="example.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs
new file mode 100644
index 00000000..17543c1a
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs
@@ -0,0 +1,20 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package ProductCode="8738B0C5-C4AA-4634-8C03-11EAA2F1E15D" Name="~TagTestPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
4 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
5
6 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
7 <ComponentGroupRef Id="ProductComponents" />
8 </Feature>
9
10 <SoftwareTag Regid="wixtoolset.org" InstallDirectory="INSTALLFOLDER" />
11 </Package>
12
13 <Fragment>
14 <Directory Id="TARGETDIR" Name="SourceDir">
15 <Directory Id="ProgramFilesFolder">
16 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
17 </Directory>
18 </Directory>
19 </Fragment>
20</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt
new file mode 100644
index 00000000..1b4ffe8a
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt
@@ -0,0 +1 @@
This is example.txt. \ No newline at end of file