// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixToolsetTest.CoreIntegration
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Example.Extension;
using WixBuildTools.TestSupport;
using WixToolset.Core.TestPackage;
using WixToolset.Data;
using WixToolset.Data.Burn;
using Xunit;
public class PatchFixture
{
private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd";
[Fact]
public void CanBuildSimplePatch()
{
var folder = TestData.Get(@"TestData\PatchSingle");
using (var fs = new DisposableFileSystem())
{
var tempFolder = fs.GetFolder();
var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0");
var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1");
var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1");
var patchPath = Path.ChangeExtension(patchPdb, ".msp");
Assert.True(File.Exists(baselinePdb));
Assert.True(File.Exists(update1Pdb));
var doc = GetExtractPatchXml(patchPath);
Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value);
var names = Query.GetSubStorageNames(patchPath);
Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names);
var cab = Path.Combine(tempFolder, "foo.cab");
Query.ExtractStream(patchPath, "foo.cab", cab);
Assert.True(File.Exists(cab));
var files = Query.GetCabinetFiles(cab);
Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray());
}
}
[Fact]
public void CanBuildSimplePatchWithNoFileChanges()
{
var folder = TestData.Get(@"TestData\PatchNoFileChanges");
using (var fs = new DisposableFileSystem())
{
var tempFolder = fs.GetFolder();
var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0");
var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1");
var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true);
var patchPath = Path.ChangeExtension(patchPdb, ".msp");
Assert.True(File.Exists(baselinePdb));
Assert.True(File.Exists(update1Pdb));
var doc = GetExtractPatchXml(patchPath);
Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value);
var names = Query.GetSubStorageNames(patchPath);
Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names);
var cab = Path.Combine(tempFolder, "foo.cab");
Query.ExtractStream(patchPath, "foo.cab", cab);
Assert.True(File.Exists(cab));
var files = Query.GetCabinetFiles(cab);
Assert.Empty(files);
}
}
[Fact(Skip = "https://github.com/wixtoolset/issues/issues/6387")]
public void CanBuildPatchFromProductWithFilesFromWixlib()
{
var folder = TestData.Get(@"TestData\PatchFromWixlib");
using (var fs = new DisposableFileSystem())
{
var tempFolderBaseline = fs.GetFolder();
var tempFolderUpdate = fs.GetFolder();
var tempFolderPatch = fs.GetFolder();
var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0");
var update1Pdb = BuildMsi("Update.msi", folder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1");
var patchPdb = BuildMsp("Patch1.msp", folder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true);
var patchPath = Path.ChangeExtension(patchPdb, ".msp");
Assert.True(File.Exists(baselinePdb));
Assert.True(File.Exists(update1Pdb));
}
}
[Fact]
public void CanBuildBundleWithNonSpecificPatches()
{
var folder = TestData.Get(@"TestData\PatchNonSpecific");
using (var fs = new DisposableFileSystem())
{
var tempFolder = fs.GetFolder();
var baselinePdb = BuildMsi("Baseline.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.0", "A", "B");
var updatePdb = BuildMsi("Update.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.1", "A", "B");
var patchAPdb = BuildMsp("PatchA.msp", Path.Combine(folder, "PatchA"), tempFolder, "1.0.1", hasNoFiles: true);
var patchBPdb = BuildMsp("PatchB.msp", Path.Combine(folder, "PatchB"), tempFolder, "1.0.1", hasNoFiles: true);
var patchCPdb = BuildMsp("PatchC.msp", Path.Combine(folder, "PatchC"), tempFolder, "1.0.1", hasNoFiles: true);
var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder);
var bundleBPdb = BuildBundle("BundleB.exe", Path.Combine(folder, "BundleB"), tempFolder);
var bundleCPdb = BuildBundle("BundleC.exe", Path.Combine(folder, "BundleC"), tempFolder);
VerifyPatchTargetCodes(bundleAPdb, new[]
{
"",
});
VerifyPatchTargetCodes(bundleBPdb, new[]
{
"",
"",
});
VerifyPatchTargetCodes(bundleCPdb, new string[0]);
}
}
[Fact]
public void CanBuildBundleWithSlipstreamPatch()
{
var folder = TestData.Get(@"TestData\PatchSingle");
using (var fs = new DisposableFileSystem())
{
var tempFolder = fs.GetFolder();
var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0");
var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1");
var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1");
var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder);
using (var wixOutput = WixOutput.Read(bundleAPdb))
{
var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName);
var doc = new XmlDocument();
doc.LoadXml(manifestData);
var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w");
var slipstreamMspNodes = doc.SelectNodes("/w:BurnManifest/w:Chain/w:MsiPackage/w:SlipstreamMsp", nsmgr);
Assert.Equal(1, slipstreamMspNodes.Count);
Assert.Equal("", slipstreamMspNodes[0].GetTestXml());
}
}
}
private static void VerifyPatchTargetCodes(string pdbPath, string[] expected)
{
using (var wixOutput = WixOutput.Read(pdbPath))
{
var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName);
var doc = new XmlDocument();
doc.LoadXml(manifestData);
var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w");
var patchTargetCodes = doc.SelectNodes("/w:BurnManifest/w:PatchTargetCode", nsmgr);
var actual = new List();
foreach (XmlNode patchTargetCodeNode in patchTargetCodes)
{
actual.Add(patchTargetCodeNode.GetTestXml());
}
WixAssert.CompareLineByLine(expected, actual.ToArray());
}
}
private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB)
{
var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
var result = WixRunner.Execute(new[]
{
"build",
Path.Combine(sourceFolder, @"Package.wxs"),
"-d", "V=" + defineV,
"-d", "A=" + defineA,
"-d", "B=" + defineB,
"-bindpath", Path.Combine(sourceFolder, ".data"),
"-intermediateFolder", Path.Combine(baseFolder, "obj"),
"-o", outputPath,
"-ext", extensionPath,
});
result.AssertSuccess();
return Path.ChangeExtension(outputPath, ".wixpdb");
}
private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable bindpaths = null, bool hasNoFiles = false)
{
var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
var args = new List
{
"build",
hasNoFiles ? "-sw1079" : " ",
Path.Combine(sourceFolder, @"Patch.wxs"),
"-d", "V=" + defineV,
"-bindpath", Path.Combine(baseFolder, "bin"),
"-intermediateFolder", Path.Combine(baseFolder, "obj"),
"-o", outputPath
};
foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty())
{
args.Add("-bindpath");
args.Add(additionaBindPath);
}
var result = WixRunner.Execute(args.ToArray());
result.AssertSuccess();
return Path.ChangeExtension(outputPath, ".wixpdb");
}
private static string BuildBundle(string outputName, string sourceFolder, string baseFolder)
{
var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
var result = WixRunner.Execute(new[]
{
"build",
Path.Combine(sourceFolder, @"Bundle.wxs"),
Path.Combine(sourceFolder, "..", "..", "BundleWithPackageGroupRef", "Bundle.wxs"),
"-bindpath", Path.Combine(sourceFolder, "..", "..", "SimpleBundle", "data"),
"-bindpath", Path.Combine(baseFolder, "bin"),
"-intermediateFolder", Path.Combine(baseFolder, "obj"),
"-o", outputPath
});
result.AssertSuccess();
return Path.ChangeExtension(outputPath, ".wixpdb");
}
private static XDocument GetExtractPatchXml(string path)
{
var buffer = new StringBuilder(65535);
var size = buffer.Capacity;
var er = MsiExtractPatchXMLData(path, 0, buffer, ref size);
if (er != 0)
{
throw new Win32Exception(er);
}
return XDocument.Parse(buffer.ToString());
}
[DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData);
[DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine);
}
}