Adding Custom Actions to NGen an Assembly upon Install

This document details how to use custom actions to Ngen (generate a native image) upon installation of a project. Assemblies that are NGen’ed have better startup performance than those that are JIT’ted: https://msdn.microsoft.com/en-us/magazine/cc163610.aspx

Step 1: Creating and building your sources

I am assuming that you have created a new project, written all your source files, built the project and are ready to go. I am assuming that your project name is MySources and you also created a solution with the same name, MySources.

 

If all is well, you should see something similar to the following, with probably a lot more files under your MySources project:

               

           

 

 

Step 2: Creating and building the NgenHelperClass

 

(a) While the MySources solution is selected, under the File menu, click Add ->New Project . If you are not seeing these options under the File menu, it is because your program is still executing and you have not exited debugging.

(b) Select under Project Types , Visual Basic -> Class Library; Name it NgenHelperClass; Click OK.

(c)  Click the NgenHelperClass project on the Solution Explorer on the right and select Project menu-> Add New Item -> Common Items -> General -> Installer Class; the default name Installer1.vb is fine. Note: If Solution Explorer cannot be seen, open up the View menu and select Solution Explorer; the NgenHelperClass project has to be selected for this to work as described.

(d) Click Add, and in the succeeding screen, click to show the code view

(e)  Add the following code to the top of the module, below

  Imports System.Configuration.Install:

Imports System.Runtime.InteropServices

Imports System.Text

(f)  Add the following declaration below Public Class Installer1:

 

Private Declare Function GetCORSystemDirectory Lib "mscoree.dll" _

(<Runtime.InteropServices.MarshalAs( _

System.Runtime.InteropServices.UnmanagedType.LPWStr)> _

ByVal Buffer As System.Text.StringBuilder, _

ByVal BufferLength As Integer, ByRef Length As Integer) As Integer

(g)  In Installer1.vb, after the End Sub statement for MyBase.New, add the following procedure to override the Install procedure of the base class:

  <Security.Permissions.SecurityPermission(Security.Permissions.SecurityAction.Demand)> _

Public Overrides Sub Install(ByVal savedState As _

  System.Collections.IDictionary)

        MyBase.Install(savedState)

        Dim Args As String = Me.Context.Parameters.Item("Args")

        If Args = "" Then

            Throw New InstallException("No arguments specified")

        End If

        ' Gets the path to the Framework directory.

        Dim Path As New System.Text.StringBuilder(1024)

        Dim Size As Integer

        GetCORSystemDirectory(Path, Path.Capacity, Size)

    Dim P As Process

        ' Quotes the arguments, in case they have a space in them.

        Dim Si As New ProcessStartInfo(Path.ToString() & "ngen.exe", " install " & Chr(34) _

           & Args & Chr(34))

        Si.WindowStyle = ProcessWindowStyle.Hidden

        Try

            P = Process.Start(Si)

            P.WaitForExit()

        Catch e As Exception

            Throw New InstallException(e.Message)

        End Try

    End Sub

    <Security.Permissions.SecurityPermission(Security.Permissions.SecurityAction.Demand)> _

   Public Overrides Sub Rollback(ByVal savedState As _

     System.Collections.IDictionary)

        MyBase.Install(savedState)

        Dim Args As String = Me.Context.Parameters.Item("Args")

        If Args = "" Then

            Throw New InstallException("No arguments specified")

        End If

        ' Gets the path to the Framework directory.

        Dim Path As New System.Text.StringBuilder(1024)

        Dim Size As Integer

        GetCORSystemDirectory(Path, Path.Capacity, Size)

        Dim P As Process

        ' Quotes the arguments, in case they have a space in them.

        Dim Si As New ProcessStartInfo(Path.ToString() & "ngen.exe", " uninstall " & Chr(34) _

           & Args & Chr(34))

        Si.WindowStyle = ProcessWindowStyle.Hidden

        Try

            P = Process.Start(Si)

            P.WaitForExit()

        Catch e As Exception

            Throw New InstallException(e.Message)

        End Try

    End Sub

    <Security.Permissions.SecurityPermission(Security.Permissions.SecurityAction.Demand)> _

     Public Overrides Sub Uninstall(ByVal savedState As _

     System.Collections.IDictionary)

        MyBase.Uninstall(savedState)

        Dim Args As String = Me.Context.Parameters.Item("Args")

        If Args = "" Then

                   Throw New InstallException("No arguments specified")

        End If

        ' Gets the path to the Framework directory.

        Dim Path As New System.Text.StringBuilder(1024)

        Dim Size As Integer

        GetCORSystemDirectory(Path, Path.Capacity, Size)

  Dim P As Process

        ' Quotes the arguments, in case they have a space in them.

  Dim Si As New ProcessStartInfo(Path.ToString() & "ngen.exe", " uninstall " & Chr(34) _ & Args & Chr(34))

  Si.WindowStyle = ProcessWindowStyle.Hidden

        Try

            P = Process.Start(Si)

            P.WaitForExit()

        Catch e As Exception

            Throw New InstallException(e.Message)

        End Try

End Sub

 

(h) Build solution; you should see that it builds two projects now, and succeeds. If not, you have done something wrong.

(i) The NgenHelperClass is now built; this class can be re-used with other projects as appropriate.

 

 

 

Step 3: Building the actual installer

 

(a) With the solution MySources selected, under the File menu, Add New Project.

(b)  Select Other Project Types -> Setup and Deployment in the project types frame.

(c)  In the Templates frame, select Setup Project

(d) In the Name field type MyInstaller; Click OK.

(e) If all went well, you should see the following screen:

 

 

 

 

(f) Click on Application Folder on the File System on Target Machine window, right click on Application folder, Select Add-> Project Output

(g) Click on Primary Output while MySources is selected in the Project field; click OK.

(h) Again repeat Step (f), and add NgenHelperClass to Primary Output by selecting NgenHelperClass on the Project field; click OK.

(i) Now you should have two primary outputs selected in the application folder as below:

 

 

(j) Now select the MyInstaller project in solution explorer, and on the View menu , select View -> Editor -> Custom Actions. The following window should now be visible:

 

        

 

 

(k) In the custom Actions (MyInstaller) window, right click on Custom Actions and select Add custom action;

(l) In the Select Item in Project, double click Application folder, and select Primary output from NgenHelperClass (Do *NOT* select the primary output from MySources)

(m) Click OK; you should see that the primary output from NgenHelperClass is now visible for all the four actions (Install/commit/rollback/uninstall). It is not generally necessary to add this for commit unless your deployment requires it. Select the “Primary Output Custom Action” on Commit and right click and select delete.

(n) Click on the primary output from NgenHelperClass for the Install folder; ON the properties window you should see CustomActionData field; in this field type the following corresponding to the executable for your sources: /Args="[TARGETDIR]MySources.exe"

(o) Repeat the above step for Rollback/Install

(p) Click Save; On the Build menu (with MyInstaller selected on solution explorer), choose Build MyInstaller. Note – the installer is not built when you choose build solution at any point – beware.

(q) In the Properties window set the field “ProductName” to the name you would like to see in the Add/Remove programs menu in control panel. We selected HelloWorldApp as the Product Name.

(r) Select Release option instead of the default debug option. On the Build menu, select Build Solution, followed by Build MyInstaller.

 

 

Step 4: Verifying whether it really worked

 

(a) To install on your development computer: Select the MyInstaller project in Solution Explorer. On the Project menu, choose Install.

(b) To deploy to another computer: In Windows Explorer, navigate to your project directory and find the built installer. The default path in Windows Vista will be C:\Users\yourloginname\My Documents\Visual Studio 2008\Projects\mysources\MyInstaller\Release\MyInstaller.msi. Copy MyInstaller.msi, Setup.exe, and all other files and subdirectories in the directory to another computer and install.

(c) Navigate to the assembly folder %windir%\assembly (for example, C:\Windows\assembly) and find MySources under the appropriate Native Images folder. If there are multiple native images folders, you have multiple runtimes installed. Select the folder based on the runtime you used to build your sources. If you found MySources, that means that your Ngen was successful!

(d)  To uninstall the application: In Windows Control Panel, double-click Add or Remove Programs. In the Add or Remove Programs dialog box, select HelloWorldApp and click Remove, and then click OK to close the dialog box.

 

This post was authored by Subramanian Ramaswamy with assistance from Momin Al-Ghosien and Ashok Kamath, members of the CLR Performance Team at Microsoft.