Глюк (недоработка) 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. Исходник примера

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

  1. Pingback: NSIS: Совсем коротко про секции | Персональный блог Толика Панкова

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *