Skip to content

Commit dbad319

Browse files
Retain original meta data during packing/signing (#120)
1 parent 2790978 commit dbad319

File tree

2 files changed

+319
-47
lines changed

2 files changed

+319
-47
lines changed

src/Otor.MsixHero.Infrastructure/Branding/MsixHeroBrandingInjector.cs

Lines changed: 78 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,17 @@ namespace Otor.MsixHero.Infrastructure.Branding
2727
{
2828
public class MsixHeroBrandingInjector
2929
{
30-
public void Inject(XDocument modifiedDocument)
30+
public enum BrandingInjectorOverrideOption
31+
{
32+
Default, // will prefer existing with exception of MsixHero, makeappx.exe and signtool.exe which must be taken over from the current toolset
33+
PreferExisting, // will prefer existing values and never overwrite anything with exception of MsixHero
34+
PreferIncoming // will replace existing values with new ones
35+
}
36+
37+
public void Inject(XDocument modifiedDocument, BrandingInjectorOverrideOption overwrite = BrandingInjectorOverrideOption.Default)
3138
{
3239
XNamespace windows10Namespace = "http://schemas.microsoft.com/appx/manifest/foundation/windows10";
3340
XNamespace appxNamespace = "http://schemas.microsoft.com/appx/2010/manifest";
34-
XNamespace uap4Namespace = XNamespace.Get("http://schemas.microsoft.com/appx/manifest/uap/windows10/4");
3541
XNamespace buildNamespace = XNamespace.Get("http://schemas.microsoft.com/developer/appx/2015/build");
3642

3743
if (modifiedDocument.Root == null)
@@ -58,64 +64,89 @@ public void Inject(XDocument modifiedDocument)
5864
namespaceAdded = true;
5965
}
6066

61-
var originalUap4Namespace = modifiedDocument.Root.Attribute(XNamespace.Xmlns + "uap4");
62-
if (originalUap4Namespace != null)
63-
{
64-
uap4Namespace = originalUap4Namespace.Value;
65-
}
66-
else
67-
{
68-
modifiedDocument.Root.SetAttributeValue(XNamespace.Xmlns + "uap4", uap4Namespace);
69-
namespaceAdded = true;
70-
}
71-
7267
if (namespaceAdded)
7368
{
7469
var ignorable = modifiedDocument.Root.Attribute("IgnorableNamespaces");
7570
if (ignorable == null)
7671
{
77-
modifiedDocument.Root.Add(new XAttribute("IgnorableNamespaces", "build uap4"));
72+
modifiedDocument.Root.Add(new XAttribute("IgnorableNamespaces", "build"));
7873
}
7974
else
8075
{
81-
modifiedDocument.Root.SetAttributeValue("IgnorableNamespaces", string.Join(" ", ignorable.Value.Split(' ').Union(new[] { "build", "uap4" })));
76+
modifiedDocument.Root.SetAttributeValue("IgnorableNamespaces", string.Join(" ", ignorable.Value.Split(' ').Union(new[] { "build" })));
8277
}
8378
}
8479

8580
var metaData = package.Element(buildNamespace + "Metadata");
86-
metaData?.Remove();
87-
88-
metaData = new XElement(buildNamespace + "Metadata");
89-
package.Add(metaData);
90-
var version = NdDll.RtlGetVersion();
91-
92-
var operatingSystem = new XElement(buildNamespace + "Item"); //( "bu", "Item");
93-
metaData.Add(operatingSystem);
94-
operatingSystem.Add(new XAttribute("Name", "OperatingSystem"));
95-
operatingSystem.Add(new XAttribute("Version", version.ToString(4)));
96-
97-
var msixHero = new XElement(buildNamespace + "Item");
98-
metaData.Add(msixHero);
99-
msixHero.Add(new XAttribute("Name", "MsixHero"));
100-
// ReSharper disable once PossibleNullReferenceException
101-
msixHero.Add(new XAttribute("Version", (Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly()).GetName().Version.ToString()));
10281

103-
var signTool = new XElement(buildNamespace + "Item");
104-
metaData.Add(signTool);
105-
signTool.Add(new XAttribute("Name", "SignTool.exe"));
106-
signTool.Add(new XAttribute("Version", GetVersion("SignTool.exe")));
107-
108-
// ReSharper disable once IdentifierTypo
109-
var makepri = new XElement(buildNamespace + "Item");
110-
metaData.Add(makepri);
111-
makepri.Add(new XAttribute("Name", "MakePri.exe"));
112-
makepri.Add(new XAttribute("Version", GetVersion("MakePri.exe")));
113-
114-
// ReSharper disable once IdentifierTypo
115-
var makeappx = new XElement(buildNamespace + "Item");
116-
metaData.Add(makeappx);
117-
makeappx.Add(new XAttribute("Name", "MakeAppx.exe"));
118-
makeappx.Add(new XAttribute("Version", GetVersion("MakeAppx.exe")));
82+
if (metaData == null)
83+
{
84+
metaData = new XElement(buildNamespace + "Metadata");
85+
package.Add(metaData);
86+
}
87+
88+
var operatingSystemVersion = NdDll.RtlGetVersion();
89+
var msixHeroVersion = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetName().Version;
90+
91+
// MSIX-Hero specific lines:
92+
InjectComponentAndVersion(metaData, buildNamespace, "MsixHero", msixHeroVersion);
93+
94+
// Generic lines:
95+
InjectComponentAndVersion(metaData, buildNamespace, "OperatingSystem", operatingSystemVersion, overwrite == BrandingInjectorOverrideOption.PreferIncoming);
96+
97+
// SDK lines:
98+
InjectComponentAndVersion(metaData, buildNamespace, "SignTool.exe", overwrite != BrandingInjectorOverrideOption.PreferExisting);
99+
InjectComponentAndVersion(metaData, buildNamespace, "MakePri.exe", overwrite == BrandingInjectorOverrideOption.PreferIncoming);
100+
InjectComponentAndVersion(metaData, buildNamespace, "MakeAppx.exe", overwrite != BrandingInjectorOverrideOption.PreferExisting);
101+
}
102+
103+
private static void InjectComponentAndVersion(XElement metadata, XNamespace buildNamespace, string name, Version version, bool overwriteExisting = true)
104+
{
105+
InjectComponentAndVersion(metadata, buildNamespace, name, version?.ToString(4), overwriteExisting);
106+
}
107+
108+
private static void InjectComponentAndVersion(XElement metadata, XNamespace buildNamespace, string sdkFileName, bool overwriteExisting = true)
109+
{
110+
InjectComponentAndVersion(metadata, buildNamespace, sdkFileName, GetVersion(sdkFileName), overwriteExisting);
111+
}
112+
113+
private static void InjectComponentAndVersion(XElement metadata, XNamespace buildNamespace, string name, string version, bool overwriteExisting = true)
114+
{
115+
var node = metadata.Elements(buildNamespace + "Item").FirstOrDefault(item => string.Equals(item.Attribute("Name")?.Value, name, StringComparison.OrdinalIgnoreCase));
116+
117+
if (node == null && name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
118+
{
119+
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(name);
120+
node = metadata.Elements(buildNamespace + "Item").FirstOrDefault(item => string.Equals(item.Attribute("Name")?.Value, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase));
121+
}
122+
123+
if (node == null)
124+
{
125+
node = new XElement(buildNamespace + "Item");
126+
node.Add(new XAttribute("Name", name));
127+
if (version != null)
128+
{
129+
node.Add(new XAttribute("Version", version));
130+
}
131+
132+
metadata.Add(node);
133+
}
134+
else
135+
{
136+
var versionAttr = node.Attribute("Version");
137+
if (versionAttr == null)
138+
{
139+
if (version != null)
140+
{
141+
versionAttr = new XAttribute("Version", version);
142+
node.Add(versionAttr);
143+
}
144+
}
145+
else if (overwriteExisting)
146+
{
147+
versionAttr.Value = version;
148+
}
149+
}
119150
}
120151

121152
private static string GetVersion(string sdkFile)
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Xml.Linq;
6+
using NUnit.Framework;
7+
using Otor.MsixHero.Infrastructure.Branding;
8+
9+
namespace Otor.MsixHero.Tests.Appx.Manifest
10+
{
11+
public class MsixHeroBrandingInjectorTests
12+
{
13+
[Test]
14+
public void TestSimpleInjection()
15+
{
16+
var manifest = this.PrepareMockManifest();
17+
18+
var injector = new MsixHeroBrandingInjector();
19+
injector.Inject(manifest);
20+
21+
Assert.NotNull(GetBuildVersion(manifest, "MsixHero"));
22+
Assert.NotNull(GetBuildVersion(manifest, "OperatingSystem"));
23+
Assert.NotNull(GetBuildVersion(manifest, "SignTool.exe"));
24+
Assert.NotNull(GetBuildVersion(manifest, "MakePri.exe"));
25+
Assert.NotNull(GetBuildVersion(manifest, "MakeAppx.exe"));
26+
}
27+
28+
[Test]
29+
public void TestOverridingDifferentCasing()
30+
{
31+
var existingValues = new Dictionary<string, string>
32+
{
33+
{ "msixHero", "91.0" },
34+
{ "operatingSystem", "92.0" },
35+
{ "MAKEPri.exe", "93.0" },
36+
{ "SIGNTool.exe", "94.0" },
37+
{ "MakeAPPX.exe", "95.0" },
38+
};
39+
40+
var manifest = this.PrepareMockManifest(existingValues);
41+
42+
var injector = new MsixHeroBrandingInjector();
43+
injector.Inject(manifest, MsixHeroBrandingInjector.BrandingInjectorOverrideOption.PreferIncoming);
44+
45+
Assert.AreNotEqual("91.0", GetBuildVersion(manifest, "MsixHero"), "By default this value must be overridden.");
46+
Assert.AreNotEqual("92.0", GetBuildVersion(manifest, "OperatingSystem"), "By default this value must be overridden.");
47+
Assert.AreNotEqual("93.0", GetBuildVersion(manifest, "MakePri.exe"), "By default this value must be overridden.");
48+
Assert.AreNotEqual("94.0", GetBuildVersion(manifest, "SignTool.exe"), "By default this value must be overridden.");
49+
Assert.AreNotEqual("95.0", GetBuildVersion(manifest, "MakeAppx.exe"), "By default this value must be overridden.");
50+
}
51+
52+
[Test]
53+
public void TestOverridingMissingExtensions()
54+
{
55+
var existingValues = new Dictionary<string, string>
56+
{
57+
{ "MsixHero", "91.0" },
58+
{ "OperatingSystem", "92.0" },
59+
{ "MakePri", "93.0" },
60+
{ "SignTool.exe", "94.0" },
61+
{ "MakeAppx", "95.0" },
62+
};
63+
64+
var manifest = this.PrepareMockManifest(existingValues);
65+
66+
var injector = new MsixHeroBrandingInjector();
67+
injector.Inject(manifest, MsixHeroBrandingInjector.BrandingInjectorOverrideOption.PreferIncoming);
68+
69+
Assert.AreNotEqual("91.0", GetBuildVersion(manifest, "MsixHero"), "By default this value must be overridden.");
70+
Assert.AreNotEqual("92.0", GetBuildVersion(manifest, "OperatingSystem"), "By default this value must be overridden.");
71+
Assert.AreNotEqual("93.0", GetBuildVersion(manifest, "MakePri.exe"), "By default this value must be overridden.");
72+
Assert.AreNotEqual("94.0", GetBuildVersion(manifest, "SignTool.exe"), "By default this value must be overridden.");
73+
Assert.AreNotEqual("95.0", GetBuildVersion(manifest, "MakeAppx.exe"), "By default this value must be overridden.");
74+
}
75+
76+
[Test]
77+
public void TestOverridingDefault()
78+
{
79+
var existingValues = new Dictionary<string, string>
80+
{
81+
{ "MsixHero", "91.0" },
82+
{ "OperatingSystem", "92.0" },
83+
{ "MakePri.exe", "93.0" },
84+
{ "SignTool.exe", "94.0" },
85+
{ "MakeAppx.exe", "95.0" },
86+
};
87+
88+
var manifest = this.PrepareMockManifest(existingValues);
89+
90+
var injector = new MsixHeroBrandingInjector();
91+
injector.Inject(manifest);
92+
93+
Assert.AreNotEqual("91.0", GetBuildVersion(manifest, "MsixHero"), "By default this value must be overridden.");
94+
Assert.AreEqual("92.0", GetBuildVersion(manifest, "OperatingSystem"), "By default this value must not be overridden.");
95+
Assert.AreEqual("93.0", GetBuildVersion(manifest, "MakePri.exe"), "By default this value must not be overridden.");
96+
Assert.AreNotEqual("94.0", GetBuildVersion(manifest, "SignTool.exe"), "By default this value must be overridden.");
97+
Assert.AreNotEqual("95.0", GetBuildVersion(manifest, "MakeAppx.exe"), "By default this value must be overridden.");
98+
}
99+
100+
[Test]
101+
public void TestOverridingPreferExisting()
102+
{
103+
var existingValues = new Dictionary<string, string>
104+
{
105+
{ "MsixHero", "91.0" },
106+
{ "OperatingSystem", "92.0" },
107+
{ "MakePri.exe", "93.0" },
108+
{ "SignTool.exe", "94.0" },
109+
{ "MakeAppx.exe", "95.0" },
110+
};
111+
112+
var manifest = this.PrepareMockManifest(existingValues);
113+
114+
var injector = new MsixHeroBrandingInjector();
115+
injector.Inject(manifest, MsixHeroBrandingInjector.BrandingInjectorOverrideOption.PreferExisting);
116+
117+
Assert.AreNotEqual("91.0", GetBuildVersion(manifest, "MsixHero"), "This value must be always overridden.");
118+
Assert.AreEqual("92.0", GetBuildVersion(manifest, "OperatingSystem"), "This value must not be overridden.");
119+
Assert.AreEqual("93.0", GetBuildVersion(manifest, "MakePri.exe"), "This value must not be overridden.");
120+
Assert.AreEqual("94.0", GetBuildVersion(manifest, "SignTool.exe"), "This value must not be overridden.");
121+
Assert.AreEqual("95.0", GetBuildVersion(manifest, "MakeAppx.exe"), "This value must not be overridden.");
122+
123+
var existingIncompleteValues = new Dictionary<string, string>
124+
{
125+
{ "SignTool.exe", "94.0" },
126+
{ "MakeAppx.exe", "95.0" },
127+
};
128+
129+
manifest = this.PrepareMockManifest(existingIncompleteValues);
130+
131+
injector.Inject(manifest, MsixHeroBrandingInjector.BrandingInjectorOverrideOption.PreferExisting);
132+
133+
Assert.NotNull(GetBuildVersion(manifest, "MsixHero"));
134+
Assert.NotNull(GetBuildVersion(manifest, "OperatingSystem"));
135+
Assert.NotNull(GetBuildVersion(manifest, "MakePri.exe"));
136+
Assert.AreEqual("94.0", GetBuildVersion(manifest, "SignTool.exe"), "This value must not be overridden.");
137+
Assert.AreEqual("95.0", GetBuildVersion(manifest, "MakeAppx.exe"), "This value must not be overridden.");
138+
}
139+
140+
[Test]
141+
public void TestOverridingPreferIncoming()
142+
{
143+
var existingValues = new Dictionary<string, string>
144+
{
145+
{ "MsixHero", "91.0" },
146+
{ "OperatingSystem", "92.0" },
147+
{ "MakePri.exe", "93.0" },
148+
{ "SignTool.exe", "94.0" },
149+
{ "MakeAppx.exe", "95.0" },
150+
};
151+
152+
var manifest = this.PrepareMockManifest(existingValues);
153+
154+
var injector = new MsixHeroBrandingInjector();
155+
injector.Inject(manifest, MsixHeroBrandingInjector.BrandingInjectorOverrideOption.PreferIncoming);
156+
157+
Assert.AreNotEqual("91.0", GetBuildVersion(manifest, "MsixHero"), "This value must be overridden.");
158+
Assert.AreNotEqual("92.0", GetBuildVersion(manifest, "OperatingSystem"), "This value must be overridden.");
159+
Assert.AreNotEqual("93.0", GetBuildVersion(manifest, "MakePri"), "This value must be overridden.");
160+
Assert.AreNotEqual("94.0", GetBuildVersion(manifest, "SignTool"), "This value must be overridden.");
161+
Assert.AreNotEqual("95.0", GetBuildVersion(manifest, "MakeAppx"), "This value must be overridden.");
162+
}
163+
164+
private XDocument PrepareMockManifest()
165+
{
166+
XNamespace windows10Namespace = "http://schemas.microsoft.com/appx/manifest/foundation/windows10";
167+
var manifest = new XDocument();
168+
manifest.Add(new XElement(windows10Namespace + "Package"));
169+
return manifest;
170+
}
171+
172+
private XDocument PrepareMockManifest(IEnumerable<KeyValuePair<string, string>> nameAndVersions)
173+
{
174+
XNamespace windows10Namespace = "http://schemas.microsoft.com/appx/manifest/foundation/windows10";
175+
var manifest = new XDocument();
176+
var package = new XElement(windows10Namespace + "Package");
177+
manifest.Add(package);
178+
179+
var buildNamespace = XNamespace.Get("http://schemas.microsoft.com/developer/appx/2015/build");
180+
var metaData = new XElement(buildNamespace + "Metadata");
181+
package.Add(metaData);
182+
183+
foreach (var item in nameAndVersions)
184+
{
185+
var node = new XElement(buildNamespace + "Item");
186+
node.Add(new XAttribute("Name", item.Key));
187+
node.Add(new XAttribute("Version", item.Value));
188+
metaData.Add(node);
189+
}
190+
191+
return manifest;
192+
}
193+
194+
private string GetBuildVersion(XDocument document, string name)
195+
{
196+
XNamespace windows10Namespace = "http://schemas.microsoft.com/appx/manifest/foundation/windows10";
197+
XNamespace appxNamespace = "http://schemas.microsoft.com/appx/2010/manifest";
198+
XNamespace uap4Namespace = XNamespace.Get("http://schemas.microsoft.com/appx/manifest/uap/windows10/4");
199+
XNamespace buildNamespace = XNamespace.Get("http://schemas.microsoft.com/developer/appx/2015/build");
200+
201+
var package = document.Element(windows10Namespace + "Package") ?? document.Element(appxNamespace + "Package");
202+
if (package == null)
203+
{
204+
throw new ArgumentException("Missing <Package /> element.", nameof(document));
205+
}
206+
207+
var metaData = package.Element(buildNamespace + "Metadata");
208+
209+
if (metaData == null)
210+
{
211+
throw new ArgumentException("Missing <Metadata /> element.", nameof(document));
212+
}
213+
214+
var node = metaData.Elements(buildNamespace + "Item").FirstOrDefault(item => string.Equals(item.Attribute("Name")?.Value, name, StringComparison.OrdinalIgnoreCase));
215+
if (node == null)
216+
{
217+
if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
218+
{
219+
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(name);
220+
node = metaData.Elements(buildNamespace + "Item").FirstOrDefault(item => string.Equals(item.Attribute("Name")?.Value, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase));
221+
if (node == null)
222+
{
223+
return null;
224+
}
225+
}
226+
else
227+
{
228+
return null;
229+
}
230+
}
231+
232+
var attribute = node.Attribute("Version");
233+
if (attribute == null)
234+
{
235+
return null;
236+
}
237+
238+
return attribute.Value;
239+
}
240+
}
241+
}

0 commit comments

Comments
 (0)