@joebigelow / wix-1 / commits / 760fb810

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