Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added WiX detection to MSI Parser. #220

Merged
merged 4 commits into from
Feb 4, 2022
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 92 additions & 76 deletions src/WingetCreateCore/Common/PackageParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.

namespace Microsoft.WingetCreateCore
Expand Down Expand Up @@ -54,14 +54,14 @@ private enum MachineType
X86 = 0x014c,
X64 = 0x8664,
}

private enum CompatibilitySet
{
None,
Exe,
Msi,
Msix,
}

private enum CompatibilitySet
{
None,
Exe,
Msi,
Msix,
}

/// <summary>
/// Gets or sets the path in the %TEMP% directory where installers are downloaded to.
Expand Down Expand Up @@ -315,14 +315,14 @@ private static Installer FindInstallerMatch(
// If we can find an exact match by comparing the installerType and the override architecture, then return match.
// Otherwise, continue and try matching based on arch detected from url and binary detection, as the user could be trying overwrite with a new architecture.
var installerTypeMatches = existingInstallers.Where(
i => (i.InstallerType ?? installerManifest.InstallerType) == newInstaller.InstallerType);

// If there are no exact installerType matches, check if there is a compatible installerType that can be matched.
if (!installerTypeMatches.Any())
{
i => (i.InstallerType ?? installerManifest.InstallerType) == newInstaller.InstallerType);

// If there are no exact installerType matches, check if there is a compatible installerType that can be matched.
if (!installerTypeMatches.Any())
{
installerTypeMatches = existingInstallers.Where(
i => IsCompatibleInstallerType(i.InstallerType ?? installerManifest.InstallerType, newInstaller.InstallerType));
}
i => IsCompatibleInstallerType(i.InstallerType ?? installerManifest.InstallerType, newInstaller.InstallerType));
}

var overrideArchMatches = installerTypeMatches.Where(i => i.Architecture == installerMetadata.OverrideArchitecture);
if (overrideArchMatches.Count() == 1)
Expand Down Expand Up @@ -374,9 +374,9 @@ private static void UpdateInstallerMetadata(Installer existingInstaller, Install
existingInstaller.Architecture = newInstaller.Architecture;
existingInstaller.InstallerUrl = newInstaller.InstallerUrl;
existingInstaller.InstallerSha256 = newInstaller.InstallerSha256;
existingInstaller.SignatureSha256 = newInstaller.SignatureSha256;

// If the newInstaller field value is null, we default to using the existingInstaller field value.
existingInstaller.SignatureSha256 = newInstaller.SignatureSha256;

// If the newInstaller field value is null, we default to using the existingInstaller field value.
existingInstaller.ProductCode = newInstaller.ProductCode ?? existingInstaller.ProductCode;
existingInstaller.MinimumOSVersion = newInstaller.MinimumOSVersion ?? existingInstaller.MinimumOSVersion;
existingInstaller.PackageFamilyName = newInstaller.PackageFamilyName ?? existingInstaller.PackageFamilyName;
Expand Down Expand Up @@ -549,60 +549,60 @@ private static string HashAppxFile(IAppxFile signatureFile)
}

return null;
}

/// <summary>
/// Checks if the provided installerTypes are compatible.
/// </summary>
/// <param name="type1">First InstallerType.</param>
/// <param name="type2">Second InstallerType.</param>
/// <returns>A boolean value indicating whether the installerTypes are compatible.</returns>
private static bool IsCompatibleInstallerType(InstallerType? type1, InstallerType? type2)
{
if (!type1.HasValue || !type2.HasValue)
{
return false;
}

InstallerType installerType1 = type1.Value;
InstallerType installerType2 = type2.Value;

if (installerType1 == installerType2)
{
return true;
}

CompatibilitySet set1 = GetCompatibilitySet(installerType1);
CompatibilitySet set2 = GetCompatibilitySet(installerType2);

if (set1 == CompatibilitySet.None || set2 == CompatibilitySet.None)
{
return false;
}

return set1 == set2;
}

private static CompatibilitySet GetCompatibilitySet(InstallerType type)
{
switch (type)
{
case InstallerType.Inno:
case InstallerType.Nullsoft:
case InstallerType.Exe:
case InstallerType.Burn:
return CompatibilitySet.Exe;
case InstallerType.Wix:
case InstallerType.Msi:
return CompatibilitySet.Msi;
case InstallerType.Msix:
case InstallerType.Appx:
return CompatibilitySet.Msix;
default:
return CompatibilitySet.None;
}
}

}

/// <summary>
/// Checks if the provided installerTypes are compatible.
/// </summary>
/// <param name="type1">First InstallerType.</param>
/// <param name="type2">Second InstallerType.</param>
/// <returns>A boolean value indicating whether the installerTypes are compatible.</returns>
private static bool IsCompatibleInstallerType(InstallerType? type1, InstallerType? type2)
{
if (!type1.HasValue || !type2.HasValue)
{
return false;
}

InstallerType installerType1 = type1.Value;
InstallerType installerType2 = type2.Value;

if (installerType1 == installerType2)
{
return true;
}

CompatibilitySet set1 = GetCompatibilitySet(installerType1);
CompatibilitySet set2 = GetCompatibilitySet(installerType2);

if (set1 == CompatibilitySet.None || set2 == CompatibilitySet.None)
{
return false;
}

return set1 == set2;
}

private static CompatibilitySet GetCompatibilitySet(InstallerType type)
{
switch (type)
{
case InstallerType.Inno:
case InstallerType.Nullsoft:
case InstallerType.Exe:
case InstallerType.Burn:
return CompatibilitySet.Exe;
case InstallerType.Wix:
case InstallerType.Msi:
return CompatibilitySet.Msi;
case InstallerType.Msix:
case InstallerType.Appx:
return CompatibilitySet.Msix;
default:
return CompatibilitySet.None;
}
}

private static bool ParseExeInstallerType(string path, Installer baseInstaller, List<Installer> newInstallers)
{
try
Expand Down Expand Up @@ -643,6 +643,20 @@ private static bool ParseExeInstallerType(string path, Installer baseInstaller,
}
}

/// <summary>
/// Checks if a MSI Installer database was generated by WiX, based on common characteristics.
/// </summary>
/// <param name="installer">A MSI Installer database.</param>
/// <returns>A boolean.</returns>
private static bool IsWix(QDatabase installer)
{
return
installer.Tables.AsEnumerable().Any(table => table.Name.ToLower().Contains("wix")) ||
installer.Properties.AsEnumerable().Any(property => property.Property.ToLower().Contains("wix") || property.Value.ToLower().Contains("wix")) ||
installer.SummaryInfo.CreatingApp.ToLower().Contains("wix") ||
installer.SummaryInfo.CreatingApp.ToLower().Contains("windows installer xml");
}

private static bool ParseMsi(string path, Installer baseInstaller, Manifests manifests, List<Installer> newInstallers)
{
DefaultLocaleManifest defaultLocaleManifest = manifests?.DefaultLocaleManifest;
Expand All @@ -651,8 +665,10 @@ private static bool ParseMsi(string path, Installer baseInstaller, Manifests man
{
using (var database = new QDatabase(path, Deployment.WindowsInstaller.DatabaseOpenMode.ReadOnly))
{
baseInstaller.InstallerType = InstallerType.Msi;

baseInstaller.InstallerType = IsWix(database)
? InstallerType.Wix
: InstallerType.Msi;

var properties = database.Properties.ToList();

if (defaultLocaleManifest != null)
Expand Down Expand Up @@ -806,7 +822,7 @@ private static AppxMetadata GetAppxMetadataAndSetInstallerProperties(string path
signatureSha256 = HashAppxFile(signatureFile);
}

baseInstaller.SignatureSha256 = signatureSha256;
baseInstaller.SignatureSha256 = signatureSha256;
baseInstaller.InstallerType = InstallerType.Msix;

// Add installer nodes for MSIX installers
Expand Down