Search code examples
c#dynamiccompiler-errorsmanifestcsharpcodeprovider

Unable to include a manifest in my dynamically compiled c# app


I was just tasked with upgrading an internal enterprise web application. The user enters some data, and the web app then compiles a custom winforms EXE (self-extractor/installer type of app), and then the website serves it up as a download.

We recently learned this custom-compiled installer displays a compatibility error/warning in Windows 7. After some research I learned that I will need to provide an Application Manifest that specifies compatibility with Windows 7:

Related links:

This is my first experience with custom/dynamically-compiled code, and application manifests.

Since this app is compiled on the fly (from a single code file and some embedded resources), I can't just add a manifest to my project. So I used the compiler's "/win32manifest" compiler option to reference the manifest file when compiling.

Here is a bit of code from the custom "Archive Compiler" class that actually does the compilation: (I've only added the Application Manifest portion)

public void CompileArchive(string archiveFilename, bool run1stItem, string iconFilename)
{
    CodeDomProvider csc = new CSharpCodeProvider();
    CompilerParameters cp = new CompilerParameters();

    cp.GenerateExecutable = true;
    cp.OutputAssembly = archiveFilename;
    cp.CompilerOptions = "/target:winexe";

    // Custom option to run a file after extraction  
    if (run1stItem) {
        cp.CompilerOptions += " /define:RUN_1ST_ITEM";
    }
    if (!string.IsNullOrEmpty(iconFilename)) {
        cp.CompilerOptions += " /win32icon:" + iconFilename;
    }
    cp.ReferencedAssemblies.Add("System.dll");
    cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");

    // Add application manifest to specify operating system compatibility (to fix compatibility warning in Windows 7)
    string AppManifestPath = Path.Combine( System.Web.HttpContext.Current.Server.MapPath( "~/Content/" ), "CustomInstaller.exe.manifest" );
    if ( File.Exists( AppManifestPath ) ) {
        cp.CompilerOptions += string.Format( " /win32manifest: \"{0}\"", AppManifestPath );
    }

    // Add compressed files as resource
    cp.EmbeddedResources.AddRange(filenames.ToArray()); 

    // Compile standalone executable with input files embedded as resource
    CompilerResults cr = csc.CompileAssemblyFromFile(cp, sourceCodeFilePath);

    // yell if compilation error
    if (cr.Errors.Count > 0) {
        string msg = "Errors building " + cr.PathToAssembly;
        foreach (CompilerError ce in cr.Errors) { msg += Environment.NewLine + ce.ToString(); }
        throw new ApplicationException(msg);
    }
}

However, when I compile, I keep running into this error:

Errors building D:\Projects\MySolution\WebAppName\App_Data\MyUsername\CustomInstaller.exe error CS2007: Unrecognized option: '/win32manifest:'

I'm having trouble finding information on this, other than articles which state that this parameter exists and is valid. The web app is in visual studio 2010 and runs on framework 2.0. The dynamically compiled app references .net 2.0 as well (verified with a decompiler). I'm not sure what EXE gets called to perform the compilation, or what else I could check to troubleshoot this. Any help would be appreciated.


Solution

  • After more research, I learned about mt.exe, which is a simple command line utility for adding application manifests. I put a copy of the exe into my web project, and then added code to call this process after the app was compiled:

    //Add an application manifest using mt.exe
    System.Diagnostics.Process process = new System.Diagnostics.Process();
    System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
    startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
    startInfo.FileName = Path.Combine( System.Web.HttpContext.Current.Server.MapPath( "~/Content/" ), "mt.exe" );
    string AppManifestPath = Path.Combine( System.Web.HttpContext.Current.Server.MapPath( "~/Content/" ), "CustomInstaller.exe.manifest" );
    startInfo.Arguments = string.Format( "-nologo -manifest \"{0}\" -outputresource:\"{1};#1\"", AppManifestPath, cp.OutputAssembly );
    process.StartInfo = startInfo;
    process.Start();
    process.WaitForExit( 10000 ); //wait until finished (up to 10 sec)
    

    This successfully adds my manifest to the compiled EXE quickly and easily. I also found this Manifest Viewer which made it easy to verify the content of my manifest.