1704 lines
58 KiB
Cheetah
Raw Normal View History

# Installer template file for creating a Windows installer using NSIS.
2025-03-11 11:29:57 -04:00
!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
2025-03-11 11:29:57 -04:00
Unicode true
2025-03-11 11:29:57 -04:00
{%- if enable_debugging %}
# Special logging build needed for ENABLE_LOGGING
# See https://nsis.sourceforge.io/Special_Builds
!define ENABLE_LOGGING
2025-03-11 11:29:57 -04:00
{%- 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
2025-03-11 11:29:57 -04:00
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"
2025-03-11 11:29:57 -04:00
{%- 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})"
2025-03-11 11:29:57 -04:00
!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
2025-03-11 11:29:57 -04:00
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}"
2025-03-11 11:29:57 -04:00
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}"
2025-03-11 11:29:57 -04:00
VIProductVersion {{ vipv }}
BrandingText /TRIMLEFT "${COMPANY}"
# Interface configuration
2025-03-11 11:29:57 -04:00
!define MUI_ICON {{ iconfile }}
!define MUI_UNICON {{ iconfile }}
!define MUI_HEADERIMAGE
2025-03-11 11:29:57 -04:00
!define MUI_HEADERIMAGE_BITMAP {{ headerimage }}
!define MUI_HEADERIMAGE_UNBITMAP {{ headerimage }}
!define MUI_ABORTWARNING
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_UNFINISHPAGE_NOAUTOCLOSE
2025-03-11 11:29:57 -04:00
!define MUI_WELCOMEFINISHPAGE_BITMAP {{ welcomeimage }}
!define MUI_UNWELCOMEFINISHPAGE_BITMAP {{ welcomeimage }}
#!define MUI_CUSTOMFUNCTION_GUIINIT GuiInit
# Pages
#!define MUI_PAGE_CUSTOMFUNCTION_SHOW OnStartup
2025-03-11 11:29:57 -04:00
{%- if custom_welcome %}
# Custom welcome file(s)
2025-03-11 11:29:57 -04:00
{{ CUSTOM_WELCOME_FILE }}
{%- else %}
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipPageIfUACInnerInstance
!insertmacro MUI_PAGE_WELCOME
2025-03-11 11:29:57 -04:00
{%- endif %}
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipPageIfUACInnerInstance
2025-03-11 11:29:57 -04:00
!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
2025-03-11 11:29:57 -04:00
{%- for page in POST_INSTALL_PAGES %}
{{ page }}
{%- endfor %}
{%- if with_conclusion_text %}
!define MUI_FINISHPAGE_TITLE {{ conclusion_title }}
!define MUI_FINISHPAGE_TITLE_3LINES
2025-03-11 11:29:57 -04:00
!define MUI_FINISHPAGE_TEXT {{ conclusion_text }}
{%- endif %}
2025-03-11 11:29:57 -04:00
{%- if custom_conclusion %}
# Custom conclusion file(s)
2025-03-11 11:29:57 -04:00
{{ CUSTOM_CONCLUSION_FILE }}
{%- else %}
!insertmacro MUI_PAGE_FINISH
2025-03-11 11:29:57 -04:00
{%- endif %}
2023-11-06 15:52:07 -05:00
!insertmacro MUI_UNPAGE_WELCOME
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.OnDirectoryLeave
!insertmacro MUI_UNPAGE_CONFIRM
2025-03-11 11:29:57 -04:00
{%- 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}
2025-03-11 11:29:57 -04:00
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\
2025-03-11 11:29:57 -04:00
> $EXEFILE /InstallationType=AllUsers$\n\
$\n\
Install for just me, add to PATH and register as system Python:$\n\
2025-03-11 11:29:57 -04:00
> $EXEFILE /RegisterPython=1 /AddToPath=1$\n\
$\n\
Install for just me, with no registry modification (for CI):$\n\
2025-03-11 11:29:57 -04:00
> $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\
2025-03-11 11:29:57 -04:00
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}
2025-03-11 11:29:57 -04:00
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}
2024-01-26 13:41:45 -05:00
${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}
2024-01-26 13:41:45 -05:00
${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}
2025-03-11 11:29:57 -04:00
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
2025-03-11 11:29:57 -04:00
${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
2025-03-11 11:29:57 -04:00
{%- 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
2025-03-11 11:29:57 -04:00
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}
2025-03-11 11:29:57 -04:00
{%- 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
2025-03-11 11:29:57 -04:00
${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.
2025-03-11 11:29:57 -04:00
{%- if initialize_conda %}
{%- if initialize_by_default %}
${If} $InstMode == ${JUST_ME}
StrCpy $Ana_AddToPath_State ${BST_CHECKED}
2025-03-11 11:29:57 -04:00
${EndIf}
{%- else %}
StrCpy $Ana_AddToPath_State ${BST_UNCHECKED}
2025-03-11 11:29:57 -04:00
{%- 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
2025-03-11 11:29:57 -04:00
${Print} "Welcome to ${NAME} ${VERSION}$\n"
Pop $R2
Pop $R1
Pop $2
Pop $1
Pop $0
FunctionEnd
2025-03-11 11:29:57 -04:00
!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
2025-03-11 11:29:57 -04:00
{%- 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:
2025-03-11 11:29:57 -04:00
${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
2025-03-11 11:29:57 -04:00
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"
2025-03-11 11:29:57 -04:00
${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}
2025-03-11 11:29:57 -04:00
${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}
2025-03-11 11:29:57 -04:00
${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"
2025-03-11 11:29:57 -04:00
{%- if check_path_spaces %}
StrCpy $R8 "$R8Please remove the space$R1 from the destination folder."
StrCpy $R9 "Error"
2025-03-11 11:29:57 -04:00
{%- else %}
StrCpy $R8 "$R8Please consider removing the space$R1."
StrCpy $R9 "Warning"
2025-03-11 11:29:57 -04:00
{%- endif %}
# Show message box then take the user back to the Directory page.
${If} ${Silent}
2025-03-11 11:29:57 -04:00
${Print} "::$R9:: $R8"
${Else}
MessageBox MB_OK|MB_ICONINFORMATION "$R9: $R8" /SD IDOK
${EndIf}
2025-03-11 11:29:57 -04:00
{%- if check_path_spaces %}
abort
2025-03-11 11:29:57 -04:00
{%- endif %}
NoSpaces:
Pop $R7
Pop $R8
Pop $R9
# List of characters not allowed anywhere in $INSTDIR
2023-11-06 15:52:07 -05:00
Push "^%!=,()"
Push $INSTDIR
Call StrCSpn
Pop $R0
StrCmp $R0 "" NoInvalidCharaceters
2025-03-11 11:29:57 -04:00
${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
2025-03-11 11:29:57 -04:00
${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:
2025-03-11 11:29:57 -04:00
${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
2025-03-11 11:29:57 -04:00
${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}
2025-03-11 11:29:57 -04:00
${Print} "::error:: AbortRetryNSExecWait: 1st argument must be 'WithLog' or 'NoLog'. You used: $1"
Abort
${EndIf}
pop $0
${If} $0 != "0"
2025-03-11 11:29:57 -04:00
${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"
2025-03-11 11:29:57 -04:00
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
2025-03-11 11:29:57 -04:00
{%- 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'
2025-03-11 11:29:57 -04:00
${Print} "Unpacking payload..."
# A conda-meta\history file is required for a valid conda prefix
SetOutPath "$INSTDIR\conda-meta"
2025-03-11 11:29:57 -04:00
File {{ conda_history }}
SetOutPath "$INSTDIR"
2025-03-11 11:29:57 -04:00
File {{ conda_exe }}
File {{ pre_uninstall }}
2025-03-11 11:29:57 -04:00
{%- 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"
2025-03-11 11:29:57 -04:00
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'
2024-01-26 13:41:45 -05:00
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'
2025-03-11 11:29:57 -04:00
# 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'
2025-03-11 11:29:57 -04:00
${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}
2025-03-11 11:29:57 -04:00
${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
2025-03-11 11:29:57 -04:00
${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
2025-03-11 11:29:57 -04:00
${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:
2025-03-11 11:29:57 -04:00
{%- 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 }}"
2025-03-11 11:29:57 -04:00
# Restore shipped conda-meta\history for remapped
# channels and retain only the first transaction
SetOutPath "{{ env.conda_meta }}"
File "{{ env.history_abspath }}"
{%- endfor %}
2025-03-11 11:29:57 -04:00
{%- for condarc in WRITE_CONDARC %}
{{ condarc }}
{%- endfor %}
2025-03-11 11:29:57 -04:00
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
2025-03-11 11:29:57 -04:00
{%- endif %}
${If} $Ana_PostInstall_State = ${BST_CHECKED}
2025-03-11 11:29:57 -04:00
${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}
2025-03-11 11:29:57 -04:00
${Print} "Clearing package cache..."
push '"$INSTDIR\_conda.exe" clean --all --yes {{ NO_RCS_ARG }}'
push 'Failed to clear package cache'
push 'WithLog'
call AbortRetryNSExecWait
${EndIf}
${If} $Ana_AddToPath_State = ${BST_CHECKED}
2025-03-11 11:29:57 -04:00
${Print} "Adding to PATH..."
push '"$INSTDIR\pythonw.exe" -E -s "$INSTDIR\Lib\_nsis.py" addpath ${PYVERSION} ${NAME} ${VERSION} ${ARCH}'
2025-03-11 11:29:57 -04:00
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"
# Delete registry entries for environment variables set by PothosSDR
# With admin rights, we can delete them
${If} ${UAC_IsAdmin}
DetailPrint "Deleting PothosSDR registry environment variables..."
!define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
DeleteRegValue ${env_hklm} "GR_PREFIX"
DeleteRegValue ${env_hklm} "GRC_BLOCKS_PATH"
DeleteRegValue ${env_hklm} "UHD_PKG_PATH"
DeleteRegValue ${env_hklm} "VOLK_PREFIX"
# Without admin rights, we have to shadow them with empty values set for the user
${Else}
DetailPrint "Overriding PothosSDR registry environment variables for user..."
!define env_hkcu 'HKCU "Environment"'
WriteRegExpandStr ${env_hkcu} "GR_PREFIX" ""
WriteRegExpandStr ${env_hkcu} "GRC_BLOCKS_PATH" ""
WriteRegExpandStr ${env_hkcu} "UHD_PKG_PATH" ""
WriteRegExpandStr ${env_hkcu} "VOLK_PREFIX" ""
${EndIf}
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
# 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}
2025-03-11 11:29:57 -04:00
${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}
2025-03-11 11:29:57 -04:00
${Print} "Done!"
SectionEnd
!macro AbortRetryNSExecWaitLibNsisCmd cmd
SetDetailsPrint both
2025-03-11 11:29:57 -04:00
${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"
2024-01-26 13:41:45 -05:00
${LogSet} on
2025-03-11 11:29:57 -04:00
${If} ${Silent}
!insertmacro un.ParseCommandLineArgs
${EndIf}
2024-01-26 13:41:45 -05:00
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'
2025-03-11 11:29:57 -04:00
# 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'
2025-03-11 11:29:57 -04:00
${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}
2025-03-11 11:29:57 -04:00
{%- if uninstall_with_conda_exe %}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "pre_uninstall"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmpath"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmreg"
2025-03-11 11:29:57 -04:00
# 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"
2025-03-11 11:29:57 -04:00
{%- endif %}
# Delete user environment variables that we set during installation
${IfNot} ${UAC_IsAdmin}
DeleteRegValue ${env_hkcu} "GR_PREFIX"
DeleteRegValue ${env_hkcu} "GRC_BLOCKS_PATH"
DeleteRegValue ${env_hkcu} "UHD_PKG_PATH"
DeleteRegValue ${env_hkcu} "VOLK_PREFIX"
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
${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:
2025-03-11 11:29:57 -04:00
${Print} "Done!"
${If} ${Silent}
# give it some time so users can read the last lines
${Print} "Closing in 3s..."
Sleep 3000
${EndIf}
SectionEnd
2025-03-11 11:29:57 -04:00
!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
2025-03-11 11:29:57 -04:00
!finalize '{{ SIGNTOOL_COMMAND }} "%1"' = 0
!uninstfinalize '{{ SIGNTOOL_COMMAND }} "%1"' = 0
!endif