Walkthrough: Using a Custom Action to Compile a Binary to Native Code at Installation
You can define custom actions to specify commands that are run after an installation. For example, in this walkthrough, you define a custom action and pass the path name of an EXE to the CustomActionData property to compile the executable to native code after the application is installed.
Note
Your computer might show different names or locations for some of the Visual Studio user interface elements in the following instructions. The Visual Studio edition that you have and the settings that you use determine these elements. For more information, see Visual Studio Settings.
To create a Web browser application to deploy
On the File menu, point to New, and then click Project.
Click Windows Forms Application.
For the Name, type BrowserSample, and then click OK.
On the View menu, click Toolbox.
Expand All Windows Forms, and drag a Panel control to the top left of the form.
In the Form Designer, drag a TextBox control and Button control to the Panel control.
In the Form Designer, drag a WebBrowser control below the Panel.
Expand the size of the form to fit all the controls.
In the Form Designer, click the Panel control.
In the Properties Window, change the Dock property under Layout to Top.
In the Form Designer, click the WebBrowser control.
In the Properties Window, change the Dock property under Layout to Fill.
In the Form Designer, click the Button control.
In the Properties Window, change the Text property under Appearance to Go.
Resize the Form, Panel, Textbox, Button, and WebBrowser to your preferences.
In the Form Designer, double-click the Go button.
The code view for the Form1 code file appears.
Add the following code, which adds Web browsing functionality to your application. The text in the TextBox control is the address bar for the WebBrowser control, and the action takes place when you click the Go button.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click WebBrowser1.Navigate(TextBox1.Text) End Sub
private void button1_Click(object sender, EventArgs e) { webBrowser1.Navigate(textBox1.Text); }
To test the browser, press F5.
The form opens.
In the text box, type https://www.microsoft.com, and then click Go.
The Microsoft Web site appears.
To create the custom action class
On the File menu, point to Add, and then click New Project.
In the Add New Project dialog box, click Windows, and then click Class Library.
In the Name box, type NGenCustomAction, and then click OK.
On the Project menu, click Add New Item.
In the Add New Item dialog box, click General, and then click Installer Class. In the Name box, type NGenCustomAction, and then click Add.
Note
Make sure that you add an Installer Class; otherwise, the code file will not have necessary using statements.
In Solution Explorer, delete the Class1 code file in the NGenCustomAction project.
To add code to the custom action
Right-click the NGenCustomAction code file in Solution Explorer (or the design surface), and then click View Code to open the Code Editor. Add the following code to the top of the module.
Imports System.IO Imports System.Diagnostics
using System.IO; using System.Diagnostics;
Update the class declaration to inherit from the System.Configuration.Install.Installer class.
Inherits System.Configuration.Install.Installer
: System.Configuration.Install.Installer
In the NGenCustomAction code file, add the following helper method to generate the native image code file for any assembly.
<System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)> _ Private Sub ngenCA(ByVal savedState As System.Collections.IDictionary, ByVal ngenCommand As String) Dim argsArray As [String]() If String.Compare(ngenCommand, "install", StringComparison.OrdinalIgnoreCase) = 0 Then Dim args As [String] = Context.Parameters("Args") If [String].IsNullOrEmpty(args) Then Throw New InstallException("No arguments specified") End If Dim separators As Char() = {";"c} argsArray = args.Split(separators) 'It is Ok to 'ngen uninstall' assemblies which were not installed savedState.Add("NgenCAArgs", argsArray) Else argsArray = DirectCast(savedState("NgenCAArgs"), [String]()) End If ' Gets the path to the Framework directory. Dim fxPath As String = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory() For i As Integer = 0 To argsArray.Length - 1 Dim arg As String = argsArray(i) ' Quotes the argument, in case it has a space in it. arg = """" & arg & """" Dim command As String = (ngenCommand & " ") + arg Dim si As New ProcessStartInfo(Path.Combine(fxPath, "ngen.exe"), command) si.WindowStyle = ProcessWindowStyle.Hidden Dim p As Process Try Context.LogMessage((">>>>" & Path.Combine(fxPath, "ngen.exe ")) + command) p = Process.Start(si) p.WaitForExit() Catch ex As Exception Throw New InstallException("Failed to ngen " & arg, ex) End Try Next End Sub
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)] private void ngenCA(System.Collections.IDictionary savedState, string ngenCommand) { String[] argsArray; if (string.Compare(ngenCommand, "install", StringComparison.OrdinalIgnoreCase) == 0) { String args = Context.Parameters["Args"]; if (String.IsNullOrEmpty(args)) { throw new InstallException("No arguments specified"); } char[] separators = { ';' }; argsArray = args.Split(separators); savedState.Add("NgenCAArgs", argsArray); //It is Ok to 'ngen uninstall' assemblies which were not installed } else { argsArray = (String[])savedState["NgenCAArgs"]; } // Gets the path to the Framework directory. string fxPath = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); for (int i = 0; i < argsArray.Length; ++i) { string arg = argsArray[i]; // Quotes the argument, in case it has a space in it. arg = "\"" + arg + "\""; string command = ngenCommand + " " + arg; ProcessStartInfo si = new ProcessStartInfo(Path.Combine(fxPath, "ngen.exe"), command); si.WindowStyle = ProcessWindowStyle.Hidden; Process p; try { Context.LogMessage(">>>>" + Path.Combine(fxPath, "ngen.exe ") + command); p = Process.Start(si); p.WaitForExit(); } catch (Exception ex) { throw new InstallException("Failed to ngen " + arg, ex); } } }
In the NGenCustomAction code file, add the following procedure to override the Install, Commit, Rollback, and Uninstall procedures of the base class.
<System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)> _ Public Overloads Overrides Sub Install(ByVal savedState As System.Collections.IDictionary) MyBase.Install(savedState) Context.LogMessage(">>>> ngenCA: install") ngenCA(savedState, "install") End Sub <System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)> _ Public Overrides Sub Commit(ByVal savedState As System.Collections.IDictionary) MyBase.Commit(savedState) Context.LogMessage(">>>> ngenCA: commit") End Sub <System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)> _ Public Overloads Overrides Sub Uninstall(ByVal savedState As System.Collections.IDictionary) MyBase.Uninstall(savedState) Context.LogMessage(">>>> ngenCA: uninstall") ngenCA(savedState, "uninstall") End Sub <System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)> _ Public Overloads Overrides Sub Rollback(ByVal savedState As System.Collections.IDictionary) MyBase.Rollback(savedState) Context.LogMessage(">>>> ngenCA: rollback") ngenCA(savedState, "uninstall") End Sub
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)] public override void Install(System.Collections.IDictionary savedState) { base.Install(savedState); Context.LogMessage(">>>> ngenCA: install"); ngenCA(savedState, "install"); } [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)] public override void Commit(IDictionary savedState) { base.Commit(savedState); Context.LogMessage(">>>> ngenCA: commit"); } [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)] public override void Uninstall(System.Collections.IDictionary savedState) { base.Uninstall(savedState); Context.LogMessage(">>>> ngenCA: uninstall"); ngenCA(savedState, "uninstall"); } [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)] public override void Rollback(System.Collections.IDictionary savedState) { base.Rollback(savedState); Context.LogMessage(">>>> ngenCA: rollback"); ngenCA(savedState, "uninstall"); }
To add a deployment project for the Browser Sample application
On the File menu, point to Add, and then click New Project.
In the Add New Project dialog box, expand Other Project Types, expand Setup and Deployment Projects, click Visual Studio Installer, and then click Setup Project.
In the Name box, type Browser Sample Installer, and then click OK.
In the File System Editor, select the Application Folder. On the Action menu, click Add.
The Add Project Output Group dialog box appears.
In the Project drop-down combo box, select BrowserSample, click Project Output, and then click OK.
In the File System Editor, select the Application Folder. On the Action menu, click Add.
The Add Project Output Group dialog box appears.
In the Project drop-down combo box, select NGenCustomAction, click Project Output, and then click OK.
To add the NGEN custom action to the Setup project
In Solution Explorer, click the Browser Sample Installer project.
On the View menu, point to Editor, and then click Custom Actions.
In the Custom Actions Editor, select the Custom Actions node.
On the Action menu, click Add Custom Action.
In the Select Item in Project dialog box, double-click the Application Folder, click Primary output from NGenCustomAction (Active), and then click OK
The NGen Custom Action is added to all four custom action nodes.
In the Install node, click Primary output from NGenCustomAction (Active).
In the Properties window, change the CustomActionData property to /Args="[TARGETDIR]BrowserSample.exe". Include the quotation marks.
Note
The [TARGETDIR] property is the location of the installed executable. The custom action uses ngen.exe to convert the installed executable to a native image.
In Solution Explorer, click the Browser Sample Installer setup project.
On the Build menu, click Build Browser Sample Installer.
To verify the native code generation
Navigate to the installation folder and find the BrowserSample.exe file. For example %PROGRAMFILES%\CompanyName\Brower Sample Installer\BrowserSample.exe.
In a Visual Studio command prompt, verify that the executable was precompiled to native code by running the following code:
ngen.exe display FullPathToExe
For example, you may run the following command:
ngen.exe display "C:\Program Files (x86)\Microsoft\Browser Sample Installer\BrowserSample.exe"
The command output appears.
Microsoft (R) CLR Native Image Generator - Version 4.0.21102.0 Copyright (c) Microsoft Corporation. All rights reserved. NGEN Roots: C:\Program Files (x86)\Microsoft\Browser Sample Installer\BrowserSample.exe NGEN Roots that depend on "c:\Program Files (x86)\Microsoft\Browser Sample Installer\BrowserSample.exe": C:\Program Files (x86)\Microsoft\Browser Sample Installer\BrowserSample.exe Native Images: BrowserSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null <debug>
Note
If ngen.exe does not display a native image, you can find the ngen logs in one of the following directories:
%SystemRoot%\Microsoft.NET\Framework\v<CLR version> %SystemRoot%\Microsoft.NET\Framework64\v<CLR version>
The ngen.log file is the most recent troubleshooting log.
See Also
Reference
Ngen.exe (Native Image Generator)