Logo
HOWTO: Create a setup for an add-in for the VBA editor of Microsoft Office for the current user (not requiring admin rights) using Inno Setup.

Author: Carlos J. Quintero (Microsoft MVP) Applies to: Microsoft Office 64-bit
Date: September 2012   Microsoft Office 32-bit
       
Introduction

This article explains how to create a setup for an add-in for the VBA editor of Microsoft Office (32-bit or 64-bit) for the current user (not requiring administrator privileges) 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 setup sample assumes that you have generated the following input files:

To install the add-in for the current user (not for all users), not requiring admin rights, the setup does the following:

The setup runs as a 32-bit application on Windows 32-bit, and as a 64-bit application on Windows 64-bit, so it can register the add-in dll as 64-bit COM component on Windows 64-bit, which is required for Microsoft Office 2010 64-bit (it doesn't support 32-bit COM add-ins).

The setup doesn't include the installation of the .NET Framework 2.0. If detects if it is installed, and if not it opens a web page where the user can download it.

You will need to adjust the value of constants used at the start of the script (customization section).

Language: Text   Copy Code Copy Code (IE only)
[Setup]

; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
; BEGIN CUSTOMIZATION SECTION: Customize these constants
; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

; Ensure that you use YOUR OWN APP_ID; DO NOT REUSE THIS ONE
#define APP_ID "{{4EC56406-AD74-492e-8BB4-56E854BE1A1E}"

#define APP_NAME "My VBA Add-in 1.0"
#define DEST_SUB_DIR "MyVBAAddin"
#define CONNECT_CLASS_FULL_NAME "MyVBAAddin.Connect"
#define COMPANY_NAME "My Company"
#define RUNTIME_VERSION "v2.0.50727"
#define COPYRIGHT_YEAR "2012"

#define INTEROP_OFFICE_FILE_NAME "MyCompany.Interop.Office12.dll"
#define INTEROP_STDOLE_FILE_NAME "MyCompany.Interop.Stdole.dll"
#define INTEROP_EXTENSIBILITY_FILE_NAME "MyCompany.Interop.Extensibility.dll"
#define INTEROP_VBA_EXTENSIBILITY_FILE_NAME "MyCompany.Interop.VBAExtensibility.dll"
#define DLL_FILE_NAME "MyVBAAddin.dll"
#define OUTPUT_FOLDER_NAME ".\Setup"
#define SETUP_FILE_NAME "MyVBAAddinSetup"
#define VERSION "1.0.0.00"
#define CONNECT_PROGID "MyVBAAddin.Connect"
#define CONNECT_CLSID "{2CA1C920-2D5E-3E17-86D7-24DEB9303A3E}"
#define ASSEMBLY_FULL_NAME "MyVBAAddin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=869cad219d7a35e2"

; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
; END CUSTOMIZATION SECTION
; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

ArchitecturesAllowed=x86 x64

; The setup must run in 64-bit mode on x64 systems to allow the installation of the VBA add-in for Office 2010 64-bit
ArchitecturesInstallIn64BitMode=x64 

AppID={#APP_ID}
VersionInfoVersion={#VERSION}
OutputBaseFilename={#SETUP_FILE_NAME}
OutputDir={#OUTPUT_FOLDER_NAME}
PrivilegesRequired=lowest
MinVersion=0,5.0.2195
AppName={#APP_NAME}
AppVerName={#APP_NAME}
DefaultGroupName={#APP_NAME}
AppPublisher={#COMPANY_NAME}
DefaultDirName={localappdata}\{#COMPANY_NAME}\{#DEST_SUB_DIR}
Compression=lzma/Max
SolidCompression=true
DisableReadyPage=true
ShowLanguageDialog=no
UninstallLogMode=append
DisableProgramGroupPage=true
VersionInfoCompany={#COMPANY_NAME}
AppCopyright=Copyright © {#COPYRIGHT_YEAR} {#COMPANY_NAME}
AlwaysUsePersonalGroup=true
InternalCompressLevel=Ultra
AllowNoIcons=true
DisableDirPage=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: bin\release\{#INTEROP_OFFICE_FILE_NAME};            DestDir: {app}; Flags: ignoreversion;
Source: bin\release\{#INTEROP_STDOLE_FILE_NAME};            DestDir: {app}; Flags: ignoreversion;
Source: bin\release\{#INTEROP_VBA_EXTENSIBILITY_FILE_NAME}; DestDir: {app}; Flags: ignoreversion;
Source: bin\release\{#INTEROP_EXTENSIBILITY_FILE_NAME};     DestDir: {app}; Flags: ignoreversion;
Source: bin\release\{#DLL_FILE_NAME};                       DestDir: {app}; Flags: ignoreversion; AfterInstall: RegisterAddin()

[UninstallDelete]
Name: {app}; Type: filesandordirs

[CustomMessages]
English.NETFramework20NotInstalled=Microsoft .NET Framework 2.0 installation was not detected. 
Spanish.NETFramework20NotInstalled=No se encontró la instalación de Microsoft .NET Framework 2.0. 

[Run]

[UninstallRun]

[Code]
var
   m_IDEsPage: TWizardPage;
   m_IDEsCheckListBox: TNewCheckListBox;

function IsVBA64Installed(): Boolean;
var
   sBitness: String;
begin

   Result := False
   
   if IsWin64() then
      begin
         if RegQueryStringValue(HKLM, 'Software\Microsoft\Office\14.0\Outlook', 'Bitness', sBitness) then
            begin
               if sBitness = 'x64' then
                  begin
                     Result := True
                  end;
            end;
      end;
end;

function IsVBA32Installed(): Boolean;
begin

   if RegKeyExists(HKLM32, 'SOFTWARE\Microsoft\Office\10.0') then
      Result := True
   else if RegKeyExists(HKLM32, 'SOFTWARE\Microsoft\Office\11.0') then
      Result := True
   else if RegKeyExists(HKLM32, 'SOFTWARE\Microsoft\Office\12.0') then
      Result := True
   else if RegKeyExists(HKLM32, 'SOFTWARE\Microsoft\Office\14.0') then
      Result := not IsVBA64Installed()
   else
      Result := False

end;

function IsVBA32Selected(): Boolean;
begin
   Result := m_IDEsCheckListBox.Checked[0]
end;

function IsVBA64Selected(): Boolean;
begin
   Result := m_IDEsCheckListBox.Checked[1]
end;

procedure IDEsCheckListBoxOnClickCheck(Sender: TObject);
var
   index: Integer;
begin

   WizardForm.NextButton.Enabled := False;

   for index := 0 to m_IDEsCheckListBox.Items.Count - 1 do
      begin
         if m_IDEsCheckListBox.Checked[index] then
            WizardForm.NextButton.Enabled := True;
      end; 
end;

//***************************************************************************************************
// InnoSetup event function                                                                        
//***************************************************************************************************
function InitializeSetup(): Boolean;
var
   iErrorCode: Integer;
begin
   // Detect if Microsoft .NET Framework 2.0 is installed
   if Not RegKeyExists(HKLM, 'SOFTWARE\Microsoft\.NETFramework\v2.0.50727') then
      begin
         MsgBox(ExpandConstant('{cm:NETFramework20NotInstalled}'), mbCriticalError, mb_Ok);
         ShellExec('open', 'http://msdn.microsoft.com/en-us/netframework/aa731542', '', '', SW_SHOW, ewNoWait, iErrorCode) 
         Result := False;
      end
   else
      begin
         Result := True;
      end
end;

procedure CreateComponentsPage();
var
   IDEsLabel: TLabel;
   bVBA32Enabled: Boolean;
   bVBA64Enabled: Boolean;
   bVBA32Checked: Boolean;
   bVBA64Checked: Boolean;
begin

   m_IDEsPage := CreateCustomPage(wpSelectComponents, SetupMessage(msgWizardSelectComponents), SetupMessage(msgSelectComponentsDesc));
   
   IDEsLabel := TLabel.Create(m_IDEsPage);
   IDEsLabel.Caption := SetupMessage(msgSelectComponentsLabel2);
   IDEsLabel.Width := m_IDEsPage.SurfaceWidth;
   IDEsLabel.Height := ScaleY(40);
   IDEsLabel.AutoSize := False;
   IDEsLabel.WordWrap := True;
   IDEsLabel.Parent := m_IDEsPage.Surface;

   m_IDEsCheckListBox := TNewCheckListBox.Create(m_IDEsPage);
   m_IDEsCheckListBox.Top := IDEsLabel.Top + IDEsLabel.Height + ScaleY(8);
   m_IDEsCheckListBox.Width := m_IDEsPage.SurfaceWidth;
   m_IDEsCheckListBox.Height := ScaleX(100);
   m_IDEsCheckListBox.Flat := True;
   m_IDEsCheckListBox.Parent := m_IDEsPage.Surface;
   m_IDEsCheckListBox.OnClickCheck := @IDEsCheckListBoxOnClickCheck;

   bVBA32Enabled := IsVBA32Installed();
   bVBA64Enabled := IsVBA64Installed();

   bVBA32Checked := bVBA32Enabled;
   bVBA64Checked := bVBA64Enabled;
  
   m_IDEsCheckListBox.AddCheckBox('My VBA Add-in - VBA Editor 32-bit (Office 2003/2007/2010 32-bit)', '', 0, 
     bVBA32Checked, bVBA32Enabled, False, True, nil)
   m_IDEsCheckListBox.AddCheckBox('My VBA Add-in - VBA Editor 64-bit (Office 2010 64-bit)', '',           0, 
      bVBA64Checked, bVBA64Enabled, False, True, nil)

end;

//***************************************************************************************************
// InnoSetup event function                                                                        
//***************************************************************************************************
procedure InitializeWizard();
begin

   CreateComponentsPage();

end;

procedure RegisterCOMClass(const iRootKey: Integer; const sProgID: String; const sCLSID: String; const sClass: String; 
  const sFileName: String; const sAssemblyFullName: String);
var
   sCodeBase: String;
   sFileFullName: String;
begin

   sFileFullName := ExpandConstant('{app}') + '\' + sFileName;
   StringChangeEx(sFileFullName, '\', '/', True);
   sCodeBase := 'file:///' + sFileFullName;

   RegWriteStringValue(iRootKey, 'Software\Classes\' + sProgID, '', sProgID);
   RegWriteStringValue(iRootKey, 'Software\Classes\' + sProgID + '\CLSID', '', sCLSID);

   RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID, '', sClass);

   (* Do not add this implemented category that identifies .NET components:

      RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + '\Implemented Categories', '', '');
      RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + 
        '\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}', '', '');
   
      because it is not absolutely needed and creating it could cause the following error:

      FIX: "Access to the Registry Key Denied" Error Message When You Register .NET Assembly for COM Interop
      http://support.microsoft.com/kb/327507
   *)

   RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + '\InprocServer32', '', 'mscoree.dll');
   RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + '\InprocServer32', 'Assembly', sAssemblyFullName);
   RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + '\InprocServer32', 'Class', sClass);
   RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + '\InprocServer32', 'CodeBase', sCodeBase);
   RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + '\InprocServer32', 'RuntimeVersion', '{#RUNTIME_VERSION}');
   RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + '\InprocServer32', 'ThreadingModel', 'Both');

   RegWriteStringValue(iRootKey, 'Software\Classes\CLSID\' + sCLSID + '\ProgId', '', sProgID);

end;

procedure UnregisterCOMClass(const iRootKey: Integer; const sProgID: String; const sCLSID: String);
begin

   if RegKeyExists(iRootKey, 'Software\Classes\' + sProgID) then
      begin
         RegDeleteKeyIncludingSubkeys(iRootKey, 'Software\Classes\' + sProgID);
      end;

   if RegKeyExists(iRootKey, 'Software\Classes\CLSID\' + sCLSID) then   
      begin
         RegDeleteKeyIncludingSubkeys(iRootKey, 'Software\Classes\CLSID\' + sCLSID);
      end;

end;

procedure RegisterAddinForIDE(const iRootKey: Integer; const sAddinSubKey: String; const sProgIDConnect: String);
begin

   RegWriteStringValue(iRootKey, sAddinSubKey + '\' + sProgIDConnect, 'FriendlyName', '{#APP_NAME}');
   RegWriteStringValue(iRootKey, sAddinSubKey + '\' + sProgIDConnect, 'Description' , '{#APP_NAME}');
   RegWriteDWordValue (iRootKey, sAddinSubKey + '\' + sProgIDConnect, 'LoadBehavior', 3);

end;

procedure UnregisterAddinForIDE(const iRootKey: Integer; const sAddinSubKey: String; const sProgIDConnect: String);
begin

   if RegKeyExists(iRootKey, sAddinSubKey + '\' + sProgIDConnect) then
      begin
         RegDeleteKeyIncludingSubkeys(iRootKey, sAddinSubKey + '\' + sProgIDConnect);
      end;

end;

procedure RegisterAddinForCOM(const iRootKey: Integer; const sProgIDConnect: String; const sCLSIDConnect: String; 
  const sConnectClassFullName: String; const sFileName: String; const sAssemblyFullName: String);
begin

   RegisterCOMClass(iRootKey, sProgIDConnect, sCLSIDConnect, sConnectClassFullName, sFileName, sAssemblyFullName);

   // Here you would register toolwindow usercontrols if required
     
end;

procedure UnregisterAddinForCOM(const iRootKey: Integer; const sProgIDConnect: String; const sCLSIDConnect: String);
begin

   UnregisterCOMClass(iRootKey, sProgIDConnect, sCLSIDConnect);

   // Here you would unregister toolwindow usercontrols if required

end;

procedure RegisterAddin();
begin
   
   if IsVBA32Selected() then
      begin
         RegisterAddinForCOM(HKCU32, '{#CONNECT_PROGID}', '{#CONNECT_CLSID}', '{#CONNECT_CLASS_FULL_NAME}', 
           '{#DLL_FILE_NAME}', '{#ASSEMBLY_FULL_NAME}');
         RegisterAddinForIDE(HKCU32, 'Software\Microsoft\VBA\VBE\6.0\Addins', '{#CONNECT_PROGID}');
      end;

   if IsVBA64Selected() then
      begin
         RegisterAddinForCOM(HKCU64, '{#CONNECT_PROGID}', '{#CONNECT_CLSID}', '{#CONNECT_CLASS_FULL_NAME}', 
            '{#DLL_FILE_NAME}', '{#ASSEMBLY_FULL_NAME}');
         RegisterAddinForIDE(HKCU64, 'Software\Microsoft\VBA\VBE\6.0\Addins64', '{#CONNECT_PROGID}');
      end;

end;

procedure UnregisterAddin();
begin

   UnregisterAddinForIDE(HKCU32, 'Software\Microsoft\VBA\VBE\6.0\Addins', '{#CONNECT_PROGID}');
   UnregisterAddinForCOM(HKCU32, '{#CONNECT_PROGID}', '{#CONNECT_CLSID}');

   if IsWin64() then
      begin
         UnregisterAddinForIDE(HKCU64, 'Software\Microsoft\VBA\VBE\6.0\Addins64', '{#CONNECT_PROGID}');
         UnregisterAddinForCOM(HKCU64, '{#CONNECT_PROGID}', '{#CONNECT_CLSID}');
      end
end;

//***************************************************************************************************
// InnoSetup event function                                                                        
//***************************************************************************************************
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin

   if CurUninstallStep = usUninstall then
      begin
         UnregisterAddin()
      end;

end;

Related articles

Top