Skip to content

Commit 80cf6ca

Browse files
committed
Enable sorting app package files and licenses into their correct package root paths
1 parent e4646cf commit 80cf6ca

4 files changed

Lines changed: 377 additions & 87 deletions

File tree

src/Applications/MobilePackageGen/Program.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ Mobile Package Generator Tool
2828

2929
BuildMetadataHandler.GetOEMInput(disks, outputFolder);
3030
BuildMetadataHandler.GetFeatureManifests(disks, outputFolder);
31+
32+
List<AppxPackage> appList = BuildMetadataHandler.GetAppList(disks, outputFolder);
33+
34+
AppBuilder.BuildApps(disks, outputFolder, appList);
35+
3136
UpdateHistory.UpdateHistory? updateHistory = BuildMetadataHandler.GetUpdateHistory(disks);
32-
BuildMetadataHandler.GetAdditionalContent(disks, outputFolder);
3337

3438
CBSBuilder.BuildCBS(disks, outputFolder, updateHistory);
3539

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
using DiscUtils;
2+
using System.Xml.Linq;
3+
4+
namespace MobilePackageGen
5+
{
6+
public class AppBuilder
7+
{
8+
public static void BuildApps(IEnumerable<IDisk> disks, string destination_path, List<AppxPackage> appList)
9+
{
10+
Logging.Log();
11+
Logging.Log("Building App Files...");
12+
Logging.Log();
13+
14+
GetAdditionalContent(disks, destination_path, appList);
15+
16+
Logging.Log();
17+
Logging.Log("Cleaning up...");
18+
Logging.Log();
19+
20+
TempManager.CleanupTempFiles();
21+
}
22+
23+
private static List<IPartition> GetPartitionsWithServicing(IEnumerable<IDisk> disks)
24+
{
25+
List<IPartition> fileSystemsWithServicing = [];
26+
27+
foreach (IDisk disk in disks)
28+
{
29+
foreach (IPartition partition in disk.Partitions)
30+
{
31+
if (partition.Name != "PreInstalled")
32+
{
33+
continue;
34+
}
35+
36+
IFileSystem? fileSystem = partition.FileSystem;
37+
38+
if (fileSystem != null)
39+
{
40+
try
41+
{
42+
// PreInstalled Partition
43+
// Extracted APPX, Licenses
44+
if (fileSystem.DirectoryExists("AppData") && fileSystem.DirectoryExists("WindowsApps"))
45+
{
46+
fileSystemsWithServicing.Add(partition);
47+
}
48+
}
49+
catch (Exception ex)
50+
{
51+
Logging.Log($"Error: Looking up file system servicing failed! {ex.Message}", LoggingLevel.Error);
52+
}
53+
}
54+
}
55+
}
56+
57+
return fileSystemsWithServicing;
58+
}
59+
60+
private static int GetPackageCount(IEnumerable<IDisk> disks)
61+
{
62+
int count = 0;
63+
64+
IEnumerable<IPartition> partitionsWithCbsServicing = GetPartitionsWithServicing(disks);
65+
66+
foreach (IPartition partition in partitionsWithCbsServicing)
67+
{
68+
IFileSystem fileSystem = partition.FileSystem!;
69+
70+
IEnumerable<string> manifestFiles = fileSystem.GetDirectories("WindowsApps", "*", SearchOption.TopDirectoryOnly);
71+
72+
count += manifestFiles.Count();
73+
}
74+
75+
return count;
76+
}
77+
78+
public static void GetAdditionalContent(IEnumerable<IDisk> disks, string destination_path, List<AppxPackage> appList)
79+
{
80+
int packagesCount = GetPackageCount(disks);
81+
82+
IEnumerable<IPartition> partitionsWithCbsServicing = GetPartitionsWithServicing(disks);
83+
84+
int i = 0;
85+
86+
foreach (IPartition partition in partitionsWithCbsServicing)
87+
{
88+
IFileSystem fileSystem = partition.FileSystem!;
89+
90+
ExtractAppLicenses(fileSystem, destination_path, appList);
91+
ExtractAppPackages(fileSystem, destination_path, packagesCount, ref i, appList);
92+
}
93+
}
94+
95+
public static string GetPFNFromAppLicense(Stream strm)
96+
{
97+
string PFM = "";
98+
99+
XDocument xdoc = XDocument.Load(strm, LoadOptions.None);
100+
XNamespace ns = xdoc.Root!.GetDefaultNamespace();
101+
102+
IEnumerable<XElement> packages = xdoc.Descendants(ns + "PFM");
103+
104+
if (packages != null)
105+
{
106+
foreach (XElement package in packages)
107+
{
108+
PFM = package.Value;
109+
break;
110+
}
111+
}
112+
113+
xdoc = null;
114+
115+
return PFM;
116+
}
117+
118+
public static void ExtractAppLicenses(IFileSystem fileSystem, string destination_path, List<AppxPackage> appList)
119+
{
120+
string[] LicenseFiles = [.. fileSystem.GetFiles("AppData", "*.xml", SearchOption.TopDirectoryOnly)];
121+
122+
foreach (string LicenseFile in LicenseFiles)
123+
{
124+
Logging.Log($"Extracting {Path.GetFileNameWithoutExtension(LicenseFile)} license file...");
125+
126+
// Fallback
127+
string destFolder = Path.Combine(destination_path, "PreInstalled", "Licenses");
128+
string destName = Path.GetFileName(LicenseFile);
129+
130+
// Actual
131+
using Stream PreInstalledLicenseFileStream = fileSystem.OpenFile(LicenseFile, FileMode.Open, FileAccess.Read);
132+
string PFM = GetPFNFromAppLicense(PreInstalledLicenseFileStream);
133+
AppxPackage? matchingAppListItem = appList.FirstOrDefault(x =>
134+
x.ID.Equals(PFM, StringComparison.InvariantCultureIgnoreCase));
135+
if (matchingAppListItem != null)
136+
{
137+
destFolder = Path.Combine(destination_path, matchingAppListItem.Path.Replace("$(mspackageroot)\\", "", StringComparison.InvariantCultureIgnoreCase));
138+
destName = matchingAppListItem.License;
139+
}
140+
141+
if (!Directory.Exists(destFolder))
142+
{
143+
Directory.CreateDirectory(destFolder);
144+
}
145+
146+
string destFile = Path.Combine(destFolder, destName);
147+
148+
if (!File.Exists(destFile))
149+
{
150+
using Stream PreInstalledFileStream = fileSystem.OpenFile(LicenseFile, FileMode.Open, FileAccess.Read);
151+
FileAttributes Attributes = fileSystem.GetAttributes(LicenseFile) & ~FileAttributes.ReparsePoint;
152+
DateTime LastWriteTime = fileSystem.GetLastWriteTime(LicenseFile);
153+
154+
using (Stream outputFile = File.Create(destFile))
155+
{
156+
PreInstalledFileStream.CopyTo(outputFile);
157+
}
158+
159+
File.SetAttributes(destFile, Attributes);
160+
File.SetLastWriteTime(destFile, LastWriteTime);
161+
}
162+
}
163+
}
164+
165+
public static void ExtractAppPackages(IFileSystem fileSystem, string destination_path, int packagesCount, ref int i, List<AppxPackage> appList)
166+
{
167+
string[] AppFolders = [.. fileSystem.GetDirectories("WindowsApps", "*", SearchOption.TopDirectoryOnly)];
168+
foreach (string AppFolder in AppFolders)
169+
{
170+
// --------------------------
171+
172+
string componentStatus = $"Creating package {i + 1} of {packagesCount} - {Path.GetFileName(AppFolder)}";
173+
if (componentStatus.Length > Console.BufferWidth - 24 - 1)
174+
{
175+
componentStatus = $"{componentStatus[..(Console.BufferWidth - 24 - 4)]}...";
176+
}
177+
178+
Logging.Log(componentStatus);
179+
string progressBarString = Logging.GetDISMLikeProgressBar((uint)Math.Round(i * 100d / packagesCount));
180+
Logging.Log(progressBarString, returnLine: false);
181+
182+
string fileStatus = "";
183+
184+
// --------------------------
185+
186+
// Ex: "Microsoft.Dynamics365.Guides_1.0.100.0_arm__8wekyb3d8bbwe"
187+
// PFM: <PFM>microsoft.dynamics365.guides_8wekyb3d8bbwe</PFM>
188+
// Arch: ARM
189+
string appPackageName = Path.GetFileName(AppFolder);
190+
string appArchitecture = appPackageName.Split("_")[^3];
191+
string appPFM = $"{appPackageName.Split("_")[0]}_{appPackageName.Split("_")[^1]}";
192+
193+
// Fallback
194+
string rootDestination = Path.Combine(destination_path, "PreInstalled", "Apps");
195+
196+
// Actual
197+
// Order by count of CPUIDs because we prefer packages that strictly bind to the same architecture instead of wow fallback
198+
appList = [.. appList.OrderBy(x => x.Architectures.Length)];
199+
AppxPackage? matchingAppListItem = appList.FirstOrDefault(x =>
200+
x.ID.Equals(appPFM, StringComparison.InvariantCultureIgnoreCase) &&
201+
(x.Architectures.Length == 0 || x.Architectures.Any(y => y.Equals(appArchitecture, StringComparison.InvariantCultureIgnoreCase))));
202+
if (matchingAppListItem != null)
203+
{
204+
rootDestination = Path.Combine(destination_path, matchingAppListItem.Path.Replace("$(mspackageroot)\\", "", StringComparison.InvariantCultureIgnoreCase), matchingAppListItem.Name);
205+
}
206+
207+
string destination = Path.Combine(rootDestination, appPackageName);
208+
209+
string[] AppFiles = [.. fileSystem.GetFiles(AppFolder, "*", SearchOption.AllDirectories)];
210+
for (int currentFileNumber = 0; currentFileNumber < AppFiles.Length; currentFileNumber++)
211+
{
212+
string AppFile = AppFiles[currentFileNumber];
213+
// This strips WindowsApps
214+
string destinationFileRootPath = string.Join("\\", AppFile[(AppFolder.Length + 1)..].Split("\\")[..^1]);
215+
string destFolder = Path.Combine(destination, destinationFileRootPath);
216+
217+
if (!Directory.Exists(destFolder))
218+
{
219+
Directory.CreateDirectory(destFolder);
220+
}
221+
222+
string destFile = Path.Combine(destFolder, Path.GetFileName(AppFile));
223+
224+
if (!File.Exists(destFile))
225+
{
226+
using Stream PreInstalledFileStream = fileSystem.OpenFile(AppFile, FileMode.Open, FileAccess.Read);
227+
FileAttributes Attributes = fileSystem.GetAttributes(AppFile) & ~FileAttributes.ReparsePoint;
228+
DateTime LastWriteTime = fileSystem.GetLastWriteTime(AppFile);
229+
230+
using (Stream outputFile = File.Create(destFile))
231+
{
232+
PreInstalledFileStream.CopyTo(outputFile);
233+
}
234+
235+
File.SetAttributes(destFile, Attributes);
236+
File.SetLastWriteTime(destFile, LastWriteTime);
237+
}
238+
}
239+
240+
// --------------------------
241+
242+
if (i != packagesCount - 1)
243+
{
244+
Console.SetCursorPosition(0, Console.CursorTop - 1);
245+
246+
Logging.Log(new string(' ', componentStatus.Length));
247+
Logging.Log(Logging.GetDISMLikeProgressBar(100));
248+
249+
if (string.IsNullOrEmpty(fileStatus))
250+
{
251+
Logging.Log(new string(' ', fileStatus.Length));
252+
Logging.Log(new string(' ', 60));
253+
}
254+
else
255+
{
256+
Logging.Log(new string(' ', fileStatus.Length));
257+
Logging.Log(Logging.GetDISMLikeProgressBar(100));
258+
}
259+
260+
Console.SetCursorPosition(0, Console.CursorTop - 4);
261+
}
262+
else
263+
{
264+
Logging.Log($"\r{Logging.GetDISMLikeProgressBar(100)}");
265+
266+
if (string.IsNullOrEmpty(fileStatus))
267+
{
268+
Logging.Log();
269+
Logging.Log(new string(' ', 60));
270+
}
271+
else
272+
{
273+
Logging.Log();
274+
Logging.Log(Logging.GetDISMLikeProgressBar(100));
275+
}
276+
}
277+
278+
i++;
279+
}
280+
}
281+
}
282+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace MobilePackageGen
2+
{
3+
public class AppxPackage
4+
{
5+
public string Name { get; set; }
6+
public string Path { get; set; }
7+
public string License { get; set; }
8+
public string ID { get; set; }
9+
10+
public string[] Architectures { get; set; }
11+
12+
public AppxPackage(string name, string path, string license, string id, string[] architectures)
13+
{
14+
Name = name;
15+
Path = path;
16+
License = license;
17+
ID = id;
18+
Architectures = architectures;
19+
}
20+
21+
public override string ToString()
22+
{
23+
return ID ?? System.IO.Path.Combine(Path, Name);
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)