void ValidateCAAssemblyImpl(string file, string refAsms) { //Debug.Assert(false); try { var bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static; //var assembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(file); //cannot prelaod all required assemblies var assembly = System.Reflection.Assembly.LoadFrom(file); var caMembers = assembly.GetTypes().SelectMany(t => t.GetMembers(bf) .Where(mem => mem.GetCustomAttributes(false) .Where(x => x.ToString() == "Microsoft.Deployment.WindowsInstaller.CustomActionAttribute").Any())).ToArray(); var invalidMembers = new List <string>(); foreach (MemberInfo mi in caMembers) { string fullName = mi.DeclaringType.FullName + "." + mi.Name; if (!mi.DeclaringType.IsPublic) { if (!invalidMembers.Contains(fullName)) { invalidMembers.Add(fullName); } } if (mi.MemberType != MemberTypes.Method) { if (!invalidMembers.Contains(fullName)) { invalidMembers.Add(fullName); } } else { var method = (mi as MethodInfo); if (!method.IsPublic || !method.IsStatic) { if (!invalidMembers.Contains(fullName)) { invalidMembers.Add(fullName); } } } } if (invalidMembers.Any()) { Compiler.OutputWriteLine("Warning: some of the type members are marked with [CustomAction] attribute but they don't meet the MakeSfxCA criteria of being public static method of a public type:\n"); foreach (var member in invalidMembers) { Compiler.OutputWriteLine(" " + member); } Compiler.OutputWriteLine(""); } } catch { } }
/// <summary> /// Resets the <see cref="Id"/> generator. This method is exercised by the Wix# compiler before any /// <c>Build</c> operations to ensure reproducibility of the <see cref="Id"/> set between <c>Build()</c> /// calls. /// </summary> static public void ResetIdGenerator() { if (!DoNotResetIdGenerator) { if (idMaps.Count > 0) { Compiler.OutputWriteLine("----------------------------"); Compiler.OutputWriteLine("Warning: Wix# compiler detected that some IDs has been auto-generated before the build started. " + "This can lead to the WiX ID duplications. To prevent this from happening either:\n" + " - Avoid evaluating the auto-generated IDs values before the call to Build*\n" + " - Set the IDs (to be evaluated) explicitly\n" + " - Prevent resetting auto-ID generator by setting WixEntity.DoNotResetIdGenerator to true"); Compiler.OutputWriteLine("----------------------------"); } idMaps.Clear(); } }
static string BuildUiPlayerResources() { var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"WixSharp\Demo_UIPlayer"); var result = Path.Combine(dir, "Demo_UIPlayer.msi"); if (System.IO.File.Exists(result)) { return(result); } else { try { Compiler.OutputWriteLine("Building UIPlayer resources..."); } catch { } if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var project = new Project("Demo_UIPlayer", new Dir(@"%ProgramFiles%\WixSharp\Demo_UIPlayer")); project.OutDir = dir; string licence = ManagedUI.Default.LicenceFileFor(project); string localization = ManagedUI.Default.LocalizationFileFor(project); string bitmap = ManagedUI.Default.DialogBitmapFileFor(project); string banner = ManagedUI.Default.DialogBannerFileFor(project); project.AddBinaries(new Binary(new Id("WixSharp_UIText"), localization), new Binary(new Id("WixSharp_LicenceFile"), licence), new Binary(new Id("WixUI_Bmp_Dialog"), bitmap), new Binary(new Id("WixUI_Bmp_Banner"), banner)); project.UI = WUI.WixUI_ProgressOnly; return(project.BuildMsi()); } }
/// <summary> /// Builds WiX Bootstrapper application from the specified <see cref="Bundle"/> project instance. /// </summary> /// <param name="project">The project.</param> /// <param name="path">The path.</param> /// <exception cref="System.ApplicationException">Wix compiler/linker cannot be found</exception> public static string Build(Bundle project, string path) { path = path.ExpandEnvVars(); if (Compiler.ClientAssembly.IsEmpty()) { Compiler.ClientAssembly = System.Reflection.Assembly.GetCallingAssembly().GetLocation(); } string oldCurrDir = Environment.CurrentDirectory; try { //System.Diagnostics.Debug.Assert(false); Compiler.TempFiles.Clear(); string compiler = Utils.PathCombine(WixLocation, "candle.exe"); string linker = Utils.PathCombine(WixLocation, "light.exe"); if (!IO.File.Exists(compiler) || !IO.File.Exists(linker)) { Compiler.OutputWriteLine("Wix binaries cannot be found. Expected location is " + compiler.PathGetDirName()); throw new ApplicationException("Wix compiler/linker cannot be found"); } else { if (!project.SourceBaseDir.IsEmpty()) { Environment.CurrentDirectory = project.SourceBaseDir; } string wxsFile = BuildWxs(project); string objFile = IO.Path.ChangeExtension(wxsFile, ".wixobj"); string pdbFile = IO.Path.ChangeExtension(wxsFile, ".wixpdb"); string extensionDlls = ""; foreach (string dll in project.WixExtensions.Distinct()) { extensionDlls += " -ext \"" + dll + "\""; } string wxsFiles = ""; foreach (string file in project.WxsFiles.Distinct()) { wxsFiles += " \"" + file + "\""; } var candleOptions = CandleOptions + " " + project.CandleOptions; string command = candleOptions + " " + extensionDlls + " \"" + wxsFile + "\" "; if (wxsFiles.IsNotEmpty()) { command += wxsFiles; string outDir = IO.Path.GetDirectoryName(wxsFile); // if multiple files are specified candle expect a path for the -out switch // or no path at all (use current directory) // note the '\' character must be escaped twice: as a C# string and as a CMD char if (outDir.IsNotEmpty()) { command += $" -out \"{outDir}\\\\\""; } } else { command += $" -out \"{objFile}\""; } command = command.ExpandEnvVars(); Run(compiler, command); if (IO.File.Exists(objFile)) { string outFile = wxsFile.PathChangeExtension(".exe"); if (path.IsNotEmpty()) { outFile = IO.Path.GetFullPath(path); } if (IO.File.Exists(outFile)) { IO.File.Delete(outFile); } //if (project.IsLocalized && IO.File.Exists(project.LocalizationFile)) // Run(linker, LightOptions + " \"" + objFile + "\" -out \"" + msiFile + "\"" + extensionDlls + " -cultures:" + project.Language + " -loc \"" + project.LocalizationFile + "\""); //else string lightOptions = LightOptions + " " + project.LightOptions; Run(linker, lightOptions + " \"" + objFile + "\" -out \"" + outFile + "\"" + extensionDlls + " -cultures:" + project.Language); if (IO.File.Exists(outFile)) { Compiler.TempFiles.Add(wxsFile); Compiler.OutputWriteLine("\n----------------------------------------------------------\n"); Compiler.OutputWriteLine("Bootstrapper file has been built: " + path + "\n"); Compiler.OutputWriteLine(" Name : " + project.Name); Compiler.OutputWriteLine(" Version : " + project.Version); Compiler.OutputWriteLine(" UpgradeCode: {" + project.UpgradeCode + "}\n"); if (!PreserveDbgFiles && !project.PreserveDbgFiles) { objFile.DeleteIfExists(); pdbFile.DeleteIfExists(); } project.DigitalSignature?.Apply(outFile); } } else { Compiler.OutputWriteLine("Cannot build " + wxsFile); Trace.WriteLine("Cannot build " + wxsFile); } } if (!PreserveTempFiles && !project.PreserveTempFiles) { foreach (var file in Compiler.TempFiles) { try { if (IO.File.Exists(file)) { IO.File.Delete(file); } } catch { } } } } finally { Environment.CurrentDirectory = oldCurrDir; } return(path); }
/// <summary> /// Builds the WiX source file (*.wxs) from the specified <see cref="Bundle"/> instance. /// </summary> /// <param name="project">The project.</param> /// <returns></returns> public static string BuildWxs(Bundle project) { lock (typeof(Compiler)) { //very important to keep "ClientAssembly = " in all "public Build*" methods to ensure GetCallingAssembly //returns the build script assembly but not just another method of Compiler. if (ClientAssembly.IsEmpty()) { ClientAssembly = System.Reflection.Assembly.GetCallingAssembly().GetLocation(); } project.Validate(); lock (Compiler.AutoGeneration.WxsGenerationSynchObject) { var oldAlgorithm = AutoGeneration.CustomIdAlgorithm; try { WixEntity.ResetIdGenerator(false); AutoGeneration.CustomIdAlgorithm = project.CustomIdAlgorithm ?? AutoGeneration.CustomIdAlgorithm; string file = IO.Path.GetFullPath(IO.Path.Combine(project.OutDir, project.OutFileName) + ".wxs"); if (IO.File.Exists(file)) { IO.File.Delete(file); } string extraNamespaces = project.WixNamespaces.Distinct() .Select(x => x.StartsWith("xmlns:") ? x : "xmlns:" + x) .ConcatItems(" "); var wix3Namespace = "http://schemas.microsoft.com/wix/2006/wi"; var wix4Namespace = "http://wixtoolset.org/schemas/v4/wxs"; var wixNamespace = Compiler.IsWix4 ? wix4Namespace : wix3Namespace; var doc = XDocument.Parse( @"<?xml version=""1.0"" encoding=""utf-8""?> " + $"<Wix xmlns=\"{wixNamespace}\" {extraNamespaces} " + @" > </Wix>"); doc.Root.Add(project.ToXml()); AutoElements.NormalizeFilePaths(doc, project.SourceBaseDir, EmitRelativePaths); project.InvokeWixSourceGenerated(doc); AutoElements.ExpandCustomAttributes(doc, project); if (WixSourceGenerated != null) { WixSourceGenerated(doc); } string xml = ""; using (IO.StringWriter sw = new StringWriterWithEncoding(Encoding.Default)) { doc.Save(sw, SaveOptions.None); xml = sw.ToString(); } //of course you can use XmlTextWriter.WriteRaw but this is just a temporary quick'n'dirty solution //http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2657663&SiteID=1 xml = xml.Replace("xmlns=\"\"", ""); DefaultWixSourceFormatedHandler(ref xml); project.InvokeWixSourceFormated(ref xml); if (WixSourceFormated != null) { WixSourceFormated(ref xml); } using (var sw = new IO.StreamWriter(file, false, Encoding.Default)) sw.WriteLine(xml); Compiler.OutputWriteLine("\n----------------------------------------------------------\n"); Compiler.OutputWriteLine("Wix project file has been built: " + file + "\n"); project.InvokeWixSourceSaved(file); if (WixSourceSaved != null) { WixSourceSaved(file); } return(file); } finally { AutoGeneration.CustomIdAlgorithm = oldAlgorithm; } } } }
/// <summary> /// Builds the WiX source file and generates batch file capable of building /// WiX/MSI bootstrapper with WiX toolset. /// </summary> /// <param name="project">The project.</param> /// <param name="path">The path to the batch file to be created.</param> /// <exception cref="System.ApplicationException">Wix compiler/linker cannot be found</exception> public static string BuildCmd(Bundle project, string path = null) { if (Compiler.ClientAssembly.IsEmpty()) { Compiler.ClientAssembly = System.Reflection.Assembly.GetCallingAssembly().GetLocation(); } if (path == null) { path = IO.Path.GetFullPath(IO.Path.Combine(project.OutDir, "Build_" + project.OutFileName) + ".cmd"); } path = path.ExpandEnvVars(); //System.Diagnostics.Debug.Assert(false); string wixLocationEnvVar = $"set WixLocation={WixLocation}" + Environment.NewLine; string compiler = Utils.PathCombine(WixLocation, "candle.exe"); string linker = Utils.PathCombine(WixLocation, "light.exe"); string batchFile = path; if (!IO.File.Exists(compiler) || !IO.File.Exists(linker)) { Compiler.OutputWriteLine("Wix binaries cannot be found. Expected location is " + IO.Path.GetDirectoryName(compiler)); throw new ApplicationException("Wix compiler/linker cannot be found"); } else { string wxsFile = BuildWxs(project); if (!project.SourceBaseDir.IsEmpty()) { Environment.CurrentDirectory = project.SourceBaseDir; } string objFile = IO.Path.ChangeExtension(wxsFile, ".wixobj"); string pdbFile = IO.Path.ChangeExtension(wxsFile, ".wixpdb"); string extensionDlls = ""; foreach (string dll in project.WixExtensions.Distinct()) { extensionDlls += " -ext \"" + dll + "\""; } string wxsFiles = ""; foreach (string file in project.WxsFiles.Distinct()) { wxsFiles += " \"" + file + "\""; } var candleOptions = CandleOptions + " " + project.CandleOptions; string batchFileContent = wixLocationEnvVar + "\"" + compiler + "\" " + candleOptions + " " + extensionDlls + " \"" + IO.Path.GetFileName(wxsFile) + "\" "; if (wxsFiles.IsNotEmpty()) { batchFileContent += wxsFiles; string outDir = IO.Path.GetDirectoryName(wxsFile); // if multiple files are specified candle expect a path for the -out switch // or no path at all (use current directory) // note the '\' character must be escaped twice: as a C# string and as a CMD char if (outDir.IsNotEmpty()) { batchFileContent += $" -out \"{outDir}\\\\\""; } } else { batchFileContent += $" -out \"{objFile}\""; } batchFileContent += "\r\n"; string lightOptions = LightOptions + " " + project.LightOptions; if (path.IsNotEmpty()) { lightOptions += " -out \"" + IO.Path.ChangeExtension(objFile, ".exe") + "\""; } batchFileContent += "\"" + linker + "\" " + lightOptions + " \"" + objFile + "\" " + extensionDlls + " -cultures:" + project.Language + "\r\npause"; batchFileContent = batchFileContent.ExpandEnvVars(); using (var sw = new IO.StreamWriter(batchFile)) sw.Write(batchFileContent); } return(path); }
internal void ValidateCAAssemblyImpl(string file, string dtfAsm, bool loadFromMemory) { //Debug.Assert(false); try { var bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static; // `ReflectionOnlyLoadFrom` cannot preload all required assemblies and triggers // "System.InvalidOperationException: 'It is illegal to reflect on the custom attributes // of a Type loaded via ReflectionOnlyGetType (see Assembly.ReflectionOnly) -- use CustomAttributeData // instead.'" exception. Thus need to use `LoadFrom`, which locks the assembly unless the operation is // performed in the temp AppDomain, which is unloaded after at the end. // Unfortunately `AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve` does not help (does not get fired). // var assembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(file); var assembly = loadFromMemory ? Reflection.Assembly.Load(System.IO.File.ReadAllBytes(file)) : Reflection.Assembly.LoadFrom(file); var caMembers = assembly.GetTypes() .SelectMany(t => t.GetMembers(bf) .Where(mem => mem.GetCustomAttributes(false) .Where(x => x.ToString() == "Microsoft.Deployment.WindowsInstaller.CustomActionAttribute") .Any())) .ToArray(); var invalidMembers = new List <string>(); foreach (MemberInfo mi in caMembers) { string fullName = mi.DeclaringType.FullName + "." + mi.Name; if (!mi.DeclaringType.IsPublic) { if (!invalidMembers.Contains(fullName)) { invalidMembers.Add(fullName); } } if (mi.MemberType != MemberTypes.Method) { if (!invalidMembers.Contains(fullName)) { invalidMembers.Add(fullName); } } else { var method = (mi as MethodInfo); if (!method.IsPublic || !method.IsStatic) { if (!invalidMembers.Contains(fullName)) { invalidMembers.Add(fullName); } } } } if (invalidMembers.Any()) { Compiler.OutputWriteLine("Warning: some of the type members are marked with [CustomAction] attribute but they don't meet the MakeSfxCA criteria of being public static method of a public type:\n"); foreach (var member in invalidMembers) { Compiler.OutputWriteLine(" " + member); } Compiler.OutputWriteLine(""); } } catch { } }
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 } } } } } } }