Absorb Tag.wixext into Core as SoftwareTag element
Resolves wixtoolset/issues#5949
Rob Mensching committed
Feb 25, 2021 at 09:58 UTC
760fb810ba5ecc3c6ce752a9bfa3755f7b7c0f6a
22 files changed
+814
-11
src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+7
@@ -380,6 +380,13 @@ namespace WixToolset.Core.Burn
380
// Update the bundle per-machine/per-user scope based on the chained packages.
381
this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades);
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
+
390
// Give the extension one last hook before generating the output files.
391
foreach (var extension in this.BackendExtensions)
392
{
src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
+1
@@ -82,6 +82,7 @@ namespace WixToolset.Core.Burn.Bind
82
case SymbolDefinitionType.WixBundleRelatedPackage:
83
case SymbolDefinitionType.WixBundleRollbackBoundary:
84
case SymbolDefinitionType.WixBundleSlipstreamMsp:
85
+ case SymbolDefinitionType.WixBundleTag:
86
case SymbolDefinitionType.WixBundleUpdate:
87
case SymbolDefinitionType.WixBundleVariable:
88
case SymbolDefinitionType.WixBuildInfo:
src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs
new
+142
@@ -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
+
3
+namespace 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
+}
src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+5
-7
@@ -295,17 +295,15 @@ namespace WixToolset.Core.Burn.Bundles
295
writer.WriteEndElement(); // </Update>
296
}
297
298
-#if TODO // Handle SWID Tags
299
- var bundleTags = this.Output.Tables["WixBundleTag"].RowsAs<Row>();
300
- foreach (var row in bundleTags)
298
+ foreach (var bundleTagSymbol in this.Section.Symbols.OfType<WixBundleTagSymbol>())
299
{
300
writer.WriteStartElement("SoftwareTag");
303
- writer.WriteAttributeString("Filename", (string)row[0]);
304
- writer.WriteAttributeString("Regid", (string)row[1]);
305
- writer.WriteCData((string)row[4]);
301
+ writer.WriteAttributeString("Filename", bundleTagSymbol.Filename);
302
+ writer.WriteAttributeString("Regid", bundleTagSymbol.Regid);
303
+ writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath);
304
+ writer.WriteCData(bundleTagSymbol.Xml);
305
writer.WriteEndElement();
306
}
308
-#endif
307
308
writer.WriteEndElement(); // </Register>
309
src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+12
@@ -282,6 +282,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
282
return null;
283
}
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
+
297
// Gather information about files that do not come from merge modules.
298
{
299
var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true);
src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
+2
-1
@@ -231,6 +231,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
231
case SymbolDefinitionType.WixPatchRef:
232
case SymbolDefinitionType.WixPatchTarget:
233
case SymbolDefinitionType.WixProperty:
234
+ case SymbolDefinitionType.WixProductTag:
235
case SymbolDefinitionType.WixSimpleReference:
236
case SymbolDefinitionType.WixSuppressAction:
237
case SymbolDefinitionType.WixSuppressModularization:
@@ -456,7 +457,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
457
458
private void AddDirectorySymbol(DirectorySymbol symbol)
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))
461
{
462
symbol.ShortName = CreateShortName(symbol.Name, false, false, "Directory", symbol.ParentDirectoryRef);
463
}
src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs
new
+131
@@ -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
+
3
+namespace WixToolset.Core.WindowsInstaller.Bind
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.IO;
8
+ using System.Linq;
9
+ using 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
+}
src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+1
-1
@@ -45,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
45
{
46
var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id);
47
48
- foreach (var file in this.UpdateFileFacades)
48
+ foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null))
49
{
50
this.UpdateFileFacade(file, assemblyNameSymbols);
51
}
src/WixToolset.Core/Bind/FileFacade.cs
+1
-1
@@ -125,7 +125,7 @@ namespace WixToolset.Core.Bind
125
126
public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers;
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
130
public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed;
131
src/WixToolset.Core/CompilerCore.cs
-1
@@ -6,7 +6,6 @@ namespace WixToolset.Core
6
using System.Collections;
7
using System.Collections.Generic;
8
using System.Diagnostics;
9
- using System.Diagnostics.CodeAnalysis;
9
using System.Globalization;
10
using System.Reflection;
11
using System.Text;
src/WixToolset.Core/CompilerErrors.cs
new
+30
@@ -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
+
3
+namespace 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
+}
src/WixToolset.Core/Compiler_2.cs
+3
@@ -316,6 +316,9 @@ namespace WixToolset.Core
316
string parentName = null;
317
this.ParseSFPCatalogElement(child, ref parentName);
318
break;
319
+ case "SoftwareTag":
320
+ this.ParsePackageTagElement(child);
321
+ break;
322
case "SummaryInformation":
323
this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet);
324
break;
src/WixToolset.Core/Compiler_Bundle.cs
+3
@@ -346,6 +346,9 @@ namespace WixToolset.Core
346
case "SetVariableRef":
347
this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable);
348
break;
349
+ case "SoftwareTag":
350
+ this.ParseBundleTagElement(child);
351
+ break;
352
case "Update":
353
this.ParseUpdateElement(child);
354
break;
src/WixToolset.Core/Compiler_Patch.cs
+3
@@ -410,6 +410,9 @@ namespace WixToolset.Core
410
case "PropertyRef":
411
this.ParsePatchChildRefElement(child, "Property");
412
break;
413
+ case "SoftwareTagRef":
414
+ this.ParseTagRefElement(child);
415
+ break;
416
case "UIRef":
417
this.ParsePatchChildRefElement(child, "WixUI");
418
break;
src/WixToolset.Core/Compiler_Tag.cs
new
+315
@@ -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
+
3
+namespace 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
+}
src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs
new
+100
@@ -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
+
3
+namespace 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
+}
src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs
new
+15
@@ -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>
src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll
new
+1
@@ -0,0 +1 @@
1
+This is fakeba.dll.
\ No newline at end of file
src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl
new
+11
@@ -0,0 +1,11 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+
3
+<!--
4
+This 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>
src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs
new
+10
@@ -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>
src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs
new
+20
@@ -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>
src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt
new
+1
@@ -0,0 +1 @@
1
+This is example.txt.
\ No newline at end of file