diff --git a/Directory.Packages.props b/Directory.Packages.props
index 4131bbc7..2441fdcd 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -33,5 +33,6 @@
+
\ No newline at end of file
diff --git a/NuGet.Config b/NuGet.Config
index 0ef48526..ae0df653 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -6,6 +6,8 @@
+
+
diff --git a/src/Sign.Core/Containers/ContainerProvider.cs b/src/Sign.Core/Containers/ContainerProvider.cs
index 73218c72..d1a8e6f1 100644
--- a/src/Sign.Core/Containers/ContainerProvider.cs
+++ b/src/Sign.Core/Containers/ContainerProvider.cs
@@ -17,6 +17,7 @@ internal sealed class ContainerProvider : IContainerProvider
private readonly IMakeAppxCli _makeAppxCli;
private readonly HashSet _nuGetExtensions;
private readonly HashSet _zipExtensions;
+ private readonly HashSet _msiExtensions;
// Dependency injection requires a public constructor.
public ContainerProvider(
@@ -68,6 +69,12 @@ public ContainerProvider(
".vsix",
".zip"
};
+
+ _msiExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ ".msi",
+ ".msm"
+ };
}
public bool IsAppxBundleContainer(FileInfo file)
@@ -98,6 +105,13 @@ public bool IsZipContainer(FileInfo file)
return _zipExtensions.Contains(file.Extension);
}
+ public bool IsMsiContainer(FileInfo file)
+ {
+ ArgumentNullException.ThrowIfNull(file, nameof(file));
+
+ return _msiExtensions.Contains(file.Extension);
+ }
+
public IContainer? GetContainer(FileInfo file)
{
ArgumentNullException.ThrowIfNull(file, nameof(file));
@@ -122,6 +136,11 @@ public bool IsZipContainer(FileInfo file)
return new NuGetContainer(file, _directoryService, _fileMatcher, _logger);
}
+ if (IsMsiContainer(file))
+ {
+ return new MsiContainer(file, _directoryService, _fileMatcher, _logger);
+ }
+
return null;
}
}
diff --git a/src/Sign.Core/Containers/IContainerProvider.cs b/src/Sign.Core/Containers/IContainerProvider.cs
index 77c26aa8..99599778 100644
--- a/src/Sign.Core/Containers/IContainerProvider.cs
+++ b/src/Sign.Core/Containers/IContainerProvider.cs
@@ -10,6 +10,7 @@ internal interface IContainerProvider
bool IsAppxContainer(FileInfo file);
bool IsNuGetContainer(FileInfo file);
bool IsZipContainer(FileInfo file);
+ bool IsMsiContainer(FileInfo file);
IContainer? GetContainer(FileInfo file);
}
}
\ No newline at end of file
diff --git a/src/Sign.Core/Containers/MsiContainer.cs b/src/Sign.Core/Containers/MsiContainer.cs
new file mode 100644
index 00000000..ca7e091d
--- /dev/null
+++ b/src/Sign.Core/Containers/MsiContainer.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE.txt file in the project root for more information.
+
+using Microsoft.Extensions.Logging;
+using WixToolset.Dtf.WindowsInstaller;
+using WixToolset.Dtf.WindowsInstaller.Package;
+
+namespace Sign.Core
+{
+ internal class MsiContainer : Container
+ {
+ private readonly IDirectoryService _directoryService;
+ private readonly ILogger _logger;
+ private readonly FileInfo _msiFile;
+
+ public MsiContainer(
+ FileInfo msiFile,
+ IDirectoryService directoryService,
+ IFileMatcher fileMatcher,
+ ILogger logger)
+ : base(fileMatcher)
+ {
+ ArgumentNullException.ThrowIfNull(msiFile, nameof(msiFile));
+ ArgumentNullException.ThrowIfNull(directoryService, nameof(directoryService));
+ ArgumentNullException.ThrowIfNull(logger, nameof(logger));
+
+ _directoryService = directoryService;
+ _logger = logger;
+ _msiFile = msiFile;
+ }
+
+ public override ValueTask OpenAsync()
+ {
+ if (TemporaryDirectory is not null)
+ {
+ throw new InvalidOperationException();
+ }
+
+ TemporaryDirectory = new TemporaryDirectory(_directoryService);
+
+ _logger.LogInformation(
+ Resources.OpeningContainer,
+ _msiFile.FullName,
+ TemporaryDirectory.Directory.FullName);
+
+ using var package = new InstallPackage(
+ _msiFile.FullName,
+ DatabaseOpenMode.ReadOnly,
+ sourceDir: null,
+ TemporaryDirectory.Directory.FullName);
+
+ package.ExtractFiles();
+
+ return ValueTask.CompletedTask;
+ }
+
+ public override ValueTask SaveAsync()
+ {
+ if (TemporaryDirectory is null)
+ {
+ throw new InvalidOperationException();
+ }
+
+ _logger.LogInformation(
+ Resources.SavingContainer,
+ _msiFile.FullName,
+ TemporaryDirectory.Directory.FullName);
+
+ using var package = new InstallPackage(
+ _msiFile.FullName,
+ DatabaseOpenMode.Direct,
+ sourceDir: null,
+ TemporaryDirectory.Directory.FullName);
+
+ package.UpdateFiles();
+
+ return ValueTask.CompletedTask;
+ }
+ }
+}
diff --git a/src/Sign.Core/DataFormatSigners/AggregatingSigner.cs b/src/Sign.Core/DataFormatSigners/AggregatingSigner.cs
index fe0361f5..ded093c9 100644
--- a/src/Sign.Core/DataFormatSigners/AggregatingSigner.cs
+++ b/src/Sign.Core/DataFormatSigners/AggregatingSigner.cs
@@ -179,6 +179,36 @@ where _containerProvider.IsAppxBundleContainer(file)
containers.Clear();
}
+ List msis = (from file in files
+ where _containerProvider.IsMsiContainer(file)
+ select file).ToList();
+
+ try
+ {
+ foreach (FileInfo msi in msis)
+ {
+ IContainer container = _containerProvider.GetContainer(msi)!;
+
+ await container.OpenAsync();
+
+ containers.Add(container);
+ }
+
+ List allFiles = containers.SelectMany(c => GetFiles(c, options)).ToList();
+
+ if (allFiles.Count > 0)
+ {
+ await SignAsync(allFiles, options);
+
+ await Parallel.ForEachAsync(containers, (c, cancellationToken) => c.SaveAsync());
+ }
+ }
+ finally
+ {
+ containers.ForEach(c => c.Dispose());
+ containers.Clear();
+ }
+
// split by code sign service and fallback to default
var grouped = (from signer in _signers
diff --git a/src/Sign.Core/Sign.Core.csproj b/src/Sign.Core/Sign.Core.csproj
index 7bd59ccd..46fc393f 100644
--- a/src/Sign.Core/Sign.Core.csproj
+++ b/src/Sign.Core/Sign.Core.csproj
@@ -19,6 +19,7 @@
+
diff --git a/test/Sign.Core.Test/TestInfrastructure/ContainerProviderStub.cs b/test/Sign.Core.Test/TestInfrastructure/ContainerProviderStub.cs
index b67bfbe7..10c71c22 100644
--- a/test/Sign.Core.Test/TestInfrastructure/ContainerProviderStub.cs
+++ b/test/Sign.Core.Test/TestInfrastructure/ContainerProviderStub.cs
@@ -43,6 +43,11 @@ public bool IsZipContainer(FileInfo file)
return _containerProvider.IsZipContainer(file);
}
+ public bool IsMsiContainer(FileInfo file)
+ {
+ return _containerProvider.IsMsiContainer(file);
+ }
+
public IContainer? GetContainer(FileInfo file)
{
ArgumentNullException.ThrowIfNull(file, nameof(file));