|
|
| Author: |
Carlos J. Quintero (Microsoft MVP) |
Applies to: |
Microsoft Visual Studio 2005 |
| Date: |
April 2008 |
|
Microsoft Visual Studio 2008 |
| |
|
|
|
Introduction
This article explains how to create a setup for a Visual Studio add-in using
Inno Setup, a popular free
installer for Windows. Inno Setup is script-based (not MSI-based) but there is a companion ISTool
script editor that allows you to create the script for its different sections
in a friendly way setting properties in dialog windows. There is also an Inno Setup
preprocessor that can be used to create compilation constants, etc. Inno Setup
and the companion Inno Setup Quick Start Pack with the ISTool and preprocessor
tools can be dowloaded here.
This article assumes that you are somewhat familiar with Inno Setup.
More Information
The sample provided assumes an add-in and setup with the following conditions:
- It creates one or more commands and one or more buttons using temporary user
interface (see the article HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in),
not permanent user interface (specifically, it does not use permanent
commandbars). The sample add-in provided below creates a command and a button
under the Tools menu.
- It uses XML registration through an .AddIn file that will be placed in the
folder C:\Documents and Settings\All Users\Application
Data\Microsoft\VisualStudio\8.0\Addins for Visual Studio 2005 and C:\Documents
and Settings\All Users\Application Data\Microsoft\VisualStudio\9.0\Addins for
Visual Studio 2008 (see the article INFO: Default
.AddIn file locations for Visual Studio add-ins).
- The add-in targets Visual Studio 2005 and Visual Studio 2008 with different
DLLs, although you can change the script to use a single one since both IDEs use
the .NET Framework 2.0 and the automation model is the same if you don't use the
EnvDTE90.dll assembly of Visual Studio 2008.
- The setup doesn't install a satellite DLL (the sample add-in doesn't use it),
but you can add it easily.
- The setup will prompt which Visual Studio versions (2005, 2008) of the add-in
will be installed through a list with checkboxes.
- The add-in is installed for all users without prompting, so it requires admin
priviledges.
- A custom action will be used on uninstalling to remove the commands of the
add-in, using the
devenv.exe /ResetAddin command of Visual Studio 2005/2008 (see the article
HOWTO: Removing commands and UI elements during
Visual Studio .NET add-in uninstallation).
- The add-in uses a help file (.chm) and a license agreement file (.rtf),
although you can change the script file to remove them.
- The setup will create in the Start, Programs section links to the help file, to
a web site and to the unsinstaller.
- The setup is multilanguage (the sample provided uses English and Spanish,
although you can add/remove languages easily). The setup uses the language of
the Regional Settings without prompting for selection (you can change it).
- The setup checks that Visual Studio is installed (Visual Studio Express
editions don't qualify since they don't support add-ins).
- The setup checks that Visual Studio is not running before installing or
uninstalling.
The code of the add-in is the following:
Imports System
Imports Microsoft.VisualStudio.CommandBars
Imports Extensibility
Imports EnvDTE
Imports System.Windows.Forms
Public Class Connect
Implements Extensibility.IDTExtensibility2, IDTCommandTarget
Private Const m_COMMAND_NAME As String = "MyCommand"
Private m_objDTE As EnvDTE.DTE
Private m_objAddIn As EnvDTE.AddIn
Private m_objCommandBarButton As CommandBarButton
Public Sub OnConnection(ByVal application As Object, ByVal connectMode As Extensibility.ext_ConnectMode, _
ByVal addInInst As Object, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnConnection
m_objDTE = CType(application, EnvDTE.DTE)
m_objAddIn = CType(addInInst, EnvDTE.AddIn)
Select Case connectMode
Case ext_ConnectMode.ext_cm_AfterStartup
OnStartupComplete(Nothing)
Case ext_ConnectMode.ext_cm_Startup
' OnStartupComplete will be called
End Select
End Sub
Public Sub OnStartupComplete(ByRef custom As System.Array) _
Implements Extensibility.IDTExtensibility2.OnStartupComplete
Dim objCommand As EnvDTE.Command = Nothing
Dim colCommandBars As CommandBars
Dim objCommandBar As CommandBar
Try
objCommand = m_objDTE.Commands.Item(m_objAddIn.ProgID & "." & m_COMMAND_NAME)
Catch
End Try
Try
If objCommand Is Nothing Then
objCommand = m_objDTE.Commands.AddNamedCommand(m_objAddIn, m_COMMAND_NAME, "My Command", _
"My Command", True, 59)
End If
colCommandBars = CType(m_objDTE.CommandBars, CommandBars)
objCommandBar = CType(colCommandBars.Item("Tools"), CommandBar)
m_objCommandBarButton = CType(objCommand.AddControl(objCommandBar), CommandBarButton)
Catch objException As Exception
MessageBox.Show(objException.ToString)
End Try
End Sub
Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, ByRef custom As System.Array) _
Implements Extensibility.IDTExtensibility2.OnDisconnection
Try
If Not (m_objCommandBarButton Is Nothing) Then
m_objCommandBarButton.Delete()
End If
Catch objException As Exception
MessageBox.Show(objException.ToString)
End Try
End Sub
Public Sub OnBeginShutdown(ByRef custom As System.Array) _
Implements Extensibility.IDTExtensibility2.OnBeginShutdown
End Sub
Public Sub OnAddInsUpdate(ByRef custom As System.Array) _
Implements Extensibility.IDTExtensibility2.OnAddInsUpdate
End Sub
Public Sub QueryStatus(ByVal CmdName As String, ByVal NeededText As EnvDTE.vsCommandStatusTextWanted, _
ByRef StatusOption As EnvDTE.vsCommandStatus, ByRef CommandText As Object) _
Implements EnvDTE.IDTCommandTarget.QueryStatus
Select Case CmdName
Case m_objAddIn.ProgID & "." & m_COMMAND_NAME
StatusOption = vsCommandStatus.vsCommandStatusSupported Or vsCommandStatus.vsCommandStatusEnabled
Case Else
StatusOption = vsCommandStatus.vsCommandStatusUnsupported
End Select
End Sub
Public Sub Exec(ByVal CmdName As String, ByVal ExecuteOption As EnvDTE.vsCommandExecOption, _
ByRef VariantIn As Object, ByRef VariantOut As Object, ByRef handled As Boolean) _
Implements EnvDTE.IDTCommandTarget.Exec
Select Case CmdName
Case m_objAddIn.ProgID & "." & m_COMMAND_NAME
MessageBox.Show("My command executed.")
End Select
End Sub
End Class
The Inno Setup script is the below. It assumes that all the source files (.dll, .chm, .rtf, .ico) and the own script (.iss) are in the same folder, and the output (.exe) will be created in a "Setup" subfolder.
; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
; To add a localization:
; 1) Add the language to the [Languages] section
; 2) Add localizations to the [Messages] section
; 3) Add localizations to the [CustomMessages] section
; 4) Add License Agreement localization
; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
[Setup]
; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
; BEGIN CUSTOMIZATION SECTION: Customize these constants
; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
; Ensure that you use YOUR OWN APP_ID; DO NOT REUSE THIS ONE
#define APP_ID "{{58534807-17E7-4254-8E2A-AAA932A00320}"
#define APP_NAME "My Add-In"
#define APP_VERSION "1.0.0.0"
#define DEFAULT_GROUP_NAME "{cm:MyAddInDefaultGroupName}"
#define HELP_SHORTCUT "{cm:MyAddInHelpFile}"
#define DEST_SUB_DIR "MyAddIn"
#define HELP_FILE_NAME "MyAddIn.chm"
#define LICENSE_AGREEMENT_FILE_NAME "LicenseAgreement.rtf"
#define DLL_FILE_NAME_VS2005 "MyAddInVS2005.dll"
#define DLL_FILE_NAME_VS2008 "MyAddInVS2008.dll"
#define ADDIN_XML_FILE_NAME "MyAddIn.AddIn"
#define MY_COMPANY_NAME "My Company"MY_COMPANY_WEB_SITE "www.mycompany.com"
#define OUTPUT_FOLDER_NAME ".\Setup"
#define OUTPUT_BASE_FILE_NAME "MyAddInSetup"
#define SETUP_ICON_FILE_NAME = "MyAddIn.ico"
#define COPYRIGHT_YEAR = "2008"
; Ensure that these values are used for the Connect class
#define CONNECT_CLASS_FULL_NAME_VS_2005 = "MyAddinVS2005.Connect"
#define CONNECT_CLASS_FULL_NAME_VS_2008 = "MyAddinVS2008.Connect"
; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
; END CUSTOMIZATION SECTION
; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
OutputDir={#OUTPUT_FOLDER_NAME}
OutputBaseFilename={#OUTPUT_BASE_FILE_NAME}
AppID={#APP_ID}
VersionInfoVersion={#APP_VERSION}
PrivilegesRequired=admin
MinVersion=0,5.0.2195
AppName={#APP_NAME}
AppVerName={#APP_NAME}
DefaultGroupName={#DEFAULT_GROUP_NAME}
AppPublisher={#MY_COMPANY_NAME}
AppPublisherURL={#MY_COMPANY_WEB_SITE}
AppSupportURL={#MY_COMPANY_WEB_SITE}
AppUpdatesURL={#MY_COMPANY_WEB_SITE}
DefaultDirName={pf}\{#DEST_SUB_DIR}
LicenseFile={#LICENSE_AGREEMENT_FILE_NAME}
Compression=lzma
SolidCompression=true
DisableReadyPage=true
ShowLanguageDialog=no
UninstallLogMode=append
SetupIconFile={#SETUP_ICON_FILE_NAME}
DisableProgramGroupPage=false
VersionInfoCompany={#MY_COMPANY_NAME}
AppCopyright=Copyright © {#COPYRIGHT_YEAR} {#MY_COMPANY_NAME}
AlwaysUsePersonalGroup=false
InternalCompressLevel=ultra
AllowNoIcons=true
LanguageDetectionMethod=locale
[Languages]
; USE ENGLISH AS THE FIRST LANGUAGE!!!
Name: English; MessagesFile: compiler:Default.isl
Name: Spanish; MessagesFile: compiler:Languages\Spanish.isl
[Types]
Name: Custom; Description: Custom; Flags: iscustom
[Files]
Source: {#DLL_FILE_NAME_VS2005}; DestDir: {app}; Flags: ignoreversion; AfterInstall: CreateAddInXMLFileVS2005(); Check: IsMyAddInVS2005Selected()
Source: {#Source: {#DLL_FILE_NAME_VS2008}; DestDir: {app}; Flags: ignoreversion; AfterInstall: CreateAddInXMLFileVS2008(); Check: IsMyAddInVS2008Selected()
Source: {#HELP_FILE_NAME}; DestDir: {app}; Flags: ignoreversion
[Icons]roup}\{cm:MyAddInWebSite}; Filename: http://www.mycompany.com
Name: {group}\{cm:UninstallProgram,{#APP_NAME}}; Filename: {uninstallexe}
Name: {group}\{#HELP_SHORTCUT}; Filename: {app}\{#HELP_FILE_NAME}; IconFilename: {app}\{#HELP_FILE_NAME}
[Messages]
English.FinishedLabel=Setup has finished installing [name] on your computer.
Spanish.FinishedLabel=La instalación de [name] en su ordenador ha finalizado.
English.UninstallStatusLabel=The uninstallation can take up to a minute while the commands of the add-in are removed from the Visual Studio and Macros IDEs.
Spanish.UninstallStatusLabel=La desinstalación puede tardar hasta un minuto mientras los comandos del complemento se eliminan de los IDEs de Visual Studio y Macros.
[CustomMessages]
English.MyAddInWebSite={{#APP_NAME} Web Site
Spanish.MyAddInWebSite=Sitio web de {#APP_NAME}
English.VSNeedsToBeClosed=Close Visual Studio before continuing.
Spanish.VSNeedsToBeClosed=Cierre Visual Studio antes de continuar.
English.RegisteringAddIn=Registering the add-in...
Spanish.RegisteringAddIn=Registrando el complemento...
English.MyAddInDefaultGroupName={#APP_NAME} for Visual Studio
Spanish.MyAddInDefaultGroupName={#APP_NAME} para Visual Studio
English.MyAddInHelpFile={#APP_NAME} Help
Spanish.MyAddInHelpFile=Ayuda de {#APP_NAME} (en inglés)
English.VSRequired=Visual Studio Standard Edition or higher is required to install this product (Express editions of Visual Studio don´t support add-ins).
Spanish.VSRequired=Se requiere Visual Studio Standard Edition o superior para instalar este producto (las ediciones Express de Visual Studio no soportan complementos).
[Code]
var
IDEsPage: TWizardPage;
IDEsCheckListBox: TNewCheckListBox;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function GetXMLAddInFullFolderName(Param: String): String;
var
sCommonAppDataFolder: String;
sResult: String;
begin
sCommonAppDataFolder := ExpandConstant('{commonappdata}');
(* Param is the version such as '8.0' or '9.0' *)
sResult := sCommonAppDataFolder + '\Microsoft\VisualStudio\' + Param + '\Addins\'
Result := sResult;
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
procedure CreateAddInXMLFile(sVersion: String; sConnectClassFullName: String; sDLLFileName: String);
var
sLines: TArrayOfString;
sXMLAddInFullFileName: String;
sAddInDLLFullFileName: String;
sInstallationFolder: String;
sFolder: String;
begin
(* Compose the full name of the add-in DLL *)
sInstallationFolder := ExpandConstant('{app}');
sAddInDLLFullFileName := sInstallationFolder + '\' + sDLLFileName;
(* Get the folder where to put the .AddIn XML registration file *)
sFolder := GetXMLAddInFullFolderName(sVersion);
(* Ensure that the folder is created *)
CreateDir(sFolder);
(* Compose the full name of the .AddIn XML registration file *)
sXMLAddInFullFileName := sFolder + '{#ADDIN_XML_FILE_NAME}';
(* Create the .AddIn XML registration file *)
SetArrayLength(sLines,16);
sLines[0] := '<?xml version="1.0" encoding="windows-1252" standalone="no"?>';
sLines[1] := '<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">';
sLines[2] := ' <HostApplication>';
sLines[3] := ' <Name>Microsoft Visual Studio</Name>';
sLines[4] := ' <Version>' + sVersion + '</Version>';
sLines[5] := ' </HostApplication>';
sLines[6] := ' <Addin>';
sLines[7] := ' <FriendlyName>{#APP_NAME}</FriendlyName>';
sLines[8] := ' <Description>{#APP_NAME}</Description>';
sLines[9] := ' <Assembly>' + sAddInDLLFullFileName + '</Assembly>';
sLines[10] := ' <FullClassName>' + sConnectClassFullName + '</FullClassName>';
sLines[11] := ' <LoadBehavior>3</LoadBehavior>';
sLines[12] := ' <CommandPreload>0</CommandPreload>';
sLines[13] := ' <CommandLineSafe>0</CommandLineSafe>';
sLines[14] := ' </Addin>';
sLines[15] := '</Extensibility>';
SaveStringsToFile(sXMLAddInFullFileName, sLines, False);
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
procedure CreateAddInXMLFileVS2005();
begin
CreateAddInXMLFile('8.0','{#CONNECT_CLASS_FULL_NAME_VS_2005}', '{#DLL_FILE_NAME_VS2005}')
end ;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
procedure CreateAddInXMLFileVS2008();
begin
CreateAddInXMLFile('9.0','{#CONNECT_CLASS_FULL_NAME_VS_2008}', '{#DLL_FILE_NAME_VS2008}')
end ;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
procedure ShowCloseVisualStudioMessage();
var
sMsg: String;
begin
sMsg := CustomMessage('VSNeedsToBeClosed');
MsgBox(sMsg, mbCriticalError, mb_Ok)
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
procedure ShowVisualStudioRequiredMessage();
var
sMsg: String;
begin
sMsg := CustomMessage('VSRequired');
MsgBox(sMsg, mbCriticalError, mb_Ok)
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function IsVSInstalledByProgID(ProgID: String):Boolean;
begin
if RegKeyExists(HKCR, ProgID) then
Result := True
else
Result := False
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function IsVSRunningByProgID(ProgID: String):Boolean;
var
IDE: Variant;
begin
try
IDE := GetActiveOleObject(ProgID);
except
end;
if VarIsEmpty(IDE) then
Result := False
else
Result := True
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function IsVS2005Installed():Boolean;
begin
if IsVSInstalledByProgID('VisualStudio.DTE.8.0') then
Result := True
else
Result := False
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function IsVS2008Installed():Boolean;
begin
if IsVSInstalledByProgID('VisualStudio.DTE.9.0') then
Result := True
else
Result := False
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function IsVSInstalled():Boolean;
begin
if IsVS2005Installed() then
Result := True
else if IsVS2008Installed() then
Result := True
else
begin
Result := False
ShowVisualStudioRequiredMessage();
end
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function IsSomeVSRunning():Boolean;
begin
if IsVSRunningByProgID('VisualStudio.DTE.8.0') then
Result := True
else if IsVSRunningByProgID('VisualStudio.DTE.9.0') then
Result := True
else
Result := False
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
procedure UnregisterAddInIfInstalled(sVersion: String; sConnectClassFullName: String);
var
sXMLAddInFullFileName: String;
sFolder: String;
sIDEFullFileName: String;
iResultCode: Integer;
begin
(* Compose the full name of the .AddIn XML registration file *)
sFolder := GetXMLAddInFullFolderName(sVersion);
sXMLAddInFullFileName := sFolder + '{#ADDIN_XML_FILE_NAME}';
if FileExists(sXMLAddInFullFileName) then
begin
(* Delete the .AddIn XML registration file to unregister the add-in *)
DeleteFile(sXMLAddInFullFileName);
(* Compose the full name of the Visual Studio IDE *)
sIDEFullFileName := ExpandConstant('{reg:HKLM\SOFTWARE\Microsoft\VisualStudio\' + sVersion + ',InstallDir}') + 'devenv.exe'
(* Remove the commands of the IDE *)
Exec(sIDEFullFileName, '/ResetAddin ' + sConnectClassFullName + ' /command File.Exit', '', SW_HIDE, ewWaitUntilTerminated, iResultCode);
end
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function IsMyAddInVS2005Selected():Boolean;
begin
Result := IDEsCheckListBox.Checked[0]
end;
(***************************************************************************************************)
(* Auxiliar function *)
(***************************************************************************************************)
function IsMyAddInVS2008Selected():Boolean;
begin
Result := IDEsCheckListBox.Checked[1]
end;
(***************************************************************************************************)
(* InnoSetup event function *)
(***************************************************************************************************)
function InitializeSetup(): Boolean;
begin
if not IsVSInstalled() then
begin
Result := False;
end
else
begin
if IsSomeVSRunning() then
begin
ShowCloseVisualStudioMessage()
Result := False;
end
else
Result := True;
end
end;
(***************************************************************************************************)
(* InnoSetup event function *)
(***************************************************************************************************)
procedure InitializeWizard();
var
IDEsLabel: TLabel;
bIsVS2005Installed: Boolean;
bIsVS2008Installed: Boolean;
begin
IDEsPage := CreateCustomPage(wpSelectComponents, SetupMessage(msgWizardSelectComponents), SetupMessage(msgSelectComponentsDesc));
IDEsLabel := TLabel.Create(IDEsPage);
IDEsLabel.Caption := SetupMessage(msgSelectComponentsLabel2);
IDEsLabel.Width := IDEsPage.SurfaceWidth;
IDEsLabel.Height := ScaleY(40);
IDEsLabel.AutoSize := False;
IDEsLabel.WordWrap := True;
IDEsLabel.Parent := IDEsPage.Surface;
IDEsCheckListBox := TNewCheckListBox.Create(IDEsPage);
IDEsCheckListBox.Top := IDEsLabel.Top + IDEsLabel.Height + ScaleY(8);
IDEsCheckListBox.Width := IDEsPage.SurfaceWidth;
IDEsCheckListBox.Height := ScaleX(100);
IDEsCheckListBox.Flat := True;
IDEsCheckListBox.Parent := IDEsPage.Surface;
bIsVS2005Installed := IsVS2005Installed();
bIsVS2008Installed := IsVS2008Installed();
IDEsCheckListBox.AddCheckBox('{#APP_NAME} - Visual Studio 2005', '', 0, bIsVS2005Installed, bIsVS2005Installed, False, True, nil)
IDEsCheckListBox.AddCheckBox('{#APP_NAME} - Visual Studio 2008', '', 0, bIsVS2008Installed, bIsVS2008Installed, False, True, nil)
end;
(***************************************************************************************************)
(* InnoSetup event function *)
(***************************************************************************************************)
function InitializeUninstall(): Boolean;
begin
if IsSomeVSRunning() then
begin
ShowCloseVisualStudioMessage()
Result := False;
end
else
begin
Result := True;
end
end;
(***************************************************************************************************)
(* InnoSetup event function *)
(***************************************************************************************************)
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then
begin
UnregisterAddInIfInstalled('8.0', '{#CONNECT_CLASS_FULL_NAME_VS_2005}' );
UnregisterAddInIfInstalled('9.0', '{#CONNECT_CLASS_FULL_NAME_VS_2008}' );
end;
end;
Go back to the 'Resources for Visual Studio .NET extensibility' section for more articles like this
You can code, design, locate code and document your apps much faster using VB.NET, C#, C++ or Visual J#: 
|