CLI приложения #25. Делаем настоящий установщик Windows

В прошлой части мы научились публиковать готовые приложения через GitHub Releases. Теперь пользователь может скачать архив, распаковать его и запустить программу. В общем-то, всё работает. Но согласитесь, не очень удобно каждый раз вручную раскладывать файлы по папкам, настраивать PATH, а при удалении вспоминать, что и куда мы скопировали.

Что ж, пора сделать нормальный установщик. Такой, чтобы запустил setup.exe, выбрал папку, нажал «Установить» — и всё готово.

И, кстати, всё, что мы сегодня рассмотрим, применимо не только к CLI, но и к обычным настольным приложениям, например WinForms или WPF.

Что вообще делает установщик?#

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

Обычно установщик может:

  • Разместить файлы программы в нужных каталогах.
  • Создать ярлыки в меню «Пуск» и на рабочем столе.
  • Зарегистрировать приложение в Windows, чтобы оно появилось в списке установленных программ.
  • Настроить переменные окружения, например PATH.
  • Обеспечить корректное обновление и удаление приложения.

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

Куда устанавливать приложение?#

В Windows есть несколько стандартных каталогов, которые используются для разных целей.

Например:

  • C:\Program Files — программы, установленные для всех пользователей компьютера.
  • %LOCALAPPDATA% — данные и приложения конкретного пользователя, не предназначенные для синхронизации между компьютерами.
  • %APPDATA% — пользовательские данные, которые могут перемещаться вместе с профилем в корпоративной среде.
  • %PROGRAMDATA% — общие данные приложения для всех пользователей.

Здесь важно не путать файлы самой программы с её настройками и пользовательскими данными. Например, исполняемый файл и библиотеки логично разместить в Program Files, а изменяемые настройки — в профиле пользователя. Почему? Потому что обычный пользователь не должен иметь возможность свободно изменять содержимое системного каталога установки.

И это актуально не только для CLI. Если вы разрабатываете WinForms-приложение, которое сохраняет настройки рядом со своим .exe, после установки в Program Files вполне можно столкнуться с проблемами доступа.

Знакомимся с Inno Setup#

Для создания установщика воспользуемся бесплатным инструментом Inno Setup. Он позволяет описать процесс установки специальным скриптом с расширением .iss, а затем собрать из него обычный setup.exe.

Скачать его можно с официального репозитория: https://github.com/jrsoftware/issrc

Сначала опубликуем наше CLI-приложение:

dotnet publish -c Release -r win-x64 --self-contained true -o ./publish/win-x64

На этот раз не будем использовать single-file, чтобы нагляднее показать, как установщик работает с несколькими файлами. После публикации получим папку с исполняемым файлом и зависимостями. Теперь создадим файл setup.iss рядом с проектом.

Описываем установку#

Начнём с простого примера:

#define AppName "MyApp"
#define AppVersion "1.0.0"

[Setup]
AppId=Demo.CliCourse.MyApp
AppName={#AppName}
AppVersion={#AppVersion}
DefaultDirName={autopf}\MyApp
DefaultGroupName=MyApp
OutputBaseFilename=MyApp-Setup
Compression=lzma
SolidCompression=yes
WizardStyle=modern
PrivilegesRequired=admin

[Files]
Source: "publish\win-x64\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs

[Icons]
Name: "{group}\MyApp"; Filename: "{app}\MyApp.exe"; WorkingDir: "{app}"

Выглядит немного непривычно, но на самом деле здесь всё довольно просто.

  • Секция [Setup] содержит основные параметры установщика: название приложения, версию, каталог установки и настройки самого мастера.
  • DefaultDirName определяет, куда по умолчанию будут установлены файлы. Константа {autopf} соответствует каталогу Program Files с учётом режима установки.
  • PrivilegesRequired=admin означает, что для установки потребуются права администратора.
  • Секция [Files] отвечает за копирование файлов. В нашем случае мы берём всё содержимое publish\win-x64 и переносим в каталог {app}, то есть туда, куда пользователь устанавливает приложение.
  • А [Icons] создаёт ярлык в меню «Пуск».

Обратите внимание на AppId. Это идентификатор приложения, по которому Inno Setup связывает последующие установки с уже установленной программой. При обновлении его не нужно менять.

А как же удаление?#

Вот здесь начинается самое интересное. Inno Setup самостоятельно создаёт деинсталлятор и регистрирует программу в списке установленных приложений Windows. То есть нам не нужно вручную писать код удаления или создавать записи в реестре только ради появления приложения в настройках системы. При удалении Inno Setup использует журнал установки, чтобы убрать файлы и ярлыки, которые были установлены через его скрипт. Однако пользовательские файлы, созданные уже после установки, автоматически удалять не следует. Например, настройки, базы данных и документы могут понадобиться пользователю даже после удаления программы.

Собираем установщик#

Открываем setup.iss в Inno Setup Compiler и выбираем Build → Compile.

Если всё настроено правильно, получим файл:

MyApp-Setup.exe

Запускаем его и проходим обычный мастер установки.

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

Можно проверить и удаление: Windows запустит созданный Inno Setup деинсталлятор.

Но мы кое-что забыли#

Помните, что мы разрабатываем CLI-приложение? После установки в Program Files оно не станет автоматически доступным по команде myapp из любого каталога. Для этого нужно добавить путь к исполняемому файлу в PATH. И здесь уже возникают дополнительные вопросы: изменять пользовательскую или системную переменную? Как не затереть существующее значение? Как корректно удалить свой путь при деинсталляции? И что делать, если в PATH уже есть такой каталог? А ещё неплохо бы научиться устанавливать приложение без прав администратора, правильно обрабатывать обновления и сохранять пользовательские данные.

В следующей части продолжим разбираться с Inno Setup и доведём установщик до более законченного состояния.

Ну а пока у нас уже есть настоящий setup.exe, который устанавливает приложение, регистрирует его в Windows и умеет корректно удалять.