private static void PatchDuplicateComponentId(XNamespace ns, XElement component, string directoryName) { var idAttribute = component.Attribute("Id"); if (idAttribute == null) { return; } var id = idAttribute.Value; idAttribute.Remove(); // include directory name in Component Id var newId = Regex.Replace(id, @"Component\.(.+)\.\d+", $"{directoryName}.$1").Replace("-", "_"); var newComponentId = $"Component.{newId}"; component.Add(new XAttribute("Id", newComponentId)); // generate a new guid based on the new Component Id component.Attribute("Guid").Value = WixGuid.NewGuid(newComponentId).ToString().ToLowerInvariant(); PatchDuplicateComponentFileId(ns, component, newId); PatchComponentRef(ns, component, id, newComponentId); }
private static void PatchDuplicateComponentId(XNamespace ns, XElement component, string directoryName) { var idAttribute = component.Attribute("Id"); if (idAttribute == null) { return; } var dir = directoryName.Replace(".", "_"); var id = idAttribute.Value; idAttribute.Remove(); var newId = id; if (WixAbbreviationRegex.IsMatch(id)) { var componentFile = component.Element(ns + "File")?.Attribute("Source")?.Value ?? throw new Exception($"Component {id} missing file source!"); var file = Path.GetFileName(componentFile); newId = $"{dir}.{file}"; } else if (WixDuplicateRegex.IsMatch(id)) { newId = Regex.Replace(id, @"Component\.(.+)\.\d+", $"{dir}.$1"); } newId = newId.Replace("-", "_"); // include directory name in Component Id var newComponentId = $"Component.{newId}"; component.Add(new XAttribute("Id", newComponentId)); // generate a new guid based on the new Component Id component.Attribute("Guid").Value = WixGuid.NewGuid(newComponentId).ToString().ToLowerInvariant(); PatchDuplicateComponentFileId(ns, component, newId); PatchComponentRef(ns, component, id, newComponentId); }
public override void PatchWixSource(XDocument document) { var documentRoot = document.Root; var ns = documentRoot.Name.Namespace; var directories = documentRoot.Descendants(ns + "Directory") .Where(c => { var childComponents = c.Elements(ns + "Component"); return(childComponents.Any() && childComponents.Any(cc => cc.Descendants(ns + "File").Any())); }); var product = documentRoot.Descendants(ns + "Product").Single(); var feature = documentRoot.Descendants(ns + "Feature").First(f => f.Attribute("Id").Value == "Complete"); var duplicateOrAbbreviatedRegex = new Regex(@"\.\d+$|\._\.\.\.(.+)$"); foreach (var directory in directories) { var directoryId = directory.Attribute("Id").Value; var componentId = "Component." + directoryId; // WixSharp appends a .{DIGIT} to duplicate file names in different directories in installer e.g. plugin_descriptor.properties // for Elasticsearch plugins. Problem is duplicated file names may not represent the same file path across versions so // fix this by including the directory name in the file name. var renameComponents = directory.Elements(ns + "Component") .Where(c => duplicateOrAbbreviatedRegex.IsMatch(c.Attribute("Id").Value)); foreach (var component in renameComponents) { PatchDuplicateComponentId(ns, component, directoryId); } directory.AddFirst(new XElement(ns + "Component", new XAttribute("Id", componentId), new XAttribute("Guid", WixGuid.NewGuid(componentId)), new XAttribute("Win64", "yes"), new XElement(ns + "RemoveFile", new XAttribute("Id", directoryId + ".all"), new XAttribute("Name", "*"), // remove all files in dir new XAttribute("On", "both") ), new XElement(ns + "RemoveFolder", new XAttribute("Id", directoryId + ".dir"), // remove (now empty) dir new XAttribute("On", "both") ) )); // Add a Directory entry to remove all files in bin/x-pack, if present if (directoryId == "INSTALLDIR.bin") { var installdirBinXpack = "INSTALLDIR.bin.xpack"; var componentInstalldirBinXpack = $"Component.{installdirBinXpack}"; directory.AddFirst(new XElement(ns + "Directory", new XAttribute("Id", installdirBinXpack), new XAttribute("Name", "x-pack"), new XElement(ns + "Component", new XAttribute("Id", componentInstalldirBinXpack), new XAttribute("Guid", WixGuid.NewGuid(componentInstalldirBinXpack)), new XAttribute("Win64", "yes"), new XElement(ns + "RemoveFile", new XAttribute("Id", installdirBinXpack), new XAttribute("Name", "*"), // remove all files in x-pack dir new XAttribute("On", "both") ), new XElement(ns + "RemoveFolder", new XAttribute("Id", installdirBinXpack + ".dir"), // remove (now empty) x-pack dir new XAttribute("On", "both") ) ) )); feature.Add(new XElement(ns + "ComponentRef", new XAttribute("Id", componentInstalldirBinXpack))); } // Add an empty plugins directory else if (directoryId == InstallDirProperty) { var installdirPlugins = "INSTALLDIR.plugins"; var componentInstalldirPlugins = $"Component.{installdirPlugins}"; // Add elements to remove the plugins folder directory.AddFirst(new XElement(ns + "Directory", new XAttribute("Id", installdirPlugins), new XAttribute("Name", "plugins"), new XElement(ns + "Component", new XAttribute("Id", componentInstalldirPlugins), new XAttribute("Guid", WixGuid.NewGuid(componentInstalldirPlugins)), new XAttribute("Win64", "yes"), new XElement(ns + "RemoveFile", new XAttribute("Id", installdirPlugins), new XAttribute("Name", "*"), // remove all top level files in plugins dir new XAttribute("On", "both") ), new XElement(ns + "RemoveFolder", new XAttribute("Id", installdirPlugins + ".dir"), // remove (now empty) plugins dir new XAttribute("On", "both") ) ) )); feature.Add(new XElement(ns + "ComponentRef", new XAttribute("Id", componentInstalldirPlugins))); var componentEmptyInstalldirPlugins = $"Component.{installdirPlugins}.empty"; // Add element to create empty plugins folder product.Add(new XElement(ns + "DirectoryRef", new XAttribute("Id", installdirPlugins), new XElement(ns + "Component", new XAttribute("Id", componentEmptyInstalldirPlugins), new XAttribute("Guid", WixGuid.NewGuid(componentEmptyInstalldirPlugins)), new XAttribute("Win64", "yes"), new XAttribute("KeyPath", "yes"), new XElement(ns + "CreateFolder") ) )); feature.Add(new XElement(ns + "ComponentRef", new XAttribute("Id", componentEmptyInstalldirPlugins))); } feature.Add(new XElement(ns + "ComponentRef", new XAttribute("Id", componentId))); } // include WixFailWhenDeferred Custom Action // see http://wixtoolset.org/documentation/manual/v3/customactions/wixfailwhendeferred.html product.Add(new XElement(ns + "CustomActionRef", new XAttribute("Id", "WixFailWhenDeferred"))); var installExecuteSequence = documentRoot.Descendants(ns + "InstallExecuteSequence").Single(); // Add condition to InstallServices installExecuteSequence.Add(new XElement(ns + "InstallServices", // Sequence number pinned from inspecting MSI with Orca new XAttribute("Sequence", "5800"), new XCData($"VersionNT AND (NOT Installed) " + $"AND ({InstallAsServiceProperty}~=\"true\" OR {InstallAsServiceProperty}=1)") )); // Add condition to StartServices installExecuteSequence.Add(new XElement(ns + "StartServices", // Sequence number pinned from inspecting MSI with Orca new XAttribute("Sequence", "5900"), new XCData($"VersionNT AND (NOT Installed) " + $"AND ({InstallAsServiceProperty}~=\"true\" OR {InstallAsServiceProperty}=1) " + $"AND ({StartAfterInstallProperty}~=\"true\" OR {StartAfterInstallProperty}=1)") )); // Add condition to StopServices to run when // 1. installing // 2. uninstalling everything and not part of an upgrade. In this scenario, we assume that the // installation of the new version will have stopped the service as part of the install. installExecuteSequence.Add(new XElement(ns + "StopServices", // Sequence number pinned from inspecting MSI with Orca new XAttribute("Sequence", "1900"), new XCData("VersionNT AND ((NOT Installed) OR ((NOT UPGRADINGPRODUCTCODE) AND REMOVE~=\"ALL\"))") )); // Update in-built progress templates // See http://web.mit.edu/ops/services/afs/openafs-1.4.1/src/src/WINNT/install/wix/lang/en_US/ActionText.wxi var ui = documentRoot.Descendants(ns + "UI").Single(); ui.Add(new XElement(ns + "ProgressText", new XAttribute("Action", "InstallFiles"), new XAttribute("Template", "Copying new files: [9][1]"), new XText("Copying new files") ), new XElement(ns + "ProgressText", new XAttribute("Action", "StopServices"), new XAttribute("Template", "Stopping Elasticsearch service"), new XText("Stopping Elasticsearch service") ), new XElement(ns + "ProgressText", new XAttribute("Action", "StartServices"), new XAttribute("Template", "Starting Elasticsearch service"), new XText("Starting Elasticsearch service") ), new XElement(ns + "ProgressText", new XAttribute("Action", "InstallServices"), new XAttribute("Template", "Installing Elasticsearch service"), new XText("Installing Elasticsearch service") ), new XElement(ns + "ProgressText", new XAttribute("Action", "DeleteServices"), new XAttribute("Template", "Removing Elasticsearch service"), new XText("Removing Elasticsearch service") )); }
private static void PatchWixSource(XDocument document) { var ns = document.Root.Name.Namespace; var directories = document.Root.Descendants(ns + "Directory") .Where(c => { var childComponents = c.Elements(ns + "Component"); return(childComponents.Any() && childComponents.Any(cc => cc.Descendants(ns + "File").Any())); }); var feature = document.Root.Descendants(ns + "Feature").Single(); foreach (var directory in directories) { var directoryId = directory.Attribute("Id").Value; var componentId = "Component." + directoryId; directory.AddFirst(new XElement(ns + "Component", new XAttribute("Id", componentId), new XAttribute("Guid", WixGuid.NewGuid()), new XAttribute("Win64", "yes"), new XElement(ns + "RemoveFile", new XAttribute("Id", directoryId), new XAttribute("Name", "*"), // remove all files in dir new XAttribute("On", "both") ), new XElement(ns + "RemoveFolder", new XAttribute("Id", directoryId + ".dir"), // remove (now empty) dir new XAttribute("On", "both") ) )); // Add a Directory entry to remove all files in bin/x-pack, if present if (directoryId == "INSTALLDIR.bin") { var installdirBinXpack = "INSTALLDIR.bin.xpack"; var componentInstalldirBinXpack = $"Component.{installdirBinXpack}"; directory.AddFirst(new XElement(ns + "Directory", new XAttribute("Id", installdirBinXpack), new XAttribute("Name", "x-pack"), new XElement(ns + "Component", new XAttribute("Id", componentInstalldirBinXpack), new XAttribute("Guid", WixGuid.NewGuid()), new XAttribute("Win64", "yes"), new XElement(ns + "RemoveFile", new XAttribute("Id", installdirBinXpack), new XAttribute("Name", "*"), // remove all files in x-pack dir new XAttribute("On", "both") ), new XElement(ns + "RemoveFolder", new XAttribute("Id", installdirBinXpack + ".dir"), // remove (now empty) x-pack dir new XAttribute("On", "both") ) ) )); feature.Add(new XElement(ns + "ComponentRef", new XAttribute("Id", componentInstalldirBinXpack))); } feature.Add(new XElement(ns + "ComponentRef", new XAttribute("Id", componentId))); } var exeName = $"{_productName}.exe"; var exeComponent = document.Root.Descendants(ns + "Component") .Where(c => c.Descendants(ns + "File").Any(f => f.Attribute("Id").Value == exeName)) .Select(c => new { Component = c, File = c.Descendants(ns + "File").First() }) .SingleOrDefault(); if (exeComponent == null) { throw new Exception($"No File element found with Id '{exeName}'"); } var fileId = exeComponent.File.Attribute("Id").Value; exeComponent.Component.Add(new XElement(ns + "ServiceControl", new XAttribute("Id", fileId), new XAttribute("Name", _productTitle), // MUST match the name of the service new XAttribute("Stop", "both"), new XAttribute("Wait", "yes") )); // include WixFailWhenDeferred Custom Action when not building a release // see http://wixtoolset.org/documentation/manual/v3/customactions/wixfailwhendeferred.html if (!_releaseMode) { var product = document.Root.Descendants(ns + "Product").First(); product.Add(new XElement(ns + "CustomActionRef", new XAttribute("Id", "WixFailWhenDeferred") ) ); } // Update in-built progress templates // See http://web.mit.edu/ops/services/afs/openafs-1.4.1/src/src/WINNT/install/wix/lang/en_US/ActionText.wxi var ui = document.Root.Descendants(ns + "UI").Single(); ui.Add(new XElement(ns + "ProgressText", new XAttribute("Action", "InstallFiles"), new XAttribute("Template", "Copying new files: [9][1]"), new XText("Copying new files") ), new XElement(ns + "ProgressText", new XAttribute("Action", "StopServices"), new XAttribute("Template", $"Stopping {_productTitle} service"), new XText($"Stopping {_productTitle} service") // TODO: default template doesn't receive service name. Might be because it's not installed with ServiceInstall table? )); }