NSIS: Сохранение лога установки в текстовый файл.

На самом деле, не совсем сохранение лога, если быть точным — сохранение текста из окна Details установщика.

1. Необходимо подключить dumplog.nsi (код взят из NSIS Wiki)
2. Вызов функции DumpLog:

StrCpy $0 "$TEMP\test.log" ;Записываем имя файла лога в переменную
Push $0 ;помещаем значение переменной в стек
Call DumpLog ;вызов функции

Примечание: Функция работать не будет, если инсталлятор запущен в тихом режиме.

С русским языком проблем тоже не возникает. Файл сохраняется в формате ANSI.

Источник

NSIS Wiki

Тестовый установщик

Код на GitHub
dumplog.nsi

NSIS. Проверка существования ключа Реестра.

Иногда бывает надо проверить, существует тот или иной ключ Реестра Windows

Решение

Для этого можно воспользоваться стандартной функцией EnumRegKey:

EnumRegKey <var> <root_key> <subkey> <index>

где:
<var> — переменная для названия ключа Реестра, которое будет возвращено функцией.
<root_key> — корневой ключ Реестра (HKCR|HKCU|HKLM|HKU|HKCC)
<subkey> — путь в Реестре, например Software\Microsoft\Windows
<index> — индекс следующего ключа.

Функция перебирает все подключи в указанной ветке Реестра. Пример для перечисления можно посмотреть здесь

Если ключ не существует, то функция EnumRegKey сгенерирует ошибку, которую можно отследить:

!include Logiclib.nsh ;подключаем библиотеку с нормальными логическими конструкциями.
;...
ClearErrors ;очищаем флаг ошибки
EnumRegKey $0 HKCU "Software\Microsoft\" 0
${If} ${Errors} ;ключ не существует
	DetailPrint "HKEY_CURRENT_USER\Software\Microsoft not EXIST!"
${Else} ;ключ существует
	DetailPrint "HKEY_CURRENT_USER\Software\Microsoft EXIST!"
${EndIf}
;...

Пример целиком на GitHub

NSIS: Создание точки восстановления системы.

Преамбула

В некоторых случаях, например, при установке драйверов, необходимых для работы вашей программы, или при внесении серьезных изменений в систему, хотелось бы задействовать механизм создания точек восстановления, чтобы в случае непредвиденной ситуации, пользователь мог бы безболезненно откатить все назад.
Как, например, поступает Driver Pack Solutions перед установкой драйверов.

Плагин SysRestore

Для создания точек восстановления в NSIS применяется плагин SysRestore, который можно скачать с официальной NSIS wiki (копия).

Установка тривиальна — надо распаковать архив в каталог NSIS, например в "C:\Program Files (x86)\NSIS\"

После подключения плагина становятся доступны 4 функции:

SysRestore::StartInstallPoint "<название>" — функция создает точку восстановления с типом «Установка», применяется для создания точки восстановления при установке программы.

SysRestore::StartUninstallPoint "<название>" — функция создает точку восстановления с типом «Удаление», применяется для создания точки восстановления при удалении программы.

В стек эти функции (как и другие функции данного модуля) помещают код возврата, который можно извлечь из стека командой

Pop <переменная>

И далее проанализировать:

0 — Функция завершена успешно, точка восстановления создана.
1 — Уже запущено создание точки восстановления, предыдущую точку восстановления надо закрыть или отменить (см. ниже).
10 — Windows запущена в «Безопасном режиме»
13 — The sequence number is invalid (сам автор плагина не знает, что это за ошибка).
80 — Ошибка с таким кодом встречается в Windows ME.
112 — Восстановление системы находится в режиме ожидания, поскольку закончилось место на жестком диске под точки восстановления.
1058 — Восстановление системы отключено.
1359 — Внутренняя ошибка при создании точки восстановления.
1460 — Время ожидания вызова истекло из-за ожидания мьютекса для установки точек восстановления (вот это уже я не знаю, что такое).

Впрочем, про коды ошибок, автор пишет следующее:

Я не знаю, являются ли эти коды актуальными кодами ошибок, я просто проанализировал соответствующие include-файлы в SDK. Я тестировал код 1058 — восстановление системы отключено, и провел тест в безопасном режиме — код ошибки 10, я предполагаю, что эти коды верны.

Так что возможно ограничиться анализом кодов 0 (ОК), 1 (см. ниже), 10 (безопасный режим) и 1058 (восстановление системы отключено). Если функция выдаст что-то кроме этих четырех понятных кодов, остальное можно (в первом приближении) отправить в великий класс Неизвестная Ошибка (Unknown Error). Хотя, если вы знаете больше, чем автор плагина, а тем более, чем я — добро пожаловать в комментарии.

Кроме вышеперечисленных инициализирующих функций, существует «закрывающая» и «отменяющая» функции. «Закрывающая»:

SysRestore::FinishRestorePoint

Ее необходимо вызывать, после того, как все изменения в системе произведены, чтобы закрыть точку восстановления, и чтобы система получила нужные данные о том, что было изменено в системных/программных файлах и Реестре. Попытка создать новую точку восстановления без закрытия предыдущей, в одном и том же инсталляторе, приводит к тому что функции StartInstallPoint/StartUninstallPoint выдадут ошибку 1.

Как я понимаю, система все равно закроет restore point после завершения работы установщика, но лучше эту функцию использовать.

«Отменяющая» функция, это:

SysRestore::RemoveRestorePoint

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

Коды возврата последних двух функций:

0 — OK
2 — Не произведен запуск точки восстановления (т.е. предварительно не вызваны функции StartInstallPoint или StartUninstallPoint)

Пример кода

В первой секции… Вообще, для этого можно создать скрытую секцию, чтоб сначала создать restore point, а потом с ней работать далее, но пример простой, так что в первой секции, до начала изменений, создаем точку восстановления

Section "Create Restore Point Example"
	;Настраиваем секцию
	;Создаем точку восстановления
	DetailPrint "Create restore point..."
	SysRestore::StartRestorePoint "Test Restore Point"
	Pop $0 ;достаем из стека код восстановления в переменную $0
	${If} $0 = 0 ;анализируем его
		DetailPrint "OK"
	${ElseIf} $0 = 1
		DetailPrint "Start point already set (start function only)."
	${ElseIf} $0 = 10
		DetailPrint "The system is running in safe mode. "
	${ElseIf} $0 = 13
		DetailPrint "The sequence number is invalid."
	${ElseIf} $0 = 80
		DetailPrint "Windows Me code."
	${ElseIf} $0 = 112
		DetailPrint "System Restore is in standby mode because disk space is low."
	${ElseIf} $0 = 1058
		DetailPrint "System Restore is disabled."
	${ElseIf} $0 = 1359
		DetailPrint "An internal error with system restore occurred."
	${ElseIf} $0 = 1460
		DetailPrint "The call timed out due to a wait on a mutex for setting restore points."
	${Else}
		DetailPrint "Unknow error."
	${EndIf}
	
	;производим установку программы, копируем файлы, пишем в Реестр
	;...
SectionEnd

;в завершающей секции

Section -FinishSection
;...
; Генерируем uninstaller, пишем соотв. ключи в Реестр
;...	
	;Закрываем точку восстановления
	SysRestore::FinishRestorePoint
	DetailPrint "Finish restore point..."
	${If} $0 = 0
		DetailPrint "OK"
	${ElseIf} $0 = 2
		DetailPrint "No Start point set (finish function only)."
	${Else}
		DetailPrint "Unknow error."
	${EndIf}

SectionEnd

Готовый пример

Тестовый установщик создает точку восстановления, далее устанавливает во временный каталог системы программу, которая при запуске показывает приветственное окно и завершает работу, прописывает ее в автозагрузку Реестра, и генерирует uninstaller во временном каталоге.

Код на GitHub
Скомпилированный инсталлятор

Скачать плагин

1. NSIS Wiki
2. Копия (на 07.11.2020)

NSIS warning 7998: ANSI targets are deprecated, как исправить.

Преамбула

Обновил NSIS с v 3.04 до 3.06.1, в логе компилятора стал вылезать warning:

Processed 1 file, writing output (x86-ansi):
warning 7998: ANSI targets are deprecated
...
warning:
  7998: ANSI targets are deprecated

Т.е. ANSI ему устарело. Warning не error, но все равно неприятно. Впрочем, исправить не составило особого труда.

Исправление

Для исправления этого дела, в начало скрипта надо добавить команду:

Unicode true

Только команду надо добавлять в самое начало скрипта, во всяком случае до секций и до определения параметра InstallDir. Если добавить после InstallDir:

;...
Name "${APPNAMEANDVERSION}"
InstallDir "$TEMP"
Unicode true

OutFile "TempUnicode.exe"
;...

То это вызовет ошибку:

Error: Can't change target charset after data already got compressed or header already changed!

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

А лучше добавлять Unicode true вообще в самое начало скрипта, первой строчкой, даже до !include, как пишут на форуме NSIS, т.к. от чего компилятор так реагирует на смену кодировки, в документации не описано.

Примечание: с русским языком в инсталляторе проблем при смене кодировки не случилось, хотя редактор передает компилятору скрипт на STDIN, а редактор сохраняет файл скрипта в ANSI (кодировка Win 1251). Во всяком случае, проблем не случилось при использовании Venis IX.

NSIS: Отслеживание кода возврата исполняемого приложения.

Преамбула

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

При вызове внешней программы надо отследить ее работу, завершена ли она корректно, или же в процессе выполнения произошла ошибка. Могу привести довольно древний пример из своего опыта, когда-то программы распространялись на CD, и производитель материнской платы давал диск с «бонусным» софтом. Но в инсталлятор была встроена программа, определявшая модель материнской платы — если модель не совпадала, установщик «халявных» программ отказывался что-либо устанавливать. Когда я распаковал установщик и проанализировал это дело, выяснилось, что установщик сначала распаковывал и запускал внешнюю утилиту, которая проверяла модель мать-доски. И возвращала код выхода: 0 — правильная мать-доска, 1 — неправильная.

Вызов внешнего приложения и получение его кода возврата из NSIS-установщика

Вызов внешнего приложения выполняется командой

ExecWait "путь_и_имя_файла параметры_программы" [переменная_для_кода_возврата]

Например:

ExecWait "$TEMP\retcode.exe 42"

Второй параметр — переменная для сохранения кода возврата вызванной программы, т.е. если мы хотим вызывать программу и анализировать ее код завершения, надо действовать так:

;...
ExecWait "$TEMP\retcode.exe 42" $0

Далее анализируем содержимое переменной $0.

Пример

Пример инсталлятора на GitHub

Источник

Reference/ExecWait

Преобразование REG-файла в код скрипта инсталлятора NSIS

Понадобилось тут добавить ключи из REG-файла в код своего инсталлятора на NSIS. Оказывается, все придумано до нас. Имеется утилита Reg2Nsis, которая этим и занимается.

Использование

reg2nsis REG-файл [ПАРАМЕТРЫ]
или
reg2nsis ключ_реестра [ПАРАМЕТРЫ]

Т.е. утилита может преобразовать в код NSIS, как содержимое REG-файла, так и данные непосредственно из реестра.

Параметры:
-r — обработать ключ реестра со всеми подключами, начиная с указанного. Срабатывает только если в качестве источника указан путь в реестре.
-o <файл> — код будет сохранен в указанный файл
Если вместо -o <файл> указать ключ -O, то код будет сохранен в файл с именем, аналогичным указанному REG-файлу, но с расширением .nsh

Без дополнительных параметров, код NSIS будет выведен на консоль (stdout)

Программа смотрит в настройки системы и автоматически преобразует стандартные системные пути в стандартные переменные NSIS. Например, встретившийся путь C:\Windows\ будет преобразован в $WINDIR\.

Это можно отключить, добавив в командной строке ключ -s.

При анализе REG-файла, программа распознает как добавление ключа/значения, что понятно, так и команду на его удаление.
Т.е. запись в REG-файле:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\oem-drv64]

будет преобразована в:

WriteRegStr HKEY_LOCAL_MACHINE "SYSTEM\CurrentControlSet\services\oem-drv64" "" ""
А запись:

[-HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\oem-drv64]

преобразуется в следующий код:

DeleteRegKey HKEY_LOCAL_MACHINE "SYSTEM\CurrentControlSet\services\oem-drv64"

Скачать

С официальной NSIS-wiki
Копия на mega.nz

NSIS: продолжение установки после перезагрузки.

Преамбула

Например, у нас есть софтина, которая в процессе своей установки требует перезагрузку, после чего доустанавливается. Например, софт устанавливает свой драйвер, обновление, или же регистрирует системный сервис. Как реализовать инсталлятор для такой программы?

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

Алгоритм

0. Проверяем наличие уникального ключа Реестра, сигнализирующего о том, что надо выполнить вторую часть установки (после перезагрузки):

DetailPrint "Read After Reboot flag..."
ReadRegStr $R0 HKCU "Software\Microsoft\Windows\CurrentVersion\"
"${APPNAME}_afterreboot"
StrCmp "$R0" "1" AfterReboot

1. Производим все действия, которые необходимо произвести до перезагрузки (устанавливаем драйвера, регистрируем DLL и т.д.)
2. Записываем в однократную автозагрузку (ключ Реестра RunOnce) наш инсталлятор, путь к exe файлу инсталлятора, который можно взять из переменной $EXEPATH.
Автозагрузку прописываем для текущего пользователя (пользователя, запустившего установщик), т.е. в ключ HKEY_CURRENT_USER.

WriteRegStr HKCU
"Software\Microsoft\Windows\CurrentVersion\RunOnce"
"${APPNAME}" "$EXEPATH"

Примечание: В RunOnce могут записаться программы, которые требуется выполнить однократно при загрузке системы. После выполнения программ, ключ RunOnce очищается автоматически, что отличает его от ключа Run, где данные не очищаются, и его можно использовать, как постоянную автозагрузку.

3. Устанавливаем уникальный флаг, по которому будем определять, что инсталлятор был запущен после перезагрузки, например, таким образом:

DetailPrint "Write After Reboot flag..."
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\"
"${APPNAME}_afterreboot" "1"

4. Выдаем запрос на немедленную перезагрузку:

DetailPrint "Reboot request."
MessageBox MB_YESNO|MB_ICONINFORMATION
"Installation will be cobntinue after reboot. Press OK to reboot now." IDYES RebootID

Если пользователь ответил «Yes«, сразу перезагружаемся:

	RebootID:
		DetailPrint "Rebooting..."
		Reboot

Если нет, ждем перезагрузки вручную:

DetailPrint "Installation continue if user restart system."
Goto SecEnd

5. После перезагрузки удаляем флаг (проверив его наличие, см выше):
DetailPrint "Delete After Reboot flag..."
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\"
"${APPNAME}_afterreboot"

6. И продолжаем установку…
DetailPrint "Continue install after reboot..."

Скриншоты



Исходник примера

На GitHub

Глюк в NSIS: проверка параметров командной строки инсталлятора не работает

Вот серьезно, бьюсь (головой об стену) уже третий день, ну не работает функция GetOptions, так, как заявлено в NSIS Wiki, уже и по колесам стучал, и фары протирал, в смысле просто тупо копировал код из примера в тестовый инсталлятор, и еще десяток написал, перепробовав все что можно и нельзя, ну не выходит каменный цветок, хоть тресни. Функция в любом случае выдает, либо что никаких ошибок не случилось, для следующего кода, соответственно, всегда получается found:

Section
	${GetOptions} "/S /T" "/T"  $R0
 
	IfErrors 0 +2
	MessageBox MB_OK "Not found" IDOK +2
	MessageBox MB_OK "Found"
SectionEnd

Либо, для следующего кода всегда остается значение по умолчанию, даже если в командной строке явно задано иное:

Section
	${GetOptions} "/AR=N" "/AR=" $R0
	DetailPrint "R0: $R0"
	
SectionEnd

Например, выполняешь TestInstaller.exe /AR=Y, все равно в переменной $R0 оказывается N

Такие дела.

NSIS: Совсем коротко про секции

Преамбула

Секции — это удобный механизм, позволяющий обеспечить пользователю выбор компонентов программы, если такие присутствуют. В каждой секции можно создавать свои ярлыки, распаковывать файлы, и производить любые другие действия, практически независимо от других секций. Естественно, значения переменных, измененные в одной секции, в других тоже будут изменены, потому что в NSIS все переменные глобальные.

Отображение секций и выбор компонентов

Для самого простого выбора компонентов в NSIS, необходимо вставить в скрипт, перед, собственно, описанием секции, следующий код:

ComponentText "Какой-то текст", например:

ComponentText "Choose which features of ${APPNAMEANDVERSION} you want to install."

Естественно, ${APPNAMEANDVERSION} должна быть заранее определена:

!define APPNAME "SectionsExample"
!define APPNAMEANDVERSION " SectionsExample 0.1"


Окно инсталлятора с выбором компонентов

Простая секция

В любом NSIS-скрипте должна быть создана хотя бы одна секция, иначе компилятор будет ругаться, и файл установщика не создаст. В самом простом случае, когда не требуется выбор компонентов, создается единственная секция без названия:

Section
	DetailPrint "Hello, world!"	
SectionEnd


Примечание: После конструкции Section идет имя секции, заключенное в кавычки. Если кавычки пропустить, то имя секции будет некорректным, и неверно отобразится в окне выбора компонентов. Попробуйте вставить следующий код в пример, и посмотрите, как он будет работать. Ссылка на пример в конце заметки.

Section Component 4

	; Wrong section description
	SetOverwrite on
	
	DetailPrint "Component #4 installing..."
	
SectionEnd

Основные компоненты. Блокируем возможность снятия checkbox’а (галочки) пользователем

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

Для того, чтобы заблокировать чекбокс, прописываем в начале секции конструкцию:

SectionIn RO

Section "Main Program and components"

	; Checkbox select and disable user changes
	SectionIn RO
	SetOverwrite on
	
	DetailPrint "Main program installing..."
	
SectionEnd

Чекбокс (галочка) выбранный по умолчанию

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

Section "Component #2"

	; Checkbox enabled by default & user will change it state
	SetOverwrite on
	
	DetailPrint "Component #2 installing..."
	
SectionEnd

Сделать, чтобы галочка была снята по умолчанию

Наверное, самый частый вопрос, задаваемый, что в рунете, что в Интернете вообще, по поводу NSIS. Так вот, ларчик открывается просто. Достаточно после Section поставить специальный флаг /o.

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

Section /o "Component #3"

	; Checkbox disabled by default & user will change it state
	SetOverwrite on
	
	DetailPrint "Component #3 installing..."
	
SectionEnd

Скрытые секции

Такие секции не будут отображены в окне выбора компонентов. Их названия пишутся без кавычек и без пробела, а перед названием ставится знак «-«. Код в таких секциях будет выполнен в любом случае, независимо от желания пользователя:

Section -HiddenSection
	DetailPrint "This is a hidden section"
	DetailPrint "This code running anyway."
SectionEnd

Специальные секции

К ним, наверное, относится одна — Uninstall, ну и еще не совсем секция, а встроенная функция, которую можно переопределить, дописав свой код, т.е. что-то типа обработчика событий в «больших» языках, например в C#

О секции Uninstall и рассказывать особо не надо, кроме ремарки, что в секции Uninstall «забываются» все значения глобальных переменных и значение переменной $INSTDIR

Исходник примера с секциями

На GitHub

Глюк (недоработка) uninstaller’а NSIS.

Или как поместить uninstaller не в директории программы.

Преамбула

Конечно, возникает вопрос «зачем?». Ну, например, мы устанавливаем некое системное приложение, драйвер, или хотим что-то пропатчить. В качестве каталога по умолчанию для такого приложения может служить, например, директория $WINDIR (обычно C:\Windows). Если мы хотим безболезненно удалить такое приложение, то нам нужно создать анинсталлер, но, совсем не обязательно чтоб он хранился в том же каталоге, что и установленное приложение. Зачем, например, загаживать каталог C:\Windows файлами uninstall.exe. Да в том то все и дело, что незачем, и вот тут мы сталкиваемся с глюками анинсталлера.

Баг uninstaller’а

В анинстайлере и инсталляторе переменная $INSTDIR определяется по-разному. В секции установки $INSTDIR можно задать, в т.ч. и автоматически, а в uninstaller’е она определяется, как директория, из которой запустился анинсталлер. Это довольно легко проиллюстрировать простым кодом, который на самом деле ничего не устанавливает, а просто предлагает пользователю выбрать каталог для установки, а сам выводит пути к каталогам и создает тестовый анинсталлер во временном каталоге. Анинсталлер тоже ничего не удаляет, просто выводит значения переменных, заданных в основном скрипте.

Любые пользовательские переменные при работе uninstall так же «забываются», вне зависимости от того, были ли они определены в секции, или в скрипте вообще.

Фрагмент кода из установщика:

; явно задаем директорию для установки
InstallDir "$PROGRAMFILES\TestUninst" 
;создаем тестовую переменную
Var TESTVAR
;[..]
;выводим окно для выбора директории для установки
DirText "Choose the folder in which to install ${APPNAME}."

Section "Inst"

	; Set Section properties
	SetOverwrite on
	
	StrCpy $TESTVAR "Test variable value"

	DetailPrint "INSTDIR (install): $INSTDIR"
	DetailPrint "TESTVAR (install): $TESTVAR"
	WriteUninstaller "$TEMP\TestUninst.exe"

SectionEnd

Секция установки:

1. Заполняем $TESTVAR
2. Выходим значение $TESTVAR
3. Выводим значение $INSTDIR
4. Создаем Uninstall’ер, причем не в $INSTDIR


В секции удаления выводим значения $INSTDIR и $TESTVAR, анинсталлер запускаем из временного каталога.

Section Uninstall
	
	SetDetailsView show
	DetailPrint "INSTDIR (uninstall): $INSTDIR"
	DetailPrint "TESTVAR (uninstall): $TESTVAR"
	
SectionEnd

Значение $INSTDIR изменилось на временный каталог, а значение $TESTVAR было потеряно. Если переместить анинсталлер в другой каталог, значение $INSTDIR опять поменяется.

Исходник, иллюстрирующий баг

Размещение uninstall в не в $INSTDIR

Из вышеизложенного напрашивается вывод, что значения $INSTDIR и нужных переменных надо сохранять на этапе установки, например в Реестр или INI-файл, а на этапе удаления восстанавливать.

В следующем примере я буду сохранять нужные значения в INI-файл, который будет располагаться в директории с uninstall.exe, а сама программа будет устанавливаться в другой каталог. Сгенерирую простой скрипт мастером скриптов и немного подправлю.

1. Меняем InstallDir на "$TEMP\TestApp"
InstallDir "$TEMP\TestApp"

2. Заводим переменную $UNINSTDIR
Var UNINSTDIR

3. В секции установки задаем ее значение, скажем "$PROGRAMFILES\TestApp"
StrCpy $UNINSTDIR "$PROGRAMFILES\TestApp"

4. Создаем каталог $UNINSTDIR
CreateDirectory "$UNINSTDIR"

5. Меняем где надо пути в создаваемых ярлыках
CreateShortCut "$SMPROGRAMS\TestApp\Uninstall.lnk" "$UNINSTDIR\uninstall.exe"

6. …путь для записи uninstall.exe
WriteUninstaller "$UNINSTDIR\uninstall.exe"

7. …и запись в Реестре для «Программ и компонентов»
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$UNINSTDIR\uninstall.exe"

8. После создания uninstall.exe в каталоге $UNINSTDIR необходимо создать INI-файл, в который сохранить пути к каталогам $UNINSTDIR и $INSTDIR

WriteINIStr "$UNINSTDIR\dirs.ini" "dirs" "I" "$INSTDIR"
WriteINIStr "$UNINSTDIR\dirs.ini" "dirs" "U" "$UNINSTDIR"

На этом с секцией установки закончили.

В секции удаления:

1. Читаем значения из INI-файла (который для uninstall.exe будет располагаться в каталоге из переменной $INSTDIR) в перезаписываемые переменные $0 и $1. Если чтение не удалось, необходимо будет обработать ошибку (см. описания функций ReadINIStr и конструкции IfErrors в справочнике копия).

ReadINIStr $0 "$INSTDIR\dirs.ini" "dirs" "I"
IfErrors Dupa 0
ReadINIStr $1 "$INSTDIR\dirs.ini" "dirs" "U"
IfErrors Dupa 0

2. Восстанавливаем значения $INSTDIR и $UNINSTDIR:

StrCpy $INSTDIR $0
StrCpy $UNINSTDIR $1

3. Удаляем uninstall.exe, INI-файл и каталог анинсталлера:

Delete "$UNINSTDIR\uninstall.exe"
Delete "$UNINSTDIR\dirs.ini"
RMDir "$UNINSTDIR\"

4. Удаляем компоненты программы и ярлыки.

5. В конце секции заводим метку OK:

6. После удаления программы переходим в конец секции
Goto OK

7. Перед меткой OK: заводим метку Dupa:, куда попадем если произошла ошибка чтения INI-файла. После метки Dupa: будет обработчик ошибки. В данном случае выводим пользователю сообщение о том, что INI-файл некорректный, и произвести автоматическое удаление программы нельзя.

Dupa:
	MessageBox MB_ICONSTOP|MB_OK "Error reading
        $INSTDIR\dirs.ini! Uninstall aborted!"

Исходники

1. Исходник, иллюстрирующий баг uninstaller’а
2. Исходник примера

Справочник по NSIS с OSZone

Не сильно хороший, но пусть будет.

Примечание: Если открываешь CHM-файл а страницы пустые, нажимаем на файле правой кнопкой мыши, заходим в свойства и разблокируем его (там кнопка будет)

Скачать с Mega.nz
На OSZone

NSIS: определение версии и архитектуры Windows

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

Отображение версии Windows

Для отображения версии Windows можно воспользоваться готовым кодом из NSIS wiki. На выбор предоставлены два скрипта: только для клиентских ОС и для клиентских и серверных. Для своих целей я пользовался вторым, правда в нем есть небольшой баг — клиентская Windows XP x64 определяется как Windows Server 2003:

Создаем отдельный пустой NSIS-скрипт, копируем туда код из wiki, и сохраняем в тот же каталог, где находится основной скрипт инсталлятора под любым удобным названием, например gvv.nsi.

В основном скрипте подключаем этот файл:

!include gvv.nsi

Далее вызываем GetWindowsVersion, записываем результат в переменную (например, в предопределенную $R0) и выводим результат:

${GetWindowsVersion} $R0
DetailPrint "Windows Version: $R0"

Выполнение кода, если обнаружена определенная версия Windows

Так сказать, «точное» определение версии. Применяется, когда надо выполнить определенные действия, например, в XP и только в XP, или в семерке и только в семерке.

Для этого в комплекте есть модуль WinVer.nsh, сначала подключим его:

!include WinVer.nsh

Далее используем if и один из макросов IsWin?, например IsWinXP, IsWin7, IsWin10:

${If} ${IsWinXP}
	DetailPrint "Is Windows XP"
${EndIf}

Определенная версия и выше

Применяется, когда нужно выполнять код, если версия ОС выше нужной или нужная.

В WinVer.nsh есть другой комплект макросов — AtLeastWin? (AtLeastWin7, AtLeastWin8, AtLeastWin10) которые также нужно использовать вместе с if. Например, следующий код сработает на «семерке» и всех последующих версиях Windows. В деталях установки отобразится соответствующее сообщение:

${If} ${AtLeastWin7}
	DetailPrint "Is Windows 7 or higher"
${EndIf}

Архитектура ОС

Для определения архитектуры ОС необходимо подключить модуль x64.nsh (также поставляется вместе с компилятором NSIS):

!include x64.nsh

Поддерживается определение архитектуры x86 (IsNativeIA32), PC (AMD) x64 (IsNativeAMD64) и ARM64 (IsNativeARM64) пример кода есть в самом файле x64.nsh 🙂 Мне ARM была не нужна, поэтому пример кода на три строки короче:

${If} ${IsNativeAMD64}
    DetailPrint "System Architecture: x64"
${ElseIf} ${IsNativeIA32}
    DetailPrint "System Architecture: x86"
${Else}
    DetailPrint "Unsupported CPU architecture!"
${EndIf}

Скриншоты

Под катом

Пример целиком

На GitHub

Источники

1. Get Windows version
2. Stackoverflow

NSIS: Hello, world.

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

Name "HelloWorld"
OutFile "helloworld.exe"

ShowInstDetails show
RequestExecutionLevel User

Section
	DetailPrint "Hello, world!"	
SectionEnd

Объяснение кода

Сначала пишем обязательную конструкцию Name и придумываем имя установщика, тут, естественно, "HelloWorld". Имя может содержать пробелы, оно будет отображаться в заголовках окон и окнах инсталлятора, а также попадет в список программ в «Программы и компоненты», в случае реального установщика.

Далее идет обязательная конструкция OutFile — файл установщика. Если его не прописать, то компилятор не будет знать под каким именем собрать программу установки и пошлет нас.

Следующим шагом (ShowInstDetails show) развернем окно с логом установки, как это было описано в предыдущей копия заметке.

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

Далее необходимо завести хотя бы одну секцию. Если в скрипте не будет хотя бы одной секции — компилятор прервет работу. Организуем единственную секцию без названия.

Section
	[ТУТ БУДЕТ КОД]
SectionEnd

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

Внутри секции пишем сообщение, выводящее заданный текст в окошко с логом (деталями установки):

DetailPrint "Hello, world!"

О редакторах кода для NSIS

На самом деле выбора не так, чтобы много, но какой-никакой есть. На официальном сайте предлагают пользоваться Eclipse и плагином к нему, но у меня на Клипсу, wim и TeX стойкая аллергия еще со студенческих времен.
Есть плагин для Visual Studio, но написан он китайцами для китайцев, так что нафиг. Впрочем, может есть и нормальные плагины, но я не нашел. Сам пользуюсь пожилой средой Venis IX, там есть мастер для генерации простых установщиков, которого вполне хватает, чтоб постоянно не писать одно и то же, создавая новый инсталлятор.

Исходник

На GitHub

NSIS: Открыть окошко с логом установки (Show details) по умолчанию

Если ничего не делать, детали установки по умолчанию скрыты:

Чтобы лог установки сразу был виден пользователю, необходимо применить один из следующих методов:

1. В коде скрипта, до секций прописываем команду:

ShowInstDetails show

Окошко с логом (деталями установки) будет открыто

2. Прописать внутри секции команду:

SetDetailsView show

Это может быть полезно, например, если вы хотите, чтобы детали показывались и при удалении. В таком случае, вышеуказанная команда добавляется в начало секции, описывающей анинсталлер:

Section Uninstall
	
	SetDetailsView show
	
	[..]
	[..]

SectionEnd

Переделал автоматический инсталлятор для Devcon. Или куча ворчания.

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

Предыдущий я сделал на Inno Setup, но Inno Setup оказался той еще заразой, нихрена он версию Винды толком определить не мог, и тихой сапой подкладывал во все винды один и тот же devcon, который подходит для Windows 7 и выше. От чего у клиента с виндой XP x64 произошел адский баттхерт, так, что забрызгало пол-Петербурга, аж до моей деревни брызги долетели. Видимо, клиент меня проклял, потому что мне на ногу упал топор (слава Ктулху обухом), у меня упали форточки, точнее форточка, но стеклянная и на пол, ко мне какого-то черта приперлась лесная охрана и стала [РОСКОМНАДЗОР], и я после этого [РОСКОМНАДЗОР] в результате, теперь страдаю [МИНЗДРАВ].

Новый сделал на NSIS, но как оказалось — NSIS тоже [РОСКОМНАДЗОР], точнее, не сам NSIS, он мне как раз понравился, за исключением одного странного недокументированного глюка, о котором как-нибудь расскажу, и скрипт-язык, вполне себе такая помесь Бейсика с Башем. А вот документация, это, товарищи, лютый [РОСКОМНАДЗОР] в последней стадии.

Русский мануал к популярному инсталл-мэйкеру писали сто лет назад какие-то [ДАННЫЕ УДАЛЕНЫ] с OSZone.net, еще и назвали свое творение «справочником», хотя что-что а справочники так не делают. Просто плохо структурированные статьи, которые на [РОСКОМНАДЗОР] запихнули в chm-файл. [РОСКОМНАДЗОРКА], поучитесь у того же Исзелиона с wasm.ru, как нужно делать справочники и мануалы. На туториале Исзелиона сотня-другая студентов если не аспирантами стали, так свой зачет по ассемблеру точно получили. Аффтар еще и жалуется на форуме, типа, «ну на[РОСКОМНАДЗОР] обновлять мануал, если его никто не читает, и очевидные вопросы задает». Так [РОСКОМНАДЗОРКА], [РОСКОМНАДЗОРКА], ты так написал этот мануал, что кошка [РОСКОМНАДЗОРОМ] по клавиатуре лучше напишет. Но ладно, я много захотел, русского мануала. Да, согласен. Но теперь «поговорим о влажности в номере», точнее об оригинальной вики от разработчиков.

Оригинальная вики тоже [РОСКОМНАДЗОРНЫЙ], [РОСКОМНАДЗОР] твою мать, [РОСКОМНАДЗОР]. Причем, я вполне себе уверен, что там есть все описания функций языка NSIS, вот только отдельной категории для списка функций нет. Функции потом ищутся гуглем, который выводит на страницу-сироту с вполне нормальным описанием, но [РОСКОМНАДЗОР] знает, почему на эту страницу нельзя попасть из самой вики. В навигации [РОСКОМНАДЗОР] не валялся, в общем без стакана не разобраться. А еще пишут, что «хорошо документированный продукт». Документирован он может и хорошо, но найти в документации что-то конкретное, это как в ГОРФ пойти к чиновнику: «вы мне справку ЕПРСТ-1488 принесли, а надо было ЖПЧШЦ-666», и [РОСКОМНАДЗОР] знает где это написано.

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

Впрочем, инсталлер сделал:

Скачать с Mega.nz
С GitHub
Исходник (файлы devcon_all.nsi и gvv.nsi)