Глюк (недоработка) uninstaller'а NSIS.
Или как поместить uninstaller не в директории программы.
Конечно, возникает вопрос "зачем?". Ну, например, мы устанавливаем некое системное приложение, драйвер, или хотим что-то пропатчить. В качестве каталога по умолчанию для такого приложения может служить, например, директория
$WINDIR
(обычно C:\Windows
). Если мы хотим безболезненно удалить такое приложение, то нам нужно создать анинсталлер, но, совсем не обязательно чтоб он хранился в том же каталоге, что и установленное приложение. Зачем, например, загаживать каталог C:\Windows
файлами uninstall.exe
. Да в том то все и дело, что незачем, и вот тут мы сталкиваемся с глюками анинсталлера.В анинстайлере и инсталляторе переменная
$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
опять поменяется.Исходник, иллюстрирующий баг
Из вышеизложенного напрашивается вывод, что значения
$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\CurrentVersi on\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. Исходник примера
Это репост с сайта http://tolik-punkoff.com
Оригинал: http://tolik-punkoff.com/2019/07/28/glyu