How To Generate Native Machine Images for a C# Application (ngen)

// This script runs the NET NGen utility against a series of DLLs.  In MSI, it should be run as a Deferred Mode In System Context Custom Action,
// with a companion SetProperty CA that sets CustomActionData for this CA to [INSTALLDIR].
// It is written so it can be run outside of MSI to [vastly] simplify debugging.
var ClassName_ScriptingRuntimeShell         = "WScript.Shell";
var ClassName_ScriptingRuntimeFilesystem     = "Scripting.FileSystemObject";

var argumentString                  = "";
var arguments                       = null;
var commandLineToRun                = "";
var netFrameworkRoot                = "";
var net2Directory                   = "";
var ngenPathname                    = "";
var installDir                      = "";


// CustomActionData format:  [INSTALLDIR]<?>
// If running in MSI, use arguments via standard CustomActionData interface, otherwise assume running outside MSI in debugger
if (DetectRunningInMSI())
    argumentString = Session.Property("CustomActionData");
else
    argumentString = "c:\\service\\";
arguments = argumentString.split("<?>");
if (arguments.length == 1)
{
    installDir = arguments[0];
}
else
{
    LogMessage("Argument string invalid (" +arguments.length+ "):  " +argumentString);
    throw 3;
}

installDir = EnsureEndsInBackslash(installDir);
// Locate native image generation tool (ngen)
netFrameworkRoot = ReadRegistryValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\.NETFramework\\InstallRoot");
netFrameworkRoot = EnsureEndsInBackslash(netFrameworkRoot);
net2Directory = netFrameworkRoot +"v2.0.50727\\";
ngenPathname = net2Directory +"ngen.exe";
LogMessage("GenerateNativeImages:  NGen located at "+ngenPathname);

// Process only EXEs.  NGen will automatically recurse into all referenced assemblies.
LogMessage("GenerateNativeImages:  Generating images for EXEs...");
CreateNativeImagesForFiles(installDir, "exe", ngenPathname);


function CreateNativeImagesForFiles( targetDirectory, targetExtension, ngenPathname )
// Compiles native machine images for all files meeting in the specified directory meeting the specified filename specification
{
    var targetDirectoryBackslash    = "";
    var allFiles                    = null;
    var filesToProcess              = new Array();
    var currFileFullPathname        = "";
    var commandLineToRun            = "";

    // Find all files in the specified directory with the specified extension
    targetDirectoryBackslash = EnsureEndsInBackslash(targetDirectory);
    allFiles = GetDirectoryFiles(targetDirectory);
    for (currFileNo = 0; currFileNo < allFiles.length; currFileNo++)
    {
        if (GetFileExtension(allFiles[currFileNo]).toUpperCase() == targetExtension.toUpperCase())
        {
            currFileFullPathname = targetDirectoryBackslash + allFiles[currFileNo];
            filesToProcess.push(currFileFullPathname);
        }
    }

    // NGen the filtered file list
    for (currFileNo = 0; currFileNo < filesToProcess.length; currFileNo++)
    {
        commandLineToRun = "\""+ ngenPathname +"\" install \"" +filesToProcess[currFileNo]+ "\"";
        RunCommand(commandLineToRun);
        // Ignore Run errors, this operation is non-critical
    }   
}

function GetDirectoryFiles( targetDirectory )
// Returns an array with all filenames in the specified directory.  Filenames are relative to targetDirectory.
{
    var fsUtil                  = null;
    var directoryContents       = null;
    var targetFilesEnumerator   = null;
    var filesArray              = null;

    fsUtil = new SafeGetActiveXObject(ClassName_ScriptingRuntimeFilesystem);
    if (fsUtil != null)
    {
        try
        {
            directoryContents = fsUtil.GetFolder(targetDirectory);
            targetFilesEnumerator = new Enumerator(directoryContents.files);
            filesArray = new Array();
            for (; !targetFilesEnumerator.atEnd(); targetFilesEnumerator.moveNext())
                filesArray.push(targetFilesEnumerator.item().Name);
        }
        catch (unexpectedException)
        {
            LogMessage("GetDirectoryFiles:  Error " +unexpectedException.number+ ":  " +unexpectedException.message);
        }
    }
    return filesArray;
}

function GetFileExtension( x )
// Returns the extension of a filename (no leading .)
{
    var separatorPos    = -1;
   
    separatorPos = x.lastIndexOf(".");
    if (separatorPos > 0)
        return x.substr(separatorPos+1);
    else
        return "";
}

function ReadRegistryValue( valuePath )
// Reads the specified registry value and returns it, or "" if not found
{
    var shellObject         = null;
    var returnVal           = "";

    shellObject = SafeGetActiveXObject(ClassName_ScriptingRuntimeShell);
    if (shellObject != null)
    {
        try
        {
            returnVal = shellObject.RegRead(valuePath);
        }
        catch (ignoreException)
        {
            // Ignore it
            returnVal = "";
        }
        shellObject = null;
    }

    return returnVal;
}

function RunCommand( commandLine )
// Runs the specified command line and returns the process exit code or failure code
{
    var     returnVal       = -1;
    var     shellUtil       = null;

    // Validate parameters
    if (commandLine == "")
        return -1;

    shellUtil = SafeGetActiveXObject(ClassName_ScriptingRuntimeShell);
    if (shellUtil != null)
    {
        try
        {
            LogMessage("RunCommand:  Running "+commandLine);
            returnVal = shellUtil.Run(commandLine, 0, true);
            if (returnVal != 0)
                LogMessage("RunCommand:  Error " +returnVal+ " returned by command line "+commandLine);
        }
        catch (runException)
        {
            if (runException.number == -2147024894)
                LogMessage("RunCommand:  File not found error running command " +commandLine+ ":  " +runException.message);
            else
                LogMessage("RunCommand:  Error " +runException.number+ " running command " +commandLine+ ":  " +runException.message);
            returnVal = runException.number;
        }
        shellUtil = null;
    }

    return returnVal;
}

function LogMessage( messageString )
// Writes a line to the MSI log, or the console if running outside MSI.
{
    var     customActionName        = "CreateRemoteExecutionService";
   
    if (DetectRunningInMSI())
    {
        var     msiMessageTypeInfo      = 0x04000000
        var     logRecord               = null;
        var     currTime                = new Date();

        logRecord = Installer.CreateRecord(1);
        logRecord.StringData(0) = currTime.toTimeString() +" "+ customActionName +": [1]";
        logRecord.StringData(1) = messageString;
        Session.Message(msiMessageTypeInfo, logRecord);
        logRecord = null;
    }
    else
    {
        var     currTime                = new Date();

        messageString = currTime.toTimeString() +" "+ customActionName +": "+ messageString;
        WScript.Echo(messageString);
    }
}

function DetectRunningInMSI()
// Returns TRUE if running in MSI, else FALSE
{
    var returnVal       = true;
    var stringVal       = "";

    try
    {
        // Try to reference the Session object, which is only provided by the MSI runtime engine
        stringVal = Session.Property("ProductCode");
    }
    catch (thisException)
    {
        // An exception is only thrown if the runtime couldn't resolve Session, which means
        // this script is not running under MSI
        returnVal = false;
    }

    return returnVal;
}

function SafeGetActiveXObject( className )
// Creates an instance of className and returns it, or null if an error occurs, and logs the error
{
    var returnVal    = null;

    try
    {
        returnVal = new ActiveXObject(className);
    }
    catch (createException)
    {
        LogMessage("SafeGetActiveXObject:  Error " +createException.number+ " creating object of class " +className+ ": " +createException.message);
    }

    return returnVal;
}

function EnsureEndsInBackslash( directoryPath )
// Returns directoryPath ending in a backslash if it does not already end in one.
{
    var returnVal = directoryPath;

    if (returnVal.substring(returnVal.length, returnVal.length-1) != "\\")
        returnVal = returnVal + "\\";

    return returnVal;
}
Comments