diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 666ddf8..5537d82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - name: Install .NET SDK uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@v1.0.2 diff --git a/Windows Build Identifier/Identification/Common.cs b/Windows Build Identifier/Identification/Common.cs index eb4246b..2b218ed 100644 --- a/Windows Build Identifier/Identification/Common.cs +++ b/Windows Build Identifier/Identification/Common.cs @@ -164,7 +164,7 @@ public static WindowsVersion GetGreaterVersion(WindowsVersion version1, WindowsV CultureInfo provider = CultureInfo.InvariantCulture; - string format = "yyMMDD-HHmm"; + const string format = "yyMMdd-HHmm"; DateTime date1 = DateTime.ParseExact(version1.CompileDate, format, provider); DateTime date2 = DateTime.ParseExact(version2.CompileDate, format, provider); @@ -249,6 +249,7 @@ public static void DisplayReport(WindowsImage report) Console.WriteLine("Architecture : " + report.Architecture); Console.WriteLine("BuildType : " + report.BuildType); Console.WriteLine("Types : " + typedisp); + Console.WriteLine("Base Sku : " + report.BaseSku); Console.WriteLine("Sku : " + report.Sku); Console.WriteLine("Editions : " + editiondisp); Console.WriteLine("Licensing : " + report.Licensing); diff --git a/Windows Build Identifier/Identification/InstalledImage/DetectionHandler.cs b/Windows Build Identifier/Identification/InstalledImage/DetectionHandler.cs index 8da805e..ac4330f 100644 --- a/Windows Build Identifier/Identification/InstalledImage/DetectionHandler.cs +++ b/Windows Build Identifier/Identification/InstalledImage/DetectionHandler.cs @@ -49,23 +49,30 @@ public static WindowsImage IdentifyWindowsNT(WindowsInstallProviderInterface ins // string kernelPath = ""; + string hvPath = ""; string shell32Path = ""; string softwareHivePath = ""; string systemHivePath = ""; string userPath = ""; var kernelEntry = fileentries.FirstOrDefault(x => - x.EndsWith( - @"\ntkrnlmp.exe", StringComparison.InvariantCultureIgnoreCase) && - !x.Contains("WinSxS", StringComparison.InvariantCultureIgnoreCase)) ?? - fileentries.FirstOrDefault(x => - x.EndsWith(@"\ntoskrnl.exe", StringComparison.InvariantCultureIgnoreCase) && - !x.Contains("WinSxS", StringComparison.InvariantCultureIgnoreCase)); + (x.EndsWith(@"\ntkrnlmp.exe", StringComparison.InvariantCultureIgnoreCase) || x.EndsWith(@"\ntoskrnl.exe", StringComparison.InvariantCultureIgnoreCase)) + && x.Contains("System32", StringComparison.InvariantCultureIgnoreCase)); if (kernelEntry != null) { kernelPath = installProvider.ExpandFile(kernelEntry); } + var hvEntry = fileentries.FirstOrDefault(x => + (x.EndsWith(@"\hvax64.exe", StringComparison.InvariantCultureIgnoreCase) // AMD64 + || x.EndsWith(@"\hvix64.exe", StringComparison.InvariantCultureIgnoreCase) // Intel64 + || x.EndsWith(@"\hvaa64.exe", StringComparison.InvariantCultureIgnoreCase)) // ARM64 + && x.Contains("System32", StringComparison.InvariantCultureIgnoreCase)); + if (hvEntry != null) + { + hvPath = installProvider.ExpandFile(hvEntry); + } + var shell32Entry = fileentries.FirstOrDefault(x => x.EndsWith(@"system32\shell32.dll", StringComparison.InvariantCultureIgnoreCase)); if (shell32Entry != null) { @@ -114,7 +121,7 @@ public static WindowsImage IdentifyWindowsNT(WindowsInstallProviderInterface ins if (!string.IsNullOrEmpty(softwareHivePath) && !string.IsNullOrEmpty(systemHivePath)) { Console.WriteLine("Extracting version information from the image 2"); - info2 = ExtractVersionInfo2(softwareHivePath, systemHivePath); + info2 = ExtractVersionInfo2(softwareHivePath, systemHivePath, hvPath); } if (!string.IsNullOrEmpty(userPath)) @@ -150,6 +157,9 @@ public static WindowsImage IdentifyWindowsNT(WindowsInstallProviderInterface ins if (!string.IsNullOrEmpty(kernelPath)) File.Delete(kernelPath); + if (!string.IsNullOrEmpty(hvPath)) + File.Delete(hvPath); + WindowsVersion correctVersion = Common.GetGreaterVersion(info.version, info2.version); correctVersion = Common.GetGreaterVersion(correctVersion, info3.version); @@ -171,8 +181,8 @@ public static WindowsImage IdentifyWindowsNT(WindowsInstallProviderInterface ins ulong buildwindow = report.BuildNumber + 50; foreach (var binary in installProvider.GetFileSystemEntries()) { - if (binary.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) || - binary.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) || + if (binary.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) || + binary.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) || binary.EndsWith(".sys", StringComparison.InvariantCultureIgnoreCase)) { string file = ""; @@ -209,7 +219,7 @@ public static WindowsImage IdentifyWindowsNT(WindowsInstallProviderInterface ins #region Edition Gathering bool IsUnstaged = fileentries.Any(x => x.StartsWith(@"packages\", StringComparison.InvariantCultureIgnoreCase)); - + if (IsUnstaged) { report.Sku = "Unstaged"; @@ -247,7 +257,9 @@ public static WindowsImage IdentifyWindowsNT(WindowsInstallProviderInterface ins else if (!string.IsNullOrEmpty(systemHivePath)) { Console.WriteLine("Extracting additional edition information"); - report.Sku = ExtractEditionFromRegistry(systemHivePath); + var skuData = ExtractEditionFromRegistry(systemHivePath, softwareHivePath); + report.BaseSku = skuData.baseSku; + report.Sku = skuData.sku; } #endregion @@ -373,7 +385,7 @@ private static VersionInfo1 ExtractVersionInfo(string kernelPath) } catch { - + } var ver = info.FileVersion; @@ -393,9 +405,33 @@ private static VersionInfo1 ExtractVersionInfo(string kernelPath) return result; } - private static string ExtractEditionFromRegistry(string systemHivePath) + private static (string baseSku, string sku) ExtractEditionFromRegistry(string systemHivePath, string softwareHivePath) { - string sku = ""; + (string baseSku, string sku) ret = ("", ""); + + using (var softHiveStream = new FileStream(softwareHivePath, FileMode.Open, FileAccess.Read)) + using (DiscUtils.Registry.RegistryHive softHive = new DiscUtils.Registry.RegistryHive(softHiveStream)) + { + try + { + DiscUtils.Registry.RegistryKey subkey = softHive.Root.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion"); + if (subkey != null) + { + var edition = subkey.GetValue("EditionID") as string; + var compositionEdition = subkey.GetValue("CompositionEditionID") as string; + + if (!string.IsNullOrEmpty(edition) && !string.IsNullOrEmpty(compositionEdition)) + { + return (compositionEdition, edition); + } + else if (!string.IsNullOrEmpty(edition)) + { + return (edition, edition); + } + } + } + catch { } + } using (var hiveStream = new FileStream(systemHivePath, FileMode.Open, FileAccess.Read)) using (DiscUtils.Registry.RegistryHive hive = new DiscUtils.Registry.RegistryHive(hiveStream)) @@ -404,45 +440,56 @@ private static string ExtractEditionFromRegistry(string systemHivePath) { DiscUtils.Registry.RegistryKey subkey = hive.Root.OpenSubKey(@"ControlSet001\Control\ProductOptions"); - if (subkey.GetValue("SubscriptionPfnList") != null) - { - var pfn = ((string[])subkey.GetValue("SubscriptionPfnList"))[0]; - var product = pfn.Split(".")[2]; - product = product.Replace("Pro", "Professional"); - sku = product; - Console.WriteLine("Effective SKU: " + sku); - } - else - { - var prodpol = (byte[])subkey.GetValue("ProductPolicy"); + var prodpol = (byte[])subkey.GetValue("ProductPolicy"); + var policies = Common.ParseProductPolicy(prodpol); - var policies = Common.ParseProductPolicy(prodpol); + var pol = policies.FirstOrDefault(x => x.Name == "Kernel-ProductInfo"); + if (pol != null && pol.Type == 4) + { + int product = BitConverter.ToInt32(pol.Data); + Console.WriteLine("Detected product id: " + product); - if (policies.Any(x => x.Name == "Kernel-ProductInfo")) + if (Enum.IsDefined(typeof(Product), product)) + { + ret.baseSku = Enum.GetName(typeof(Product), product); + } + else { - var pol = policies.First(x => x.Name == "Kernel-ProductInfo"); + ret.baseSku = $"UnknownAdditional{product.ToString("X")}"; + } - if (pol.Type == 4) - { - int product = BitConverter.ToInt32(pol.Data); - Console.WriteLine("Detected product id: " + product); + ret.sku = ret.baseSku; + Console.WriteLine("Base SKU: " + ret.baseSku); + } - if (Enum.IsDefined(typeof(Product), product)) - { - sku = Enum.GetName(typeof(Product), product); - } - else - { - sku = $"UnknownAdditional{product.ToString("X")}"; - } + pol = policies.FirstOrDefault(x => x.Name == "Kernel-BrandingInfo"); + if (pol != null && pol.Type == 4) + { + int product = BitConverter.ToInt32(pol.Data); + Console.WriteLine("Detected product id: " + product); - Console.WriteLine("Effective SKU: " + sku); - } + if (Enum.IsDefined(typeof(Product), product)) + { + ret.sku = Enum.GetName(typeof(Product), product); + } + else + { + ret.sku = $"UnknownAdditional{product.ToString("X")}"; + } + + if (string.IsNullOrEmpty(ret.baseSku)) + { + ret.baseSku = ret.sku; } + + Console.WriteLine("Branding SKU: " + ret.sku); } + + return ret; } catch { }; + string sku = ""; if (string.IsNullOrEmpty(sku)) { try @@ -503,12 +550,13 @@ private static string ExtractEditionFromRegistry(string systemHivePath) } catch { }; } + + return (sku, sku); } - return sku; } - private static VersionInfo2 ExtractVersionInfo2(string softwareHivePath, string systemHivePath) + private static VersionInfo2 ExtractVersionInfo2(string softwareHivePath, string systemHivePath, string hvPath) { VersionInfo2 result = new VersionInfo2(); @@ -526,9 +574,25 @@ private static VersionInfo2 ExtractVersionInfo2(string softwareHivePath, string string releaseId = (string)subkey.GetValue("ReleaseId"); - int? UBR = (int?)subkey.GetValue("UBR"); int? Major = (int?)subkey.GetValue("CurrentMajorVersionNumber"); + string Build = (string)subkey.GetValue("CurrentBuildNumber"); int? Minor = (int?)subkey.GetValue("CurrentMinorVersionNumber"); + int? UBR = (int?)subkey.GetValue("UBR"); + string Branch = null; + + subkey = hive.Root.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion\Update\TargetingInfo\Installed"); + if (subkey != null) + { + foreach (DiscUtils.Registry.RegistryKey sub in subkey.SubKeys) + { + if (!sub.Name.Contains(".OS.")) + { + continue; + } + + Branch = sub.GetValue("Branch") as string; + } + } if (!string.IsNullOrEmpty(buildLab) && buildLab.Count(x => x == '.') == 2) { @@ -549,11 +613,6 @@ private static VersionInfo2 ExtractVersionInfo2(string softwareHivePath, string result.version.BuildNumber = ulong.Parse(splitLabEx[0]); } - if (UBR.HasValue) - { - result.version.DeltaVersion = (ulong)UBR.Value; - } - if (Major.HasValue) { result.version.MajorVersion = (ulong)Major.Value; @@ -564,6 +623,21 @@ private static VersionInfo2 ExtractVersionInfo2(string softwareHivePath, string result.version.MinorVersion = (ulong)Minor.Value; } + if (!string.IsNullOrEmpty(Build)) + { + result.version.BuildNumber = ulong.Parse(Build); + } + + if (UBR.HasValue) + { + result.version.DeltaVersion = (ulong)UBR.Value; + } + + if (!string.IsNullOrEmpty(Branch)) + { + result.version.BranchName = Branch; + } + if (!string.IsNullOrEmpty(releaseId)) { result.Tag = releaseId; @@ -571,27 +645,63 @@ private static VersionInfo2 ExtractVersionInfo2(string softwareHivePath, string } catch { }; + if (!string.IsNullOrEmpty(hvPath)) + { + try + { + var content = File.ReadAllText(hvPath, System.Text.Encoding.ASCII).AsSpan(); + content = content[content.IndexOf("GitEnlistment")..]; + content = content[(content.IndexOf('.') + 1)..]; + content = content[..11]; + + result.version.CompileDate = new string(content); + } + catch { }; + } + try { string productId = ""; + bool found = false; - DiscUtils.Registry.RegistryKey subkey = hive.Root.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion\DefaultProductKey"); + DiscUtils.Registry.RegistryKey subkey = hive.Root.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion"); if (subkey != null) { - productId = (string)subkey.GetValue("ProductId"); + var pidData = subkey.GetValue("DigitalProductId4") as byte[]; + if (pidData != null) + { + pidData = pidData[0x3f8..0x458]; + + Span pidString = new char[0x30]; + System.Text.Encoding.Unicode.GetChars(pidData, pidString); + pidString = pidString[..pidString.IndexOf('\0')]; + + string licenseType = new string(pidString); + result.Licensing = (Licensing)Enum.Parse(typeof(Licensing), licenseType.Split(':')[0]); + found = true; + } } - else + + if(!found) { - subkey = hive.Root.OpenSubKey(@"Microsoft\Windows\CurrentVersion"); + subkey = hive.Root.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion\DefaultProductKey"); if (subkey != null) { productId = (string)subkey.GetValue("ProductId"); } - } + else + { + subkey = hive.Root.OpenSubKey(@"Microsoft\Windows\CurrentVersion"); + if (subkey != null) + { + productId = (string)subkey.GetValue("ProductId"); + } + } - if (!string.IsNullOrEmpty(productId)) - { - result.Licensing = productId.Contains("OEM") ? Licensing.OEM : Licensing.Retail; + if (!string.IsNullOrEmpty(productId)) + { + result.Licensing = productId.Contains("OEM") ? Licensing.OEM : Licensing.Retail; + } } } catch { }; diff --git a/Windows Build Identifier/Identification/MediaHandler.cs b/Windows Build Identifier/Identification/MediaHandler.cs index f571f52..147611f 100644 --- a/Windows Build Identifier/Identification/MediaHandler.cs +++ b/Windows Build Identifier/Identification/MediaHandler.cs @@ -21,30 +21,27 @@ public class UnsupportedWIMException : Exception { } public class UnsupportedWIMXmlException : Exception { } - private static string ExtractWIMXml(Stream wimstream) + private static string ExtractWIMXml(ArchiveFile archiveFile) { try { - using (ArchiveFile archiveFile = new ArchiveFile(wimstream, SevenZipFormat.Wim)) + if (archiveFile.Entries.Any(x => x.FileName == "[1].xml")) { - if (archiveFile.Entries.Any(x => x.FileName == "[1].xml")) - { - Entry wimXmlEntry = archiveFile.Entries.First(x => x.FileName == "[1].xml"); - - string xml; - using (MemoryStream memoryStream = new MemoryStream()) - { - wimXmlEntry.Extract(memoryStream); + Entry wimXmlEntry = archiveFile.Entries.First(x => x.FileName == "[1].xml"); - xml = Encoding.Unicode.GetString(memoryStream.ToArray(), 2, (int)memoryStream.Length - 2); - } - - return xml; - } - else if (archiveFile.Entries.Any(x => x.FileName == "Windows")) + string xml; + using (MemoryStream memoryStream = new MemoryStream()) { - return archiveFile.GetArchiveComment(); + wimXmlEntry.Extract(memoryStream); + + xml = Encoding.Unicode.GetString(memoryStream.ToArray(), 2, (int)memoryStream.Length - 2); } + + return xml; + } + else if (archiveFile.Entries.Any(x => x.FileName == "Windows")) + { + return archiveFile.GetArchiveComment(); } throw new UnsupportedWIMException(); @@ -80,7 +77,9 @@ private static WindowsImageIndex[] IdentifyWindowsNTFromWIM(Stream wimstream, bo Console.WriteLine("Gathering WIM information XML file"); - string xml = ExtractWIMXml(wimstream); + + using ArchiveFile archiveFile = new ArchiveFile(wimstream, SevenZipFormat.Wim); + string xml = ExtractWIMXml(archiveFile); Console.WriteLine("Parsing WIM information XML file"); XmlFormats.WIMXml.WIM wim = GetWIMClassFromXml(xml); @@ -88,14 +87,16 @@ private static WindowsImageIndex[] IdentifyWindowsNTFromWIM(Stream wimstream, bo Console.WriteLine($"Found {wim.IMAGE.Length} images in the wim according to the XML"); Console.WriteLine("Evaluating relevant images in the WIM according to the XML"); - int irelevantcount2 = (wim.IMAGE.Any(x => x.DESCRIPTION.Contains("winpe", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0) + - (wim.IMAGE.Any(x => x.DESCRIPTION.Contains("setup", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0) + - (wim.IMAGE.Any(x => x.DESCRIPTION.Contains("preinstallation", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0) + - (wim.IMAGE.Any(x => x.DESCRIPTION.Contains("winre", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0) + - (wim.IMAGE.Any(x => x.DESCRIPTION.Contains("recovery", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0); + int irelevantcount2 = (wim.IMAGE.Any(x => x.DESCRIPTION != null && x.DESCRIPTION.Contains("winpe", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0) + + (wim.IMAGE.Any(x => x.DESCRIPTION != null && x.DESCRIPTION.Contains("setup", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0) + + (wim.IMAGE.Any(x => x.DESCRIPTION != null && x.DESCRIPTION.Contains("preinstallation", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0) + + (wim.IMAGE.Any(x => x.DESCRIPTION != null && x.DESCRIPTION.Contains("winre", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0) + + (wim.IMAGE.Any(x => x.DESCRIPTION != null && x.DESCRIPTION.Contains("recovery", StringComparison.InvariantCultureIgnoreCase)) ? 1 : 0); Console.WriteLine($"Found {irelevantcount2} irrelevant images in the wim according to the XML"); + var provider = new WIMInstallProviderInterface(archiveFile); + foreach (var image in wim.IMAGE) { Console.WriteLine(); @@ -105,11 +106,11 @@ private static WindowsImageIndex[] IdentifyWindowsNTFromWIM(Stream wimstream, bo // If what we're trying to identify isn't just a winpe, and we are accessing a winpe image // skip the image // - int irelevantcount = (image.DESCRIPTION.Contains("winpe", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0) + - (image.DESCRIPTION.Contains("setup", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0) + - (image.DESCRIPTION.Contains("preinstallation", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0) + - (image.DESCRIPTION.Contains("winre", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0) + - (image.DESCRIPTION.Contains("recovery", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0); + int irelevantcount = (image.DESCRIPTION != null && image.DESCRIPTION.Contains("winpe", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0) + + (image.DESCRIPTION != null && image.DESCRIPTION.Contains("setup", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0) + + (image.DESCRIPTION != null && image.DESCRIPTION.Contains("preinstallation", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0) + + (image.DESCRIPTION != null && image.DESCRIPTION.Contains("winre", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0) + + (image.DESCRIPTION != null && image.DESCRIPTION.Contains("recovery", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0); Console.WriteLine($"Index contains {irelevantcount} flags indicating this is a preinstallation environment"); @@ -125,7 +126,6 @@ private static WindowsImageIndex[] IdentifyWindowsNTFromWIM(Stream wimstream, bo if (index != null && wim.IMAGE[0].INDEX == "0") { - using ArchiveFile archiveFile = new ArchiveFile(wimstream, SevenZipFormat.Wim); if (!archiveFile.Entries.Any(x => x.FileName.StartsWith("0\\"))) { WorkaroundForWIMFormatBug = true; @@ -140,12 +140,10 @@ private static WindowsImageIndex[] IdentifyWindowsNTFromWIM(Stream wimstream, bo Console.WriteLine($"Index value: {index}"); - var provider = new WIMInstallProviderInterface(wimstream, index); + provider.SetIndex(index); var report = InstalledImage.DetectionHandler.IdentifyWindowsNT(provider); - provider.Close(); - // fallback if ((string.IsNullOrEmpty(report.Sku) || report.Sku == "TerminalServer") && !string.IsNullOrEmpty(image.FLAGS)) { @@ -205,7 +203,7 @@ private static WindowsImageIndex[] IdentifyWindowsNTFromWIM(Stream wimstream, bo results.Add(imageIndex); } - wimstream.Dispose(); + provider.Close(); return results.ToArray(); } @@ -575,7 +573,7 @@ public static FileItem[] IdentifyWindowsFromISO(string isopath, bool deep, bool using FileStream isoStream = File.Open(isopath, FileMode.Open, FileAccess.Read); VfsFileSystemFacade cd = new CDReader(isoStream, true); - if (cd.FileExists(@"README.TXT")) + if (cd.FileExists(@"README.TXT") || cd.Root.GetDirectories().Length == 0) { cd = new UdfReader(isoStream); } @@ -646,6 +644,7 @@ public static WindowsImageIndex[] IdentifyWindowsFromWIM(Stream wim, bool includ Console.WriteLine(ex.ToString()); } + wim.Dispose(); return result; } diff --git a/Windows Build Identifier/Identification/Structures.cs b/Windows Build Identifier/Identification/Structures.cs index 19709ac..6201ee3 100644 --- a/Windows Build Identifier/Identification/Structures.cs +++ b/Windows Build Identifier/Identification/Structures.cs @@ -23,6 +23,8 @@ public enum Licensing Volume } + // Taken from Pivotman319 / BetaWiki project + // https://docs.google.com/document/d/1hpphLcfgcygpFuXGl9WrXeBpdLECAhXuImmR7uimnZY/edit public enum Product { Undefined = 0x00000000, @@ -32,64 +34,64 @@ public enum Product Enterprise = 0x00000004, HomeBasicN = 0x00000005, Business = 0x00000006, - StandardServer = 0x00000007, - DatacenterServer = 0x00000008, - SmallBusinessServer = 0x00000009, - EnterpriseServer = 0x0000000A, + ServerStandard = 0x00000007, + ServerDatacenter = 0x00000008, + ServerSmallBusiness = 0x00000009, + ServerEnterprise = 0x0000000A, Starter = 0x0000000B, - DatacenterServerCore = 0x0000000C, - StandardServerCore = 0x0000000D, - EnterpriseServerCore = 0x0000000E, - EnterpriseServerIA64 = 0x0000000F, + ServerDatacenterCore = 0x0000000C, + ServerStandardCore = 0x0000000D, + ServerEnterpriseCore = 0x0000000E, + ServerEnterpriseIA64 = 0x0000000F, BusinessN = 0x00000010, - WebServer = 0x00000011, - ClusterServer = 0x00000012, - HomeServer = 0x00000013, - StorageExpressServer = 0x00000014, - StorageStandardServer = 0x00000015, - StorageWorkgroupServer = 0x00000016, - StorageEnterpriseServer = 0x00000017, + ServerWeb = 0x00000011, + ServerCluster = 0x00000012, + ServerHome = 0x00000013, + ServerStorageExpress = 0x00000014, + ServerStorageStandard = 0x00000015, + ServerStorageWorkgroup = 0x00000016, + ServerStorageEnterprise = 0x00000017, ServerForSmallBusiness = 0x00000018, - SmallBusinessServerPremium = 0x00000019, + ServerSmallBusinessPremium = 0x00000019, HomePremiumN = 0x0000001A, EnterpriseN = 0x0000001B, UltimateN = 0x0000001C, - WebServerCore = 0x0000001D, - MediumBusinessServerManagement = 0x0000001E, - MediumBusinessServerSecurity = 0x0000001F, - MediumBusinessServerMessaging = 0x00000020, + ServerWebCore = 0x0000001D, + ServerMediumBusinessManagement = 0x0000001E, + ServerMediumBusinessSecurity = 0x0000001F, + ServerMediumBusinessMessaging = 0x00000020, ServerFoundation = 0x00000021, - HomePremiumServer = 0x00000022, + ServerHomePremium = 0x00000022, ServerForSmallBusinessV = 0x00000023, - StandardServerV = 0x00000024, - DatacenterServerV = 0x00000025, - EnterpriseServerV = 0x00000026, - DatacenterServerCoreV = 0x00000027, - StandardServerCoreV = 0x00000028, - EnterpriseServerCoreV = 0x00000029, - HyperV = 0x0000002A, - StorageExpressServerCore = 0x0000002B, - StorageServerStandardCore = 0x0000002C, - StorageWorkgroupServerCore = 0x0000002D, - StorageEnterpriseServerCore = 0x0000002E, + ServerStandardV = 0x00000024, + ServerDatacenterV = 0x00000025, + ServerEnterpriseV = 0x00000026, + ServerDatacenterVCor = 0x00000027, + ServerStandardVCor = 0x00000028, + ServerEnterpriseVCor = 0x00000029, + ServerHyperCore = 0x0000002A, + ServerStorageExpressCore = 0x0000002B, + ServerStorageStandardCore = 0x0000002C, + ServerStorageWorkgroupCore = 0x0000002D, + ServerStorageEnterpriseCore = 0x0000002E, StarterN = 0x0000002F, Professional = 0x00000030, ProfessionalN = 0x00000031, - SBSolutionServer = 0x00000032, - ServerForSBSolutions = 0x00000033, - StandardServerSolutions = 0x00000034, - StandardServerSolutionsCore = 0x00000035, - SBSolutionServerEM = 0x00000036, - ServerForSBSolutionsEM = 0x00000037, - SolutionEmbeddedServer = 0x00000038, - SolutionEmbeddedServerCore = 0x00000039, + ServerSolution = 0x00000032, + ServerSBSolution = 0x00000033, + ServerStandardSolution = 0x00000034, + ServerStandardSolutionCore = 0x00000035, + ServerSBSolutionEmbedded = 0x00000036, + ServerForSmallBusinessEmbedded = 0x00000037, + ServerEmbeddedSolution = 0x00000038, + ServerEmbeddedSolutionCore = 0x00000039, ProfessionalEmbedded = 0x0000003A, - EssentialBusinessServerMGMT = 0x0000003B, - EssentialBusinessServerADDL = 0x0000003C, - EssentialBusinessServerMGMTSVC = 0x0000003D, - EssentialBusinessServerADDLSVC = 0x0000003E, - SmallBusinessServerPremiumCore = 0x0000003F, - ClusterServerV = 0x00000040, + ServerEssentialBusinessMgmt = 0x0000003B, + ServerEssentialBusinessAddl = 0x0000003C, + ServerEssentialBusinessMgmtSvc = 0x0000003D, + ServerEssentialBusinessAddlSvc = 0x0000003E, + ServerSmallBusinessPremiumCore = 0x0000003F, + ServerClusterV = 0x00000040, Embedded = 0x00000041, StarterE = 0x00000042, HomeBasicE = 0x00000043, @@ -97,18 +99,15 @@ public enum Product ProfessionalE = 0x00000045, EnterpriseE = 0x00000046, UltimateE = 0x00000047, - EnterpriseEvaluation = 0x00000048, - Unknown49, + EnterpriseEval = 0x00000048, Prerelease = 0x0000004A, - Unknown4B, - MultipointStandardServer = 0x0000004C, - MultipointPremiumServer = 0x0000004D, - StandardEvaluationServer = 0x0000004F, - DatacenterEvaluationServer = 0x00000050, + ServerMultipointStandard = 0x0000004C, + ServerMultipointPremium = 0x0000004D, + ServerStandardEval = 0x0000004F, + ServerDatacenterEval = 0x00000050, PrereleaseARM = 0x00000051, PrereleaseN = 0x00000052, - Unknown53, - EnterpriseNEvaluation = 0x00000054, + EnterpriseNEval = 0x00000054, EmbeddedAutomotive = 0x00000055, EmbeddedIndustryA = 0x00000056, ThinPC = 0x00000057, @@ -117,93 +116,74 @@ public enum Product EmbeddedE = 0x0000005A, EmbeddedIndustryE = 0x0000005B, EmbeddedIndustryAE = 0x0000005C, - Unknown5D, - Unknown5E, - StorageWorkgroundEvaluationServer = 0x0000005F, - StorageStandardEvaluationServer = 0x00000060, + ProfessionalPlus = 0x0000005D, + ServerStorageWorkgroupEval = 0x0000005F, + ServerStorageStandardEval = 0x00000060, CoreARM = 0x00000061, CoreN = 0x00000062, CoreCountrySpecific = 0x00000063, CoreSingleLanguage = 0x00000064, Core = 0x00000065, - Unknown66, ProfessionalWMC = 0x00000067, - Unknown68, + MobileCore = 0x00000068, EmbeddedIndustryEval = 0x00000069, EmbeddedIndustryEEval = 0x0000006A, EmbeddedEval = 0x0000006B, EmbeddedEEval = 0x0000006C, - NanoServer = 0x0000006D, - CloudStorageServer = 0x0000006E, + ServerNano = 0x0000006D, + ServerCloudStorage = 0x0000006E, CoreConnected = 0x0000006F, ProfessionalStudent = 0x00000070, CoreConnectedN = 0x00000071, ProfessionalStudentN = 0x00000072, CoreConnectedSingleLanguage = 0x00000073, CoreConnectedCountrySpecific = 0x00000074, - ConnectedCAR = 0x00000075, + ConnectedCar = 0x00000075, IndustryHandheld = 0x00000076, PPIPro = 0x00000077, - ARM64Server = 0x00000078, + ServerARM64 = 0x00000078, Education = 0x00000079, EducationN = 0x0000007A, IoTUAP = 0x0000007B, - CloudHostInfrastructureServer = 0x0000007C, + ServerCloudHostInfrastructure = 0x0000007C, EnterpriseS = 0x0000007D, EnterpriseSN = 0x0000007E, ProfessionalS = 0x0000007F, ProfessionalSN = 0x00000080, - EnterpriseSEvaluation = 0x00000081, - EnterpriseSNEvaluation = 0x00000082, - Unknown83, - Unknown84, - Unknown85, - Unknown86, + EnterpriseSEval = 0x00000081, + EnterpriseSNEval = 0x00000082, + IoTUAPCommercial = 0x00000083, + MobileEnterprise = 0x00000085, Holographic = 0x00000087, HolographicBusiness = 0x00000088, - ProSingleLanguage = 0x0000008A, - ProChina = 0x0000008B, + ProfessionalSingleLanguage = 0x0000008A, + ProfessionalCountrySpecific = 0x0000008B, EnterpriseSubscription = 0x0000008C, EnterpriseSubscriptionN = 0x0000008D, - Unknown8E, - DatacenterNanoServer = 0x0000008F, - StandardNanoServer = 0x00000090, - DatacenterAServerCore = 0x00000091, - StandardAServerCore = 0x00000092, - DatacenterWSServerCore = 0x00000093, - StandardWSServerCore = 0x00000094, + ServerDatacenterNano = 0x0000008F, + ServerStandardNano = 0x00000090, + ServerDatacenterACor = 0x00000091, + ServerStandardACor = 0x00000092, + ServerDatacenterWSCor = 0x00000093, + ServerStandardWSCor = 0x00000094, UtilityVM = 0x00000095, - Unknown96, - Unknown97, - Unknown98, - Unknown99, - Unknown9A, - Unknown9B, - Unknown9C, - Unknown9D, - Unknown9E, - DatacenterEvaluationServerCore = 0x0000009F, - StandardEvaluationServerCore = 0x000000A0, - ProWorkstation = 0x000000A1, - ProWorkstationN = 0x000000A2, - UnknownA3, - ProForEducation = 0x000000A4, - ProForEducationN = 0x000000A5, - UnknownA6, - UnknownA7, - AzureServerCore = 0x000000A8, - AzureNanoServer = 0x000000A9, + ServerDatacenterEvalCore = 0x0000009F, + ServerStandardEvalCore = 0x000000A0, + ProfessionalWorkstation = 0x000000A1, + ProfessionalWorkstationN = 0x000000A2, + ProfessionalEducation = 0x000000A4, + ProfessionalEducationN = 0x000000A5, + ServerAzureCore = 0x000000A8, + ServerAzureNano = 0x000000A9, EnterpriseG = 0x000000AB, EnterpriseGN = 0x000000AC, - UnknownAD, - UnknownAE, - ServerRDSH = 0x000000AF, - UnknownB0, - UnknownB1, + // Business = 0x000000AD, + // BusinessN = 0x000000AE, + ServerRdsh = 0x000000AF, + ServerRdshCore = 0x000000B0, Cloud = 0x000000B2, CloudN = 0x000000B3, HubOS = 0x000000B4, - UnknownB5, OneCoreUpdateOS = 0x000000B6, CloudE = 0x000000B7, Andromeda = 0x000000B8, @@ -211,9 +191,22 @@ public enum Product CloudEN = 0x000000BA, IoTEdgeOS = 0x000000BB, IoTEnterprise = 0x000000BC, - Lite = 0x000000BD, - UnknownBE, - IoTEnterpriseS = 0x000000BF + WindowsCore = 0x000000BD, + IoTEnterpriseS = 0x000000BF, + XboxSystemOS = 0x000000C0, + XboxNativeOS = 0x000000C1, + XboxGameOS = 0x000000C2, + XboxEraOS = 0x000000C3, + XboxDurangoHostOS = 0x000000C4, + XboxScarlettHostOS = 0x000000C5, + ServerAzureCloudHost = 0x000000C7, + ServerAzureCloudMOS = 0x000000C8, + CloudEditionN = 0x000000CA, + CloudEdition = 0x000000CB, + ServerAzureStackHCICor = 0x00000196, + ServerTurbine = 0x00000197, + ServerTurbineCor = 0x00000198, + } public class PolicyValue @@ -244,6 +237,7 @@ public class WindowsImage public MachineType Architecture; public BuildType BuildType; public HashSet Types = new HashSet(); + public string BaseSku; public string Sku; public string[] Editions; public Licensing Licensing; diff --git a/Windows Build Identifier/Interfaces/WIMInstallProviderInterface.cs b/Windows Build Identifier/Interfaces/WIMInstallProviderInterface.cs index 0c2ae72..e582aab 100644 --- a/Windows Build Identifier/Interfaces/WIMInstallProviderInterface.cs +++ b/Windows Build Identifier/Interfaces/WIMInstallProviderInterface.cs @@ -29,20 +29,26 @@ namespace WindowsBuildIdentifier.Interfaces { public class WIMInstallProviderInterface : WindowsInstallProviderInterface { - private readonly Stream _wimstream; - private readonly string _index; private readonly ArchiveFile archiveFile; - public WIMInstallProviderInterface(Stream wimstream, string index) + private string _index; + + public WIMInstallProviderInterface(ArchiveFile archiveFile, string index = "") + { + _index = index; + this.archiveFile = archiveFile; + } + + public void SetIndex(string index) { - _wimstream = wimstream; _index = index; - archiveFile = new ArchiveFile(_wimstream, SevenZipFormat.Wim); } public string ExpandFile(string Entry) { - string pathprefix = string.IsNullOrEmpty(_index) ? "" : _index + @"\"; + string pathprefix = string.IsNullOrEmpty(_index) + ? "" + : _index + (Entry.StartsWith('\\') ? "" : "\\"); if (!archiveFile.Entries.Any(x => x.FileName.Equals(pathprefix + Entry, StringComparison.InvariantCultureIgnoreCase))) { @@ -76,7 +82,6 @@ public string[] GetFileSystemEntries() public void Close() { - archiveFile.Dispose(); } } } diff --git a/Windows Build Identifier/Program.cs b/Windows Build Identifier/Program.cs index 06482c9..ac9eba4 100644 --- a/Windows Build Identifier/Program.cs +++ b/Windows Build Identifier/Program.cs @@ -96,7 +96,7 @@ public class IdentifyOptions [Option('m', "media", Required = true, HelpText = "The media file to work on.")] public string Media { get; set; } - [Option('o', "output", Required = true, HelpText = "The destination path for the windows index file.")] + [Option('o', "output", HelpText = "The destination path for the windows index file.")] public string Output { get; set; } } @@ -119,7 +119,7 @@ public class RenameOptions [Option('m', "media", Required = true, HelpText = "The media file to work on.")] public string Media { get; set; } - [Option('w', "windows-index", Required = true, HelpText = "The path of the windows index file.")] + [Option('w', "windows-index", HelpText = "The path of the windows index file.")] public string WindowsIndex { get; set; } } @@ -154,7 +154,19 @@ static int Main(string[] args) public static string ReplaceInvalidChars(string filename) { - return Path.Join(filename.Replace(Path.GetFileName(filename), ""), string.Join("_", Path.GetFileName(filename).Split(Path.GetInvalidFileNameChars()))); + string dir = Path.GetDirectoryName(filename); + char[] file = Path.GetFileName(filename).ToCharArray(); + + char[] remove = Path.GetInvalidFileNameChars(); + for (int i = 0; i < file.Length; i++) + { + if (remove.Contains(file[i])) + { + file[i] = '_'; + } + } + + return Path.Join(dir, file); } private static (string, string) GetAdequateNameFromImageIndexes(WindowsImageIndex[] imageIndexes) @@ -171,11 +183,11 @@ private static (string, string) GetAdequateNameFromImageIndexes(WindowsImageInde } var types = f.Types; - var licensings = new HashSet { f.Licensing }; - var languages = f.LanguageCodes != null ? f.LanguageCodes.ToHashSet() : new HashSet() { "lang-unknown" }; - var skus = new HashSet { f.Sku.Replace("Server", "") }; - - string archstring = f.Architecture.ToString() + f.BuildType.ToString(); + var licensings = new SortedSet { f.Licensing }; + var languages = new SortedSet(f.LanguageCodes != null ? f.LanguageCodes : new string[] { "lang-unknown" }); + var skus = new SortedSet { f.Sku.Replace("Server", "") }; + var baseSkus = new SortedSet { f.Sku.Replace("Server", "") }; + var archs = new SortedSet { $"{f.Architecture}{f.BuildType}" }; for (int i = 1; i < imageIndexes.Length; i++) { @@ -187,20 +199,47 @@ private static (string, string) GetAdequateNameFromImageIndexes(WindowsImageInde { licensings.Add(d.Licensing); } - languages = languages.Union(d.LanguageCodes).ToHashSet(); + languages = new SortedSet(languages.Union(d.LanguageCodes)); + if (!skus.Contains(d.Sku.Replace("Server", ""))) { skus.Add(d.Sku.Replace("Server", "")); } - archstring += "-" + d.Architecture.ToString() + d.BuildType.ToString(); + + if (d.BaseSku != null && !baseSkus.Contains(d.BaseSku.Replace("Server", ""))) + { + baseSkus.Add(d.BaseSku.Replace("Server", "")); + } + + if (!archs.Contains($"{f.Architecture}{f.BuildType}")) + { + archs.Add($"{f.Architecture}{f.BuildType}"); + } + } + + // Don't include core SKUs in filename list if full is also included + foreach (string sku in skus.ToArray()) + { + if (sku.Length >= 5 && sku.EndsWith("Core") && skus.Contains(sku[..^4])) + { + skus.Remove(sku); + } + } + foreach (string baseSku in baseSkus.ToArray()) + { + if (baseSku.Length >= 5 && baseSku.EndsWith("Core") && baseSkus.Contains(baseSku[..^4])) + { + baseSkus.Remove(baseSku); + } } Console.WriteLine($"Build tag: {buildtag}"); Console.WriteLine(); - string licensingstr = licensings.Count == 0 ? "" : "_" + string.Join(" -", licensings); + string skustr = skus.Count > 5 && baseSkus.Count < skus.Count ? string.Join("-", baseSkus) + "-multi" : string.Join("-", skus); + string licensingstr = licensings.Count == 0 ? "" : "_" + string.Join("-", licensings); - var filename = $"{archstring}_{string.Join("-", types)}-{string.Join("-", skus)}{licensingstr}_{string.Join("-", languages)}"; + var filename = $"{string.Join("-", archs)}_{string.Join("-", types)}-{skustr}{licensingstr}_{string.Join("-", languages)}"; return (buildtag, filename); } @@ -230,7 +269,15 @@ private static int RunBulkRenameAndReturnExitCode(BulkRenameOptions opts) case "wim": case "esd": { - result = new FileItem[] { new FileItem() { Metadata = new MetaData() { WindowsImageIndexes = Identification.MediaHandler.IdentifyWindowsFromWIM(File.OpenRead(file), true) } } }; + var images = Identification.MediaHandler.IdentifyWindowsFromWIM(File.OpenRead(file), true); + if (images.Length > 0) + { + result = new FileItem[] { new FileItem() { Metadata = new MetaData() { WindowsImageIndexes = images } } }; + } + else + { + continue; + } break; } default: @@ -341,7 +388,15 @@ private static int RunBulkSortAndReturnExitCode(BulkSortOptions opts) case "wim": case "esd": { - result = new FileItem[] { new FileItem() { Metadata = new MetaData() { WindowsImageIndexes = Identification.MediaHandler.IdentifyWindowsFromWIM(File.OpenRead(file), true) } } }; + var images = Identification.MediaHandler.IdentifyWindowsFromWIM(File.OpenRead(file), true); + if (images.Length > 0) + { + result = new FileItem[] { new FileItem() { Metadata = new MetaData() { WindowsImageIndexes = images } } }; + } + else + { + continue; + } break; } default: @@ -417,6 +472,11 @@ private static int RunRenameAndReturnExitCode(RenameOptions opts) { PrintBanner(); + if (string.IsNullOrEmpty(opts.WindowsIndex)) + { + opts.WindowsIndex = $"{opts.Media}.meta_id.xml"; + } + Console.WriteLine("Input xml file: " + opts.WindowsIndex); Console.WriteLine("Input media: " + opts.Media); @@ -458,19 +518,35 @@ private static int RunRenameAndReturnExitCode(RenameOptions opts) if (!string.IsNullOrEmpty(label)) dst = filename + "-" + label + "." + fileextension; - dst = string.Join(@"\", opts.Media.Split(@"\")[0..^1]) + @"\" + dst; + dst = Path.Combine(Path.GetDirectoryName(opts.WindowsIndex), ReplaceInvalidChars(dst)); Console.WriteLine($"Target filename: {dst}"); Console.WriteLine(); - if (opts.Media == ReplaceInvalidChars(dst)) + if (opts.Media == dst) { Console.WriteLine("Nothing to do, file name is already good"); } else { Console.WriteLine("Renaming"); - File.Move(opts.Media, ReplaceInvalidChars(dst)); + File.Move(opts.Media, dst); + + if (opts.WindowsIndex.Contains(opts.Media)) + { + File.Move(opts.WindowsIndex, opts.WindowsIndex.Replace(opts.Media, dst)); + } + + string sha1File = $"{opts.Media}.sha1"; + if (File.Exists(sha1File)) + { + Console.WriteLine("Update SHA-1 Checksum File"); + string sha1Content = File.ReadAllText(sha1File); + sha1Content = sha1Content.Replace(opts.Media, Path.GetFileName(dst)); + sha1Content = sha1Content.Replace(Path.GetFileName(opts.Media), Path.GetFileName(dst)); + File.WriteAllText($"{dst}.sha1", sha1Content); + File.Delete(sha1File); + } } Console.WriteLine("Done."); @@ -592,6 +668,11 @@ private static int RunIdentifyAndReturnExitCode(IdentifyOptions opts) DiscUtils.Complete.SetupHelper.SetupComplete(); var file = opts.Media; + if (string.IsNullOrEmpty(opts.Output)) + { + opts.Output = $"{file}.meta_id.xml"; + } + var extension = file.Split(".")[^1]; switch (extension.ToLower()) @@ -634,8 +715,7 @@ private static int RunIdentifyAndReturnExitCode(IdentifyOptions opts) File.WriteAllText(opts.Output, xml); } - - if (result.Any(x => x.Location.ToLower() == @"\sources\install.esd")) + else if (result.Any(x => x.Location.ToLower() == @"\sources\install.esd")) { var wimtag = result.First(x => x.Location.ToLower() == @"\sources\install.esd"); @@ -659,8 +739,31 @@ private static int RunIdentifyAndReturnExitCode(IdentifyOptions opts) File.WriteAllText(opts.Output, xml); } + else if (result.Any(x => x.Location.ToLower() == @"\sources\boot.wim")) + { + var wimtag = result.First(x => x.Location.ToLower() == @"\sources\boot.wim"); - if (result.Any(x => x.Location.ToLower().EndsWith(@"\txtsetup.sif"))) + XmlSerializer xsSubmit = new XmlSerializer(typeof(WindowsImageIndex[])); + string xml = ""; + + using (var sww = new StringWriter()) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.IndentChars = " "; + settings.NewLineOnAttributes = false; + settings.OmitXmlDeclaration = true; + + using (XmlWriter writer = XmlWriter.Create(sww, settings)) + { + xsSubmit.Serialize(writer, wimtag.Metadata.WindowsImageIndexes); + xml = sww.ToString(); + } + } + + File.WriteAllText(opts.Output, xml); + } + else if (result.Any(x => x.Location.ToLower().EndsWith(@"\txtsetup.sif"))) { var txtsetups = result.Where(x => x.Location.ToLower().EndsWith(@"\txtsetup.sif")).Select(x => x.Metadata.WindowsImageIndexes); @@ -853,6 +956,7 @@ private static void PrintBanner() Console.WriteLine("Release Identifier Tool"); Console.WriteLine("Release Database Indexing Toolset"); Console.WriteLine("Gustave Monce (@gus33000) (c) 2009-2020"); + Console.WriteLine("Thomas Hounsell (c) 2021"); Console.WriteLine(); } diff --git a/Windows Build Identifier/Windows Build Identifier.csproj b/Windows Build Identifier/Windows Build Identifier.csproj index 6c84c9a..d6700cc 100644 --- a/Windows Build Identifier/Windows Build Identifier.csproj +++ b/Windows Build Identifier/Windows Build Identifier.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6.0 WindowsBuildIdentifier WindowsBuildIdentifier.Program 1.0.0.0 @@ -26,10 +26,10 @@ - + - - + +