Создаём гибкий инсталлятор приложения в Inno Setup

Очень часто требуется создать такой установщик, который бы был способен устанавливать программу как в каталог %PROGRAMFILES% (при наличии прав локального администратора), так и в %LOCALAPPDATA% (при отсутствии оных).

В данном HOWTO мы подробно рассмотрим процесс создания такого инсталлятора, приведём примеры работающего кода и опишем подводные камни, которые вам могут встретится.

Создание инсталлятора

  1. Откройте исходный код сценария установки (файл *.iss) в любом текстовом редакторе или самой среде Inno Setup.
  2. В блоке [Setup] найдите директиву DefaultDirName и замените её следующую:
    DefaultDirName={code:GetDefRoot}\App Name

    Здесь App Name — название вашего приложения (каталог, в который будет установлена программа; рекомендуется использовать только латинские буквы и цифры).

  3. В этот же [Setup] добавьте:
    PrivilegesRequired=none

    У этой директивы возможны следующие значения:

    1. admin (по умолчанию, либо если отсутствует в конфиге) — для работы созданного инсталлятора потребуются администраторские привилегии;
    2. poweruser — потребуется членство в группе PowerUsers или Администраторы;
    3. none — запуск возможен от любого пользователя, но если у пользователя есть права администратора, то они будут использоваться;
    4. lowest — запуск также возможен от любого пользователя, но всегда будет использоваться права обычного пользователя (не рекомендуется).
  4. Теперь переходите в блок [ Code] (если его нет, то создаём в самом конце скрипта) и пропишите две следующие функции:
    function GetDefRoot(Param: String): String;
    begin
      if not IsAdmin then
        Result := ExpandConstant('{localappdata}')
      else
        Result := ExpandConstant('{pf}')
    end;
  5. Соберите проект.

Подводные камни

  1. Не устанавливайте значение директивы PrivilegesRequired в lowest, т.к. в таком случае программа установки будет всегда работать с правами пользователя и установиться в %PROGRAMFILES% не сможет.
  2. В случае если вы собираетесь устанавливать динамические библиотеки или например шрифты в системные каталоги Windows, то обязательно делайте дополнительную проверку, например:
    Source: "dll\isxdl.dll"; DestDir: "{app}"; Flags: ignoreversion; Check: not IsAdmin()

    В противном случае вы получите критическую ошибку.

  3. При записи в разделы реестра, отличные от HKEY_CURRENT_USER, также делайте проверку наличия прав администратора (аналогично файлам).

Примеры

Готовый пример вы можете найти в нашем git репозитории проекта SRC Repair.

25 commentaries to post

  1. Спасибо, помогло.
    Простое решение проблемы, какую папку предложить пользователю, если у него нет прав администратора.

  2. Приветствую. Надеюсь на вас!)))

    Как сделать возможность выбора из нескольких вариантов папки установки?
    У меня есть приложение, которое устанавливается в папку дополнений программы:

    Сама папка дополнений программы находится здесь — C:\Users\Admin\AppData\Roaming\Name
    Name — это название программы.

    В Inno я это прописал так:

    DefaultDirName={userappdata}\Name
    И все отлично работает.

    Но мое приложение пригодно и для другого софта. Поясню.
    Есть комната №1 — Name
    Но есть и другие. Например, Name2 и т. д. принадлежащие этой же сети.

    Вопрос. Есть ли возможность на этапе выбора места установки, предоставить пользователю возможность выбора между предложенными вариантами?
    Чтобы он из 4-5 вариантов поставил галочку на нужную ему и мое приложение установилось по выбранному пути.

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

  3. @Михаил
    Можно написать свою собственную функцию в секции Code, которая будет проверять список каталогов и затем выводить отдельное окно или даже форму с вариантами выбора.

  4. Доброго времени суток. Скажите пожалуйста сможете ли вы написать код проверки имени пользователя через реестр на стадии инициализации установки? Разъясняю, если имя пользователя на русском языке установка прекращалась с выводом диалогового окна что-то типа установка не возможна, если имя пользователя на английском языке, установка шла обычно. Или какие есть варианты, или подскажите в каком направлении идти. Спасибо.

  5. @Сергей
    Текущее имя пользователя, от имени которого запущен установщик, хранится в константе {username}, а также может быть получено посредством функции GetUserName().

  6. Про константу {username} и функцию GetUserName(), я знаю, я просто не могу взять значение из данной ветки реестра:
    Root: HKLM; SubKey: SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-2222678472-2290265271-4291998698-1000; ValueType: expandsz; ValueName: ProfileImagePath; ValueData: {cd}\Users\{UserName}; Flags: uninsdeletevalue uninsdeletekeyifempty так-как S-1-5-21-2222678472-2290265271-4291998698-1000 у каждого компа разный. Я хочу чтобы данная строка была по короче и применима ко всем компам и какой константой можно заменить S-1-5-21-2222678472-2290265271-4291998698-1000

  7. @Сергей
    Следует использовать именно GetUserName(), после чего проверять это значение на наличие запрещённых символов внутри блока code например при помощи регулярного выражения, перекрыв ряд системных функций.

    S-1-X — это уникальные идентификаторы пользователей в системе Windows NT. Они генерируется при создании учётной записи и представляет собой случайное уникальное значение, сгенерированное по шаблону (более подробно в MSDN). Ни в коем случае следует работать с этими значениями напрямую!

  8. Тогда как я понял проверку имени пользователя через реестр взять нельзя?. Тогда откуда брать проверку если скажем в системе два пользователя, один с правами администратора, другой простой пользователь. Если можно не большой примерчик, откуда брать имя пользователя с правами администратора, и с чем сравнивать (ну например русский шрифт верхнего и нижнего регистра).

  9. @Сергей
    Функция GetUserName() всегда возвращает имя пользователя, от имени которого запущена программа установки. Зачем вам проверять другие учётные записи? Программа всё равно установится для данного пользователя.

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

  10. Объясню еще раз, нужен код для проверки имени пользователя (на разных компах), если имя пользователя на кириллице установщик выдает окно где будет сказано что установка с таким именем запрещена, если имя пользователя на латинице тогда обычная установка.
    Есть такие примеры на проверку через реестр по имени программы, если программа установлена тогда установщик выдает сообщение что прога с таким именем установлена и закрывается (пример есть в справке), так вот хочу типа такого.

  11. @Сергей
    Это-то понятно. Мне вот только не очень понятно зачем проверять все остальные учётные записи в системе? Если программа устанавливается от конкретной, то она и работать будет от неё, поэтому результата функции GetUserName() для ваших целей будет достаточно. В блоке code переопределяете системные функции завершения процесса подготовки к установке и в них проверяете значение GetUserName() на наличие запрещённых символов по регулярному выражению или посимвольно, а затем продолжаете или отменяете установку. Это достаточно просто.

    Но если уж хочется всё-таки проверить все остальные учётные записи, то потребуется написать специальную функцию в блоке code, которая будет импортировать ряд функций WinAPI через external stdcall, через которые получать список пользователей ОС и уже затем проверять их все. Подробнее в MSDN.

    Пример работы с вызовами WinAPI из инсталляторов InnoSetup можно найти здесь.

  12. Нет я не сказал что проверять нужно все учётные записи. Проверяется учётная запись от имени которой запустили установщик.
    function GetUserName(): Boolean;
    begin
    if GetUserName(ExpandConstant(‘{cd}\users\{username}’,
    MsgBox(‘Программа не может быть установлена’
    Result:= False;
    Подредактируйте где не правильно или дополните, а дальше я уж сам.

  13. Vitaly
    Значит реального примера нет?

  14. @Сергей
    1. Не называйте пользовательские функции именами встроенных. Это вызовет ошибку компиляции.

    2. Ваш код составлен неправильно. Вам нет необходимости использовать ExpandConstant вообще. Просто вызываете GetUserName(), затем проверяете наличие в ней запрещённых символов. Она вернёт только имя пользователя, от имени которого запущена программа установки, например UserName.

    Для путей установки также есть готовые константы.

  15. @Сергей
    В качестве примера можно переопределить одну из штатных функций InnoSetup:

    function CheckUserName(UserName: String): Boolean;
    begin
      # Здесь выполняются проверки. Проверять содержимое переменной UserName.
      # Функция должна возвращать булево: True - верно; False - неверно (русские буквы).
    end;
    
    function InitializeSetup(): Boolean;
    begin
      result := CheckUserName(GetUserName());
    end;

    Пример посимвольной проверки строки на наличие символов на Pascal здесь.

  16. Vitaly
    Дааа, Сколько вы мне написали, За это время можно было накидать примерный код, а подправить как нибудь бы смог сам.

  17. Ладно не хотите помочь, тогда не надо, Бог вам судья!

    1. Вам все объяснили, что не так? Возможно вам просто не стоит заниматься программированием

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

    Дописать код проверки строки на наличие запрещенных символов на Паскале по готовому примеру — задача для студента первого курса любого ВУЗа.

  19. KT
    Наглым и халявщиком по жизни некогда не был, и с совестью на сделку никогда не шол!
    Если для вас это все разжёвано до мякиша то у меня ничего не получается с проверкой имени пользователя. Готового решения от вас мне не надо. Просто я с паскалям никак. И процедуры с функциями с ноля ни разу не писал.

  20. Вот ваша функция:
    что означает Result := True; и Result := false; это понятно результат верный и неверный.
    Интересует как должна начинаться строка:
    if CheckUserName(‘АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя’) then begin Result := False; — так будет правильно.

    1. Необходимо править лишь содержимое функции CheckUserName(). Переопределённую стандартную InitializeSetup() изменять не следует вообще.

      Все действия должны выполняться внутри CheckUserName(). Сама функция возвращает булево: True (в имени пользователя нет запрещённых символов) или False (запрещённые символы есть).

      Ссылка на готовый алгоритм поиска запрещённых символов на языке Pascal методом посимвольного обхода строки здесь. Можно переписать (если конечно в InnoSetup операции с множествами не вырезаны) на проверку вхождения символа в множество допустимых [‘0′..’9′,’a’..’z’,’A’..’Z’,’ ‘] (латинские буквы и цифры, а также пробел) посредством оператора in.

      1. Если вдруг вырезано, то можно взять обычный компилятор Delphi или FreePascal, создать проект динамической библиотеки, реализовать функцию с проверками там, скомпилировать как dll файл, подгрузить в проект InnoSetup и загружать через external.

        1. При использовании external библиотеки можно и более правильным способом проверять — посредством регулярного выражения, причём не посимвольно, а строку целиком.

Обсуждение закрыто.