Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/certbot/certbot.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'windows-installer')
-rw-r--r--windows-installer/.gitignore2
-rw-r--r--windows-installer/certbot.icobin0 -> 183198 bytes
-rw-r--r--windows-installer/construct.py192
-rw-r--r--windows-installer/renew-down.ps16
-rw-r--r--windows-installer/renew-up.ps117
-rw-r--r--windows-installer/run.bat31
-rw-r--r--windows-installer/template.nsi260
7 files changed, 508 insertions, 0 deletions
diff --git a/windows-installer/.gitignore b/windows-installer/.gitignore
new file mode 100644
index 000000000..a1a48d6b8
--- /dev/null
+++ b/windows-installer/.gitignore
@@ -0,0 +1,2 @@
+build
+build.*
diff --git a/windows-installer/certbot.ico b/windows-installer/certbot.ico
new file mode 100644
index 000000000..364c32098
--- /dev/null
+++ b/windows-installer/certbot.ico
Binary files differ
diff --git a/windows-installer/construct.py b/windows-installer/construct.py
new file mode 100644
index 000000000..f0724f5f4
--- /dev/null
+++ b/windows-installer/construct.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+import contextlib
+import ctypes
+import os
+import shutil
+import struct
+import subprocess
+import sys
+import tempfile
+import time
+
+PYTHON_VERSION = (3, 7, 4)
+PYTHON_BITNESS = 32
+PYWIN32_VERSION = 227 # do not forget to edit pywin32 dependency accordingly in setup.py
+NSIS_VERSION = '3.04'
+
+
+def main():
+ build_path, repo_path, venv_path, venv_python = _prepare_environment()
+
+ _copy_assets(build_path, repo_path)
+
+ installer_cfg_path = _generate_pynsist_config(repo_path, build_path)
+
+ _prepare_build_tools(venv_path, venv_python, repo_path)
+ _compile_wheels(repo_path, build_path, venv_python)
+ _build_installer(installer_cfg_path, venv_path)
+
+ print('Done')
+
+
+def _build_installer(installer_cfg_path, venv_path):
+ print('Build the installer')
+ subprocess.check_call([os.path.join(venv_path, 'Scripts', 'pynsist.exe'), installer_cfg_path])
+
+
+def _compile_wheels(repo_path, build_path, venv_python):
+ print('Compile wheels')
+
+ wheels_path = os.path.join(build_path, 'wheels')
+ os.makedirs(wheels_path)
+
+ certbot_packages = ['acme', 'certbot']
+ # Uncomment following line to include all DNS plugins in the installer
+ # certbot_packages.extend([name for name in os.listdir(repo_path) if name.startswith('certbot-dns-')])
+ wheels_project = [os.path.join(repo_path, package) for package in certbot_packages]
+
+ with _prepare_constraints(repo_path) as constraints_file_path:
+ command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_path, '--constraint', constraints_file_path]
+ command.extend(wheels_project)
+ subprocess.check_call(command)
+
+
+def _prepare_build_tools(venv_path, venv_python, repo_path):
+ print('Prepare build tools')
+ subprocess.check_call([sys.executable, '-m', 'venv', venv_path])
+ subprocess.check_call([venv_python, os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'pipstrap.py')])
+ subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'pynsist'])
+ subprocess.check_call(['choco', 'upgrade', '--allow-downgrade', '-y', 'nsis', '--version', NSIS_VERSION])
+
+
+@contextlib.contextmanager
+def _prepare_constraints(repo_path):
+ requirements = os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'dependency-requirements.txt')
+ constraints = subprocess.check_output(
+ [sys.executable, os.path.join(repo_path, 'tools', 'strip_hashes.py'), requirements],
+ universal_newlines=True)
+ workdir = tempfile.mkdtemp()
+ try:
+ constraints_file_path = os.path.join(workdir, 'constraints.txt')
+ with open(constraints_file_path, 'a') as file_h:
+ file_h.write(constraints)
+ file_h.write('pywin32=={0}'.format(PYWIN32_VERSION))
+ yield constraints_file_path
+ finally:
+ shutil.rmtree(workdir)
+
+
+def _copy_assets(build_path, repo_path):
+ print('Copy assets')
+ if os.path.exists(build_path):
+ os.rename(build_path, '{0}.{1}.bak'.format(build_path, int(time.time())))
+ os.makedirs(build_path)
+ shutil.copy(os.path.join(repo_path, 'windows-installer', 'certbot.ico'), build_path)
+ shutil.copy(os.path.join(repo_path, 'windows-installer', 'run.bat'), build_path)
+ shutil.copy(os.path.join(repo_path, 'windows-installer', 'template.nsi'), build_path)
+ shutil.copy(os.path.join(repo_path, 'windows-installer', 'renew-up.ps1'), build_path)
+ shutil.copy(os.path.join(repo_path, 'windows-installer', 'renew-down.ps1'), build_path)
+
+
+def _generate_pynsist_config(repo_path, build_path):
+ print('Generate pynsist configuration')
+
+ pywin32_paths_file = os.path.join(build_path, 'pywin32_paths.py')
+
+ # Pywin32 uses non-standard folders to hold its packages. We need to instruct pynsist bootstrap
+ # explicitly to add them into sys.path. This is done with a custom "pywin32_paths.py" that is
+ # referred in the pynsist configuration as an "extra_preamble".
+ # Reference example: https://github.com/takluyver/pynsist/tree/master/examples/pywebview
+ with open(pywin32_paths_file, 'w') as file_h:
+ file_h.write('''\
+pkgdir = os.path.join(os.path.dirname(installdir), 'pkgs')
+
+sys.path.extend([
+ os.path.join(pkgdir, 'win32'),
+ os.path.join(pkgdir, 'win32', 'lib'),
+])
+
+# Preload pywintypes and pythoncom
+pwt = os.path.join(pkgdir, 'pywin32_system32', 'pywintypes{0}{1}.dll')
+pcom = os.path.join(pkgdir, 'pywin32_system32', 'pythoncom{0}{1}.dll')
+import warnings
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ import imp
+imp.load_dynamic('pywintypes', pwt)
+imp.load_dynamic('pythoncom', pcom)
+'''.format(PYTHON_VERSION[0], PYTHON_VERSION[1]))
+
+ installer_cfg_path = os.path.join(build_path, 'installer.cfg')
+
+ certbot_pkg_path = os.path.join(repo_path, 'certbot')
+ certbot_version = subprocess.check_output([sys.executable, '-c', 'import certbot; print(certbot.__version__)'],
+ universal_newlines=True, cwd=certbot_pkg_path).strip()
+
+ with open(installer_cfg_path, 'w') as file_h:
+ file_h.write('''\
+[Application]
+name=Certbot
+version={certbot_version}
+icon=certbot.ico
+publisher=Electronic Frontier Foundation
+target=$INSTDIR\\run.bat
+
+[Build]
+directory=nsis
+nsi_template=template.nsi
+installer_name=certbot-beta-installer-{installer_suffix}.exe
+
+[Python]
+version={python_version}
+bitness={python_bitness}
+
+[Include]
+local_wheels=wheels\\*.whl
+files=run.bat
+ renew-up.ps1
+ renew-down.ps1
+
+[Command certbot]
+entry_point=certbot.main:main
+extra_preamble=pywin32_paths.py
+'''.format(certbot_version=certbot_version,
+ installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32',
+ python_bitness=PYTHON_BITNESS,
+ python_version='.'.join([str(item) for item in PYTHON_VERSION])))
+
+ return installer_cfg_path
+
+
+def _prepare_environment():
+ print('Prepare environment')
+ try:
+ subprocess.check_output(['choco', '--version'])
+ except subprocess.CalledProcessError:
+ raise RuntimeError('Error: Chocolatey (https://chocolatey.org/) needs '
+ 'to be installed to run this script.')
+ script_path = os.path.realpath(__file__)
+ repo_path = os.path.dirname(os.path.dirname(script_path))
+ build_path = os.path.join(repo_path, 'windows-installer', 'build')
+ venv_path = os.path.join(build_path, 'venv-config')
+ venv_python = os.path.join(venv_path, 'Scripts', 'python.exe')
+
+ return build_path, repo_path, venv_path, venv_python
+
+
+if __name__ == '__main__':
+ if not os.name == 'nt':
+ raise RuntimeError('This script must be run under Windows.')
+
+ if ctypes.windll.shell32.IsUserAnAdmin() == 0:
+ # Administrator privileges are required to properly install NSIS through Chocolatey
+ raise RuntimeError('This script must be run with administrator privileges.')
+
+ if sys.version_info[:2] != PYTHON_VERSION[:2]:
+ raise RuntimeError('This script must be run with Python {0}'
+ .format('.'.join([str(item) for item in PYTHON_VERSION[0:2]])))
+
+ if struct.calcsize('P') * 8 != PYTHON_BITNESS:
+ raise RuntimeError('This script must be run with a {0} bit version of Python.'
+ .format(PYTHON_BITNESS))
+ main()
diff --git a/windows-installer/renew-down.ps1 b/windows-installer/renew-down.ps1
new file mode 100644
index 000000000..60dc4d9e6
--- /dev/null
+++ b/windows-installer/renew-down.ps1
@@ -0,0 +1,6 @@
+$taskName = "Certbot Renew Task"
+
+$exists = Get-ScheduledTask | Where-Object {$_.TaskName -like $taskName}
+if ($exists) {
+ Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
+}
diff --git a/windows-installer/renew-up.ps1 b/windows-installer/renew-up.ps1
new file mode 100644
index 000000000..224458748
--- /dev/null
+++ b/windows-installer/renew-up.ps1
@@ -0,0 +1,17 @@
+function Get-ScriptDirectory { Split-Path $MyInvocation.ScriptName }
+$down = Join-Path (Get-ScriptDirectory) 'renew-down.ps1'
+& $down
+
+$taskName = "Certbot Renew Task"
+
+$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-NoProfile -WindowStyle Hidden -Command "certbot renew"'
+$delay = New-TimeSpan -Hours 12
+$triggerAM = New-ScheduledTaskTrigger -Daily -At 12am -RandomDelay $delay
+$triggerPM = New-ScheduledTaskTrigger -Daily -At 12pm -RandomDelay $delay
+# NB: For now scheduled task is set up under Administrators group account because Certbot Installer installs Certbot for all users.
+# If in the future we allow the Installer to install Certbot for one specific user, the scheduled task will need to
+# switch to this user, since Certbot will be available only for him.
+$adminsSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
+$adminsGroupID = $adminsSID.Translate([System.Security.Principal.NTAccount]).Value
+$principal = New-ScheduledTaskPrincipal -GroupId $adminsGroupID -RunLevel Highest
+Register-ScheduledTask -Action $action -Trigger $triggerAM,$triggerPM -TaskName $taskName -Description "Execute twice a day the 'certbot renew' command, to renew managed certificates if needed." -Principal $principal
diff --git a/windows-installer/run.bat b/windows-installer/run.bat
new file mode 100644
index 000000000..efba28800
--- /dev/null
+++ b/windows-installer/run.bat
@@ -0,0 +1,31 @@
+@echo off
+
+:: BatchGotAdmin
+:-------------------------------------
+REM --> Check for permissions
+ IF "%PROCESSOR_ARCHITECTURE%" EQU "amd64" (
+>nul 2>&1 "%SYSTEMROOT%\SysWOW64\cacls.exe" "%SYSTEMROOT%\SysWOW64\config\system"
+) ELSE (
+>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
+)
+
+REM --> If error flag set, we do not have admin.
+if '%errorlevel%' NEQ '0' (
+ echo Requesting administrative privileges...
+ goto UACPrompt
+) else ( goto gotAdmin )
+
+:UACPrompt
+ echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
+ set params= %*
+ echo UAC.ShellExecute "cmd.exe", "/c ""%~s0"" %params:"=""%", "", "runas", 1 >> "%temp%\getadmin.vbs"
+
+ "%temp%\getadmin.vbs"
+ del "%temp%\getadmin.vbs"
+ exit /B
+
+:gotAdmin
+ pushd "%CD%"
+ CD /D "%~dp0"
+:--------------------------------------
+cmd.exe /k echo You can run 'certbot' commands here. Type 'certbot --help' for more information.
diff --git a/windows-installer/template.nsi b/windows-installer/template.nsi
new file mode 100644
index 000000000..50a03865f
--- /dev/null
+++ b/windows-installer/template.nsi
@@ -0,0 +1,260 @@
+; This NSIS template is based on the built-in one in pynsist 2.3.
+; Added lines are enclosed within "CERTBOT CUSTOM BEGIN/END" comments.
+; If pynsist is upgraded, this template must be updated if necessary using the new built-in one.
+; Original file can be found here: https://github.com/takluyver/pynsist/blob/2.4/nsist/pyapp.nsi
+
+!define PRODUCT_NAME "[[ib.appname]]"
+!define PRODUCT_VERSION "[[ib.version]]"
+!define PY_VERSION "[[ib.py_version]]"
+!define PY_MAJOR_VERSION "[[ib.py_major_version]]"
+!define BITNESS "[[ib.py_bitness]]"
+!define ARCH_TAG "[[arch_tag]]"
+!define INSTALLER_NAME "[[ib.installer_name]]"
+!define PRODUCT_ICON "[[icon]]"
+
+; Marker file to tell the uninstaller that it's a user installation
+!define USER_INSTALL_MARKER _user_install_marker
+
+SetCompressor lzma
+
+; CERTBOT CUSTOM BEGIN
+; Administrator privileges are required to insert a new task in Windows Scheduler.
+; Also comment out some options to disable ability to choose AllUsers/CurrentUser install mode.
+; As a result, installer run always with admin privileges (because of MULTIUSER_EXECUTIONLEVEL),
+; using the AllUsers installation mode by default (because of MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER
+; not set), and this default behavior cannot be overridden (because of MULTIUSER_MUI not set).
+; See https://nsis.sourceforge.io/Docs/MultiUser/Readme.html
+!define MULTIUSER_EXECUTIONLEVEL Admin
+;!define MULTIUSER_EXECUTIONLEVEL Highest
+;!define MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER
+;!define MULTIUSER_MUI
+;!define MULTIUSER_INSTALLMODE_COMMANDLINE
+; CERTBOT CUSTOM END
+!define MULTIUSER_INSTALLMODE_INSTDIR "[[ib.appname]]"
+[% if ib.py_bitness == 64 %]
+!define MULTIUSER_INSTALLMODE_FUNCTION correct_prog_files
+[% endif %]
+!include MultiUser.nsh
+
+[% block modernui %]
+; Modern UI installer stuff
+!include "MUI2.nsh"
+!define MUI_ABORTWARNING
+!define MUI_ICON "[[icon]]"
+!define MUI_UNICON "[[icon]]"
+
+; UI pages
+[% block ui_pages %]
+!insertmacro MUI_PAGE_WELCOME
+[% if license_file %]
+!insertmacro MUI_PAGE_LICENSE [[license_file]]
+[% endif %]
+; CERTBOT CUSTOM BEGIN
+; Disable the installation mode page (AllUsers/CurrentUser)
+;!insertmacro MULTIUSER_PAGE_INSTALLMODE
+; CERTBOT CUSTOM END
+!insertmacro MUI_PAGE_DIRECTORY
+!insertmacro MUI_PAGE_INSTFILES
+!insertmacro MUI_PAGE_FINISH
+[% endblock ui_pages %]
+!insertmacro MUI_LANGUAGE "English"
+[% endblock modernui %]
+
+; CERTBOT CUSTOM BEGIN
+Name "${PRODUCT_NAME} (beta) ${PRODUCT_VERSION}"
+;Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
+; CERTBOT CUSTOM END
+OutFile "${INSTALLER_NAME}"
+ShowInstDetails show
+
+Section -SETTINGS
+ SetOutPath "$INSTDIR"
+ SetOverwrite ifnewer
+SectionEnd
+
+[% block sections %]
+
+Section "!${PRODUCT_NAME}" sec_app
+ SetRegView [[ib.py_bitness]]
+ SectionIn RO
+ File ${PRODUCT_ICON}
+ SetOutPath "$INSTDIR\pkgs"
+ File /r "pkgs\*.*"
+ SetOutPath "$INSTDIR"
+
+ ; Marker file for per-user install
+ StrCmp $MultiUser.InstallMode CurrentUser 0 +3
+ FileOpen $0 "$INSTDIR\${USER_INSTALL_MARKER}" w
+ FileClose $0
+ SetFileAttributes "$INSTDIR\${USER_INSTALL_MARKER}" HIDDEN
+
+ [% block install_files %]
+ ; Install files
+ [% for destination, group in grouped_files %]
+ SetOutPath "[[destination]]"
+ [% for file in group %]
+ File "[[ file ]]"
+ [% endfor %]
+ [% endfor %]
+
+ ; Install directories
+ [% for dir, destination in ib.install_dirs %]
+ SetOutPath "[[ pjoin(destination, dir) ]]"
+ File /r "[[dir]]\*.*"
+ [% endfor %]
+ [% endblock install_files %]
+
+ [% block install_shortcuts %]
+ ; Install shortcuts
+ ; The output path becomes the working directory for shortcuts
+ SetOutPath "%HOMEDRIVE%\%HOMEPATH%"
+ [% if single_shortcut %]
+ [% for scname, sc in ib.shortcuts.items() %]
+ CreateShortCut "$SMPROGRAMS\[[scname]].lnk" "[[sc['target'] ]]" \
+ '[[ sc['parameters'] ]]' "$INSTDIR\[[ sc['icon'] ]]"
+ [% endfor %]
+ [% else %]
+ [# Multiple shortcuts: create a directory for them #]
+ CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
+ [% for scname, sc in ib.shortcuts.items() %]
+ CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\[[scname]].lnk" "[[sc['target'] ]]" \
+ '[[ sc['parameters'] ]]' "$INSTDIR\[[ sc['icon'] ]]"
+ [% endfor %]
+ [% endif %]
+ SetOutPath "$INSTDIR"
+ [% endblock install_shortcuts %]
+
+ [% block install_commands %]
+ [% if has_commands %]
+ DetailPrint "Setting up command-line launchers..."
+ nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_assemble_launchers.py" [[ python ]] "$INSTDIR\bin"'
+
+ StrCmp $MultiUser.InstallMode CurrentUser 0 AddSysPathSystem
+ ; Add to PATH for current user
+ nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add_user "$INSTDIR\bin"'
+ GoTo AddedSysPath
+ AddSysPathSystem:
+ ; Add to PATH for all users
+ nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add "$INSTDIR\bin"'
+ AddedSysPath:
+ [% endif %]
+ [% endblock install_commands %]
+
+ ; Byte-compile Python files.
+ DetailPrint "Byte-compiling Python modules..."
+ nsExec::ExecToLog '[[ python ]] -m compileall -q "$INSTDIR\pkgs"'
+ WriteUninstaller $INSTDIR\uninstall.exe
+ ; Add ourselves to Add/remove programs
+ WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
+ "DisplayName" "${PRODUCT_NAME}"
+ WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
+ "UninstallString" '"$INSTDIR\uninstall.exe"'
+ WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
+ "InstallLocation" "$INSTDIR"
+ WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
+ "DisplayIcon" "$INSTDIR\${PRODUCT_ICON}"
+ [% if ib.publisher is not none %]
+ WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
+ "Publisher" "[[ib.publisher]]"
+ [% endif %]
+ WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
+ "DisplayVersion" "${PRODUCT_VERSION}"
+ WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
+ "NoModify" 1
+ WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
+ "NoRepair" 1
+
+ ; CERTBOT CUSTOM BEGIN
+ ; Execute ps script to create the certbot renew task
+ DetailPrint "Setting up certbot renew scheduled task"
+ nsExec::ExecToStack 'powershell -inputformat none -ExecutionPolicy RemoteSigned -File "$INSTDIR\renew-up.ps1"'
+ ; CERTBOT CUSTOM END
+
+ ; Check if we need to reboot
+ IfRebootFlag 0 noreboot
+ MessageBox MB_YESNO "A reboot is required to finish the installation. Do you wish to reboot now?" \
+ /SD IDNO IDNO noreboot
+ Reboot
+ noreboot:
+SectionEnd
+
+Section "Uninstall"
+ ; CERTBOT CUSTOM BEGIN
+ ; Execute ps script to remove the certbot renew task
+ nsExec::ExecToStack 'powershell -inputformat none -ExecutionPolicy RemoteSigned -File "$INSTDIR\renew-down.ps1"'
+ ; CERTBOT CUSTOM END
+
+ SetRegView [[ib.py_bitness]]
+ SetShellVarContext all
+ IfFileExists "$INSTDIR\${USER_INSTALL_MARKER}" 0 +3
+ SetShellVarContext current
+ Delete "$INSTDIR\${USER_INSTALL_MARKER}"
+
+ Delete $INSTDIR\uninstall.exe
+ Delete "$INSTDIR\${PRODUCT_ICON}"
+ RMDir /r "$INSTDIR\pkgs"
+
+ ; Remove ourselves from %PATH%
+ [% block uninstall_commands %]
+ [% if has_commands %]
+ nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" remove "$INSTDIR\bin"'
+ [% endif %]
+ [% endblock uninstall_commands %]
+
+ [% block uninstall_files %]
+ ; Uninstall files
+ [% for file, destination in ib.install_files %]
+ Delete "[[pjoin(destination, file)]]"
+ [% endfor %]
+ ; Uninstall directories
+ [% for dir, destination in ib.install_dirs %]
+ RMDir /r "[[pjoin(destination, dir)]]"
+ [% endfor %]
+ [% endblock uninstall_files %]
+
+ [% block uninstall_shortcuts %]
+ ; Uninstall shortcuts
+ [% if single_shortcut %]
+ [% for scname in ib.shortcuts %]
+ Delete "$SMPROGRAMS\[[scname]].lnk"
+ [% endfor %]
+ [% else %]
+ RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}"
+ [% endif %]
+ [% endblock uninstall_shortcuts %]
+ RMDir $INSTDIR
+ DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
+SectionEnd
+
+[% endblock sections %]
+
+; Functions
+
+Function .onMouseOverSection
+ ; Find which section the mouse is over, and set the corresponding description.
+ FindWindow $R0 "#32770" "" $HWNDPARENT
+ GetDlgItem $R0 $R0 1043 ; description item (must be added to the UI)
+
+ [% block mouseover_messages %]
+ StrCmp $0 ${sec_app} "" +2
+ SendMessage $R0 ${WM_SETTEXT} 0 "STR:${PRODUCT_NAME}"
+
+ [% endblock mouseover_messages %]
+FunctionEnd
+
+Function .onInit
+ !insertmacro MULTIUSER_INIT
+FunctionEnd
+
+Function un.onInit
+ !insertmacro MULTIUSER_UNINIT
+FunctionEnd
+
+[% if ib.py_bitness == 64 %]
+Function correct_prog_files
+ ; The multiuser machinery doesn't know about the different Program files
+ ; folder for 64-bit applications. Override the install dir it set.
+ StrCmp $MultiUser.InstallMode AllUsers 0 +2
+ StrCpy $INSTDIR "$PROGRAMFILES64\${MULTIUSER_INSTALLMODE_INSTDIR}"
+FunctionEnd
+[% endif %] \ No newline at end of file