radioconda/constructor/nsis/main.nsi.tmpl.orig

1675 lines
57 KiB
Cheetah
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Installer template file for creating a Windows installer using NSIS.
!if "${NSIS_PACKEDVERSION}" < 0x3008000
!error "NSIS 3.08 or higher is required to build this installer!"
# conda install "nsis>=3.08" (includes extra unicode plugins)
!endif
Unicode true
{%- if enable_debugging %}
# Special logging build needed for ENABLE_LOGGING
# See https://nsis.sourceforge.io/Special_Builds
!define ENABLE_LOGGING
{%- endif %}
# Comes from https://nsis.sourceforge.io/Logging:Enable_Logs_Quickly
!define LogSet "!insertmacro LogSetMacro"
!macro LogSetMacro SETTING
!ifdef ENABLE_LOGGING
LogSet ${SETTING}
!endif
!macroend
!define LogText "!insertmacro LogTextMacro"
!macro LogTextMacro INPUT_TEXT
!ifdef ENABLE_LOGGING
LogText ${INPUT_TEXT}
!endif
!macroend
var /global QuietMode # "0" = print normally, "1" = do not print
var /global StdOutHandle
var /global StdOutHandleSet
!define Print "!insertmacro PrintMacro"
!macro PrintMacro INPUT_TEXT
DetailPrint "${INPUT_TEXT}"
${If} ${Silent}
${AndIf} $QuietMode != "1"
${IfNot} $StdOutHandleSet == "1"
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
System::Call 'kernel32::AttachConsole(i -1)i.r1'
${If} $0 = 0
${OrIf} $1 = 0
System::Call 'kernel32::AllocConsole()'
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
${EndIf}
StrCpy $StdOutHandle $0
StrCpy $StdOutHandleSet "1"
${EndIf}
FileWrite $StdOutHandle "${INPUT_TEXT}$\n"
${EndIf}
!macroend
!include "WinMessages.nsh"
!include "WordFunc.nsh"
!include "LogicLib.nsh"
!include "WinVer.nsh"
!include "MUI2.nsh"
!include "x64.nsh"
!include "FileFunc.nsh"
!insertmacro GetParameters
!insertmacro GetOptions
!include "UAC.nsh"
!include "nsDialogs.nsh"
!include "Utils.nsh"
{%- if uninstall_with_conda_exe %}
!include "StandaloneUninstallerOptions.nsh"
{%- endif %}
!define NAME {{ installer_name }}
!define VERSION {{ installer_version }}
!define COMPANY {{ company }}
!define ARCH {{ arch }}
!define PLATFORM {{ installer_platform }}
!define CONSTRUCTOR_VERSION {{ constructor_version }}
!define PY_VER {{ pyver_components[:2] | join(".") }}
!define PYVERSION_JUSTDIGITS {{ pyver_components | join("") }}
!define PYVERSION {{ pyver_components | join(".") }}
!define PYVERSION_MAJOR {{ pyver_components[0] }}
!define DEFAULT_PREFIX {{ default_prefix }}
!define DEFAULT_PREFIX_DOMAIN_USER {{ default_prefix_domain_user }}
!define DEFAULT_PREFIX_ALL_USERS {{ default_prefix_all_users }}
!define PRE_INSTALL_DESC {{ pre_install_desc }}
!define POST_INSTALL_DESC {{ post_install_desc }}
!define ENABLE_SHORTCUTS {{ enable_shortcuts }}
!define SHOW_REGISTER_PYTHON {{ show_register_python }}
!define SHOW_ADD_TO_PATH {{ show_add_to_path }}
!define PRODUCT_NAME "${NAME} ${VERSION} (${ARCH})"
!define UNINSTALL_NAME "{{ UNINSTALL_NAME }}"
!define UNINSTREG "SOFTWARE\Microsoft\Windows\CurrentVersion\
\Uninstall\${UNINSTALL_NAME}"
var /global INSTDIR_JUSTME
var /global INSTALLER_VERSION
var /global INSTALLER_NAME_FULL
# UAC shield overlay
!ifndef BCM_SETSHIELD
!define BCM_SETSHIELD 0x0000160C
!endif
var /global ARGV
var /global ARGV_Help
var /global ARGV_InstallationType
var /global ARGV_AddToPath
var /global ARGV_KeepPkgCache
var /global ARGV_RegisterPython
var /global ARGV_NoRegistry
var /global ARGV_NoScripts
var /global ARGV_NoShortcuts
var /global ARGV_CheckPathLength
var /global ARGV_QuietMode
{%- if uninstall_with_conda_exe %}
var /global ARGV_Uninst_RemoveConfigFiles
var /global ARGV_Uninst_RemoveUserData
var /global ARGV_Uninst_RemoveCaches
{%- endif %}
var /global IsDomainUser
var /global CheckPathLength
var /global LongPathsEnabled
var /global InstDirLen
var /global InstModePage_RadioButton_JustMe
var /global InstModePage_RadioButton_AllUsers
var /global InstMode # 0 = Just Me, 1 = All Users.
!define JUST_ME 0
!define ALL_USERS 1
# Include this one after our defines
!include "OptionsDialog.nsh"
CRCCheck On
# Basic options
Name "${PRODUCT_NAME}"
OutFile {{ outfile }}
ShowInstDetails "hide"
ShowUninstDetails "hide"
# This installer contains tar.bz2 files, which are already compressed
SetCompress "off"
# Start off with the lowest permissions and work our way up.
RequestExecutionLevel user
# Version information & branding text
VIAddVersionKey "ProductName" "${PRODUCT_NAME}"
VIAddVersionKey "FileVersion" "${VERSION}"
VIAddVersionKey "ProductVersion" "${VERSION}"
VIAddVersionKey "CompanyName" "${COMPANY}"
VIAddVersionKey "LegalCopyright" "(c) ${COMPANY}"
VIAddVersionKey "FileDescription" "${NAME} Installer"
VIAddVersionKey "Comments" "Created by constructor ${CONSTRUCTOR_VERSION}"
VIProductVersion {{ vipv }}
BrandingText /TRIMLEFT "${COMPANY}"
# Interface configuration
!define MUI_ICON {{ iconfile }}
!define MUI_UNICON {{ iconfile }}
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP {{ headerimage }}
!define MUI_HEADERIMAGE_UNBITMAP {{ headerimage }}
!define MUI_ABORTWARNING
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_UNFINISHPAGE_NOAUTOCLOSE
!define MUI_WELCOMEFINISHPAGE_BITMAP {{ welcomeimage }}
!define MUI_UNWELCOMEFINISHPAGE_BITMAP {{ welcomeimage }}
#!define MUI_CUSTOMFUNCTION_GUIINIT GuiInit
# Pages
#!define MUI_PAGE_CUSTOMFUNCTION_SHOW OnStartup
{%- if custom_welcome %}
# Custom welcome file(s)
{{ CUSTOM_WELCOME_FILE }}
{%- else %}
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipPageIfUACInnerInstance
!insertmacro MUI_PAGE_WELCOME
{%- endif %}
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipPageIfUACInnerInstance
!insertmacro MUI_PAGE_LICENSE {{ licensefile }}
Page Custom InstModePage_Create InstModePage_Leave
!define MUI_PAGE_CUSTOMFUNCTION_PRE DisableBackButtonIfUACInnerInstance
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE OnDirectoryLeave
!insertmacro MUI_PAGE_DIRECTORY
# Custom options now differ depending on installation mode.
Page Custom mui_AnaCustomOptions_Show
!insertmacro MUI_PAGE_INSTFILES
{%- for page in POST_INSTALL_PAGES %}
{{ page }}
{%- endfor %}
{%- if with_conclusion_text %}
!define MUI_FINISHPAGE_TITLE {{ conclusion_title }}
!define MUI_FINISHPAGE_TITLE_3LINES
!define MUI_FINISHPAGE_TEXT {{ conclusion_text }}
{%- endif %}
{%- if custom_conclusion %}
# Custom conclusion file(s)
{{ CUSTOM_CONCLUSION_FILE }}
{%- else %}
!insertmacro MUI_PAGE_FINISH
{%- endif %}
!insertmacro MUI_UNPAGE_WELCOME
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.OnDirectoryLeave
!insertmacro MUI_UNPAGE_CONFIRM
{%- if uninstall_with_conda_exe %}
UninstPage Custom un.UninstCustomOptions_Show
{%- endif %}
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
# Language
!insertmacro MUI_LANGUAGE "English"
Function SkipPageIfUACInnerInstance
${LogSet} on
${If} ${UAC_IsInnerInstance}
Abort
${EndIf}
FunctionEnd
!macro DoElevation
GetDlgItem $1 $HWNDParent 1
System::Call user32::GetFocus()i.s
# Disable 'Next' button.
EnableWindow $1 0
!insertmacro UAC_PageElevation_RunElevated
EnableWindow $1 1
System::call user32::SetFocus(is)
${If} $2 = 0x666
MessageBox MB_ICONEXCLAMATION \
"You need to log in with an administrative account \
in order to perform an 'All Users' installation."
Abort
${ElseIf} $0 = 1223
# UAC canceled by user.
Abort
${Else}
${If} $0 <> 0
${If} $0 = 1062
MessageBox MB_ICONSTOP \
"Elevation failed; Secondary Logon service is \
not running."
${Else}
MessageBox MB_ICONSTOP \
"Elevation failed; error code: $0."
${EndIf}
Abort
${EndIf}
${EndIf}
# UAC worked, we're the outer installer, so we can quit.
Quit
!macroend
!macro ParseCommandLineArgs
ClearErrors
${GetParameters} $ARGV
${GetOptions} $ARGV "/?" $ARGV_Help
${IfNot} ${Errors}
SetSilent silent
${Print} "\
Installs ${NAME} ${VERSION}$\n\
$\n\
USAGE$\n\
-----$\n\
$\n\
$EXEFILE [options]$\n\
$\n\
OPTIONS$\n\
-------$\n\
$\n\
/InstallationType=AllUsers [default: JustMe]$\n\
/AddToPath=[0|1] [default: 0]$\n\
/KeepPkgCache=[0|1] [default: {{ 1 if keep_pkgs else 0 }}]$\n\
/RegisterPython=[0|1] [default: AllUsers: 1, JustMe: 0]$\n\
/NoRegistry=[0|1] [default: AllUsers: 0, JustMe: 0]$\n\
/NoScripts=[0|1] [default: 0]$\n\
/NoShortcuts=[0|1] [default: 0]$\n\
/CheckPathLength=[0|1] [default: 1]$\n\
/? (show this help message)$\n\
/S (run in CLI/headless mode)$\n\
/Q (quiet mode, do not print output to console)$\n\
/D=[installation directory] (must be last parameter)$\n"
# There seems to be a limit to how many chars per ${Print} we can pass.
# The message will get truncated silently, no errors.
# That's why we split the help message in two calls.
${Print} "\
EXAMPLES$\n\
--------$\n\
$\n\
Install for all users, but don't add to PATH env var:$\n\
> $EXEFILE /InstallationType=AllUsers$\n\
$\n\
Install for just me, add to PATH and register as system Python:$\n\
> $EXEFILE /RegisterPython=1 /AddToPath=1$\n\
$\n\
Install for just me, with no registry modification (for CI):$\n\
> $EXEFILE /NoRegistry=1$\n\
$\n\
Install via CLI (no GUI) into C:\${NAME}$\n\
> cmd /C START /WAIT $EXEFILE /S /D=C:\${NAME}$\n\
$\n\
NOTE: If you install for AllUsers, then the option to AddToPath$\n\
is disabled (i.e. if /InstallationType=AllUsers, then$\n\
/AddToPath=1 will be ignored)."
Abort
${EndIf}
ClearErrors
${GetOptions} $ARGV "/InstallationType=" $ARGV_InstallationType
${IfNot} ${Errors}
${If} $ARGV_InstallationType == "AllUsers"
StrCpy $InstMode ${ALL_USERS}
${Else}
StrCpy $InstMode ${JUST_ME}
${EndIf}
${EndIf}
ClearErrors
${GetOptions} $ARGV "/RegisterPython=" $ARGV_RegisterPython
${IfNot} ${Errors}
${If} $ARGV_RegisterPython = "1"
StrCpy $Ana_RegisterSystemPython_State ${BST_CHECKED}
${ElseIf} $ARGV_RegisterPython = "0"
StrCpy $Ana_RegisterSystemPython_State ${BST_UNCHECKED}
${EndIf}
${EndIf}
ClearErrors
${GetOptions} $ARGV "/KeepPkgCache=" $ARGV_KeepPkgCache
${If} ${Errors}
StrCpy $ARGV_KeepPkgCache "{{ 1 if keep_pkgs else 0 }}"
${EndIf}
ClearErrors
${GetOptions} $ARGV "/NoRegistry=" $ARGV_NoRegistry
${If} ${Errors}
StrCpy $ARGV_NoRegistry "0"
${EndIf}
ClearErrors
${GetOptions} $ARGV "/NoScripts=" $ARGV_NoScripts
${IfNot} ${Errors}
${If} $ARGV_NoScripts = "1"
StrCpy $Ana_PostInstall_State ${BST_UNCHECKED}
${ElseIf} $ARGV_NoScripts = "0"
StrCpy $Ana_PostInstall_State ${BST_CHECKED}
${EndIf}
${EndIf}
${If} "${ENABLE_SHORTCUTS}" == "yes"
ClearErrors
${GetOptions} $ARGV "/NoShortcuts=" $ARGV_NoShortcuts
${IfNot} ${Errors}
${If} $ARGV_NoShortcuts = "1"
StrCpy $Ana_CreateShortcuts_State ${BST_UNCHECKED}
${ElseIf} $ARGV_NoShortcuts = "0"
StrCpy $Ana_CreateShortcuts_State ${BST_CHECKED}
${EndIf}
${EndIf}
${Else}
StrCpy $Ana_CreateShortcuts_State ${BST_UNCHECKED}
${EndIf}
ClearErrors
${GetOptions} $ARGV "/CheckPathLength=" $ARGV_CheckPathLength
${IfNot} ${Errors}
${If} $ARGV_CheckPathLength = "0"
StrCpy $CheckPathLength "0"
${ElseIf} $ARGV_CheckPathLength = "1"
StrCpy $CheckPathLength "1"
${EndIf}
${EndIf}
ClearErrors
${GetOptions} $ARGV "/Q" $ARGV_QuietMode
${IfNot} ${Errors}
StrCpy $QuietMode "1"
${EndIf}
!macroend
Function OnInit_Release
${LogSet} on
!insertmacro ParseCommandLineArgs
# Parsing the AddToPath option here (and not in ParseCommandLineArgs) to prevent the MessageBox from showing twice.
# For more context, see https://github.com/conda/constructor/pull/584#issuecomment-1347688020
ClearErrors
${GetOptions} $ARGV "/AddToPath=" $ARGV_AddToPath
${IfNot} ${Errors}
${If} $ARGV_AddToPath = "1"
${If} $InstMode == ${ALL_USERS}
# To address CVE-2022-26526.
# In AllUsers install mode, do not allow AddToPath as an option.
MessageBox MB_OK|MB_ICONEXCLAMATION "/AddToPath=1 is disabled and ignored in 'All Users' installations" /SD IDOK
${Print} "/AddToPath=1 is disabled and ignored in 'All Users' installations"
StrCpy $Ana_AddToPath_State ${BST_UNCHECKED}
${Else}
StrCpy $Ana_AddToPath_State ${BST_CHECKED}
${EndIf}
${ElseIf} $ARGV_AddToPath = "0"
StrCpy $Ana_AddToPath_State ${BST_UNCHECKED}
${EndIf}
${EndIf}
FunctionEnd
Function InstModePage_RadioButton_OnClick
${LogSet} on
Exch $0
Push $1
Push $2
nsDialogs::GetUserData $0
Pop $1
GetDlgItem $2 $HWNDParent 1
SendMessage $2 ${BCM_SETSHIELD} 0 $1
Pop $2
Pop $1
Exch $0
FunctionEnd
Function InstModePage_Create
${LogSet} on
Push $0
Push $1
Push $2
Push $3
${If} ${UAC_IsInnerInstance}
Abort
${EndIf}
!insertmacro MUI_HEADER_TEXT_PAGE \
"Select Installation Type" \
"Please select the type of installation you would like to perform \
for ${PRODUCT_NAME}."
GetFunctionAddress $0 InstModePage_RadioButton_OnClick
nsDialogs::Create /NOUNLOAD 1018
Pop $1
${NSD_OnBack} RemoveNextBtnShield
${NSD_CreateLabel} 0 20u 75% 20u "Install for:"
${NSD_CreateRadioButton} 0 40u 75% 15u "Just Me (recommended)"
Pop $2
#MessageBox MB_OK "OnClick 2! 0: $0, 1: $1, 2: $2"
StrCpy $InstModePage_RadioButton_JustMe $2
nsDialogs::OnClick $2 $0
nsDialogs::SetUserData $2 0
SendMessage $2 ${BM_CLICK} 0 0
${NSD_CreateRadioButton} 0 60u 75% 15u \
"All Users (requires admin privileges)"
#MessageBox MB_OK "OnClick 3! 0: $0, 1: $1, 2: $2, 3: $3"
Pop $3
StrCpy $InstModePage_RadioButton_AllUsers $3
nsDialogs::OnClick $3 $0
nsDialogs::SetUserData $3 1
${IfThen} $InstMode <> ${JUST_ME} ${|} SendMessage $3 ${BM_CLICK} 0 0 ${|}
Push $3
nsDialogs::Show
Pop $3
Pop $2
Pop $1
Pop $0
FunctionEnd
Function DisableBackButtonIfUACInnerInstance
${LogSet} on
Push $0
${If} ${UAC_IsInnerInstance}
GetDlgItem $0 $HWNDParent 3
EnableWindow $0 0
${EndIf}
Pop $0
FunctionEnd
Function RemoveNextBtnShield
${LogSet} on
Push $0
GetDlgItem $0 $HWNDParent 1
SendMessage $0 ${BCM_SETSHIELD} 0 0
Pop $0
FunctionEnd
Function InstModeChanged
${LogSet} on
# When using the installer with /S (silent mode), the /D option sets $INSTDIR,
# and it is therefore important not to overwrite $INSTDIR here, but it is also
# important that we do call SetShellVarContext with the appropriate value.
Push $0
${If} $InstMode = ${JUST_ME}
SetShellVarContext Current
# If we're on Vista+, the installation directory will
# have a nice, no-space name like:
# C:\Users\Trent\AppData\Local\Continuum\Anaconda.
# On 2003/XP, it will be in C:\Documents and Settings,
# with a space. We're allowing spaces now.
${IfNot} ${Silent}
StrCpy $INSTDIR $INSTDIR_JUSTME
${EndIf}
${Else}
SetShellVarContext All
${IfNot} ${Silent}
ExpandEnvStrings $0 ${DEFAULT_PREFIX_ALL_USERS}
StrCpy $INSTDIR $0
${Endif}
${EndIf}
Pop $0
FunctionEnd
!macro SetInstMode mode
StrCpy $InstMode ${mode}
Call InstModeChanged
!macroend
Function InstModePage_Leave
${LogSet} on
Push $0
Push $1
Push $2
${NSD_GetState} $InstModePage_RadioButton_AllUsers $0
${If} $0 = 0
!insertmacro SetInstMode ${JUST_ME}
${Else}
!insertmacro SetInstMode ${ALL_USERS}
${IfNot} ${UAC_IsAdmin}
!insertmacro DoElevation
${EndIf}
${EndIf}
Pop $2
Pop $1
Pop $0
FunctionEnd
Function .onInit
${LogSet} on
Push $0
Push $1
Push $2
Push $R1
Push $R2
InitPluginsDir
{%- if TEMP_EXTRA_FILES | length != 0 %}
SetOutPath $PLUGINSDIR
{%- for file in TEMP_EXTRA_FILES %}
File {{ file }}
{%- endfor %}
{%- endif %}
!insertmacro ParseCommandLineArgs
# Select the correct registry to look at, depending
# on whether it's a 32-bit or 64-bit installer
SetRegView {{ BITS }}
{%- if win64 %}
# If we're a 64-bit installer, make sure it's 64-bit Windows
${IfNot} ${RunningX64}
MessageBox MB_OK|MB_ICONEXCLAMATION \
"This installer is for a 64-bit version for ${NAME}$\n\
but your system is 32-bit. Please use the 32-bit Windows$\n\
${NAME} installer." \
/SD IDOK
Abort
${EndIf}
{%- endif %}
!insertmacro UAC_PageElevation_OnInit
${If} ${UAC_IsInnerInstance}
${AndIfNot} ${UAC_IsAdmin}
SetErrorLevel 0x666
Quit
${EndIf}
# Look for a number of signs that indicate the user is a domain user and
# alter the default installation directory for 'Just Me' accordingly. We
# want to ensure that if we're a user domain account, we always install to
# %LOCALAPPDATA% (i.e. C:\Users\Trent\AppData\Local\Continuum\Anaconda),
# as this is the only place guaranteed to not be backed by a network share
# or included in a user's roaming profile. However, if we're a normal user
# account, then C:\Users\Trent\Anaconda is fine.
ReadEnvStr $0 USERDNSDOMAIN
${If} $0 != ""
# If not null, USERDNSDOMAIN is an unambiguous indication that we're
# logged into a domain account.
StrCpy $IsDomainUser 1
${Else}
# If it's not set, apply some simple heuristics to discern whether or
# not we're logged in as a domain user.
ReadEnvStr $0 LOGONSERVER
${If} $0 == ""
# This should never be unset; but if it is, we're definitely not
# a domain user.
StrCpy $IsDomainUser 0
${Else}
StrCpy $1 $0 "" 2 # lop-off the leading \\.
${StrFilter} $1 "+" "" "" $2 # convert to uppercase, store in $2
${If} $2 == "MICROSOFTACCOUNT"
# The new Windows 8.x live accounts have \\MicrosoftAccount
# set as LOGONSERVER; interpret this as being a non-domain
# user.
StrCpy $IsDomainUser 0
${Else}
ReadEnvStr $R1 COMPUTERNAME
${If} $R1 == ""
# This should never be unset either; if it is, assume
# we're not a domain user.
StrCpy $IsDomainUser 0
${Else}
# We've got a value for both LOGONSERVER and COMPUTERNAME
# environment variables (which should always be the case).
# Proceed to compare LOGONSERVER[-2:] to COMPUTERNAME; if
# they match, assume we're not a domain user account.
${StrFilter} $R1 "+" "" "" $R2 # convert to uppercase
${If} $2 != $R2
# COMPUTERNAME doesn't match LOGONSERVER; assume we're
# logged in via a domain account.
StrCpy $IsDomainUser 1
${Else}
# COMPUTERNAME matches LOGONSERVER; safe to assume
# we're logged in as a user account. (I guess there's
# the remote possibility a domain user has logged onto
# a server that has the same NetBIOS name as the Active
# Directory name... if that's the case, potentially
# installing Anaconda into an area that gets picked up
# by a roaming profile is the very least of your
# problems.)
StrCpy $IsDomainUser 0
${EndIf} # LOGONSERVER[-2:] != COMPUTERNAME
${EndIf} # COMPUTERNAME != ""
${EndIf} # LOGONSERVER != "\\MicrosoftAccount"
${EndIf} # LOGONSERVER != ""
${EndIf} # USERDNSDOMAIN != ""
${If} $IsDomainUser = 0
ExpandEnvStrings $0 ${DEFAULT_PREFIX}
StrCpy $INSTDIR_JUSTME $0
${ElseIf} $IsDomainUser = 1
ExpandEnvStrings $0 ${DEFAULT_PREFIX_DOMAIN_USER}
StrCpy $INSTDIR_JUSTME $0
${Else}
# Should never happen; indicates a logic error above.
MessageBox MB_OK "Internal error: IsUserDomain not set properly!" \
/SD IDOK
Abort
${EndIf}
${If} $InstMode == ""
StrCpy $InstMode ${JUST_ME}
${IfThen} ${UAC_IsAdmin} ${|} StrCpy $InstMode ${ALL_USERS} ${|}
# If running as 'SYSTEM' then JustMe is not appropriate; note that
# we should advise against this. SCCM has an option to run as user
System::Call "advapi32::GetUserName(t .r0, *i ${NSIS_MAX_STRLEN} r1) i.r2"
${IfThen} $0 == "SYSTEM" ${|} StrCpy $InstMode ${ALL_USERS} ${|}
${EndIf}
call InstModeChanged
${If} ${Silent}
${If} $InstMode == ${ALL_USERS}
${IfNot} ${UAC_IsAdmin}
MessageBox MB_ICONSTOP "Installation for all users requires an elevated prompt."
Abort
${EndIf}
${EndIf}
${EndIf}
; /D was not used, add default based on install type
${If} $InstDir == ""
${If} $InstMode == ${ALL_USERS}
ExpandEnvStrings $0 ${DEFAULT_PREFIX_ALL_USERS}
StrCpy $INSTDIR $0
${Else}
strcpy $INSTDIR $INSTDIR_JUSTME
${EndIf}
${EndIf}
; Set default value
${If} $CheckPathLength == ""
StrCpy $CheckPathLength "1"
${EndIf}
# Initialize the default settings for the anaconda custom options
Call mui_AnaCustomOptions_InitDefaults
# Override custom options with explicitly given values from contruct.yaml.
# If initialize_by_default (register_python_default) is None, do nothing.
{%- if initialize_conda %}
{%- if initialize_by_default %}
${If} $InstMode == ${JUST_ME}
StrCpy $Ana_AddToPath_State ${BST_CHECKED}
${EndIf}
{%- else %}
StrCpy $Ana_AddToPath_State ${BST_UNCHECKED}
{%- endif %}
{%- endif %}
{%- if register_python %}
StrCpy $Ana_RegisterSystemPython_State {{ '${BST_CHECKED}' if register_python_default else '${BST_UNCHECKED}' }}
{%- endif %}
StrCpy $CheckPathLength "{{ 1 if check_path_length else 0 }}"
StrCpy $Ana_ClearPkgCache_State {{ '${BST_UNCHECKED}' if keep_pkgs else '${BST_CHECKED}' }}
StrCpy $Ana_PreInstall_State {{ '${BST_CHECKED}' if pre_install_exists else '${BST_UNCHECKED}' }}
StrCpy $Ana_PostInstall_State {{ '${BST_CHECKED}' if post_install_exists else '${BST_UNCHECKED}' }}
Call OnInit_Release
${Print} "Welcome to ${NAME} ${VERSION}$\n"
Pop $R2
Pop $R1
Pop $2
Pop $1
Pop $0
FunctionEnd
!macro un.ParseCommandLineArgs
ClearErrors
${GetParameters} $ARGV
${GetOptions} $ARGV "/?" $ARGV_Help
${IfNot} ${Errors}
SetSilent silent
${Print} "\
Uninstalls ${NAME} ${VERSION}$\n\
$\n\
USAGE$\n\
-----$\n\
$\n\
Uninstall-${NAME}.exe [options]$\n\
$\n\
OPTIONS$\n\
-------$\n\
$\n\
/? (show this help message)$\n\
/S (run in CLI/headless mode)$\n\
/Q (quiet mode, do not print output to console)$\n\
{%- if uninstall_with_conda_exe %}
/RemoveCaches=[0|1] [default: 0]$\n\
/RemoveConfigFiles=[none|users|system|all] [default: none]$\n\
/RemoveUserData=[0|1] [default: 0]$\n\
{%- endif %}
/_?=[installation directory] (must be last parameter)$\n\
$\n\
EXAMPLES$\n\
--------$\n\
$\n\
Uninstall via CLI (no GUI) from C:\${NAME}$\n\
> cmd /C START /WAIT Uninstall-${NAME}.exe /S /_?=C:\${NAME}$\n\
$\n\
Closing in 10s..."
# Give it some time so users can read it the pop-up console
# The pop-up console happens because the uninstaller copies itself to
# a temporary location, so we can't get the parent console handle
Sleep 10000
Abort
${EndIf}
ClearErrors
${GetOptions} $ARGV "/Q" $ARGV_QuietMode
${IfNot} ${Errors}
StrCpy $QuietMode "1"
${EndIf}
{%- if uninstall_with_conda_exe %}
ClearErrors
${GetOptions} $ARGV "/RemoveConfigFiles=" $ARGV_Uninst_RemoveConfigFiles
${IfNot} ${Errors}
${IfNot} ${UAC_IsAdmin}
${If} $ARGV_Uninst_RemoveConfigFiles == "all"
${OrIf} $ARGV_Uninst_RemoveConfigFiles == "system"
MessageBox MB_ICONSTOP "Removing system .condarc files requires an elevated prompt."
Abort
${EndIf}
${EndIf}
${If} $ARGV_Uninst_RemoveConfigFiles == "user"
StrCpy $UninstRemoveConfigFiles_User_State ${BST_CHECKED}
StrCpy $UninstRemoveConfigFiles_System_State ${BST_UNCHECKED}
${ElseIf} $ARGV_Uninst_RemoveConfigFiles == "system"
StrCpy $UninstRemoveConfigFiles_User_State ${BST_UNCHECKED}
StrCpy $UninstRemoveConfigFiles_System_State ${BST_CHECKED}
${ElseIf} $ARGV_Uninst_RemoveConfigFiles == "all"
StrCpy $UninstRemoveConfigFiles_User_State ${BST_CHECKED}
StrCpy $UninstRemoveConfigFiles_System_State ${BST_CHECKED}
${Else}
StrCpy $UninstRemoveConfigFiles_User_State ${BST_UNCHECKED}
StrCpy $UninstRemoveConfigFiles_System_State ${BST_UNCHECKED}
${EndIf}
${EndIf}
ClearErrors
${GetOptions} $ARGV "/RemoveUserData=" $ARGV_Uninst_RemoveUserData
${IfNot} ${Errors}
${If} $ARGV_Uninst_RemoveUserData = "1"
StrCpy $UninstRemoveUserData_State ${BST_CHECKED}
${ElseIf} $ARGV_Uninst_RemoveUserData = "0"
StrCpy $UninstRemoveUserData_State ${BST_UNCHECKED}
${EndIf}
${EndIf}
ClearErrors
${GetOptions} $ARGV "/RemoveCaches=" $ARGV_Uninst_RemoveCaches
${IfNot} ${Errors}
${If} $ARGV_Uninst_RemoveCaches = "1"
StrCpy $UninstRemoveCaches_State ${BST_CHECKED}
${ElseIf} $ARGV_Uninst_RemoveCaches = "0"
StrCpy $UninstRemoveCaches_State ${BST_UNCHECKED}
${EndIf}
${EndIf}
{%- endif %}
!macroend
Function un.onInit
{%- if uninstall_with_conda_exe %}
Call un.UninstCustomOptions_InitDefaults
{%- endif %}
Push $0
Push $1
Push $2
Push $3
# Resolve INSTDIR
GetFullPathName $0 $INSTDIR
# If the directory does not exist or cannot be resolved, $0 will be empty
StrCmp $0 "" invalid_dir
StrCpy $INSTDIR $0
# Never run the uninstaller when $INSTDIR points at system-critical directories
StrLen $InstDirLen $INSTDIR
# INSTDIR is a full path and has no trailing backslash,
# so if its length is 2, it is pointed at a system root
StrCmp $InstdirLen 2 invalid_dir
# Never delete anything inside Windows
StrCpy $0 $INSTDIR 7 3
StrCmp $0 "Windows" invalid_dir
StrCpy $0 "ALLUSERSPROFILE APPDATA LOCALAPPDATA PROGRAMDATA PROGRAMFILES PROGRAMFILES(x86) PUBLIC SYSTEMDRIVE SYSTEMROOT USERPROFILE"
StrCpy $1 1
loop_critical:
${WordFind} $0 " " "E+$1" $2
IfErrors endloop_critical
ReadEnvStr $3 $2
StrCmp $3 $INSTDIR invalid_dir
IntOp $1 $1 + 1
goto loop_critical
endloop_critical:
# Primitive check to see that $INSTDIR points to a conda directory
StrCpy $0 "_conda.exe conda-meta\history"
StrCpy $1 1
loop_conda:
${WordFind} $0 " " "E+$1" $2
IfErrors endloop_conda
IfFileExists $INSTDIR\$2 0 invalid_dir
IntOp $1 $1 + 1
goto loop_conda
endloop_conda:
# All checks have passed
goto valid_dir
invalid_dir:
${Print} "::error:: $INSTDIR is not a valid conda directory. Please run the uninstaller from a conda directory."
MessageBox MB_OK|MB_ICONSTOP \
"Error: $INSTDIR is not a valid conda directory. Please run the uninstaller from a conda directory." \
/SD IDABORT
abort
valid_dir:
# Select the correct registry to look at, depending
# on whether it's a 32-bit or 64-bit installer
SetRegView {{ BITS }}
# Since the switch to a dual-mode installer (All Users/Just Me), the
# uninstaller will inherit the requested execution level of the main
# installer -- which we now have to set to 'user'. Thus, Windows will
# not automatically elevate the uninstaller for us -- we need to do it
# ourselves if we're not a 'Just Me' installation.
!insertmacro UAC_PageElevation_OnInit
${IfNot} ${FileExists} "$INSTDIR\.nonadmin"
${AndIfNot} ${UAC_IsAdmin}
!insertmacro DoElevation
${EndIf}
${If} ${FileExists} "$INSTDIR\.nonadmin"
SetShellVarContext Current
${Else}
SetShellVarContext All
${EndIf}
Pop $3
Pop $2
Pop $1
Pop $0
FunctionEnd
# http://nsis.sourceforge.net/Check_for_spaces_in_a_directory_path
Function CheckForSpaces
${LogSet} on
Exch $R0
Push $R1
Push $R2
Push $R3
StrCpy $R1 -1
StrCpy $R3 $R0
StrCpy $R0 0
loop:
StrCpy $R2 $R3 1 $R1
IntOp $R1 $R1 - 1
StrCmp $R2 "" done
StrCmp $R2 " " 0 loop
IntOp $R0 $R0 + 1
Goto loop
done:
Pop $R3
Pop $R2
Pop $R1
Exch $R0
FunctionEnd
# http://nsis.sourceforge.net/StrCSpn,_StrCSpnReverse:_Scan_strings_for_characters
Function StrCSpn
${LogSet} on
Exch $R0 ; string to check
Exch
Exch $R1 ; string of chars
Push $R2 ; current char
Push $R3 ; current char
Push $R4 ; char loop
Push $R5 ; char loop
StrCpy $R4 -1
NextChar:
StrCpy $R2 $R1 1 $R4
IntOp $R4 $R4 - 1
StrCmp $R2 "" StrOK
StrCpy $R5 -1
NextCharCheck:
StrCpy $R3 $R0 1 $R5
IntOp $R5 $R5 - 1
StrCmp $R3 "" NextChar
StrCmp $R3 $R2 0 NextCharCheck
StrCpy $R0 $R2
Goto Done
StrOK:
StrCpy $R0 ""
Done:
Pop $R5
Pop $R4
Pop $R3
Pop $R2
Pop $R1
Exch $R0
FunctionEnd
# http://stackoverflow.com/a/29569614/1170370
!macro _IsNonEmptyDirectory _a _b _t _f
!insertmacro _LOGICLIB_TEMP
!insertmacro _IncreaseCounter
Push $0
FindFirst $0 $_LOGICLIB_TEMP "${_b}\*"
_IsNonEmptyDirectory_loop${LOGICLIB_COUNTER}:
StrCmp "" $_LOGICLIB_TEMP _IsNonEmptyDirectory_done${LOGICLIB_COUNTER}
StrCmp "." $_LOGICLIB_TEMP +2
StrCmp ".." $_LOGICLIB_TEMP 0 _IsNonEmptyDirectory_done${LOGICLIB_COUNTER}
FindNext $0 $_LOGICLIB_TEMP
Goto _IsNonEmptyDirectory_loop${LOGICLIB_COUNTER}
_IsNonEmptyDirectory_done${LOGICLIB_COUNTER}:
FindClose $0
Pop $0
!insertmacro _!= "" $_LOGICLIB_TEMP `${_t}` `${_f}`
!macroend
!define IsNonEmptyDirectory `"" IsNonEmptyDirectory`
Function OnDirectoryLeave
${LogSet} on
${If} ${IsNonEmptyDirectory} "$InstDir"
${Print} "::error:: Directory '$INSTDIR' is not empty, please choose a different location."
MessageBox MB_OK|MB_ICONEXCLAMATION \
"Directory '$INSTDIR' is not empty,$\n\
please choose a different location." \
/SD IDOK
Abort
${EndIf}
ReadRegStr $LongPathsEnabled HKLM "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled"
StrLen $InstDirLen "$InstDir"
${If} $CheckPathLength == "1"
${AndIf} $LongPathsEnabled == "0"
${AndIf} $InstDirLen > 46
; With windows 10, we can enable support for long path, for earlier
; version, suggest user to use shorter installation path
${If} ${AtLeastWin10}
${AndIfNot} $ARGV_NoRegistry = "1"
; If we have admin right, we enable long path on windows
${If} ${UAC_IsAdmin}
WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled" 1
; If we don't have admin right, we suggest a shorter path or suggest to run with admin right
${Else}
${Print} "::error:: The installation path should be shorter than 46 characters or \
the installation requires administrator rights to enable long \
path on Windows."
MessageBox MB_OK|MB_ICONSTOP "The installation path should be shorter than 46 characters or \
the installation requires administrator rights to enable long \
path on Windows." \
/SD IDOK
Abort
${EndIf}
; If we don't have admin right, we suggest a shorter path or suggest to run with admin right
${Else}
${Print} "::error:: The installation path should be shorter than 46 characters. \
Please choose another location."
MessageBox MB_OK|MB_ICONSTOP "The installation path should be shorter than 46 characters. \
Please choose another location." \
/SD IDOK
Abort
${EndIf}
${EndIf}
# Call the CheckForSpaces function.
Push $INSTDIR # Input string (install path).
Call CheckForSpaces
Pop $R0 # The function returns the number of spaces found in the input string.
Push $R7
Push $R8
Push $R9
# Check if any spaces exist in $INSTDIR.
StrCmp $R0 0 NoSpaces
# Plural if more than 1 space in $INSTDIR.
StrCmp $R0 1 0 +3
StrCpy $R1 ""
Goto +2
StrCpy $R1 "s"
${If} ${Silent}
StrCpy $R7 " "
${Else}
StrCpy $R7 "$\n"
${EndIf}
StrCpy $R8 "'Destination Folder' contains $R0 space$R1.$R7This can cause problems with several conda packages.$R7"
{%- if check_path_spaces %}
StrCpy $R8 "$R8Please remove the space$R1 from the destination folder."
StrCpy $R9 "Error"
{%- else %}
StrCpy $R8 "$R8Please consider removing the space$R1."
StrCpy $R9 "Warning"
{%- endif %}
# Show message box then take the user back to the Directory page.
${If} ${Silent}
${Print} "::$R9:: $R8"
${Else}
MessageBox MB_OK|MB_ICONINFORMATION "$R9: $R8" /SD IDOK
${EndIf}
{%- if check_path_spaces %}
abort
{%- endif %}
NoSpaces:
Pop $R7
Pop $R8
Pop $R9
# List of characters not allowed anywhere in $INSTDIR
Push "^%!=,()"
Push $INSTDIR
Call StrCSpn
Pop $R0
StrCmp $R0 "" NoInvalidCharaceters
${Print} "::error:: 'Destination Folder' contains the following invalid character: $R0"
MessageBox MB_OK|MB_ICONEXCLAMATION \
"Error: 'Destination Folder' contains the following invalid character: $R0" \
/SD IDOK
abort
NoInvalidCharaceters:
UnicodePathTest::SpecialCharPathTest $INSTDIR
Pop $R1
StrCmp $R1 "nothingspecial" nothing_special_path
${Print} "::error:: 'Destination Folder' contains the following invalid character$R1"
MessageBox MB_OK|MB_ICONEXCLAMATION \
"Error: 'Destination Folder' contains the following invalid character$R1" \
/SD IDOK
abort
nothing_special_path:
; test if path contains unicode characters
UnicodePathTest::UnicodePathTest $INSTDIR
Pop $R1
# Python 3 can be installed in a CP_ACP path until MKL is Unicode capable.
# (mkl_rt.dll calls LoadLibraryA() to load mkl_intel_thread.dll)
# Python 2 can only be installed to an ASCII path.
StrCmp $R1 "ascii" valid_path
StrCmp ${PY_VER} "2.7" not_cp_acp_capable
StrCmp $R1 "ascii_cp_acp" valid_path
not_cp_acp_capable:
${Print} "::error:: Due to incompatibility with several \
Python libraries, 'Destination Folder' cannot contain non-ascii characters \
(special characters or diacritics). Please choose another location."
MessageBox MB_OK|MB_ICONEXCLAMATION "Error: Due to incompatibility with several \
Python libraries, 'Destination Folder' cannot contain non-ascii characters \
(special characters or diacritics). Please choose another location." \
/SD IDOK
abort
valid_path:
Push $R1
${IsWritable} $INSTDIR $R1
IntCmp $R1 0 pathgood
Pop $R1
${Print} "::error: Path $INSTDIR is not writable. Please check permissions or \
try respawning the installer with elevated privileges."
MessageBox MB_OK|MB_ICONEXCLAMATION \
"Error: Path $INSTDIR is not writable. Please check permissions or \
try respawning the installer with elevated privileges." \
/SD IDOK
Abort
pathgood:
Pop $R1
FunctionEnd
Function .onVerifyInstDir
${LogSet} on
StrLen $0 $Desktop
StrCpy $0 $INSTDIR $0
StrCmp $0 $Desktop 0 PathGood
Abort
PathGood:
FunctionEnd
Function un.OnDirectoryLeave
MessageBox MB_YESNO \
"Are you sure you want to remove '$INSTDIR' and all of its contents?" \
/SD IDYES \
IDYES confirmed_yes IDNO confirmed_no
confirmed_no:
MessageBox MB_OK|MB_ICONSTOP "Uninstallation aborted by user." /SD IDOK
Quit
confirmed_yes:
FunctionEnd
# Make function available for both installer and uninstaller
# Uninstaller functions need an `un.` prefix, so we use a macro to do both
# see https://nsis.sourceforge.io/Sharing_functions_between_Installer_and_Uninstaller
!macro AbortRetryNSExecWaitMacro un
Function ${un}AbortRetryNSExecWait
# This function expects three arguments in the stack
# $1: 'WithLog' or 'NoLog': Use ExecToLog or just Exec, respectively
# $2: The message to show if an error occurred
# $3: The command to run, quoted
# Note that the args need to be pushed to the stack in reverse order!
# Search 'AbortRetryNSExecWait' in this script to see examples
${LogSet} on
Pop $1
Pop $2
Pop $3
${Do}
${If} $1 == "WithLog"
nsExec::ExecToLog $3
${ElseIf} $1 == "NoLog"
nsExec::Exec $3
${Else}
${Print} "::error:: AbortRetryNSExecWait: 1st argument must be 'WithLog' or 'NoLog'. You used: $1"
Abort
${EndIf}
pop $0
${If} $0 != "0"
${Print} "::error:: $2"
MessageBox MB_ABORTRETRYIGNORE|MB_ICONEXCLAMATION|MB_DEFBUTTON3 \
$2 /SD IDIGNORE IDABORT abort IDRETRY retry
; IDIGNORE: Continue anyway
StrCpy $0 "0"
goto retry
abort:
; Abort installation
Abort
retry:
; Retry the nsExec command
${EndIf}
${LoopWhile} $0 != "0"
FunctionEnd
!macroend
!insertmacro AbortRetryNSExecWaitMacro ""
!insertmacro AbortRetryNSExecWaitMacro "un."
# Installer sections
Section "Install"
${LogSet} on
${If} ${Silent}
call OnDirectoryLeave
${EndIf}
SetOutPath "$INSTDIR\Lib"
File "{{ NSIS_DIR }}\_nsis.py"
File "{{ NSIS_DIR }}\_system_path.py"
# Resolve INSTDIR so that paths and registry keys do not contain '..' or similar strings.
# $0 is empty if the directory doesn't exist, but the File commands should have created it already.
GetFullPathName $0 $INSTDIR
${If} $0 == ""
MessageBox MB_ICONSTOP "Error resolving installation directory." /SD IDABORT
Quit
${EndIf}
StrCpy $INSTDIR $0
{%- if has_license %}
SetOutPath "$INSTDIR"
File {{ licensefile }}
${Print} "By continuing this installation you are accepting this license agreement:"
${Print} "$INSTDIR\{{ LICENSEFILENAME }}"
${Print} "Please run the installer in GUI mode to read the details.$\n"
{%- endif %}
${Print} "${NAME} will now be installed into this location:"
${Print} "$INSTDIR$\n"
ReadEnvStr $0 SystemRoot
# set PATH for the installer process, so that MSVC runtimes get found OK
# This is also isolating PATH to be just us and Windows core stuff, which hopefully avoids
# clashes with other stuff on PATH
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("PATH", \
"$INSTDIR;$INSTDIR\Library\mingw-w64\bin;$INSTDIR\Library\usr\bin;$INSTDIR\Library\bin;$INSTDIR\Scripts;$INSTDIR\bin;$0;$0\system32;$0\system32\Wbem").r0'
${Print} "Unpacking payload..."
# A conda-meta\history file is required for a valid conda prefix
SetOutPath "$INSTDIR\conda-meta"
File {{ conda_history }}
SetOutPath "$INSTDIR"
File {{ conda_exe }}
File {{ pre_uninstall }}
{%- for path, files in EXTRA_FILES | items %}
SetOutPath {{ path }}
{%- for file in files %}
File {{ file }}
{%- endfor %}
{%- endfor %}
${If} $InstMode = ${JUST_ME}
SetOutPath "$INSTDIR"
FileOpen $0 ".nonadmin" w
FileClose $0
${EndIf}
SetOutPath "$INSTDIR\pkgs"
File {{ urls_file }}
File {{ urls_txt_file }}
{%- if pre_install_exists %}
File {{ pre_install }}
{%- endif %}
File {{ post_install }}
File /nonfatal /r {{ index_cache }}
File /r {{ repodata_record }}
{%- for key, escaped_val in SCRIPT_ENV_VARIABLES | items %}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("{{ key }}", {{ escaped_val }}).r0'
{%- endfor %}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_SAFETY_CHECKS", "disabled").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_EXTRA_SAFETY_CHECKS", "no").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_ROOT_PREFIX", "$INSTDIR")".r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_PKGS_DIRS", "$INSTDIR\pkgs")".r0'
# Spinners in conda write a new character with each movement of the spinner.
# For long installation times, this may cause a buffer overflow, crashing the installer.
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_QUIET", "1")".r0'
# Extra info for pre and post install scripts
# NOTE: If more vars are added, make sure to update the examples/scripts tests too
# There's a similar block for the pre_uninstall script, further down this file.
# Update that one as well!
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("PREFIX", "$INSTDIR").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_NAME", "${NAME}").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_VER", "${VERSION}").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_PLAT", "${PLATFORM}").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_TYPE", "EXE").r0'
${If} ${Silent}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_UNATTENDED", "1").r0'
${Else}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_UNATTENDED", "0").r0'
${EndIf}
${If} '{{ VIRTUAL_SPECS }}' != ''
# We need to specify CONDA_SOLVER=classic for conda-standalone
# to work around this bug in conda-libmamba-solver:
# https://github.com/conda/conda-libmamba-solver/issues/480
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_SOLVER", "classic").r0'
SetDetailsPrint TextOnly
${Print} "Checking virtual specs compatibility: {{ VIRTUAL_SPECS_DEBUG }}"
push '"$INSTDIR\_conda.exe" create --dry-run --prefix "$INSTDIR\envs\_virtual_specs_checks" --offline {{ VIRTUAL_SPECS }} {{ NO_RCS_ARG }}'
push 'Failed to check virtual specs: {{ VIRTUAL_SPECS_DEBUG }}'
push 'WithLog'
call AbortRetryNSExecWait
SetDetailsPrint both
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_SOLVER", "").r0'
${EndIf}
{%- for dist in DISTS %}
File {{ dist }}
{%- endfor %}
SetDetailsPrint TextOnly
${Print} "Setting up the package cache..."
push '"$INSTDIR\_conda.exe" constructor --prefix "$INSTDIR" --extract-conda-pkgs'
push 'Failed to extract packages'
push 'NoLog'
# We use NoLog here because TQDM progress bars are parsed as a single line in NSIS 3.08
# These can crash the installer if they get too long (a few packages is enough!)
call AbortRetryNSExecWait
SetDetailsPrint both
IfFileExists "$INSTDIR\pkgs\pre_install.bat" 0 NoPreInstall
${Print} "Running pre_install scripts..."
ReadEnvStr $5 SystemRoot
ReadEnvStr $6 windir
# This 'FileExists' also returns True for directories
${If} ${FileExists} "$5"
push '"$5\System32\cmd.exe" /D /C "$INSTDIR\pkgs\pre_install.bat"'
${ElseIf} ${FileExists} "$6"
push '"$6\System32\cmd.exe" /D /C "$INSTDIR\pkgs\pre_install.bat"'
${Else}
# Cross our fingers CMD is in PATH
push 'cmd.exe /D /C "$INSTDIR\pkgs\pre_install.bat"'
${EndIf}
push "Failed to run pre_install"
push 'WithLog'
call AbortRetryNSExecWait
NoPreInstall:
{%- for env in SETUP_ENVS %}
{%- set channels = env.final_channels|join(",") %}
# Set up {{ env.name }} env
SetDetailsPrint both
${Print} "Setting up the {{ env.name }} environment..."
SetDetailsPrint listonly
# List of packages to install
SetOutPath "{{ env.env_txt_dir }}"
File "{{ env.env_txt_abspath }}"
# A conda-meta\history file is required for a valid conda prefix
SetOutPath "{{ env.conda_meta }}"
File "{{ env.history_abspath }}"
# Set channels
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_CHANNELS", "{{ channels }}").r0'
# Set register_envs
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_REGISTER_ENVS", "{{ env.register_envs }}").r0'
# Run conda install
${If} $Ana_CreateShortcuts_State = ${BST_CHECKED}
${Print} "Installing packages for {{ env.name }}, creating shortcuts if necessary..."
push '"$INSTDIR\_conda.exe" install --offline -yp "{{ env.prefix }}" --file "{{ env.env_txt }}" {{ env.shortcuts }} {{ env.no_rcs_arg }}'
${Else}
${Print} "Installing packages for {{ env.name }}..."
push '"$INSTDIR\_conda.exe" install --offline -yp "{{ env.prefix }}" --file "{{ env.env_txt }}" --no-shortcuts {{ env.no_rcs_arg }}'
${EndIf}
push 'Failed to link extracted packages to {{ env.prefix }}!'
push 'WithLog'
SetDetailsPrint listonly
call AbortRetryNSExecWait
SetDetailsPrint both
# Cleanup {{ env.name }} env.txt
SetOutPath "$INSTDIR"
Delete "{{ env.env_txt }}"
# Restore shipped conda-meta\history for remapped
# channels and retain only the first transaction
SetOutPath "{{ env.conda_meta }}"
File "{{ env.history_abspath }}"
{%- endfor %}
{%- for condarc in WRITE_CONDARC %}
{{ condarc }}
{%- endfor %}
AddSize {{ SIZE }}
{%- if has_conda %}
${Print} "Initializing conda directories..."
push '"$INSTDIR\pythonw.exe" -E -s "$INSTDIR\Lib\_nsis.py" mkdirs'
push 'Failed to initialize conda directories'
push 'WithLog'
call AbortRetryNSExecWait
{%- endif %}
${If} $Ana_PostInstall_State = ${BST_CHECKED}
${Print} "Running post install..."
push '"$INSTDIR\pythonw.exe" -E -s "$INSTDIR\Lib\_nsis.py" post_install'
push 'Failed to run post install script'
push 'WithLog'
call AbortRetryNSExecWait
${EndIf}
${If} $Ana_ClearPkgCache_State = ${BST_CHECKED}
${Print} "Clearing package cache..."
push '"$INSTDIR\_conda.exe" clean --all --force-pkgs-dirs --yes {{ NO_RCS_ARG }}'
push 'Failed to clear package cache'
push 'WithLog'
call AbortRetryNSExecWait
${EndIf}
${If} $Ana_AddToPath_State = ${BST_CHECKED}
${Print} "Adding to PATH..."
push '"$INSTDIR\pythonw.exe" -E -s "$INSTDIR\Lib\_nsis.py" addpath ${PYVERSION} ${NAME} ${VERSION} ${ARCH}'
push 'Failed to add {{ NAME }} to the system PATH'
push 'WithLog'
call AbortRetryNSExecWait
${EndIf}
# Create registry entries saying this is the system Python
# (for this version)
!define PYREG "Software\Python\PythonCore\${PY_VER}"
${If} $Ana_RegisterSystemPython_State == ${BST_CHECKED}
WriteRegStr SHCTX "${PYREG}\Help\Main Python Documentation" \
"Main Python Documentation" \
"$INSTDIR\Doc\python${PYVERSION_JUSTDIGITS}.chm"
WriteRegStr SHCTX "${PYREG}\InstallPath" "" "$INSTDIR"
WriteRegStr SHCTX "${PYREG}\InstallPath\InstallGroup" \
"" "Python ${PY_VER}"
WriteRegStr SHCTX "${PYREG}\Modules" "" ""
WriteRegStr SHCTX "${PYREG}\PythonPath" \
"" "$INSTDIR\Lib;$INSTDIR\DLLs"
${EndIf}
${If} $ARGV_NoRegistry == "0"
# Registry uninstall info
WriteRegStr SHCTX "${UNINSTREG}" "DisplayName" "${UNINSTALL_NAME}"
WriteRegStr SHCTX "${UNINSTREG}" "DisplayVersion" "${VERSION}"
WriteRegStr SHCTX "${UNINSTREG}" "Publisher" "${COMPANY}"
WriteRegStr SHCTX "${UNINSTREG}" "UninstallString" \
"$\"$INSTDIR\Uninstall-${NAME}.exe$\""
WriteRegStr SHCTX "${UNINSTREG}" "QuietUninstallString" \
"$\"$INSTDIR\Uninstall-${NAME}.exe$\" /S"
WriteRegStr SHCTX "${UNINSTREG}" "DisplayIcon" \
"$\"$INSTDIR\Uninstall-${NAME}.exe$\""
WriteRegDWORD SHCTX "${UNINSTREG}" "NoModify" 1
WriteRegDWORD SHCTX "${UNINSTREG}" "NoRepair" 1
${EndIf}
WriteUninstaller "$INSTDIR\Uninstall-${NAME}.exe"
# To address CVE-2022-26526.
# Revoke the write permission on directory "$INSTDIR" for Users if this is
# being run with administrative privileges. Users are:
# AU - authenticated users
# BU - built-in (local) users
# DU - domain users
${If} ${UAC_IsAdmin}
${Print} "Setting installation directory permissions..."
AccessControl::DisableFileInheritance "$INSTDIR"
AccessControl::RevokeOnFile "$INSTDIR" "(AU)" "GenericWrite"
AccessControl::RevokeOnFile "$INSTDIR" "(DU)" "GenericWrite"
AccessControl::RevokeOnFile "$INSTDIR" "(BU)" "GenericWrite"
AccessControl::SetOnFile "$INSTDIR" "(BU)" "GenericRead + GenericExecute"
AccessControl::SetOnFile "$INSTDIR" "(DU)" "GenericRead + GenericExecute"
${EndIf}
${Print} "Done!"
SectionEnd
!macro AbortRetryNSExecWaitLibNsisCmd cmd
SetDetailsPrint both
${Print} "Running ${cmd} scripts..."
SetDetailsPrint listonly
${If} ${Silent}
push '"$INSTDIR\pythonw.exe" -E -s "$INSTDIR\Lib\_nsis.py" ${cmd}'
${Else}
push '"$INSTDIR\python.exe" -E -s "$INSTDIR\Lib\_nsis.py" ${cmd}'
${EndIf}
push "Failed to run ${cmd}"
push 'WithLog'
call un.AbortRetryNSExecWait
SetDetailsPrint both
!macroend
Section "Uninstall"
${LogSet} on
${If} ${Silent}
!insertmacro un.ParseCommandLineArgs
${EndIf}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_ROOT_PREFIX", "$INSTDIR")".r0'
# ensure that MSVC runtime DLLs are on PATH during uninstallation
ReadEnvStr $0 PATH
# set PATH for the installer process, so that MSVC runtimes get found OK
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("PATH", \
"$INSTDIR;$INSTDIR\Library\mingw-w64\bin;$INSTDIR\Library\usr\bin;$INSTDIR\Library\bin;$INSTDIR\Scripts;$INSTDIR\bin;$0;$0\system32;$0\system32\Wbem").r0'
# our newest Python builds have a patch that allows us to control the PATH search stuff much more
# carefully. More info at https://docs.conda.io/projects/conda/en/latest/user-guide/troubleshooting.html#solution
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_DLL_SEARCH_MODIFICATION_ENABLE", "1").r0'
# Spinners in conda write a new character with each movement of the spinner.
# For long installation times, this may cause a buffer overflow, crashing the installer.
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_QUIET", "1")".r0'
# Read variables the uninstaller needs from the registry
StrCpy $R0 "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
StrLen $R1 "Uninstall-${NAME}.exe"
IntOp $R1 $R1 + 3
StrCpy $0 0
loop_path:
EnumRegKey $1 SHCTX $R0 $0
StrCmp $1 "" endloop_path
StrCpy $2 "$R0\$1"
ReadRegStr $4 SHCTX $2 "UninstallString"
StrLen $5 $4
IntOp $5 $5 - $R1
StrCpy $4 $4 $5 1
${If} $4 == $INSTDIR
StrCpy $INSTALLER_NAME_FULL $1
ReadRegStr $INSTALLER_VERSION SHCTX $2 "DisplayVersion"
goto endloop_path
${EndIf}
IntOp $0 $0 + 1
goto loop_path
endloop_path:
# Extra info for pre_uninstall scripts
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("PREFIX", "$INSTDIR").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_NAME", "${NAME}").r0'
StrCpy $0 ${VERSION}
${If} $INSTALLER_VERSION != ""
StrCpy $0 $INSTALLER_VERSION
${EndIf}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_VER", "$0").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_PLAT", "${PLATFORM}").r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_TYPE", "EXE").r0'
${If} ${Silent}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_UNATTENDED", "1").r0'
${Else}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_UNATTENDED", "0").r0'
${EndIf}
{%- if uninstall_with_conda_exe %}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "pre_uninstall"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmpath"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmreg"
# Parse arguments
StrCpy $R0 ""
${If} $UninstRemoveConfigFiles_User_State == ${BST_CHECKED}
${If} $UninstRemoveConfigFiles_System_State == ${BST_CHECKED}
StrCpy $R0 "$R0 --remove-config-files=all"
${Else}
StrCpy $R0 "$R0 --remove-config-files=user"
${EndIf}
${ElseIf} $UninstRemoveConfigFiles_System_State == ${BST_CHECKED}
StrCpy $R0 "$R0 --remove-config-files=system"
${EndIf}
${If} $UninstRemoveUserData_State == ${BST_CHECKED}
StrCpy $R0 "$R0 --remove-user-data"
${EndIf}
${If} $UninstRemoveCaches_State == ${BST_CHECKED}
StrCpy $R0 "$R0 --remove-caches"
${EndIf}
${Print} "Removing files and folders..."
push '"$INSTDIR\_conda.exe" constructor uninstall $R0 --prefix "$INSTDIR"'
push 'Failed to remove files and folders. Please see the log for more information.'
push 'WithLog'
SetDetailsPrint listonly
call un.AbortRetryNSExecWait
SetDetailsPrint both
# The uninstallation may leave the install.log, the uninstaller,
# and .conda_trash files behind, so remove those manually.
${If} ${FileExists} "$INSTDIR"
RMDir /r /REBOOTOK "$INSTDIR"
${EndIf}
{%- else %}
{%- for env in SETUP_ENVS | reverse %}
{%- set subdir = ("\envs\%(name)s" | format(name=env.name)) if env.name != "base" else "" %}
SetDetailsPrint both
${Print} "Deleting ${NAME} menus in {{ env.name }}..."
SetDetailsPrint listonly
push '"$INSTDIR\_conda.exe" constructor --prefix "$INSTDIR{{ subdir }}" --rm-menus'
push 'Failed to delete menus in {{ env.name }}'
push 'WithLog'
call un.AbortRetryNSExecWait
SetDetailsPrint both
{%- endfor %}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "pre_uninstall"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmpath"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmreg"
${Print} "Removing files and folders..."
nsExec::Exec 'cmd.exe /D /C RMDIR /Q /S "$INSTDIR"'
# In case the last command fails, run the slow method to remove leftover
RMDir /r /REBOOTOK "$INSTDIR"
{%- endif %}
${If} $INSTALLER_NAME_FULL != ""
DeleteRegKey SHCTX "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$INSTALLER_NAME_FULL"
${EndIf}
# If Anaconda was registered as the official Python for this version,
# remove it from the registry
StrCpy $R0 "SOFTWARE\Python\PythonCore"
StrCpy $0 0
loop_py:
EnumRegKey $1 SHCTX $R0 $0
StrCmp $1 "" endloop_py
ReadRegStr $2 SHCTX "$R0\$1\InstallPath" ""
${If} $2 == $INSTDIR
StrCpy $R1 $1
DeleteRegKey SHCTX "$R0\$1"
goto endloop_py
${EndIf}
IntOp $0 $0 + 1
goto loop_py
endloop_py:
${Print} "Done!"
${If} ${Silent}
# give it some time so users can read the last lines
${Print} "Closing in 3s..."
Sleep 3000
${EndIf}
SectionEnd
!if '{{ SIGNTOOL_COMMAND }}' != ''
# Signing for installer and uninstaller; nsis 3.08 required for uninstfinalize!
# "= 0" comparison required to prevent both tasks running in parallel, which would cause signtool to fail
# %1 is replaced by the installer and uninstaller paths, respectively
!finalize '{{ SIGNTOOL_COMMAND }} "%1"' = 0
!uninstfinalize '{{ SIGNTOOL_COMMAND }} "%1"' = 0
!endif