In un mio recente progetto, ho avuto la necessità di creare ,per Internet Explorer, un Browser Helper Object (un plugin che sfrutta le stesse interfacce e tecnologie delle Toolbar) che eseguisse delle operazioni custom su un particolare sito in ambiente Intranet.
La realizzazione del BHO, è relativamente semplice usando le interfacce IObjectWithSite e le direttive di interoperabilità del codice.
Per il setup invece è tutto particolarmente difficile. Le interfacce del BHO vanno registrate nel registry di Windows e rese accessibili al sistema di plugin di IE.
Per far questo ho usato un software free, INNO SETUP, che permette di generare degli installer a partire da uno script.
Inno Setup
Lo script, permette di definire il comportamento del setup, le informazioni da visualizzare nelle maschere, e tutte le operazioni da effettuare sul registry di Windows, compresa la problematica di differenze di posizionamento e nome di alcune voci tra i sistemi a 32 e a 64 bit. Il Bho di cui realizzo il setup è compilato in maniera da funzionare compatibilmente con tutti i sistemi e quindi richiede un setup compatibile sia con i sistemi a 32 che a 64 bit.
Lo script inizia con una serie di definizioni di costanti usate poi nel resto delle direttive, tra cui il nome della dll, il nome del namespace utilizzato, e il nome della classe interna che viene esposta al browser, contenente i metodi pubblici da utilizzare.
#define MyAppDescription "My Helper Objects"
#define MyAppName "MyBHO"
#define MyAppVersion "1.0.0.0"
#define MyAppPublisher "Metalide"
#define MyAppURL "https://www.metalide.com"
#define MyAppDllName "MyBHO.dll"
#define MyAppId "{A80C384E-4178-4AD0-85BC-3196241437C5}"
#define MyAppNamespace "MyBHO.MyHelperObject"
#define MyAppClass "MyBHOExtension"
Subito dopo si crea una sezione [Setup] per associare le costanti ai parametri dell’installer e altri attributi:
[Setup]
AppId={#MyAppName}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\MyTools\MyHelperObjects
DefaultGroupName=My_IETOOLS
DisableProgramGroupPage=yes
DisableDirPage=yes
CreateAppDir=yes
OutputBaseFilename=My_IETOOLSBHOSetup
Compression=lzma
SolidCompression=yes
Viene poi impostata la lingua del setup e il path di destinazione della dll (che in questo caso va registrata nella GAC di Windows), e l’eventuale icona da usare per le voci di unistall:
[Languages]
Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl"
[Files]
Source: "D:\Dev\MyBho\MyBHOBHOSETUP\MyBHO.dll"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Fatto questo si passa alla sezione [Code] che mantiene delle funzioni (sono scritte in Pascal) generiche richiamate dai metodi “hook” gestiti da Inno stesso, come ad esempio il methodo InitializeSetup che viene eseguito a startup dell’installazione.
In questa sezione ho messo un metodo per la rilevazione dell’installazione su sistemi a 64 e 32 bit e sulla verifica del framework .net installato (il bho che ho realizzato in c# richiede, visto il codice managed, la presenza di .net 4.0)
[Code]
function IsDotNetDetected(version: string; service: cardinal): boolean;
// 'v1.1.4322' .NET Framework 1.1
// 'v2.0.50727' .NET Framework 2.0
// 'v3.0' .NET Framework 3.0
// 'v3.5' .NET Framework 3.5
// 'v4\Client' .NET Framework 4.0 Client Profile
// 'v4\Full' .NET Framework 4.0 Full Installation
//service: per la presenza richiesta di service pack:
// 0 No service packs
// 1, 2, etc. Service pack 1, 2, etc..
var
key: string;
install, serviceCount: cardinal;
success: boolean;
begin
key := 'SOFTWARE\Microsoft\NET Framework Setup\NDP\' + version;
// .NET 3.0 usa InstallSuccess nella chiave Setup
if Pos('v3.0', version) = 1 then begin
success := RegQueryDWordValue(HKLM, key + '\Setup', 'InstallSuccess', install);
end else begin
success := RegQueryDWordValue(HKLM, key, 'Install', install);
end;
// .NET 4.0 usa Servicing al posto di SP
if Pos('v4', version) = 1 then begin
success := success and RegQueryDWordValue(HKLM, key, 'Servicing', serviceCount);
end else begin
success := success and RegQueryDWordValue(HKLM, key, 'SP', serviceCount);
end;
result := success and (install = 1) and (serviceCount >= service);
end;
function Install32BitOn64Bit(S: String):string;
begin
if IsWin64 then
Result := 'Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{#MyAppId}'
else
Result := 'Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{#MyAppId}'
end;
function InitializeSetup(): Boolean;
begin
if IsDotNetDetected('v4\Client', 0) or IsDotNetDetected('v4\Full', 0) then
Result :=true
else begin
MsgBox('Attenzione Framework .NET 4.0 non rilevato', mbError, mb_Ok);
Result :=false;
end
end;
Come ultimo step del nostro script andiamo ad indicare nella sezione [Registry] tutte le chiavi da impostare (e sono molte) per il funzionamento del nostro componente come plugin di IE. Il guid che si passa, è il guid associato serve a indicare i componenti .net.
[Registry]
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; Flags: uninsdeletekey
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: ; ValueData: {#MyAppNamespace}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: MenuText; ValueData: {#MyAppName}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: HelpText; ValueData: {#MyAppDescription}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\Implemented Categories\{{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ; ValueData: mscoree.dll
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ThreadingModel; ValueData: Both
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Class; ValueData: {#MyAppNamespace}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: CodeBase; ValueData: file:///{pf}\MyTools\MyBrowserHelperObjects\{#MyAppDllName}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Assembly; ValueData: {#MyAppName}, Version={#MyAppVersion}, Culture=neutral, PublicKeyToken=dedfcbc464c1d691
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: RuntimeVersion; ValueData: v4.0.30319
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: ; ValueData: {#MyAppNamespace}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: MenuText; ValueData: {#MyAppName}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: HelpText; ValueData: {#MyAppDescription}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\Implemented Categories\{{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ; ValueData: mscoree.dll
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ThreadingModel; ValueData: Both
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Class; ValueData: {#MyAppNamespace}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: CodeBase; ValueData: file:///{pf}\MyTools\MyBrowserHelperObjects\{#MyAppDllName}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Assembly; ValueData: {#MyAppName}, Version={#MyAppVersion}, Culture=neutral, PublicKeyToken=dedfcbc464c1d691
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: RuntimeVersion; ValueData: v4.0.30319
Root: HKLM; Subkey: {code:Install32BitOn64Bit}; Flags: uninsdeletekey
Root: HKLM; Subkey: {code:Install32BitOn64Bit}; ValueType: dword; ValueName: NoExplorer; ValueData: 1
Fatto questo, eseguendo lo script dal compilatore di Inno Setup, è possibile ottenere l’installer/uninstaller del nostro componente.
Ovviamente questo tutorial è il frutto di molte ricerche sulla rete, e di riferimenti presi da diversi blogger che hanno affrontato l’argomento, a cui vanno i miei ringraziamenti, e a quel pozzo infinito di spunti e riflessioni che è la comunità di StackOverflow, a cui va la mia gratitudine eterna 🙂 .