четверг, 6 октября 2011 г.

Создание инсталлятора с помощью Inno Setup

При создании программы под windows, всегда в конечном итоге возникает необходимость ее как-то распространять, и лучшего способа, чем создания инсталлера нет. Существует несколько приложений для этого, но на мой взгляд Inno Setup является лучшей. Простота, бесплатность и гибкость для меня явились решающими факторами.
В данной статье будут рассмотрены некоторые моменты, которые позволят установить, обновить, деинсталировать и прописать программу в автозагрузку.

Для создания инсталлятора в Inno Setup используются скрипты, в которых и прописывается весь алгоритм установки и удаления программы. Далее подробно опишем каждый блок кода скрипта, и в итоге получим готовый универсальный скрипт.





1. Определяем директивы

#define MyAppName "Наименование приложения"
#define MyAppVersion "0.1"
#define MyAppPublisher "Автор"
#define MyAppURL "http://www.сайт проекта.org"
#define MyAppExeName "Наименование.exe"

Мы определили наименование программы, текущую версию, имя компании или лица, кто разработал программу, сайт и наименование исполняемого файла.

2. Блок кода "Setup"

AppId={{0633B77F-A148-49B9-BF2E-21469B353423} - Значение AppId идентифицирует этого приложения и прописывается при установки в реестр. Нельзя использовать одно и тоже значение AppId для разных приложений.
AppName={#MyAppName} - имя приложения.
AppVersion={#MyAppVersion} - версия программы.
AppPublisher={#MyAppPublisher} - имя собственника программы.
AppPublisherURL={#MyAppURL} - ссылка на сайт собственника программы.
AppSupportURL={#MyAppURL} - ссылка на страницу тех. поддержки.
AppUpdatesURL={#MyAppURL} - ссылка на страницу обновлений.
DefaultDirName={pf}\{#MyAppName} - путь, куда будет установлена программа, {pf} - Program Files.
DefaultGroupName={#MyAppName} - папка с названием программы.
LicenseFile=C:\project\LICENSE - путь к лицензии.
OutputBaseFilename=Имя программы 0.2 - имя инсталлятора.
SetupIconFile=C:\project\Имя программы.ico - иконка для инсталлятора.
Compression=lzma - параметр, указывающий алгоритм сжатия, при создании инсталлятора.
SolidCompression=yes - создавать или нет непрерывный архив.

3. Блок кода "Languages"

Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"

Inno Setup поддерживает множество языков, которые можно подключить в этом блоке кода, после чего установка будет идти на языке в зависимости от локализации windows.

4. Блок кода "Tasks"

Здесь можно указать, куда дополнительно поместить иконку программы, рабочий стол, панель быстрого запуска. При этом пользователю предоставится выбор, поместить туда иконку или нет.

Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1

5. Блок кода "Files"

Source: "C:\project\name.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\project\file1.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\project\file2.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\project\src\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs

Первые три строчки являются ссылками на исполняемый файл и файлы библиотек, последняя строчка является ссылкой на директорию, recursesubdirs и createallsubdirs  параметры означают, что будут включены все файлы и папки лежащие уровнем ниже.

6. Блок кода "Icons"

Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
Name: "{commonstartup}\Имя приложения"; Filename: "{app}\{#MyAppExeName}"

Назначение блока выходит из названия, указываются места, куда будут прописаны иконки с программой, это список программ на удаление, рабочий стол, панель быстрого запуска, и в последней строке приложение прописывается в автозагрузку.

7. Блок кода "Run"

Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, "&", "&&")}}"; Flags: nowait postinstall skipifsilent

Указывается путь и  параметры запуска программы после установки.

8. Блок кода "Code"

Это самая интересная часть. Здесь прописываются различные пользовательские скрипты, что делать до установки, например определить установлена ли уже эта программа или нет, и вывести соответствующее сообщение, также можно определить множество других действий, во время установки программы, после установки, перед деинсталяцией.
Мы напишем код, который будет работать перед установкой программы и перед ее удалением.
Определимся, что нам необходимо для нормального функционирования инсталлятора.
  • Перед установкой, если уже установлена старая версия программы, то ее необходимо удалить.
  • Перед удалением необходимо просмотреть список запущенных программ и если наша в этом списке имеется, то ее надо закрыть.
Данный код ищет среди запущенных процессов наш, и если находит, то посылает команду на его закрытие.

const
WM_CLOSE = 16;

function ProcessRunning(): Boolean;
var winHwnd: longint;
    retVal : boolean;
    strProg: string;
begin
  Result := true;
  try
    strProg := 'Vertaler';
    winHwnd := FindWindowByWindowName(strProg);
    Log('winHwnd: ' + inttostr(winHwnd));
    if winHwnd <> 0 then
      retVal:=postmessage(winHwnd,WM_CLOSE,0,0);
      if retVal then
        Result := True
 except
 end;
end;

В этой части прописано, если установлена старая версия программы, то она удаляется.

function InitializeSetup : Boolean;
begin
   Result := ProcessRunning();
end;

function InitializeUninstall: Boolean;
begin
   Result := ProcessRunning();
end;
    
function GetUninstallString(): String;
var
  sUnInstPath: String;
  sUnInstallString: String;
begin
  sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
  sUnInstallString := '';
  if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
    RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
  Result := sUnInstallString;
end;

function IsUpgrade(): Boolean;
begin
  Result := (GetUninstallString() <> '');
end;

function UnInstallOldVersion(): Integer;
var
  sUnInstallString: String;
  iResultCode: Integer;
begin
// Return Values:
// 1 - uninstall string is empty
// 2 - error executing the UnInstallString
// 3 - successfully executed the UnInstallString

  // default return value
  Result := 0;

  // get the uninstall string of the old app
  sUnInstallString := GetUninstallString();
  if sUnInstallString <> '' then begin
    sUnInstallString := RemoveQuotes(sUnInstallString);
    if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
      Result := 3
    else
      Result := 2;
  end else
    Result := 1;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
  if (CurStep=ssInstall) then
  begin
    if (IsUpgrade()) then
    begin
      UnInstallOldVersion();
    end;
  end;
end;

Ну и теперь весь код скрипта.

#define MyAppName "Наименование приложения"
#define MyAppVersion "0.2" #define MyAppPublisher "Автор" #define MyAppURL "http://www.сайт проекта.org" #define MyAppExeName "Наименование.exe"


[Setup]

AppId={{0633B77F-A148-49B9-BF2E-21469B353423}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName}
LicenseFile=C:\project\LICENSE
OutputBaseFilename=Имя программы 0.2
SetupIconFile=C:\project\Имя программы.ico
Compression=lzma
SolidCompression=yes
[Languages]


Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"

[Tasks]

Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1

[Files]

Source: "C:\project\name.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\project\file1.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\project\file2.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\project\src\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs

[Icons]

Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
Name: "{commonstartup}\Имя приложения"; Filename: "{app}\{#MyAppExeName}"

[Run]

Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, "&", "&&")}}"; Flags: nowait postinstall skipifsilent

[Code]

const
WM_CLOSE = 16;

function ProcessRunning(): Boolean;
var winHwnd: longint;
    retVal : boolean;
    strProg: string;
begin
  Result := true;
  try
    strProg := 'Vertaler';
    winHwnd := FindWindowByWindowName(strProg);
    Log('winHwnd: ' + inttostr(winHwnd));
    if winHwnd <> 0 then
      retVal:=postmessage(winHwnd,WM_CLOSE,0,0);
      if retVal then
        Result := True
 except
 end;
end;


function InitializeSetup : Boolean;
begin
   Result := ProcessRunning();
end;

function InitializeUninstall: Boolean;
begin
   Result := ProcessRunning();
end;
    
function GetUninstallString(): String;
var
  sUnInstPath: String;
  sUnInstallString: String;
begin
  sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
  sUnInstallString := '';
  if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
    RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
  Result := sUnInstallString;
end;

function IsUpgrade(): Boolean;
begin
  Result := (GetUninstallString() <> '');
end;

function UnInstallOldVersion(): Integer;
var
  sUnInstallString: String;
  iResultCode: Integer;
begin
// Return Values:
// 1 - uninstall string is empty
// 2 - error executing the UnInstallString
// 3 - successfully executed the UnInstallString

  // default return value
  Result := 0;

  // get the uninstall string of the old app
  sUnInstallString := GetUninstallString();
  if sUnInstallString <> '' then begin
    sUnInstallString := RemoveQuotes(sUnInstallString);
    if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
      Result := 3
    else
      Result := 2;
  end else
    Result := 1;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
  if (CurStep=ssInstall) then
  begin
    if (IsUpgrade()) then
    begin
      UnInstallOldVersion();
    end;
  end;
end;



4 комментария:

  1. Код нерабочий и неполноценный. Пропущены фигурная скобочка и точка с запятой и ещё что-то очень важное для понимания принципа работы.

    Для чего нужны процедура CurStepChanged, функции InitializeSetup и InitializeUninstall, если они нигде в тексте не используются?
    Самостоятельно InnoSetup указанные функции не цепляет, соответственно не происходят ни деинсталляция предыдущей программы, ни завершение активного процесса, а установщик требует перезагрузку.

    ОтветитьУдалить
  2. Неплохой обзор. Вообще, при создании инсталляторов есть ряд тонкостей. Все очень сильно зависит от того, что должен уметь инсталлятор.
    Если вам интересно, то еще несколько бесплатных аналогов можно найти в обзоре по адресу http://ida-freewares.ru/programming/7-best-free-program-setup-builder.html

    ОтветитьУдалить
  3. Пригодилось, спасибо!

    ОтветитьУдалить