Несмотря на то, что сборник программ kIT Programs PowerPack существует уже более 5 лет, широкой публике он был не доступен в первую очередь потому, что не имел установщика. С выходом версии 10.9 (сентябрь 2010) было решено впервые создать установщик, который будет выполнять, кроме самого копирования, и ряд рутинных операций: создание переменной окружения, регистрация некоторых расширений и удаление старой версии, в конце концов.
После тщетных попыток смастерить радующий глаз установщик с помощью Visual Studio 2010, я решил все-таки изучить скриптовый язык, используемый в NSIS, и создать действительно удобный и компактный дистрибутив своего сборника.
Историю развития сборника я бы не назвал последовательной. Это были скорее эксперименты с таким мощным файловым менеджером как Total Commander. За годы развития были сформулированы основные принципы, которыми я руководствуюсь при развитии и совершенствовании сборника. Например, происходит постепенный отказ от варезных программ, а в версии 10.10 таких не будет ни одной.
С самого начала я создавал сборник как полностью portable. Так в самом Total Commander’е все пути настроены через системную переменную %PROGRAMSPATH% (хотя я думаю над изменением этой переменной для возможности сосуществования разных версий сборника). Все программы тестируются на работу без установки, а многие из них использованы непосредственно в portable виде.
Для развёртывания в компьютерных лабораториях моего института было не так уж слишком сложно просто копировать одну папку со всеми программами, тем более этим занимался я, а помогать мне брались и студенты-работники и групповые политики.
Но не буду слишком останавливаться на истории и составе сборника, тем более что всё необходимое можно найти на страничке на сайте моей фирмы. Главное, что потребность в создании установщика рано или поздно испытывают все разработчики и интеграторы. Я до этого тоже дорос, а, познакомившись с NSIS, понял, на сколько всё просто!
Главным пособием для меня была серия статей на сайте winreview.ru. Статьи написаны очень простым языком, затрагивают большинство вопросов, которые возникнуть при создании установщика новичком, а также снабжены достаточно полезными примерами.
За ответами на более серьёзные вопросы я обращался к документации NSIS. Для меня там не стал препятствием английский язык, так как всё описывается по существу, хорошо структурированно и снабжено отличными примерами. Хотя, наверное, мне было легко из-за владения некоторыми языками программирования. Но создать установщик ведь и понадобится только программисту!
Ну и наконец, хочу упомянуть форум разработчиков Nullsoft на сайте winamp.com.
Саму программу для создания установщиков Nullsoft Scriptable Install System можно скачать со страницы на официальном сайте. Я пользовался последней на то время версией: 2.46.
Как известно, для создания установщика нужно написать скрипт на специальном скриптовом языке, который обычно сохраняется в файлах с расширением «nsi».
Сразу приведу почти полный текст получившегося скрипта (совсем полный вариант можно скачать со страницы выпуска 10.9 сборника, а потом остановлюсь на ключевых, на мой взгляд, местах.
Кстати, очень не советую использовать для создания скрипта обычный блокнот! Я использовал Notepad++, хотя и в других хороших редакторах есть подсветка синтаксиса NSIS.
В скрипте очень удобно использовать значения, заданные глобально. Удобно потому, что однажды задав значение, можно применять псевдоним повсюду, а вполне возможно десятки раз! Если возникнет потребность (а она возникала у меня не раз!) поменять какое-то значение, например, название группы для программы в меню Пуск, то менять придётся лишь в одном месте.
Объявляются такие глобальные константы с помощью ключевого слова !define. Например, так я объявил название группы в Пуске:
А использовать эту константу можно так:
Это скопирует ярлык одну из подпапок в меню пуск.
Обратите внимание: $SMPROGRAMS — это встроенная константа, фигурные скобки не ставятся, а ${STARTMENUGROUP} — пользовательская, объявленная мной, заключается в фигурные скобки.
Существует довольно большое количество встроенных констант, и я перечислю только те, что применил:
$INSTDIR — хранит путь, выбранный пользователем для установки программы. Путь по умолчанию (или единственно возможный, если выбор пользователю не предоставляется) задаётся командой InstallDir.
$STARTMENU — Главное меню в Меню Пуск.
$SMPROGRAMS — Все программы в Меню Пуск.
$DESKTOP — Рабочий стол.
$TEMP — временная папка.
Стандартные заготовки в NSIS очень разнообразны и охватывают практически все аспекты, которые могут встретиться в более или менее простых установщиках. С помощью ключевого слова !insertmacro можно задать, например, что в установщике будет показываться страница приветствия (MUI_PAGE_WELCOME), выбора целевой папки (MUI_PAGE_DIRECTORY) и т.д. То есть по сути можно в декларативной форме определить алгоритм работы программы-установщика.
Таким же образом можно зарегистрировать ассоциацию с расширением:
Можно зарегистрировать локализованные описания различных пунктов, как это сделано в конце моего скрипта.
Я уверен, что не открыл для себя и 5% из существующих макросов. Очевидно, в процессе создания установщика будут возникать задачи, которые наверняка уже решены стандартными макросами, а если нет, то NSIS поддерживает плагины, которые могут предоставлять собственные макросы!
Специфика kIT Programs PowerPack требовала обнаружить старые версии сборника и предложить пользователю удалить их. Подобные вещи должны быть выполнены ещё до начала основной установки, а функция .onInit как раз выполняется сразу после запуска установщика.
Здесь я сначала читаю из реестра, зарегистрирована ли в системе переменная окружения, и, если да, значит, как минимум старая сборка в системе используется (возможно, просто скопирована). Затем — имеется ли запись о сборнике и его версии в стандартном для этого месте.
После чего нехитрыми условными выражениями ${If}, ${AndIf} и ${EndIf} я принимаю решение: либо просто удаляю старую сборку, либо запускаю деинсталлятор. Кстати, библиотека, подключённая строкой !include «LogicLib.nsh», предоставляет не только условные операторы, но цикла, выбора и т.д.
По окончании работы данной функции начинается, собственно, установка.
Секции в скрипте NSIS служат для объединения нескольких действий в одно комплексное. На самом деле вне секций вообще нельзя задавать какие бы то ни было действия — только декларации и подключения макросов. Секции одновременно являются и объектами установки, то есть именно отдельные секции (точнее их имена) будут отображены в списке устанавливаемых компонентов.
Секцию можно сделать невидимой, снабдив имя знаком минуса в начале.
Можно пометить секцию важной (восклицательным знаком), тогда соответствующая строчка выводится жирной.
Управлять расстановкой галочек на компонентах можно с помощью ключевого слова SectionIn:
Предусмотрены также несколько встроенных типов секций. Их имена записываются без кавычек. Наиболее часто применяемая из них — секция деинсталлятора Uninstall. Внутри неё можно записать все действия, которые должен совершать деинсталлятор. NSIS не поддерживает технологию Windows Installer, поэтому деинсталлятор — это отдельный exe-файл, который, впрочем, создаётся установщиком при встрече подобной секции:
Ну и последнее, о чём хочу сказать здесь, секции можно объединять в группы. При этом и в списке компонентов для установки формируется некая иерархия, так что, ставя/снимая галочку с группы, пользователь делает это для всех элементов этой группы. Кстати, скрытыми и важными (жирными) могут быть и группы:
Внутри секций могут быть любые мыслимые действия: работа с файлами, реестром, правами доступа и т.д.
Естественно, чаще других применяются следующие команды.
SetOutPath — позволяет задать текущую папку на целевой системе:
File — копирует в указанные файлы в текущую папку. Здесь важно понять один момент: в аргументе указывается имя файла (с путём), расположенного на машине создателя установщика, а NSIS автоматически упакует эти файлы в дистрибутив, и на целевой машине они будут копироваться из сжатого архива. Другими словами эта команда убивает двух зайцев! Очень полезной опцией этой команды является ключ /r — рекурсивное копирование всей указанной папки.
SetShellVarContext — позволяет управлять обработкой таких папок, как Мои документы, Рабочий стол, Главное меню и т.д. Существуют как общие для всех пользователей такие папки, так и индивидуальные. Я использовал параметр all, то есть копировал, в частности, ярлыки для всех пользователей, а значение current сделало бы это только для текущего пользователя.
WriteRegExpandStr — позволяет создать запись в реестре. В параметрах указываются путь (то есть ветка), название ключа и его значение:
ExecWait (и просто Exec) — позволяют запустить сторонний исполняемый файл и дождаться (или нет) его завершения. Обычно такие программы предварительно копируются в папку TEMP:
Ещё стоит отметить возможность использовать локальные и глобальные переменные. Глобальные имеют имена $0, $1, …, $9 и могут передавать значения сквозь секции, а локальные — $R0, $R1, …, $R9, и действуют только внутри одной секции. Тип для переменных, к счастью, не нужно объявлять явно.
Я успел наступить на грабли с деинсталлятором, надеюсь, читателю эта ремарка поможет этого избежать.
Дело в том, что в деинсталляторе (в секции Uninstall) переменная $INSTDIR хранит путь, где лежит сам деинсталлятор, а не путь, куда ставится программа! С одной стороны может показаться нелогичным, что в одном и том же скрипте одна и та же глобальная переменная принимает разные значения, но следует помнить, что деинсталлятор — это отдельный и независимый исполняемый файл. Кроме того, его обычно помещают прямо в корень папки, куда устанавливается программа, поэтому путь в $INSTDIR совпадает в обоих случаях.
Я же по непонятным причинам решил поместить деинсталлятор в папку C:\Windows… Каково же было моё удивление, когда он за несколько секунд снёс всё, что смог из этой папки командой:
и, естественно, убил мою виртуальную машину с Windows XP…
На момент написания этой статьи я уже приступил к созданию kIT Programs PowerPack версии 10.10, и понимаю, что в установщике придётся вносить совсем незначительные изменения. То есть создание установщика — это очень даже благодарный и не утомительный труд! Продукт ощущается гораздо более законченным и добротным, когда имеет дистрибутив.
Изучая NSIS, я успел узнать, что он позволяет с лёгкостью создавать собственные страницы мастера установки почти как в системах визуального программирования, определять и вызывать пользовательские функции, которые могут использовать серьёзный API NSIS, и многое другое. Но, как видно, эти возможности мне не потребовались для данного дистрибутива. Наверное, это только пока!
После тщетных попыток смастерить радующий глаз установщик с помощью Visual Studio 2010, я решил все-таки изучить скриптовый язык, используемый в NSIS, и создать действительно удобный и компактный дистрибутив своего сборника.
Кратко о сборнике kIT Programs PowerPack
Историю развития сборника я бы не назвал последовательной. Это были скорее эксперименты с таким мощным файловым менеджером как Total Commander. За годы развития были сформулированы основные принципы, которыми я руководствуюсь при развитии и совершенствовании сборника. Например, происходит постепенный отказ от варезных программ, а в версии 10.10 таких не будет ни одной.
С самого начала я создавал сборник как полностью portable. Так в самом Total Commander’е все пути настроены через системную переменную %PROGRAMSPATH% (хотя я думаю над изменением этой переменной для возможности сосуществования разных версий сборника). Все программы тестируются на работу без установки, а многие из них использованы непосредственно в portable виде.
Для развёртывания в компьютерных лабораториях моего института было не так уж слишком сложно просто копировать одну папку со всеми программами, тем более этим занимался я, а помогать мне брались и студенты-работники и групповые политики.
Но не буду слишком останавливаться на истории и составе сборника, тем более что всё необходимое можно найти на страничке на сайте моей фирмы. Главное, что потребность в создании установщика рано или поздно испытывают все разработчики и интеграторы. Я до этого тоже дорос, а, познакомившись с NSIS, понял, на сколько всё просто!
Материалы, которые мне пригодились
Главным пособием для меня была серия статей на сайте winreview.ru. Статьи написаны очень простым языком, затрагивают большинство вопросов, которые возникнуть при создании установщика новичком, а также снабжены достаточно полезными примерами.
За ответами на более серьёзные вопросы я обращался к документации NSIS. Для меня там не стал препятствием английский язык, так как всё описывается по существу, хорошо структурированно и снабжено отличными примерами. Хотя, наверное, мне было легко из-за владения некоторыми языками программирования. Но создать установщик ведь и понадобится только программисту!
Ну и наконец, хочу упомянуть форум разработчиков Nullsoft на сайте winamp.com.
Саму программу для создания установщиков Nullsoft Scriptable Install System можно скачать со страницы на официальном сайте. Я пользовался последней на то время версией: 2.46.
Скрипт создания установщика
Как известно, для создания установщика нужно написать скрипт на специальном скриптовом языке, который обычно сохраняется в файлах с расширением «nsi».
Сразу приведу почти полный текст получившегося скрипта (совсем полный вариант можно скачать со страницы выпуска 10.9 сборника, а потом остановлюсь на ключевых, на мой взгляд, местах.
Кстати, очень не советую использовать для создания скрипта обычный блокнот! Я использовал Notepad++, хотя и в других хороших редакторах есть подсветка синтаксиса NSIS.
!macro RegisterExtension EXT NAME PROG ICON
WriteRegStr HKLM "Software\Classes\.${EXT}" "" "${NAME}"
WriteRegStr HKLM "Software\Classes\${NAME}" "" "${NAME}"
WriteRegStr HKLM "Software\Classes\${NAME}\DefaultIcon" "" "$INSTDIR\TC\ICONS\${ICON}.ico"
WriteRegStr HKLM "Software\Classes\${NAME}\shell" "" "Open"
WriteRegStr HKLM "Software\Classes\${NAME}\shell\Open\command" "" "$\"$INSTDIR\${PROG}$\" $\"%1$\""
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.${EXT}\UserChoice"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.${EXT}\UserChoice" "Progid" "${NAME}"
!macroend
;====================
!define PRODUCT_NAME "kIT Programs PowerPack"
!define PRODUCT_VERSION "10.9"
!define PKGDIR "d:\+WORK\kIT PPP\KITPPP\"
!define COMMANDER_NAME "Total Commander 7.55a"
!define STARTMENUGROUP "Программы (kIT PPP)"
!define MINI_NAME "kITPPP"
!define HKLM_ENV 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
!define HKCU_ENV 'HKCU "Environment"'
!define HKLM_SOFT 'HKLM SOFTWARE\kITPPP'
!include "MUI.nsh"
!include "LogicLib.nsh"
!include "winmessages.nsh"
SetCompressor /SOLID lzma
!define MUI_ABORTWARNING
!define MUI_ICON "${PKGDIR}PROGRAMS\TC\ICONS\kitppp.ico"
!define MUI_UNICON "${PKGDIR}kitppp_uninstall.ico"
!define MUI_COMPONENTSPAGE_SMALLDESC
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
!insertmacro MUI_LANGUAGE "Russian"
InstType "Полная"
InstType "Сокращенная"
InstType "Минимальная"
InstType /CUSTOMSTRING=Пользовательская
Name "${PRODUCT_NAME}"
Caption "Установка ${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "D:\Release\kITPPP${PRODUCT_VERSION}.exe"
InstallDir "C:\PROGRAMS"
ShowInstDetails show
Function .onInit
ReadRegStr $R0 ${HKLM_ENV} PROGRAMSPATH
ReadRegStr $R1 ${HKLM_SOFT} Version
${If} $R0 != ""
${AndIf} $R1 == ""
MessageBox MB_ICONQUESTION|MB_YESNO "Обнаружена старая версия ${PRODUCT_NAME}, скопированная в папку $R0. Удалить?" IDYES delete
Goto done
${EndIf}
${If} $R1 != ""
MessageBox MB_ICONQUESTION|MB_YESNO "Установлена ${PRODUCT_NAME} $R1 в папку $R0. Рекомендуется удалить старую версию. Удалить?" IDYES uninstall
Goto done
${EndIf}
delete:
RMDir /r "$R0"
RMDir /r "$SMPROGRAMS\${STARTMENUGROUP}"
RMDir /r "$SMPROGRAMS\ПРОГРАММЫ (kIT PowerPack)" ;старое название
RMDir /r "$STARTMENU\ПРОГРАММЫ (kIT PowerPack)" ;старое название
Goto done
uninstall:
ExecWait '"$R0\uninstall.exe" _?=$INSTDIR'
done:
FunctionEnd
Section "!${COMMANDER_NAME}" secCOMMANDER
SectionIn 1 2 3 RO
SetOutPath "$INSTDIR"
File /r "${PKGDIR}\PROGRAMS\TC"
;прописаться в реестре
WriteRegExpandStr ${HKLM_ENV} PROGRAMSPATH $INSTDIR
WriteRegExpandStr ${HKLM_SOFT} Product "${PRODUCT_NAME}"
WriteRegExpandStr ${HKLM_SOFT} Version "${PRODUCT_VERSION}"
SectionEnd
Section "-Environment"
WriteRegExpandStr ${HKLM_ENV} PROGRAMSPATH $INSTDIR
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
SectionEnd
SectionGroup "Создать ярлыки"
Section "в меню Пуск"
SectionIn 1 2 3
SetShellVarContext all
SetOutPath "$SMPROGRAMS\${STARTMENUGROUP}"
File "${PKGDIR}\Shortcuts\Total Commander — менеджер файлов.lnk"
!define CREATESHORTCUTS "true"
SectionEnd
Section "на Рабочем столе"
SectionIn 1 2 3
SetShellVarContext all
SetOutPath "$DESKTOP"
File "${PKGDIR}\Shortcuts\Total Commander — менеджер файлов.lnk"
SectionEnd
SectionGroupEnd
SectionGroup "!Графические редакторы"
Section "Paint.NET 3.5.5" secPAINTNET
SectionIn 1 2
SetOutPath "$TEMP"
File "${PKGDIR}\Paint.NET.3.5.5.Install.exe"
ExecWait "$TEMP\Paint.NET.3.5.5.Install.exe /auto TARGETDIR=$INSTDIR\EDIT\Paint.NET DESKTOPSHORTCUT=0 PROGRAMSGROUP=$\"${STARTMENUGROUP}\Графические редакторы$\""
Delete "$TEMP\Paint.NET.3.5.5.Install.exe"
;внешний инстолятор сам создаст ярлык
SectionEnd
Section "AWicons Pro 10.0" secAWICONS
SectionIn 1
SetOutPath "$INSTDIR\EDIT"
File /r "${PKGDIR}\PROGRAMS\EDIT\AWiconsPro"
${If} ${CREATESHORTCUTS} == "true"
SetShellVarContext all
SetOutPath "$SMPROGRAMS\${STARTMENUGROUP}\Графические редакторы"
File "${PKGDIR}\Shortcuts\Графические редакторы\AWicons Pro.lnk"
${EndIf}
SectionEnd
SectionGroupEnd
SectionGroup "!Текстовые редакторы"
Section "Notepad++ 5.7" secNOTEPADPP
SectionIn 1 2 3 RO
SetOutPath "$INSTDIR\EDIT"
File /r "${PKGDIR}\PROGRAMS\EDIT\Notepad++"
${If} ${CREATESHORTCUTS} == "true"
SetShellVarContext all
SetOutPath "$SMPROGRAMS\${STARTMENUGROUP}\Текстовые редакторы"
File "${PKGDIR}\Shortcuts\Текстовые редакторы\Notepad++.lnk"
${EndIf}
SectionEnd
Section "RJ TextEd 6.60" secRJTEXTED
SectionIn 1 2
SetOutPath "$INSTDIR\EDIT"
File /r "${PKGDIR}\PROGRAMS\EDIT\RJ TextEd"
${If} ${CREATESHORTCUTS} == "true"
SetShellVarContext all
SetOutPath "$SMPROGRAMS\${STARTMENUGROUP}\Текстовые редакторы"
File "${PKGDIR}\Shortcuts\Текстовые редакторы\RJ TextEd.lnk"
${EndIf}
SectionEnd
...
SectionGroupEnd
Section "-Uninstaller"
WriteUninstaller "$INSTDIR\uninstall.exe"
WriteRegStr HKLM SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MINI_NAME} "DisplayName" "${PRODUCT_NAME} ${PRODUCT_VERSION}"
WriteRegStr HKLM SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MINI_NAME} "UninstallString" "$INSTDIR\uninstall.exe"
SectionEnd
Section Uninstall secUNINSTALL
SetShellVarContext all
;удаляем Paint.NET через MSI HKEY_LOCAL_MACHINE\SOFTWARE\Paint.NET
ReadRegStr $R0 HKLM "SOFTWARE\Paint.NET" "ProductCode"
${IfNot} $R0 == ""
ExecWait "msiexec.exe /x $R0 /qn"
${EndIf}
;удаляем папку
RMDir /r "$INSTDIR"
;удаляем ярлыки
SetShellVarContext all
RMDir /r "$SMPROGRAMS\${STARTMENUGROUP}"
Delete "$DESKTOP\Total Commander — менеджер файлов.lnk"
;удаляем из реестра
DeleteRegKey HKLM SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MINI_NAME}
DeleteRegKey HKLM SOFTWARE\${MINI_NAME}
;удаляем переменную среды
DeleteRegValue ${HKLM_ENV} PROGRAMSPATH
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
SectionEnd
LangString descCOMMANDER ${LANG_RUSSIAN} "Файловый менеджер. Дополнен множеством плагинов. Имеет улучшенную панель инструментов."
LangString descPAINTNET ${LANG_RUSSIAN} "Растровый редактор, поддерживающий работу со слоями и различные эффекты."
LangString descAWICONS ${LANG_RUSSIAN} "Редактор иконок и курсоров."
LangString descNOTEPADPP ${LANG_RUSSIAN} "Простой в использовании, но мощный текстовый редактор."
LangString descRJTEXTED ${LANG_RUSSIAN} "Мощный текстовый редактор, ориентированный на программирование и web-разработку."
...
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${secCOMMANDER} $(descCOMMANDER)
!insertmacro MUI_DESCRIPTION_TEXT ${secPAINTNET} $(descPAINTNET)
!insertmacro MUI_DESCRIPTION_TEXT ${secAWICONS} $(descAWICONS)
!insertmacro MUI_DESCRIPTION_TEXT ${secNOTEPADPP} $(descNOTEPADPP)
!insertmacro MUI_DESCRIPTION_TEXT ${secRJTEXTED} $(descRJTEXTED)
...
!insertmacro MUI_FUNCTION_DESCRIPTION_END
Глобальные константы
В скрипте очень удобно использовать значения, заданные глобально. Удобно потому, что однажды задав значение, можно применять псевдоним повсюду, а вполне возможно десятки раз! Если возникнет потребность (а она возникала у меня не раз!) поменять какое-то значение, например, название группы для программы в меню Пуск, то менять придётся лишь в одном месте.
Объявляются такие глобальные константы с помощью ключевого слова !define. Например, так я объявил название группы в Пуске:
!define STARTMENUGROUP "Программы (kIT PPP)"
А использовать эту константу можно так:
SetOutPath "$SMPROGRAMS\${STARTMENUGROUP}\Системные утилиты"
File "${PKGDIR}\Shortcuts\Системные утилиты\Autoruns — диспетчер автозапуска.lnk"
Это скопирует ярлык одну из подпапок в меню пуск.
Обратите внимание: $SMPROGRAMS — это встроенная константа, фигурные скобки не ставятся, а ${STARTMENUGROUP} — пользовательская, объявленная мной, заключается в фигурные скобки.
Существует довольно большое количество встроенных констант, и я перечислю только те, что применил:
$INSTDIR — хранит путь, выбранный пользователем для установки программы. Путь по умолчанию (или единственно возможный, если выбор пользователю не предоставляется) задаётся командой InstallDir.
$STARTMENU — Главное меню в Меню Пуск.
$SMPROGRAMS — Все программы в Меню Пуск.
$DESKTOP — Рабочий стол.
$TEMP — временная папка.
Подключение модулей и стандартных заготовок
Стандартные заготовки в NSIS очень разнообразны и охватывают практически все аспекты, которые могут встретиться в более или менее простых установщиках. С помощью ключевого слова !insertmacro можно задать, например, что в установщике будет показываться страница приветствия (MUI_PAGE_WELCOME), выбора целевой папки (MUI_PAGE_DIRECTORY) и т.д. То есть по сути можно в декларативной форме определить алгоритм работы программы-установщика.
Таким же образом можно зарегистрировать ассоциацию с расширением:
!insertmacro RegisterExtension "7z" "7z.Archive" "TOOL\7Z\7zFM.exe" "7z"
Можно зарегистрировать локализованные описания различных пунктов, как это сделано в конце моего скрипта.
Я уверен, что не открыл для себя и 5% из существующих макросов. Очевидно, в процессе создания установщика будут возникать задачи, которые наверняка уже решены стандартными макросами, а если нет, то NSIS поддерживает плагины, которые могут предоставлять собственные макросы!
Функция .onInit
Специфика kIT Programs PowerPack требовала обнаружить старые версии сборника и предложить пользователю удалить их. Подобные вещи должны быть выполнены ещё до начала основной установки, а функция .onInit как раз выполняется сразу после запуска установщика.
Здесь я сначала читаю из реестра, зарегистрирована ли в системе переменная окружения, и, если да, значит, как минимум старая сборка в системе используется (возможно, просто скопирована). Затем — имеется ли запись о сборнике и его версии в стандартном для этого месте.
ReadRegStr $R0 ${HKLM_ENV} PROGRAMSPATH
ReadRegStr $R1 ${HKLM_SOFT} Version
После чего нехитрыми условными выражениями ${If}, ${AndIf} и ${EndIf} я принимаю решение: либо просто удаляю старую сборку, либо запускаю деинсталлятор. Кстати, библиотека, подключённая строкой !include «LogicLib.nsh», предоставляет не только условные операторы, но цикла, выбора и т.д.
По окончании работы данной функции начинается, собственно, установка.
Роль секций
Секции в скрипте NSIS служат для объединения нескольких действий в одно комплексное. На самом деле вне секций вообще нельзя задавать какие бы то ни было действия — только декларации и подключения макросов. Секции одновременно являются и объектами установки, то есть именно отдельные секции (точнее их имена) будут отображены в списке устанавливаемых компонентов.
Секцию можно сделать невидимой, снабдив имя знаком минуса в начале.
Section "-Environment"
WriteRegExpandStr ${HKLM_ENV} PROGRAMSPATH $INSTDIR
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
SectionEnd
Здесь, кстати, и регистрируется системная переменная. Причём системе подаётся сигнал обновить окружение.Можно пометить секцию важной (восклицательным знаком), тогда соответствующая строчка выводится жирной.
Управлять расстановкой галочек на компонентах можно с помощью ключевого слова SectionIn:
Section "!${COMMANDER_NAME}" secCOMMANDER
SectionIn 1 2 3 RO
...
Здесь цифры 1, 2 и 3 относятся к чуть выше объявленным с помощью ключевого слова InstType типам установки: «Полная», «Сокращённая» и «Минимальная». Пометка RO означает Read Only, то есть компонент показывается, галочка на нем стоит, но снять её пользователь не сможет — очень удобно для критически необходимых компонентов.Предусмотрены также несколько встроенных типов секций. Их имена записываются без кавычек. Наиболее часто применяемая из них — секция деинсталлятора Uninstall. Внутри неё можно записать все действия, которые должен совершать деинсталлятор. NSIS не поддерживает технологию Windows Installer, поэтому деинсталлятор — это отдельный exe-файл, который, впрочем, создаётся установщиком при встрече подобной секции:
Section "-Uninstaller"
WriteUninstaller "$INSTDIR\uninstall.exe"
WriteRegStr HKLM SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MINI_NAME} "DisplayName" "${PRODUCT_NAME} ${PRODUCT_VERSION}"
WriteRegStr HKLM SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MINI_NAME} "UninstallString" "$INSTDIR\uninstall.exe"
SectionEnd
Эти записи в реестре поместят пункт для программы в список Диспетчера установки и удаления программ.Ну и последнее, о чём хочу сказать здесь, секции можно объединять в группы. При этом и в списке компонентов для установки формируется некая иерархия, так что, ставя/снимая галочку с группы, пользователь делает это для всех элементов этой группы. Кстати, скрытыми и важными (жирными) могут быть и группы:
SectionGroup "!Графические редакторы"
Section "Paint.NET 3.5.5" secPAINTNET
SectionIn 1 2
...
Действия внутри секций
Внутри секций могут быть любые мыслимые действия: работа с файлами, реестром, правами доступа и т.д.
Естественно, чаще других применяются следующие команды.
SetOutPath — позволяет задать текущую папку на целевой системе:
SetOutPath "$INSTDIR\EDIT"
File — копирует в указанные файлы в текущую папку. Здесь важно понять один момент: в аргументе указывается имя файла (с путём), расположенного на машине создателя установщика, а NSIS автоматически упакует эти файлы в дистрибутив, и на целевой машине они будут копироваться из сжатого архива. Другими словами эта команда убивает двух зайцев! Очень полезной опцией этой команды является ключ /r — рекурсивное копирование всей указанной папки.
File /r "${PKGDIR}\PROGRAMS\EDIT\Notepad++"
SetShellVarContext — позволяет управлять обработкой таких папок, как Мои документы, Рабочий стол, Главное меню и т.д. Существуют как общие для всех пользователей такие папки, так и индивидуальные. Я использовал параметр all, то есть копировал, в частности, ярлыки для всех пользователей, а значение current сделало бы это только для текущего пользователя.
WriteRegExpandStr — позволяет создать запись в реестре. В параметрах указываются путь (то есть ветка), название ключа и его значение:
WriteRegExpandStr ${HKLM_SOFT} Product "${PRODUCT_NAME}"
ExecWait (и просто Exec) — позволяют запустить сторонний исполняемый файл и дождаться (или нет) его завершения. Обычно такие программы предварительно копируются в папку TEMP:
SetOutPath "$TEMP"
File "${PKGDIR}\Paint.NET.3.5.5.Install.exe"
ExecWait "$TEMP\Paint.NET.3.5.5.Install.exe /auto TARGETDIR=$INSTDIR\EDIT\Paint.NET DESKTOPSHORTCUT=0 PROGRAMSGROUP=$\"${STARTMENUGROUP}\Графические редакторы$\""
Delete "$TEMP\Paint.NET.3.5.5.Install.exe"
Ещё стоит отметить возможность использовать локальные и глобальные переменные. Глобальные имеют имена $0, $1, …, $9 и могут передавать значения сквозь секции, а локальные — $R0, $R1, …, $R9, и действуют только внутри одной секции. Тип для переменных, к счастью, не нужно объявлять явно.
Особенности деинсталлятора
Я успел наступить на грабли с деинсталлятором, надеюсь, читателю эта ремарка поможет этого избежать.
Дело в том, что в деинсталляторе (в секции Uninstall) переменная $INSTDIR хранит путь, где лежит сам деинсталлятор, а не путь, куда ставится программа! С одной стороны может показаться нелогичным, что в одном и том же скрипте одна и та же глобальная переменная принимает разные значения, но следует помнить, что деинсталлятор — это отдельный и независимый исполняемый файл. Кроме того, его обычно помещают прямо в корень папки, куда устанавливается программа, поэтому путь в $INSTDIR совпадает в обоих случаях.
Я же по непонятным причинам решил поместить деинсталлятор в папку C:\Windows… Каково же было моё удивление, когда он за несколько секунд снёс всё, что смог из этой папки командой:
RMDir /r "$INSTDIR"
и, естественно, убил мою виртуальную машину с Windows XP…
В заключении
На момент написания этой статьи я уже приступил к созданию kIT Programs PowerPack версии 10.10, и понимаю, что в установщике придётся вносить совсем незначительные изменения. То есть создание установщика — это очень даже благодарный и не утомительный труд! Продукт ощущается гораздо более законченным и добротным, когда имеет дистрибутив.
Изучая NSIS, я успел узнать, что он позволяет с лёгкостью создавать собственные страницы мастера установки почти как в системах визуального программирования, определять и вызывать пользовательские функции, которые могут использовать серьёзный API NSIS, и многое другое. Но, как видно, эти возможности мне не потребовались для данного дистрибутива. Наверное, это только пока!