internal static void HandleEmptyDirectories(XDocument doc) { XElement product = doc.Root.Select("Product"); var dummyDirs = product.Descendants("Directory") .SelectMany(x => x.Elements("Component")) .Where(e => e.HasAttribute("Id", v => v.EndsWith(".EmptyDirectory"))) .Select(x => x.Parent("Directory")); if (SupportEmptyDirectories == CompilerSupportState.Automatic) { SupportEmptyDirectories = dummyDirs.Any() ? CompilerSupportState.Enabled : CompilerSupportState.Disabled; //it wasn't set by user so set it if any empty dir is detected Compiler.OutputWriteLine("Wix# support for EmptyDirectories is automatically " + SupportEmptyDirectories.ToString().ToLower()); } if (SupportEmptyDirectories == CompilerSupportState.Enabled) { if (dummyDirs.Any()) { foreach (var item in dummyDirs) { XElement parent = item.Parent("Directory"); while (parent != null) { if (parent.Element("Component") == null) { var dirId = parent.Attribute("Id").Value; if (Compiler.EnvironmentConstantsMapping.ContainsValue(dirId)) { break; //stop when reached start of user defined subdirs chain: TARGETDIR/ProgramFilesFolder!!!/ProgramFilesFolder.Company/INSTALLDIR } //just folder with nothing in it but not the last leaf doc.CrteateComponentFor(parent); } parent = parent.Parent("Directory"); } } } foreach (XElement xDir in product.Descendants("Directory").ToArray()) { var dirComponents = xDir.Elements("Component"); if (dirComponents.Any()) { var componentsWithNoFiles = dirComponents.Where(x => !x.ContainsFiles()).ToArray(); //'EMPTY DIRECTORY' support processing section foreach (XElement item in componentsWithNoFiles) { // Ridiculous MSI constrains: // * you cannot install install empty folders // - workaround is to insert empty component with CreateFolder element // * if Component+CreateFolder element is inserted the folder will not be removed on uninstall // - workaround is to insert RemoveFolder element in to empty component as well // * if Component+CreateFolder+RemoveFolder elements are placed in a dummy component to handle an empty folder // any parent folder with no files/components will not be removed on uninstall. // - workaround is to insert Component+Create+RemoveFolder elements in any parent folder with no files. // // OMG!!!! If it is not over-engineering I don't know what is. bool oldAlgorithm = false; if (!oldAlgorithm) { //current approach InsertCreateFolder(item); if (!xDir.ContainsAnyRemoveFolder()) { InsertRemoveFolder(xDir, item, "uninstall"); } } else { //old approach if (!item.Attribute("Id").Value.EndsWith(".EmptyDirectory")) { InsertCreateFolder(item); } else if (!xDir.ContainsAnyRemoveFolder()) { InsertRemoveFolder(xDir, item, "uninstall"); //to keep WiX/compiler happy and allow removal of the dummy directory } } } } } } }
internal static void InjectAutoElementsHandler(XDocument doc, Project project) { ExpandCustomAttributes(doc, project); InjectShortcutIcons(doc); HandleEmptyDirectories(doc); InjectPlatformAttributes(doc); XElement product = doc.Root.Select("Product"); int?absPathCount = null; foreach (XElement dir in product.Element("Directory").Elements("Directory")) { XElement installDir = dir; XAttribute installDirName = installDir.Attribute("Name"); if (IO.Path.IsPathRooted(installDirName.Value)) { string absolutePath = installDirName.Value; if (dir == product.Element("Directory").Elements("Directory").First()) //only for the first root dir { //ManagedUI will need some hint on the install dir as it cannot rely on the session action (e.g. Set_INSTALLDIR_AbsolutePath) //because it is running outside of the sequence and analyses the tables directly for the INSTALLDIR product.AddElement("Property", "Id=INSTALLDIR_ABSOLUTEPATH; Value=" + absolutePath); } installDirName.Value = $"ABSOLUTEPATH{absPathCount}"; //<SetProperty> for INSTALLDIR is an attractive approach but it doesn't allow conditional setting of 'ui' and 'execute' as required depending on UI level // it is ether hard-coded 'both' or hard coded-both 'ui' or 'execute' // <SetProperty Id="INSTALLDIR" Value="C:\My Company\MyProduct" Sequence="both" Before="AppSearch"> string actualDirName = installDir.Attribute("Id").Value; string customAction = $"Set_DirAbsolutePath{absPathCount}"; product.Add(new XElement("CustomAction", new XAttribute("Id", customAction), new XAttribute("Property", actualDirName), new XAttribute("Value", absolutePath))); product.SelectOrCreate("InstallExecuteSequence").Add( new XElement("Custom", $"(NOT Installed) AND (UILevel < 5) AND ({actualDirName} = ABSOLUTEPATH{absPathCount})", new XAttribute("Action", customAction), new XAttribute("Before", "AppSearch"))); product.SelectOrCreate("InstallUISequence").Add( new XElement("Custom", $"(NOT Installed) AND (UILevel = 5) AND ({actualDirName} = ABSOLUTEPATH{absPathCount})", new XAttribute("Action", customAction), new XAttribute("Before", "AppSearch"))); if (absPathCount == null) { absPathCount = 0; } absPathCount++; } } foreach (XElement xDir in product.Descendants("Directory").ToArray()) { var dirComponents = xDir.Elements("Component"); if (dirComponents.Any()) { var componentsWithNoFilesOrRegestry = dirComponents.Where(x => !x.ContainsFilesOrRegistries()).ToArray(); foreach (XElement item in componentsWithNoFilesOrRegestry) { //if (!item.Attribute("Id").Value.EndsWith(".EmptyDirectory")) EnsureKeyPath(item); if (!xDir.ContainsAnyRemoveFolder()) { InsertRemoveFolder(xDir, item, "uninstall"); //to keep WiX/compiler happy and allow removal of the dummy directory } } } foreach (XElement xComp in dirComponents) { if (xDir.InUserProfile()) { if (!xDir.ContainsAnyRemoveFolder()) { InsertRemoveFolder(xDir, xComp); } if (!xComp.ContainsDummyUserProfileRegistry()) { InsertDummyUserProfileRegistry(xComp); } } else { if (xComp.ContainsNonAdvertisedShortcuts()) { if (!xComp.ContainsDummyUserProfileRegistry()) { InsertDummyUserProfileRegistry(xComp); } } } foreach (XElement xFile in xComp.Elements("File")) { if (xFile.ContainsAdvertisedShortcuts() && !xComp.ContainsDummyUserProfileRegistry()) { SetFileKeyPath(xFile); } } } if (!xDir.ContainsComponents() && xDir.InUserProfile()) { if (!xDir.IsUserProfileRoot()) { XElement xComp1 = doc.CrteateComponentFor(xDir); if (!xDir.ContainsAnyRemoveFolder()) { InsertRemoveFolder(xDir, xComp1); } if (!xComp1.ContainsDummyUserProfileRegistry()) { InsertDummyUserProfileRegistry(xComp1); } } } } //Not a property Id as MSI requires Predicate <string> needsProperty = value => value.Contains("\\") || value.Contains("//") || value.Contains("%") || value.Contains("[") || value.Contains("]"); foreach (XElement xShortcut in product.Descendants("Shortcut")) { if (xShortcut.HasAttribute("WorkingDirectory", x => needsProperty(x))) { string workingDirectory = xShortcut.Attribute("WorkingDirectory").Value; if (workingDirectory.StartsWith("%") && workingDirectory.EndsWith("%")) //%INSTALLDIR% { workingDirectory = workingDirectory.ExpandWixEnvConsts(); xShortcut.SetAttributeValue("WorkingDirectory", workingDirectory.Replace("%", "")); } else if (workingDirectory.StartsWith("[") && workingDirectory.EndsWith("]")) //[INSTALLDIR] { xShortcut.SetAttributeValue("WorkingDirectory", workingDirectory.Replace("[", "").Replace("]", "")); } else { string workinDirPath = workingDirectory.ReplaceWixSharpEnvConsts(); XElement existingProperty = product.Descendants("Property") .Where(p => p.HasAttribute("Value", workingDirectory)) .FirstOrDefault(); if (existingProperty != null) { xShortcut.SetAttributeValue("WorkingDirectory", existingProperty.Attribute("Id").Value); } else { string propId = xShortcut.Attribute("Id").Value + ".WorkDir"; product.AddElement("Property", "Id=" + propId + "; Value=" + workinDirPath); xShortcut.SetAttributeValue("WorkingDirectory", propId); } } } } }
internal static void InjectAutoElementsHandler(XDocument doc) { InjectPlatformAttributes(doc); ExpandCustomAttributes(doc); InjectShortcutIcons(doc); XElement installDir = doc.Root.Select("Product").Element("Directory").Element("Directory"); XAttribute installDirName = installDir.Attribute("Name"); if (IO.Path.IsPathRooted(installDirName.Value)) { var product = installDir.Parent("Product"); string absolutePath = installDirName.Value; installDirName.Value = "ABSOLUTEPATH"; //<SetProperty> is an attractive approach but it doesn't allow conditional setting of 'ui' and 'execute' as required depending on UI level // it is ether hard coded 'both' or hard coded both 'ui' or 'execute' // <SetProperty Id="INSTALLDIR" Value="C:\My Company\MyProduct" Sequence="both" Before="AppSearch"> product.Add(new XElement("CustomAction", new XAttribute("Id", "Set_INSTALLDIR_AbsolutePath"), new XAttribute("Property", installDir.Attribute("Id").Value), new XAttribute("Value", absolutePath))); product.SelectOrCreate("InstallExecuteSequence").Add( new XElement("Custom", "(NOT Installed) AND (UILevel < 5)", new XAttribute("Action", "Set_INSTALLDIR_AbsolutePath"), new XAttribute("Before", "CostFinalize"))); product.SelectOrCreate("InstallUISequence").Add( new XElement("Custom", "(NOT Installed) AND (UILevel = 5)", new XAttribute("Action", "Set_INSTALLDIR_AbsolutePath"), new XAttribute("Before", "CostFinalize"))); } foreach (XElement xDir in doc.Root.Descendants("Directory").ToArray()) { var dirComponents = xDir.Elements("Component"); if (dirComponents.Any()) { var componentsWithNoFiles = dirComponents.Where(x => !x.ContainsFiles()).ToArray(); foreach (XElement item in componentsWithNoFiles) { if (!item.Attribute("Id").Value.EndsWith(".EmptyDirectory")) { InsertCreateFolder(item); } else if (!xDir.ContainsAnyRemoveFolder()) { InsertRemoveFolder(xDir, item, "both"); //to keep WiX/compiler happy and allow removal of the dummy directory } } } foreach (XElement xComp in dirComponents) { if (xDir.InUserProfile()) { if (!xDir.ContainsAnyRemoveFolder()) { InsertRemoveFolder(xDir, xComp); } if (!xComp.ContainsDummyUserProfileRegistry()) { InsertDummyUserProfileRegistry(xComp); } } else { if (xComp.ContainsNonAdvertisedShortcuts()) { if (!xComp.ContainsDummyUserProfileRegistry()) { InsertDummyUserProfileRegistry(xComp); } } } foreach (XElement xFile in xComp.Elements("File")) { if (xFile.ContainsAdvertisedShortcuts() && !xComp.ContainsDummyUserProfileRegistry()) { SetFileKeyPath(xFile); } } } if (!xDir.ContainsComponents() && xDir.InUserProfile()) { if (!xDir.IsUserProfileRoot()) { XElement xComp1 = doc.CrteateComponentFor(xDir); if (!xDir.ContainsAnyRemoveFolder()) { InsertRemoveFolder(xDir, xComp1); } if (!xComp1.ContainsDummyUserProfileRegistry()) { InsertDummyUserProfileRegistry(xComp1); } } } } }