Или как поместить 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\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. Исходник примера
Pingback: NSIS: Совсем коротко про секции | Персональный блог Толика Панкова