Pull to refresh

Inno Setup: создание инсталлятора на примере развертывания C# приложения

Reading time 13 min
Views 195K

Введение



Я не являюсь профессиональным программистом. В том смысле, что не зарабатываю денег этим ремеслом, а использую свои навыки в качестве инструмента для основной, научной, деятельности. Поэтому все мои «поделки» живут лишь отведенный им на решение конкретной задачи период и не выходят за пределы каталогов проекта. Кроме того, уже довольно давно я отошел от разработки под ОС Windows, ибо Linux для решения моих задач более удобен.

Однако ученым тоже хочется кушать, прилично одеваться и заправлять машину. Поэтому (правда довольно редко) возникает необходимость немного пофрилансить.

Недавно мне подкинули не слишком сложный проект — одна фирма хочет написать аналог программы, имеющейся у другой. Немного реверсинга, немного кодинга, в целом проект вполне обыденный. Однако тут же возник вопрос о создании инсталлятора — клиент ведь желает продукт «под ключ», чтобы клацнуть на «сетап», понажимать «Далее» и получить готовую к работе программу.

Созданием инсталляторов я не занимался никогда. Поэтому данный вопрос был основательно «загуглен», в числе прочего попалась и такая статья с Хабра. Выбор средств для подобной задачи довольно широк, и включает как проприетарные, так и открытые продукты. Вот список того, что я «пощупал»

  1. InstallShield — классика жанра, достаточно солидный проприетарный продукт
  2. Adnvanced Installer — проприетарный инструмент с широкими возможностями кастомизации через GUI. На сайте сказано, что если Вы блоггер и будете писать об этом продукте много хороших слов, то у Вас есть возможность получить Free License
  3. WiX — открытый бесплатный продукт, основанный на XML-скриптах. Мощная, хорошо документированная штука. Разбираться с ним я пока не стал, ибо время дорого (да и к XML душа лежит не очень). Возможно когда нибудь я к нему вернусь. Да, к нему есть плагины для Visual Studio, что несомненный плюс.
  4. Inno Setup — опенсорсный проект, код которого доступен на гитхабе. В силу бесплатности и низкого порога вхождения мой выбор остановился именно на нем, как инструменте позволившем выполнить работу быстро и качественно.


Так что в статье мы будем рассматривать пример использования Inno Setup, для которого имеется полезный фронтэнд Inno Script Studio, позволяющий выполнять создание простых инсталляторов с помощью мастера и менять настройки через GUI. GUI понадобился мне для первого знакомства, с продуктом, но мы не будем уделять ему большого внимания — мой «линукс головного мозга» в последнее время всё больше и больше уводит меня от желания использовать разного рода «мастера» (это субъективно, прошу не пинать). Мы рассмотрим хардкорный способ написания скрипта с чистого листа.



1. Установка, настройка и простой (но довольно солидный) скрипт



Думаю, что скачать программу с официального сайта и установить её труда не составит. Запускаем Inno Setup Compiler и видим такое окно

Пугающе уныло встречает нас Inno Setup...


Что это? По сути это просто-напросто редактор для набора скриптов, снабженный подсветкой синтаксиса и кнопками компиляции и запуска. От нас ждут, что мы начнем набирать в этом окне текст скрипта, определяющий логику работы будущего инсталлятора. Ну так и не будем терять время.

Прежде всего определим необходимые константы
;------------------------------------------------------------------------------
;
;       Пример установочного скрипта для Inno Setup 5.5.5
;       (c) maisvendoo, 15.04.2015
;
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;   Определяем некоторые константы
;------------------------------------------------------------------------------

; Имя приложения
#define   Name       "Miramishi Painter"
; Версия приложения
#define   Version    "0.0.1"
; Фирма-разработчик
#define   Publisher  "Miramishi"
; Сафт фирмы разработчика
#define   URL        "http://www.miramishi.com"
; Имя исполняемого модуля
#define   ExeName    "Miramishi.exe"


Эти строки будут часто встречаться в коде скрипта, поэтому определяем их, как и в C, с помощью дерективы #define

Тело скрипта разделяется на секции, каждая из которых несет свое функциональное назначение. Обязательная секция [Setup] задает глобальные параметры работы инсталлятора и деинсталатора.

;------------------------------------------------------------------------------
;   Параметры установки
;------------------------------------------------------------------------------
[Setup]

; Уникальный идентификатор приложения, 
;сгенерированный через Tools -> Generate GUID
AppId={{F3E2EDB6-78E8-4539-9C8B-A78F059D8647}

; Прочая информация, отображаемая при установке
AppName={#Name}
AppVersion={#Version}
AppPublisher={#Publisher}
AppPublisherURL={#URL}
AppSupportURL={#URL}
AppUpdatesURL={#URL}

; Путь установки по-умолчанию
DefaultDirName={pf}\{#Name}
; Имя группы в меню "Пуск"
DefaultGroupName={#Name}

; Каталог, куда будет записан собранный setup и имя исполняемого файла
OutputDir=E:\work\test-setup
OutputBaseFileName=test-setup

; Файл иконки
SetupIconFile=E:\work\Mirami\Mirami\icon.ico

; Параметры сжатия
Compression=lzma
SolidCompression=yes


Пристальное внимание уделаем опции AddId — уникальный идентификатор приложения (GUID), используемый для регистрации приложения в реестре Windows. Его пишем не «от фонаря», а генерируем, открывая фигурную скобку, и выбрав в меню пункт Tools -> Generate GUID (или используя хот-кей Shift + Ctrl + G). Далее указываем имя приложения, под которым оно будет установлено в системе, его версию, данные фирмы разработчика, адреса сайтов разработчика, технической поддержки и обновления.

Путь, по умолчанию предлагаемый инсталлятором для установки определяем опцией DefaultDirName. При этом переменная {pf} — это путь в каталог Program Files соответствующей разрядности. Опция DefaultGroupName определяет имя группы программы в меню «Пуск». Обратите внимание на то, что для указания имени приложения мы используем данное нами выше макроопределение Name, обрамляя его фигурными скобками и решеткой.

Пара опций OutputDir и OutputBaseFileName задают каталог, куда будет записан скомпилированный «сетап» и его имя (без расширения). Кроме этого, указываем где взять иконку для test-setup.exe опцией SetupIconFile.

Последние опции в этой секции определяют алгоритм сжатия (LZMA) и указывают, что все файлы сжимаются одновременно, а не по отдельности (SolidCompression) что ускоряет процесс распаковки при большом количестве однотипных файлов.

В хорошем исталяторе должна быть поддержка нескольких языков. Включаем её в наш «сетап», используя опциональную секцию [Languages]. При отсутствии данной секции будет использоваться английский язык.

;------------------------------------------------------------------------------
;   Устанавливаем языки для процесса установки
;------------------------------------------------------------------------------
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"; LicenseFile: "License_ENG.txt"
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"; LicenseFile: "License_RUS.txt"


Каждая строка в данной секции задает один из используемых при установке языков. Синтаксис строки таков

<имя параметра>: <значение параметра>


в качестве разделителя параметров используется точка с запятой. Параметр Name говорит сам за себя — «имя» языка, допускаются общепринятые двухбуквенные сокращения («en», «ru», «de» и так далее). Параметр MessagesFile сообщает компилятору в каком месте взять шаблон сообщений, выводимых при инсталляции. Эти шаблоны берем в каталоге компилятора Inno Setup, о чем мы сообщаем директивой compiler. Для английского языка годится шаблон Default.isl, для русского — Languages\Russian.isl

Параметр LicenseFile задает путь к файлу с текстом лицензии на соответствующем языке.

Обычно установщик предлагает нам, например, определится, хотим мы или не хотим создать ярлык на рабочем столе. Такие опции установки определяются необязательной секцией [Tasks]

;------------------------------------------------------------------------------
;   Опционально - некоторые задачи, которые надо выполнить при установке
;------------------------------------------------------------------------------
[Tasks]
; Создание иконки на рабочем столе
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked


Здесь Name задает имя операции — «desktopicom» — создание иконки на рабочем столе; Description — описание чекбокса с опцией, которое увидит пользователь. Конструкция

{cm:<имя сообщения>}


задает стандартный текст сообщения, соответствующий выбранному в начале инсталляции языку. Параметр GroupDescription — заголовок группы чекбоксов с опциями. Параметр Flags задает определенные действия и состояния элементов управления, в данном случае указывая, что галочка «создать ярлык на рабочем столе» должна быть снята.

Чтобы было понятно - так выглядит результат



Теперь укажем, какие файлы надо включить в дистрибутив и где их надо поместить при установке. Для этого используется обязательная секция [Files]

;------------------------------------------------------------------------------
;   Файлы, которые надо включить в пакет установщика
;------------------------------------------------------------------------------
[Files]

; Исполняемый файл
Source: "E:\work\Mirami\Mirami\bin\Release\Miramishi.exe"; DestDir: "{app}"; Flags: ignoreversion

; Прилагающиеся ресурсы
Source: "E:\work\Mirami\Mirami\bin\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs


Здесь

  • Source — путь к файлу-источнику. У меня всё необходимое программе для работы лежит в каталоге Release проекта MS VS
  • DestDir — каталог установки, переменная {app} содержит путь, выбранный пользователем в окне установщика
  • Flags — разнообразные флаги. В нашем примере для исполняемого файла: игнорирование версии программы при перезаписи исполняемого модуля, если он уже существует в системе (ignorevarsion); для остальных файлов и каталогов так же игнорируем версию, рекурсивно включаем все подкаталоги и файлы источника (recursesubdirs) и создаем подкаталоги, если их нет (createallsubdirs)


Наконец, чтобы всё было красиво, опционально укажем компилятору, где брать иконки для размещения в меню программ и на рабочем столе

;------------------------------------------------------------------------------
;   Указываем установщику, где он должен взять иконки
;------------------------------------------------------------------------------ 
[Icons]

Name: "{group}\{#Name}"; Filename: "{app}\{#ExeName}"

Name: "{commondesktop}\{#Name}"; Filename: "{app}\{#ExeName}"; Tasks: desktopicon


Тут я указываю, что для группы в меню «Пуск» и для рабочего стола иконку надо брать из исполняемого модуля. Естественно, что иконка должна быть в него «вкомпилена», иначе в требуемых местах мы увидим стандартный значок из коллекции винды.

Итак, всё вроде готово. Жмем Ctrl + F9 и пытаемся собрать инсталлятор. Если не допущены синтаксические ошибки, начнется процесс сборки

Inno Setup собирает инсталлятор


После успешной сборки инсталлятор можно запустить, нажав F9. Если Вы работаете под учеткой с ограниченными правами (а я работаю в винде именно так), то придется полезть в каталог с результатами компиляции, который мы указали в скрипте, и запустить инсталлятор с правами админа

Запуск инсталлятора под ограниченной учетной записью


В итоге мы увидим до боли знакомое каждому пользователю Windows окно выбора языка

приветствие мастера

лицензионное соглашение


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

Ну что сказать? Ура! Мы написали свой первый «сетап» и могли бы радоваться, но

2. Развертывание .NET Framework



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

  1. Определиться с тем, как будем получать дистрибутив фреймворка. Тут есть два варианта — поставлять его вместе с дистрибутивом программы или, при необходимости установки загрузить его онлайн на целевую машину. Мы остановимся на варианте включения фреймворка в дистрибутив. Сам фреймфорк, скажем версии 4.0, можно взять бесплатно у Майкрософта
  2. Детектировать наличие/отсутствие данного фреймфорка в целевой системе. Для этого потребуется написать логику, анализирующую состояние системного реестра.


Значения в реестре, которые необходимо проверить приведены в официальной документации Microsoft, в статье я приведу краткую выжимку из неё

Таблица 1. Ключи реестра, для проверки установленной версии .NET Framework
Версия .NET Ключ реестра Значение
3.0 HKLM\Software\Microsoft\NET Framework Setup\NDP\v3.0\Setup\InstallSuccess 1
3.5 HKLM\Software\Microsoft\NET Framework Setup\NDP\v3.0\Setup\Install 1
4.0 Client Profile HKLM\Software\Microsoft\NET Framework Setup\NDP\v4.0\Client\Install 1
4.0 Full Profile HKLM\Software\Microsoft\NET Framework Setup\NDP\v4.0\Full\Install 1
4.5 HKLM\Software\Microsoft\NET Framework Setup\NDP\v4.0\Full\Release номер релиза


Для реализации произвольной логики работы инсталлятора в Inno Setup предусмотрена секция [Code]. В пределах этой секции размещается код реализующих логику функций на языке Pascal. Содежимое этой секции мы вынесем в отдельный файл dotnet.pas и включим в основной скрипт дерективой #include

;------------------------------------------------------------------------------
;   Секция кода включенная из отдельного файла
;------------------------------------------------------------------------------
[Code]
#include "dotnet.pas"


хотя можно набить код и непосредственно в секции [Code]. Надо помнить, что внутри этой секции используется синтаксис Pascal, и комментарии предваряются последовательностью "//" вместо используемой в основной части скрипта точки с запятой.

Напишем функцию, определяющую наличие в системе нужной версии .NET

//-----------------------------------------------------------------------------
//  Проверка наличия нужного фреймворка
//-----------------------------------------------------------------------------
function IsDotNetDetected(version: string; release: cardinal): boolean;

var 
    reg_key: string; // Просматриваемый подраздел системного реестра
    success: boolean; // Флаг наличия запрашиваемой версии .NET
    release45: cardinal; // Номер релиза для версии 4.5.x
    key_value: cardinal; // Прочитанное из реестра значение ключа
    sub_key: string;

begin

    success := false;
    reg_key := 'SOFTWARE\Microsoft\NET Framework Setup\NDP\';
    
    // Вресия 3.0
    if Pos('v3.0', version) = 1 then
      begin
          sub_key := 'v3.0';
          reg_key := reg_key + sub_key;
          success := RegQueryDWordValue(HKLM, reg_key, 'InstallSuccess', key_value);
          success := success and (key_value = 1);
      end;

    // Вресия 3.5
    if Pos('v3.5', version) = 1 then
      begin
          sub_key := 'v3.5';
          reg_key := reg_key + sub_key;
          success := RegQueryDWordValue(HKLM, reg_key, 'Install', key_value);
          success := success and (key_value = 1);
      end;

     // Вресия 4.0 клиентский профиль
     if Pos('v4.0 Client Profile', version) = 1 then
      begin
          sub_key := 'v4\Client';
          reg_key := reg_key + sub_key;
          success := RegQueryDWordValue(HKLM, reg_key, 'Install', key_value);
          success := success and (key_value = 1);
      end;

     // Вресия 4.0 расширенный профиль
     if Pos('v4.0 Full Profile', version) = 1 then
      begin
          sub_key := 'v4\Full';
          reg_key := reg_key + sub_key;
          success := RegQueryDWordValue(HKLM, reg_key, 'Install', key_value);
          success := success and (key_value = 1);
      end;

     // Вресия 4.5
     if Pos('v4.5', version) = 1 then
      begin
          sub_key := 'v4\Full';
          reg_key := reg_key + sub_key;
          success := RegQueryDWordValue(HKLM, reg_key, 'Release', release45);
          success := success and (release45 >= release);
      end;
        
    result := success;

end;


Не смотря на обилие кода, логика его работы достаточно проста — в зависимости от значения параметра version с помощью функции RegQueryDWordValue(...) читается значение соответствующего ключа реестра и сравнивается с требуемым значением (смотрим таблицу 1). Для версии 4.5 дополнительно передаем номер релиза в параметре release.

Для того приложения, которое мы будем развертывать, нужна вполне определенная версия .NET, поэтому напишем функцию-обертку для её определения в целевой системе

//-----------------------------------------------------------------------------
//  Функция-обертка для детектирования конкретной нужной нам версии
//-----------------------------------------------------------------------------
function IsRequiredDotNetDetected(): boolean;
begin
    result := IsDotNetDetected('v4.0 Full Profile', 0);
end;


Для того, чтобы перед началом установки проверить наличие фреймворка и сообщить пользователю о предпринимаемых действиях используем Callback-функцию InitializeSetup()

//-----------------------------------------------------------------------------
//    Callback-функция, вызываемая при инициализации установки
//-----------------------------------------------------------------------------
function InitializeSetup(): boolean;
begin

  // Если нет тербуемой версии .NET выводим сообщение о том, что инсталлятор
  // попытается установить её на данный компьютер
  if not IsDotNetDetected('v4.0 Full Profile', 0) then
    begin
      MsgBox('{#Name} requires Microsoft .NET Framework 4.0 Full Profile.'#13#13
             'The installer will attempt to install it', mbInformation, MB_OK);
    end;   

  result := true;
end;


Теперь в секцию [Files] добавим запись о том, где компилятор должен взять дистрибутив .NET, куда инсталлятор он должен её распаковать, и при каких условиях следует выполнять распаковку

; .NET Framework 4.0
Source: "E:\install\dotNetFx40_Full_x86_x64.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall; Check: not IsRequiredDotNetDetected


Во флагах скажем, что надо удалить дистрибутив .NET после установки (deleteafterinstall). Условие, при котором требуется распаковка задаем опцией Check, где вызываем функцию IsRequiredDotNetDetected(), выполняя распаковку ели она вернет false.

Сам запуск инсталляции фрейворка можно выполнить после установки основной программы, поэтому включаем в скрипт секцию [Run], в которой указывается, что необходимо запускать по окончании установки

;------------------------------------------------------------------------------
;   Секция кода включенная из отдельного файла
;------------------------------------------------------------------------------
[Code]
#include "dotnet.pas"

[Run]
;------------------------------------------------------------------------------
;   Секция запуска после инсталляции
;------------------------------------------------------------------------------
Filename: {tmp}\dotNetFx40_Full_x86_x64.exe; Parameters: "/q:a /c:""install /l /q"""; Check: not IsRequiredDotNetDetected; StatusMsg: Microsoft Framework 4.0 is installed. Please wait...


Обратите внимание на то, что мы сначала указываем имя секции [Run], чтобы закрыть секцию [Code], а затем пишем комментарий начинающийся с точки с запятой. Это необходимо из-за различия синтаксиса основного скрипта и секции [Code], в противном случае при компиляции мы получим синтаксическую ошибку.

В секции задается путь к инсталлятору фреймворка — предварительно он распакован нами во временный каталог (переменная {tmp} содержит путь к веременному каталогу); задаются параметры командной строки. Опция Check определяет условие запуска инсталляции — это отсутствие в целевой системе нужного нам фреймворка. Опция StatusMsg определяет сообщение, которое увидит пользователь в окне инсталлера.

Снова компилируем наш проект. Теперь, при запуске на «чистой» винде инсталлятор выдаст сообщение

Нам сообщают что требуется .NET Framework 4.0 и для нас его установят


При распаковке мы заметим, что дополнительно распаковывает дистрибутив .NET во временную папку



а затем процесс переключается на установку .NET

Майкрософт просит нас принять лицензию...


Установка .NET


После этого мы получаем работоспособное C# приложение установленное «по взрослому»

Заключение



Я не профессионал и во многих вещах могу ошибаться. Прошу отнестись к этому с пониманием. Статья писалась нубом для нубов, её основная цель — задать вектор поиска при решении задачи написания инсталлятора. За остальными вопросами можно обратится к документации, поставляемой вместе с Inno Setup.

Код данного примера доступен в моем репозитории на Github. «Кракозябры» в комментах вызваны несовпадением кирилических кодировок. Для себя всегда пишу английские комментарии, но для лучшего понимания кода допустил этот ляп. При скачивании в винде всё просматривается замечательно, так что прошу простить мне и эту несуразность.

В остальном, полагаю «хаутушка» вышла достойной и благодарю за уделенное мне внимание.
Tags:
Hubs:
+16
Comments 14
Comments Comments 14

Articles