1!define PRODUCT_NAME "Salt Minion"
2!define PRODUCT_NAME_OTHER "Salt"
3!define PRODUCT_PUBLISHER "SaltStack, Inc"
4!define PRODUCT_WEB_SITE "http://saltstack.org"
5!define PRODUCT_CALL_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\salt-call.exe"
6!define PRODUCT_CP_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\salt-cp.exe"
7!define PRODUCT_KEY_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\salt-key.exe"
8!define PRODUCT_MASTER_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\salt-master.exe"
9!define PRODUCT_MINION_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\salt-minion.exe"
10!define PRODUCT_RUN_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\salt-run.exe"
11!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
12!define PRODUCT_UNINST_KEY_OTHER "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME_OTHER}"
13!define PRODUCT_UNINST_ROOT_KEY "HKLM"
14!define OUTFILE "Salt-Minion-${PRODUCT_VERSION}-Py${PYTHON_VERSION}-${CPUARCH}-Setup.exe"
15!define /date TIME_STAMP "%Y-%m-%d-%H-%M-%S"
16
17# Request admin rights
18RequestExecutionLevel admin
19
20# Import Libraries
21!include "FileFunc.nsh"
22!include "LogicLib.nsh"
23!include "MoveFileFolder.nsh"
24!include "MUI2.nsh"
25!include "nsDialogs.nsh"
26!include "StrFunc.nsh"
27!include "WinMessages.nsh"
28!include "WinVer.nsh"
29!include "x64.nsh"
30${StrLoc}
31${StrStrAdv}
32
33# Required by MoveFileFolder.nsh
34!insertmacro Locate
35
36!ifdef SaltVersion
37    !define PRODUCT_VERSION "${SaltVersion}"
38!else
39    !define PRODUCT_VERSION "Undefined Version"
40!endif
41
42!ifdef PythonVersion
43    !define PYTHON_VERSION "${PythonVersion}"
44!else
45    !define PYTHON_VERSION "3"
46!endif
47
48!if "$%PROCESSOR_ARCHITECTURE%" == "AMD64"
49    !define CPUARCH "AMD64"
50!else if "$%PROCESSOR_ARCHITEW6432%" == "AMD64"
51    !define CPUARCH "AMD64"
52!else
53    !define CPUARCH "x86"
54!endif
55
56# Part of the Trim function for Strings
57!define Trim "!insertmacro Trim"
58!macro Trim ResultVar String
59    Push "${String}"
60    Call Trim
61    Pop  "${ResultVar}"
62!macroend
63
64# Part of the Explode function for Strings
65!define Explode "!insertmacro Explode"
66!macro Explode Length Separator String
67    Push "${Separator}"
68    Push "${String}"
69    Call Explode
70    Pop  "${Length}"
71!macroend
72
73# Part of the StrContains function for Strings
74!define StrContains "!insertmacro StrContains"
75!macro StrContains OUT NEEDLE HAYSTACK
76    Push "${HAYSTACK}"
77    Push "${NEEDLE}"
78    Call StrContains
79    Pop  "${OUT}"
80!macroend
81
82
83###############################################################################
84# Configure Pages, Ordering, and Configuration
85###############################################################################
86!define MUI_ABORTWARNING
87!define MUI_ICON "salt.ico"
88!define MUI_UNICON "salt.ico"
89!define MUI_WELCOMEFINISHPAGE_BITMAP "panel.bmp"
90!define MUI_UNWELCOMEFINISHPAGE_BITMAP "panel.bmp"
91
92
93# Welcome page
94!insertmacro MUI_PAGE_WELCOME
95
96# License page
97!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
98
99# Install location page
100!define MUI_PAGE_CUSTOMFUNCTION_SHOW pageCheckExistingInstall
101!insertmacro MUI_PAGE_DIRECTORY
102
103# Configure Minion page
104Page custom pageMinionConfig pageMinionConfig_Leave
105
106# Instfiles page
107!insertmacro MUI_PAGE_INSTFILES
108
109# Finish page (Customized)
110!define MUI_PAGE_CUSTOMFUNCTION_SHOW pageFinish_Show
111!define MUI_PAGE_CUSTOMFUNCTION_LEAVE pageFinish_Leave
112!insertmacro MUI_PAGE_FINISH
113
114# Uninstaller pages
115!insertmacro MUI_UNPAGE_INSTFILES
116
117# Language files
118!insertmacro MUI_LANGUAGE "English"
119
120
121###############################################################################
122# Custom Dialog Box Variables
123###############################################################################
124Var Dialog
125Var Label
126Var MinionStart_ChkBox
127Var MinionStartDelayed_ChkBox
128Var MasterHost_Cfg
129Var MasterHost_TxtBox
130Var MasterHost
131Var MinionName_Cfg
132Var MinionName_TxtBox
133Var MinionName
134Var ExistingConfigFound
135Var ConfigType_DropList
136Var ConfigType
137Var CustomConfig_TxtBox
138Var CustomConfig_Btn
139Var CustomConfig
140Var CustomConfigWarning_Lbl
141Var ExistingConfigWarning_Lbl
142Var DefaultConfigWarning_Lbl
143Var MoveExistingConfig_ChkBox
144Var MoveExistingConfig
145Var StartMinion
146Var StartMinionDelayed
147Var DeleteInstallDir
148Var DeleteRootDir
149Var ConfigWriteMinion
150Var ConfigWriteMaster
151# For new method installation
152Var RegInstDir
153Var RegRootDir
154Var RootDir
155Var SysDrive
156Var ExistingInstallation
157Var CustomLocation
158
159
160###############################################################################
161# Directory Picker Dialog Box
162###############################################################################
163Function pageCheckExistingInstall
164    # If this is an Existing Installation we want to disable the directory
165    # picker functionality
166    # https://nsis-dev.github.io/NSIS-Forums/html/t-166727.html
167    # Use the winspy tool (https://sourceforge.net/projects/winspyex/) to get
168    # the Control ID for the items you want to disable
169    # The Control ID is in the Details tab
170    # It is a Hex value that needs to be converted to an integer
171    ${If} $ExistingInstallation == 1
172        # 32770 is Class name used by all NSIS dialog boxes
173        FindWindow $R0 "#32770" "" $HWNDPARENT
174        # 1019 is the Destination Folder text field (0x3FB)
175        GetDlgItem $R1 $R0 1019
176        EnableWindow $R1 0
177        # 1001 is the Browse button (0x3E9)
178        GetDlgItem $R1 $R0 1001
179        EnableWindow $R1 0
180        # Disabling the Location Picker causes the buttons to behave incorrectly
181        # Esc and Enter don't work. Nor can you use Alt+N and Alt+B. Setting
182        # the focus to the Next button seems to fix this
183        # Set focus on Next button (0x1)
184        GetDlgItem $R1 $HWNDPARENT 1
185        SendMessage $HWNDPARENT ${WM_NEXTDLGCTL} $R1 1
186    ${EndIf}
187FunctionEnd
188
189
190###############################################################################
191# Minion Settings Dialog Box
192###############################################################################
193Function pageMinionConfig
194
195    # Set Page Title and Description
196    !insertmacro MUI_HEADER_TEXT "Minion Settings" "Set the Minion Master and ID"
197    nsDialogs::Create 1018
198    Pop $Dialog
199
200    ${If} $Dialog == error
201        Abort
202    ${EndIf}
203
204    # Master IP or Hostname Dialog Control
205    ${NSD_CreateLabel} 0 0 100% 9u "&Master IP or Hostname:"
206    Pop $Label
207
208    ${NSD_CreateText} 0 10u 100% 12u $MasterHost
209    Pop $MasterHost_TxtBox
210
211    # Minion ID Dialog Control
212    ${NSD_CreateLabel} 0 30u 100% 9u "Minion &Name:"
213    Pop $Label
214
215    ${NSD_CreateText} 0 40u 100% 12u $MinionName
216    Pop $MinionName_TxtBox
217
218    # Config Drop List
219    ${NSD_CreateDropList} 0 60u 25% 36u ""
220    Pop $ConfigType_DropList
221    ${NSD_CB_AddString} $ConfigType_DropList "Default Config"
222    ${NSD_CB_AddString} $ConfigType_DropList "Custom Config"
223    ${NSD_OnChange} $ConfigType_DropList pageMinionConfig_OnChange
224
225    # Add Existing Config Warning Label
226    ${NSD_CreateLabel} 0 75u 100% 50u "The values above are taken from an \
227        existing configuration found in `$RootDir\conf\minion`.$\n\
228        $\n\
229        Clicking `Install` will leave the existing config unchanged."
230    Pop $ExistingConfigWarning_Lbl
231    CreateFont $0 "Arial" 10 500 /ITALIC
232    SendMessage $ExistingConfigWarning_Lbl ${WM_SETFONT} $0 1
233    SetCtlColors $ExistingConfigWarning_Lbl 0xBB0000 transparent
234
235    # Add Checkbox to move root_dir
236    ${NSD_CreateCheckBox} 0 125u 100% 10u \
237        " Move &existing root directory (C:\salt) to %ProgramData%\Salt."
238    Pop $MoveExistingConfig_ChkBox
239    CreateFont $0 "Arial" 10 500
240    SendMessage $MoveExistingConfig_ChkBox ${WM_SETFONT} $0 1
241    ${If} $MoveExistingConfig == 1
242        ${NSD_Check} $MoveExistingConfig_ChkBox
243    ${EndIf}
244
245    # Add Default Config Warning Label
246    ${NSD_CreateLabel} 0 75u 100% 60u "Clicking `Install` will backup the \
247        the existing minion config file and minion.d directories. The values \
248        above will be used in the new default config.$\n\
249            $\n\
250            NOTE: If Master IP is set to `salt` and Minion Name is set to \
251            `hostname` no changes will be made."
252    Pop $DefaultConfigWarning_Lbl
253    CreateFont $0 "Arial" 10 500 /ITALIC
254    SendMessage $DefaultConfigWarning_Lbl ${WM_SETFONT} $0 1
255    SetCtlColors $DefaultConfigWarning_Lbl 0xBB0000 transparent
256
257    # Add Custom Config File Selector and Warning Label
258    ${NSD_CreateText} 26% 60u 64% 12u $CustomConfig
259    Pop $CustomConfig_TxtBox
260    ${NSD_CreateButton} 91% 60u 9% 12u "..."
261    Pop $CustomConfig_Btn
262    ${NSD_OnClick} $CustomConfig_Btn pageCustomConfigBtn_OnClick
263
264    ${If} $ExistingConfigFound == 0
265        ${NSD_CreateLabel} 0 75u 100% 60u "Values entered above will be used \
266            in the custom config.$\n\
267            $\n\
268            NOTE: If Master IP is set to `salt` and Minion Name is set to \
269            `hostname` no changes will be made."
270    ${Else}
271        ${NSD_CreateLabel} 0 75u 100% 60u "Clicking `Install` will backup the \
272            the existing minion config file and minion.d directories. The \
273            values above will be used in the custom config.$\n\
274            $\n\
275            NOTE: If Master IP is set to `salt` and Minion Name is set to \
276            `hostname` no changes will be made."
277    ${Endif}
278    Pop $CustomConfigWarning_Lbl
279    CreateFont $0 "Arial" 10 500 /ITALIC
280    SendMessage $CustomConfigWarning_Lbl ${WM_SETFONT} $0 1
281    SetCtlColors $CustomConfigWarning_Lbl 0xBB0000 transparent
282
283    # If existing config found, add the Existing Config option to the Drop List
284    # If not, hide the Default Warning
285    ${If} $ExistingConfigFound == 1
286        ${NSD_CB_AddString} $ConfigType_DropList "Existing Config"
287    ${Else}
288        ShowWindow $DefaultConfigWarning_Lbl ${SW_HIDE}
289    ${Endif}
290
291    ${NSD_CB_SelectString} $ConfigType_DropList $ConfigType
292    ${NSD_SetText} $CustomConfig_TxtBox $CustomConfig
293
294    Call pageMinionConfig_OnChange
295
296    nsDialogs::Show
297
298FunctionEnd
299
300
301Function pageMinionConfig_OnChange
302
303    # You have to pop the top handle to keep the stack clean
304    Pop $R0
305
306    # Assign the current checkbox state to the variable
307    ${NSD_GetText} $ConfigType_DropList $ConfigType
308
309    # Update Dialog
310    ${Switch} $ConfigType
311        ${Case} "Existing Config"
312            # Enable Master/Minion and set values
313            EnableWindow $MasterHost_TxtBox 0
314            EnableWindow $MinionName_TxtBox 0
315            ${NSD_SetText} $MasterHost_TxtBox $MasterHost_Cfg
316            ${NSD_SetText} $MinionName_TxtBox $MinionName_Cfg
317            # Hide Custom File Picker
318            ShowWindow $CustomConfig_TxtBox ${SW_HIDE}
319            ShowWindow $CustomConfig_Btn ${SW_HIDE}
320            # Hide Warnings
321            ShowWindow $DefaultConfigWarning_Lbl ${SW_HIDE}
322            ShowWindow $CustomConfigWarning_Lbl ${SW_HIDE}
323            # Show Existing Warning
324            ShowWindow $ExistingConfigWarning_Lbl ${SW_SHOW}
325            ${If} $RootDir == "C:\salt"
326                ShowWindow $MoveExistingConfig_ChkBox ${SW_SHOW}
327            ${Else}
328                ShowWindow $MoveExistingConfig_ChkBox ${SW_HIDE}
329            ${EndIf}
330            ${Break}
331        ${Case} "Custom Config"
332            # Enable Master/Minion and set values
333            EnableWindow $MasterHost_TxtBox 1
334            EnableWindow $MinionName_TxtBox 1
335            ${NSD_SetText} $MasterHost_TxtBox $MasterHost
336            ${NSD_SetText} $MinionName_TxtBox $MinionName
337            # Show Custom File Picker
338            ShowWindow $CustomConfig_TxtBox ${SW_SHOW}
339            ShowWindow $CustomConfig_Btn ${SW_SHOW}
340            # Hide Warnings
341            ShowWindow $DefaultConfigWarning_Lbl ${SW_HIDE}
342            ShowWindow $ExistingConfigWarning_Lbl ${SW_HIDE}
343            ShowWindow $MoveExistingConfig_ChkBox ${SW_HIDE}
344            # Show Custom Warning
345            ShowWindow $CustomConfigWarning_Lbl ${SW_SHOW}
346            ${Break}
347        ${Case} "Default Config"
348            # Enable Master/Minion and set values
349            EnableWindow $MasterHost_TxtBox 1
350            EnableWindow $MinionName_TxtBox 1
351            ${NSD_SetText} $MasterHost_TxtBox $MasterHost
352            ${NSD_SetText} $MinionName_TxtBox $MinionName
353            # Hide Custom File Picker
354            ShowWindow $CustomConfig_TxtBox ${SW_HIDE}
355            ShowWindow $CustomConfig_Btn ${SW_HIDE}
356            # Hide Warnings
357            ShowWindow $ExistingConfigWarning_Lbl ${SW_HIDE}
358            ShowWindow $MoveExistingConfig_ChkBox ${SW_HIDE}
359            ShowWindow $CustomConfigWarning_Lbl ${SW_HIDE}
360            # Show Default Warning, if there is an existing config
361            ${If} $ExistingConfigFound == 1
362                ShowWindow $DefaultConfigWarning_Lbl ${SW_SHOW}
363            ${Endif}
364            ${Break}
365    ${EndSwitch}
366
367FunctionEnd
368
369# File Picker Definitions
370!define OFN_FILEMUSTEXIST 0x00001000
371!define OFN_DONTADDTOREC 0x02000000
372!define OPENFILENAME_SIZE_VERSION_400 76
373!define OPENFILENAME 'i,i,i,i,i,i,i,i,i,i,i,i,i,i,&i2,&i2,i,i,i,i'
374Function pageCustomConfigBtn_OnClick
375
376    Pop $0
377    System::Call '*(&t${NSIS_MAX_STRLEN})i.s'  # Allocate OPENFILENAME.lpstrFile buffer
378    System::Call '*(${OPENFILENAME})i.r0'      # Allocate OPENFILENAME struct
379    System::Call '*$0(${OPENFILENAME})(${OPENFILENAME_SIZE_VERSION_400}, \
380                      $hwndparent, , , , , , sr1, ${NSIS_MAX_STRLEN} , , , , \
381                      t"Select Custom Config File", \
382                      ${OFN_FILEMUSTEXIST} | ${OFN_DONTADDTOREC})'
383
384    # Populate file name field
385    ${NSD_GetText} $CustomConfig_TxtBox $2
386    System::Call "*$1(&t${NSIS_MAX_STRLEN}r2)" ; Set lpstrFile to the old path (if any)
387
388    # Open the dialog
389    System::Call 'COMDLG32::GetOpenFileName(ir0)i.r2'
390
391    # Get file name field
392    ${If} $2 <> 0
393        System::Call "*$1(&t${NSIS_MAX_STRLEN}.r2)"
394        ${NSD_SetText} $CustomConfig_TxtBox $2
395    ${EndIf}
396
397    # Free resources
398    System::Free $1
399    System::Free $0
400
401FunctionEnd
402
403
404Function pageMinionConfig_Leave
405
406    # Save the State
407    ${NSD_GetText} $MasterHost_TxtBox $MasterHost
408    ${NSD_GetText} $MinionName_TxtBox $MinionName
409    ${NSD_GetText} $ConfigType_DropList $ConfigType
410    ${NSD_GetText} $CustomConfig_TxtBox $CustomConfig
411    ${NSD_GetState} $MoveExistingConfig_ChkBox $MoveExistingConfig
412
413    # Abort if config file not found
414    ${If} $ConfigType == "Custom Config"
415        IfFileExists "$CustomConfig" continue 0
416            MessageBox MB_OK|MB_ICONEXCLAMATION "File not found: $CustomConfig" /SD IDOK
417            Abort
418    ${EndIf}
419
420    ${If} $MoveExistingConfig == 1
421
422        # This makes the $APPDATA variable point to the ProgramData folder instead
423        # of the current user's roaming AppData folder
424        SetShellVarContext all
425
426        # Get directory status
427        ${DirState} "$APPDATA\Salt Project\Salt" $R0  # 0=Empty, 1=full, -1=Not Found
428        StrCmp $R0 "1" 0 move_files  # Move files if directory empty or missing
429        MessageBox MB_OKCANCEL \
430            "The $APPDATA\Salt Project\Salt directory is not empty.$\n\
431            These files will need to be moved manually." \
432            /SD IDOK IDCANCEL cancel
433        StrCpy $MoveExistingConfig 0
434        Goto continue
435
436        cancel:
437            ${NSD_UNCHECK} $MoveExistingConfig_ChkBox
438            Abort
439
440        move_files:
441            # Make sure the target directory exists
442            nsExec::Exec "md $APPDATA\Salt Project\Salt"
443            # Take ownership of the C:\salt directory
444            nsExec::Exec "takeown /F $RootDir /R"
445            # Move the C:\salt directory to the new location
446            StrCpy $switch_overwrite 0
447            !insertmacro MoveFolder "$RootDir" "$APPDATA\Salt Project\Salt" "*.*"
448            # Make RootDir the new location
449            StrCpy $RootDir "$APPDATA\Salt Project\Salt"
450
451    ${EndIf}
452
453    continue:
454    Call BackupExistingConfig
455
456FunctionEnd
457
458
459###############################################################################
460# Custom Finish Page
461###############################################################################
462Function pageFinish_Show
463
464    # Imports so the checkboxes will show up
465    !define SWP_NOSIZE 0x0001
466    !define SWP_NOMOVE 0x0002
467    !define HWND_TOP 0x0000
468
469    # Create Start Minion Checkbox
470    ${NSD_CreateCheckbox} 120u 90u 100% 12u "&Start salt-minion"
471    Pop $MinionStart_ChkBox
472    SetCtlColors $MinionStart_ChkBox "" "ffffff"
473    # This command required to bring the checkbox to the front
474    System::Call "User32::SetWindowPos(i, i, i, i, i, i, i) b ($MinionStart_ChkBox, ${HWND_TOP}, 0, 0, 0, 0, ${SWP_NOSIZE}|${SWP_NOMOVE})"
475
476    # Create Start Minion Delayed ComboBox
477    ${NSD_CreateCheckbox} 130u 102u 100% 12u "&Delayed Start"
478    Pop $MinionStartDelayed_ChkBox
479    SetCtlColors $MinionStartDelayed_ChkBox "" "ffffff"
480    # This command required to bring the checkbox to the front
481    System::Call "User32::SetWindowPos(i, i, i, i, i, i, i) b ($MinionStartDelayed_ChkBox, ${HWND_TOP}, 0, 0, 0, 0, ${SWP_NOSIZE}|${SWP_NOMOVE})"
482
483    # Load current settings for Minion
484    ${If} $StartMinion == 1
485        ${NSD_Check} $MinionStart_ChkBox
486    ${EndIf}
487
488    # Load current settings for Minion Delayed
489    ${If} $StartMinionDelayed == 1
490        ${NSD_Check} $MinionStartDelayed_ChkBox
491    ${EndIf}
492
493FunctionEnd
494
495
496Function pageFinish_Leave
497
498    # Assign the current checkbox states
499    ${NSD_GetState} $MinionStart_ChkBox $StartMinion
500    ${NSD_GetState} $MinionStartDelayed_ChkBox $StartMinionDelayed
501
502FunctionEnd
503
504
505###############################################################################
506# Installation Settings
507###############################################################################
508Name "${PRODUCT_NAME} ${PRODUCT_VERSION} (Python ${PYTHON_VERSION})"
509OutFile "${OutFile}"
510InstallDir "C:\Program Files\Salt Project\Salt"
511InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
512ShowInstDetails show
513ShowUnInstDetails show
514
515
516Section -copy_prereqs
517    # Copy prereqs to the Plugins Directory
518    # These files are downloaded by build_pkg.bat
519    # This directory gets removed upon completion
520    SetOutPath "$PLUGINSDIR\"
521    File /r "..\prereqs\"
522SectionEnd
523
524# Check if the  Windows 10 Universal C Runtime (KB2999226) is installed
525# Python 3 needs the updated ucrt on Windows 8.1 / 2012R2 and lower
526# They are installed via KB2999226, but we're not going to patch the system here
527# Instead, we're going to copy the .dll files to the \salt\bin directory
528Section -install_ucrt
529
530    Var /GLOBAL UcrtFileName
531
532    # Get the Major.Minor version Number
533    # Windows 10 introduced CurrentMajorVersionNumber
534    ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" \
535        CurrentMajorVersionNumber
536
537    # Windows 10/2016 will return a value here, skip to the end if returned
538    StrCmp $R0 '' lbl_needs_ucrt 0
539
540    # Found Windows 10
541    detailPrint "KB2999226 does not apply to this machine"
542    goto lbl_done
543
544    lbl_needs_ucrt:
545    # UCRT only needed on Windows Server 2012R2/Windows 8.1 and below
546    # The first ReadRegStr command above should have skipped to lbl_done if on
547    # Windows 10 box
548
549    # Is the update already installed
550    ClearErrors
551
552    # Use WMI to check if it's installed
553    detailPrint "Checking for existing UCRT (KB2999226) installation"
554    nsExec::ExecToStack 'cmd /q /c wmic qfe get hotfixid | findstr "^KB2999226"'
555    # Clean up the stack
556    Pop $R0 # Gets the ErrorCode
557    Pop $R1 # Gets the stdout, which should be KB2999226 if it's installed
558
559    # If it returned KB2999226 it's already installed
560    StrCmp $R1 'KB2999226' lbl_done
561
562    detailPrint "UCRT (KB2999226) not found"
563
564    # Use RunningX64 here to get the Architecture for the system running the installer
565    # CPUARCH is defined when the installer is built and is based on the machine that
566    # built the installer, not the target system as we need here.
567    ${If} ${RunningX64}
568        StrCpy $UcrtFileName "ucrt_x64.zip"
569    ${Else}
570        StrCpy $UcrtFileName "ucrt_x86.zip"
571    ${EndIf}
572
573    ClearErrors
574
575    detailPrint "Unzipping UCRT dll files to $INSTDIR\bin"
576    CreateDirectory $INSTDIR\bin
577    nsisunz::UnzipToLog "$PLUGINSDIR\$UcrtFileName" "$INSTDIR\bin"
578
579    # Clean up the stack
580    Pop $R0  # Get Error
581
582    ${IfNot} $R0 == "success"
583        detailPrint "error: $R0"
584        Sleep 3000
585    ${Else}
586        detailPrint "UCRT dll files copied successfully"
587    ${EndIf}
588
589    lbl_done:
590
591SectionEnd
592
593
594# Check and install Visual C++ redist 2013 packages
595# Hidden section (-) to install VCRedist
596Section -install_vcredist_2013
597
598    Var /GLOBAL VcRedistName
599    Var /GLOBAL VcRedistGuid
600    Var /GLOBAL NeedVcRedist
601
602    # GUIDs can be found by installing them and then running the following command:
603    # wmic product where "Name like '%2013%minimum runtime%'" get Name, Version, IdentifyingNumber
604    !define VCREDIST_X86_NAME "vcredist_x86_2013"
605    !define VCREDIST_X86_GUID "{8122DAB1-ED4D-3676-BB0A-CA368196543E}"
606    !define VCREDIST_X64_NAME "vcredist_x64_2013"
607    !define VCREDIST_X64_GUID "{53CF6934-A98D-3D84-9146-FC4EDF3D5641}"
608
609    # Only install 64bit VCRedist on 64bit machines
610    ${If} ${CPUARCH} == "AMD64"
611        StrCpy $VcRedistName ${VCREDIST_X64_NAME}
612        StrCpy $VcRedistGuid ${VCREDIST_X64_GUID}
613        Call InstallVCRedist
614    ${Else}
615        # Install 32bit VCRedist on all machines
616        StrCpy $VcRedistName ${VCREDIST_X86_NAME}
617        StrCpy $VcRedistGuid ${VCREDIST_X86_GUID}
618        Call InstallVCRedist
619    ${EndIf}
620
621SectionEnd
622
623
624Function InstallVCRedist
625    # Check to see if it's already installed
626    Call MsiQueryProductState
627    ${If} $NeedVcRedist == "True"
628        detailPrint "System requires $VcRedistName"
629        MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 \
630            "$VcRedistName is currently not installed. Would you like to install?" \
631            /SD IDYES IDNO endVCRedist
632
633        # If an output variable is specified ($0 in the case below),
634        # ExecWait sets the variable with the exit code (and only sets the
635        # error flag if an error occurs; if an error occurs, the contents
636        # of the user variable are undefined).
637        # http://nsis.sourceforge.net/Reference/ExecWait
638        ClearErrors
639        detailPrint "Installing $VcRedistName..."
640        ExecWait '"$PLUGINSDIR\$VcRedistName.exe" /install /quiet /norestart' $0
641        IfErrors 0 CheckVcRedistErrorCode
642            MessageBox MB_OK|MB_ICONEXCLAMATION \
643                "$VcRedistName failed to install. Try installing the package manually." \
644                /SD IDOK
645            detailPrint "An error occurred during installation of $VcRedistName"
646
647        CheckVcRedistErrorCode:
648        # Check for Reboot Error Code (3010)
649        ${If} $0 == 3010
650            MessageBox MB_OK|MB_ICONINFORMATION \
651                "$VcRedistName installed but requires a restart to complete." \
652                /SD IDOK
653            detailPrint "Reboot and run Salt install again"
654
655        # Check for any other errors
656        ${ElseIfNot} $0 == 0
657            MessageBox MB_OK|MB_ICONEXCLAMATION \
658                "$VcRedistName failed with ErrorCode: $0. Try installing the package manually." \
659                /SD IDOK
660            detailPrint "An error occurred during installation of $VcRedistName"
661            detailPrint "Error: $0"
662        ${EndIf}
663
664        endVCRedist:
665
666    ${EndIf}
667
668FunctionEnd
669
670
671Section "MainSection" SEC01
672
673    SetOutPath "$INSTDIR\"
674    SetOverwrite off
675    CreateDirectory "$RootDir\conf\pki\minion"
676    CreateDirectory "$RootDir\conf\minion.d"
677    CreateDirectory "$RootDir\var\cache\salt\minion\extmods\grains"
678    CreateDirectory "$RootDir\var\cache\salt\minion\proc"
679    CreateDirectory "$RootDir\var\log\salt\minion"
680    CreateDirectory "$RootDir\var\run"
681    File /r "..\buildenv\"
682    nsExec::Exec 'icacls $RootDir /inheritance:r /grant:r "*S-1-5-32-544":(OI)(CI)F /grant:r "*S-1-5-18":(OI)(CI)F'
683
684SectionEnd
685
686
687Function .onInit
688
689    InitPluginsDir
690    Call parseInstallerCommandLineSwitches
691
692    # Uninstall msi-installed salt
693    # Source: https://nsis-dev.github.io/NSIS-Forums/html/t-303468.html
694    !define upgradecode {FC6FB3A2-65DE-41A9-AD91-D10A402BD641}    ;Salt upgrade code
695    StrCpy $0 0
696    loop:
697    System::Call 'MSI::MsiEnumRelatedProducts(t "${upgradecode}",i0,i r0,t.r1)i.r2'
698    ${If} $2 = 0
699	# Now $1 contains the product code
700        DetailPrint product:$1
701        push $R0
702          StrCpy $R0 $1
703          Call UninstallMSI
704        pop $R0
705        IntOp $0 $0 + 1
706        goto loop
707    ${Endif}
708
709    # If custom config passed, verify its existence before continuing so we
710    # don't uninstall an existing installation and then fail
711    ${If} $ConfigType == "Custom Config"
712        IfFileExists "$CustomConfig" customConfigExists 0
713        Abort
714    ${EndIf}
715
716    customConfigExists:
717        # Check for existing installation
718        ReadRegStr $R0 HKLM \
719            "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
720            "UninstallString"
721        StrCmp $R0 "" checkOther  # If it's empty, not installed
722
723        # Set InstDir to the parent directory so that we can uninstall it
724        ${GetParent} $R0 $INSTDIR
725
726        # Found existing installation, prompt to uninstall
727        MessageBox MB_OKCANCEL|MB_USERICON \
728            "${PRODUCT_NAME} is already installed.$\n$\n\
729            Click `OK` to remove the existing installation." \
730            /SD IDOK IDOK uninst
731        Abort
732
733    checkOther:
734        # Check for existing installation of full salt
735        ReadRegStr $R0 HKLM \
736            "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME_OTHER}" \
737            "UninstallString"
738        StrCmp $R0 "" skipUninstall  # If it's empty, not installed
739
740        # Set InstDir to the parent directory so that we can uninstall it
741        ${GetParent} $R0 $INSTDIR
742
743        # Found existing installation, prompt to uninstall
744        MessageBox MB_OKCANCEL|MB_USERICON \
745            "${PRODUCT_NAME_OTHER} is already installed.$\n$\n\
746            Click `OK` to remove the existing installation." \
747            /SD IDOK IDOK uninst
748        Abort
749
750    uninst:
751
752        # Get current Silent status
753        StrCpy $R0 0
754        ${If} ${Silent}
755            StrCpy $R0 1
756        ${EndIf}
757
758        # Turn on Silent mode
759        SetSilent silent
760
761        # Don't remove all directories when upgrading (old method)
762        StrCpy $DeleteInstallDir 0
763
764        # Don't remove RootDir when upgrading (new method)
765        StrCpy $DeleteRootDir 0
766
767        # Uninstall silently
768        Call uninstallSalt
769
770        # Set it back to Normal mode, if that's what it was before
771        ${If} $R0 == 0
772            SetSilent normal
773        ${EndIf}
774
775    skipUninstall:
776
777    Call getExistingInstallation
778
779    Call getExistingMinionConfig
780
781    ${If} $ExistingConfigFound == 0
782    ${AndIf} $ConfigType == "Existing Config"
783        StrCpy $ConfigType "Default Config"
784    ${EndIf}
785
786    IfSilent 0 +2
787        ${If} $ConfigType ==  "Existing Config"
788        ${AndIf} $MoveExistingConfig == 0
789            Call BackupExistingConfig
790        ${EndIf}
791
792FunctionEnd
793
794Function getExistingInstallation
795    # Try to detect an existing installation. There are three possible scenarios
796    # 1. Existing New Method Installation
797    # 2. Existing Old Method Installation
798    # 3. New Installation
799    # The results of this function will determine if the user is allowed to set
800    # the install location in the GUI. If there is an existing installation
801    # present, the location picker will be grayed out
802    # This function also sets the RootDir and INSTDIR variables used by the
803    # installer.
804
805    # Reset ExistingInstallation
806    StrCpy $ExistingInstallation 0
807
808    # Get ProgramFiles
809    # Use RunningX64 here to get the Architecture for the system running the
810    # installer. CPUARCH is defined when the installer is built and is based on
811    # the machine that built the installer, not the target system
812    # There are 3 scenarios here:
813    ${If} ${RunningX64}
814        ${If} ${CPUARCH} == "AMD64"
815            # 64 bit Salt on 64 bit system (C:\Program Files)
816            StrCpy $INSTDIR "$ProgramFiles64\Salt Project\Salt"
817        ${Else}
818            # 32 bit Salt on 64 bit system (C:\Program Files (x86))
819            StrCpy $INSTDIR "$ProgramFiles32\Salt Project\Salt"
820        ${EndIf}
821    ${Else}
822        # 32 bit Salt on 32 bit system (C:\Program Files)
823        StrCpy $INSTDIR "$ProgramFiles\Salt Project\Salt"
824    ${EndIf}
825
826    # This makes the $APPDATA variable point to the ProgramData folder instead
827    # of the current user's roaming AppData folder
828    SetShellVarContext all
829
830    # Set default location of for salt config
831    StrCpy $RootDir "$APPDATA\Salt Project\Salt"
832
833    # The NSIS installer is a 32bit application and will use the WOW6432Node in
834    # the registry by default. We need to look in the 64 bit location on 64 bit
835    # systems
836    ${If} ${RunningX64}
837        # This would only apply if we are installing the 64 bit version of Salt
838        ${If} ${CPUARCH} == "AMD64"
839            # https://nsis.sourceforge.io/Docs/Chapter4.html#setregview
840            SetRegView 64  # View the 64 bit portion of the registry
841        ${EndIf}
842    ${EndIf}
843
844    # Check for existing new method installation from registry
845    # Look for `install_dir` in HKLM\SOFTWARE\Salt Project\Salt
846    ReadRegStr $R0 HKLM "SOFTWARE\Salt Project\Salt" "install_dir"
847    StrCmp $R0 "" checkOldInstallation
848    StrCpy $ExistingInstallation 1
849    # Set INSTDIR to the location in the registry
850    StrCpy $INSTDIR $R0
851    # Set RootDir, if defined
852    ReadRegStr $R0 HKLM "SOFTWARE\Salt Project\Salt" "root_dir"
853    StrCmp $R0 "" finished
854    StrCpy $RootDir $R0
855    Goto finished
856
857    # Check for existing old method installation
858    # Look for `python.exe` in C:\salt\bin
859    checkOldInstallation:
860    IfFileExists "C:\salt\bin\python.exe" 0 newInstallation
861    StrCpy $ExistingInstallation 1
862    StrCpy $INSTDIR "C:\salt"
863    StrCpy $RootDir "C:\salt"
864    Goto finished
865
866    # This is a new installation
867    # Check if custom location was passed via command line
868    newInstallation:
869    ${IfNot} $CustomLocation == ""
870        StrCpy $INSTDIR $CustomLocation
871    ${EndIf}
872
873    finished:
874        SetRegView 32  # View the 32 bit portion of the registry
875
876FunctionEnd
877
878
879# Time Stamp Definition
880Function BackupExistingConfig
881
882    ${If} $ExistingConfigFound == 1                     # If existing config found
883    ${AndIfNot} $ConfigType == "Existing Config"  # If not using Existing Config
884
885        # Backup the minion config
886        Rename "$RootDir\conf\minion" "$RootDir\conf\minion-${TIME_STAMP}.bak"
887        IfFileExists "$RootDir\conf\minion.d" 0 +2
888            Rename "$RootDir\conf\minion.d" "$RootDir\conf\minion.d-${TIME_STAMP}.bak"
889
890    ${EndIf}
891
892    # By this point there should be no existing config
893    # It was either backed up or wasn't there to begin with
894    ${If} $ConfigType == "Custom Config"  # If we're using Custom Config
895    ${AndIfNot} $CustomConfig == ""       # If a custom config is passed
896
897        # Check for a file name
898        # Named file should be in the same directory as the installer
899        CreateDirectory "$RootDir\conf"
900        IfFileExists "$EXEDIR\$CustomConfig" 0 checkFullPath
901            CopyFiles /SILENT /FILESONLY "$EXEDIR\$CustomConfig" "$RootDir\conf\minion"
902            goto finished
903
904        # Maybe it was a full path to a file
905        checkFullPath:
906        IfFileExists "$CustomConfig" 0 finished
907            CopyFiles /SILENT /FILESONLY "$CustomConfig" "$RootDir\conf\minion"
908
909        finished:
910
911    ${EndIf}
912
913FunctionEnd
914
915
916Section -Post
917
918    WriteUninstaller "$INSTDIR\uninst.exe"
919
920    # Write Uninstall Registry Entries
921    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
922        "DisplayName" "$(^Name)"
923    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
924        "UninstallString" "$INSTDIR\uninst.exe"
925    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
926        "DisplayIcon" "$INSTDIR\salt.ico"
927    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
928        "DisplayVersion" "${PRODUCT_VERSION}"
929    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
930        "URLInfoAbout" "${PRODUCT_WEB_SITE}"
931    WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
932        "Publisher" "${PRODUCT_PUBLISHER}"
933    WriteRegStr HKLM "SYSTEM\CurrentControlSet\services\salt-minion" \
934        "DependOnService" "nsi"
935
936    # Set the estimated size
937    ${GetSize} "$INSTDIR\bin" "/S=OK" $0 $1 $2
938    IntFmt $0 "0x%08X" $0
939    WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
940        "EstimatedSize" "$0"
941
942    # Write Commandline Registry Entries
943    WriteRegStr HKLM "${PRODUCT_CALL_REGKEY}" "" "$INSTDIR\salt-call.bat"
944    WriteRegStr HKLM "${PRODUCT_CALL_REGKEY}" "Path" "$INSTDIR\bin\"
945    WriteRegStr HKLM "${PRODUCT_MINION_REGKEY}" "" "$INSTDIR\salt-minion.bat"
946    WriteRegStr HKLM "${PRODUCT_MINION_REGKEY}" "Path" "$INSTDIR\bin\"
947
948    # The NSIS installer is a 32bit application and will use the WOW6432Node in
949    # the registry by default. We need to look in the 64 bit location on 64 bit
950    # systems
951    ${If} ${RunningX64}
952        # This would only apply if we are installing the 64 bit version of Salt
953        ${If} ${CPUARCH} == "AMD64"
954            # https://nsis.sourceforge.io/Docs/Chapter4.html#setregview
955            SetRegView 64  # View 64 bit portion of the registry
956        ${EndIf}
957    ${EndIf}
958
959    # Write Salt Configuration Registry Entries
960    # We want to write EXPAND_SZ string types to allow us to use environment
961    # variables. It's OK to use EXPAND_SZ even if you don't use an environment
962    # variable so we'll just do that whether it's new location or old.
963
964    # Check for Program Files
965    # Set the current setting for INSTDIR... we'll only change it if it contains
966    # Program Files
967    StrCpy $RegInstDir $INSTDIR
968
969    # Check Program Files (x86) first
970    ${StrContains} $0 "Program Files (x86)" $INSTDIR
971    StrCmp $0 "" +2  # If it's empty, skip the next line
972        StrCpy $RegInstDir "%ProgramFiles(x86)%\Salt Project\Salt"
973
974    # Check normal Program Files next
975    ${StrContains} $0 "Program Files" $INSTDIR
976    StrCmp $0 "" +2  # If it's empty, skip the next line
977        StrCpy $RegInstDir "%ProgramFiles%\Salt Project\Salt"
978
979    # Check for ProgramData
980    # Set the current setting for RootDir. we'll only change it if it contains
981    # ProgramData
982    StrCpy $RegRootDir $RootDir
983
984    ${StrContains} $0 "ProgramData" $INSTDIR
985    StrCmp $0 "" +2  # If it's empty, skip the next line
986        StrCpy $RegRootDir "%ProgramData%\Salt Project\Salt"
987
988    WriteRegExpandStr HKLM "SOFTWARE\Salt Project\Salt" "install_dir" "$RegInstDir"
989    WriteRegExpandStr HKLM "SOFTWARE\Salt Project\Salt" "root_dir" "$RegRootDir"
990
991    # Puts the nullsoft installer back to its default
992    SetRegView 32  # Set it back to the 32 bit portion of the registry
993
994    # Register the Salt-Minion Service
995    nsExec::Exec `$INSTDIR\bin\ssm.exe install salt-minion "$INSTDIR\bin\python.exe" -E -s """$INSTDIR\bin\Scripts\salt-minion""" -c """$RootDir\conf""" -l quiet`
996    nsExec::Exec "$INSTDIR\bin\ssm.exe set salt-minion Description Salt Minion from saltstack.com"
997    nsExec::Exec "$INSTDIR\bin\ssm.exe set salt-minion Start SERVICE_AUTO_START"
998    nsExec::Exec "$INSTDIR\bin\ssm.exe set salt-minion AppStopMethodConsole 24000"
999    nsExec::Exec "$INSTDIR\bin\ssm.exe set salt-minion AppStopMethodWindow 2000"
1000    nsExec::Exec "$INSTDIR\bin\ssm.exe set salt-minion AppRestartDelay 60000"
1001
1002    # There is a default minion config laid down in the $INSTDIR directory
1003    ${Switch} $ConfigType
1004        ${Case} "Existing Config"
1005            # If this is an Existing Config, we just remove it
1006            RMDir /r "$INSTDIR\conf"
1007            ${Break}
1008        ${Case} "Custom Config"
1009            # If this is a Custom Config, we remove it and update the custom config
1010            RMDir /r "$INSTDIR\conf"
1011            Call updateMinionConfig
1012            ${Break}
1013        ${Case} "Default Config"
1014            # If this is the default config, we move it and update it
1015            StrCpy $switch_overwrite 1
1016
1017            # This makes the $APPDATA variable point to the ProgramData folder instead
1018            # of the current user's roaming AppData folder
1019            SetShellVarContext all
1020
1021            !insertmacro MoveFolder "$INSTDIR\conf" "$APPDATA\Salt Project\Salt\conf" "*.*"
1022            Call updateMinionConfig
1023            ${Break}
1024    ${EndSwitch}
1025
1026    # Add $INSTDIR in the Path
1027    EnVar::SetHKLM
1028    EnVar::AddValue Path "$INSTDIR"
1029
1030SectionEnd
1031
1032
1033Function .onInstSuccess
1034
1035    # If StartMinionDelayed is 1, then set the service to start delayed
1036    ${If} $StartMinionDelayed == 1
1037        nsExec::Exec "$INSTDIR\bin\ssm.exe set salt-minion Start SERVICE_DELAYED_AUTO_START"
1038    ${EndIf}
1039
1040    # If start-minion is 1, then start the service
1041    ${If} $StartMinion == 1
1042        nsExec::Exec 'net start salt-minion'
1043    ${EndIf}
1044
1045FunctionEnd
1046
1047
1048Function un.onInit
1049
1050    Call un.parseUninstallerCommandLineSwitches
1051
1052    MessageBox MB_USERICON|MB_YESNO|MB_DEFBUTTON1 \
1053        "Are you sure you want to completely remove $(^Name) and all of its components?" \
1054        /SD IDYES IDYES +2
1055    Abort
1056
1057FunctionEnd
1058
1059
1060Section Uninstall
1061
1062    Call un.uninstallSalt
1063
1064    # Remove $INSTDIR from the Path
1065    EnVar::SetHKLM
1066    EnVar::DeleteValue Path "$INSTDIR"
1067
1068SectionEnd
1069
1070
1071!macro uninstallSalt un
1072Function ${un}uninstallSalt
1073
1074    # WARNING: Any changes made here need to be reflected in the MSI uninstaller
1075
1076    # Make sure we're in the right directory
1077    ${If} $INSTDIR == "c:\salt\bin\Scripts"
1078      StrCpy $INSTDIR "C:\salt"
1079    ${EndIf}
1080    # $ProgramFiles is different depending on the CPU Architecture
1081    # x86 : C:\Program Files
1082    # x64 : C:\Program Files (x86)
1083    ${If} $INSTDIR == "$ProgramFiles\Salt Project\Salt\bin\Scripts"
1084      StrCpy $INSTDIR "$ProgramFiles\Salt Project\Salt"
1085    ${EndIf}
1086    # $ProgramFiles64 is the C:\Program Files directory
1087    ${If} $INSTDIR == "$ProgramFiles64\Salt Project\Salt\bin\Scripts"
1088      StrCpy $INSTDIR "$ProgramFiles64\Salt Project\Salt"
1089    ${EndIf}
1090
1091    # The NSIS installer is a 32bit application and will use the WOW6432Node in
1092    # the registry by default. We need to look in the 64 bit location on 64 bit
1093    # systems
1094    ${If} ${RunningX64}
1095        # This would only apply if we are installing the 64 bit version of Salt
1096        ${If} ${CPUARCH} == "AMD64"
1097            # https://nsis.sourceforge.io/Docs/Chapter4.html#setregview
1098            SetRegView 64  # View 64 bit portion of the registry
1099        ${EndIf}
1100    ${EndIf}
1101
1102    # Get Root Directory from the Registry
1103    ReadRegStr $RootDir HKLM "SOFTWARE\Salt Project\Salt" "root_dir"
1104    SetRegView 32  # Set it back to the 32 bit portion (default)
1105
1106    # Stop and Remove salt-minion service
1107    nsExec::Exec 'net stop salt-minion'
1108    nsExec::Exec 'sc delete salt-minion'
1109
1110    # Stop and remove the salt-master service
1111    nsExec::Exec 'net stop salt-master'
1112    nsExec::Exec 'sc delete salt-master'
1113
1114    # Remove files
1115    Delete "$INSTDIR\uninst.exe"
1116    # TODO: The service needs to be unregistered before removing SSM
1117    Delete "$INSTDIR\ssm.exe"
1118    Delete "$INSTDIR\salt*"
1119    Delete "$INSTDIR\vcredist.exe"
1120    RMDir /r "$INSTDIR\bin"
1121
1122    # Remove Registry entries
1123    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
1124    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY_OTHER}"
1125
1126    # Remove Command Line Registry entries
1127    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_CALL_REGKEY}"
1128    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_CP_REGKEY}"
1129    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_KEY_REGKEY}"
1130    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_MASTER_REGKEY}"
1131    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_MINION_REGKEY}"
1132    DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_RUN_REGKEY}"
1133
1134    # Remove Salt Configuration Registry entries
1135    # The NSIS installer is a 32bit application and will use the WOW6432Node in
1136    # the registry by default. We need to look in the 64 bit location on 64 bit
1137    # systems
1138    ${If} ${RunningX64}
1139        # This would only apply if we are installing the 64 bit version of Salt
1140        ${If} ${CPUARCH} == "AMD64"
1141            # https://nsis.sourceforge.io/Docs/Chapter4.html#setregview
1142            SetRegView 64  # View 64 bit portion of the registry
1143        ${EndIf}
1144    ${EndIf}
1145    DeleteRegKey HKLM "SOFTWARE\Salt Project"
1146    SetRegView 32  # Set it back to the 32 bit portion (default)
1147
1148    # SystemDrive is not a built in NSIS constant, so we need to get it from
1149    # the environment variables
1150    ReadEnvStr $0 "SystemDrive"  # Get the SystemDrive env var
1151    StrCpy $SysDrive "$0\"
1152
1153    # Automatically close when finished
1154    SetAutoClose true
1155
1156    # Old Method Installation
1157    ${If} $INSTDIR == "C:\salt"
1158
1159        # Prompt to remove the Installation Directory. This is because that
1160        # directory is also the root_dir which includes the config and pki
1161        # directories
1162        ${IfNot} $DeleteInstallDir == 1
1163            MessageBox MB_YESNO|MB_DEFBUTTON2|MB_USERICON \
1164                "Would you like to completely remove $INSTDIR and all of its contents?" \
1165                /SD IDNO IDNO finished
1166        ${EndIf}
1167
1168        SetOutPath "$SysDrive"  # Can't remove CWD
1169        RMDir /r "$INSTDIR"
1170
1171    ${Else}
1172
1173        # New Method Installation
1174        # This makes the $APPDATA variable point to the ProgramData folder instead
1175        # of the current user's roaming AppData folder
1176        SetShellVarContext all
1177
1178        # We can always remove the Installation Directory on New Method Installs
1179        # because it only contains binary data
1180
1181        # Remove INSTDIR
1182        # Make sure you're not removing important system directory such as
1183        # Program Files, C:\Windows, or C:
1184        ${If} $INSTDIR != $ProgramFiles
1185        ${AndIf} $INSTDIR != $ProgramFiles64
1186        ${AndIf} $INSTDIR != $SysDrive
1187        ${AndIf} $INSTDIR != $WinDir
1188            SetOutPath "$SysDrive"  # Can't remove CWD
1189            RMDir /r $INSTDIR
1190        ${EndIf}
1191
1192        # Remove INSTDIR (The parent)
1193        # For example, though salt is installed in ProgramFiles\Salt Project\Salt
1194        # We want to remove ProgramFiles\Salt Project
1195        # Only delete Salt Project directory if it's in Program Files
1196        # Otherwise, we can't guess where the user may have installed salt
1197        ${GetParent} $INSTDIR $0  # Get parent directory (Salt Project)
1198        ${If} $0 == "$ProgramFiles\Salt Project" # Make sure it's not ProgramFiles
1199        ${OrIf} $0 == "$ProgramFiles64\Salt Project" # Make sure it's not Program Files (x86)
1200            SetOutPath "$SysDrive"  # Can't remove CWD
1201            RMDir /r $0
1202        ${EndIf}
1203
1204        # Prompt for the removal of the Root Directory which contains the config
1205        # and pki directories
1206        ${IfNot} $DeleteRootDir == 1
1207            MessageBox MB_YESNO|MB_DEFBUTTON2|MB_USERICON \
1208                "Would you like to completely remove the Root Directory ($RootDir) and all of its contents?" \
1209                /SD IDNO IDNO finished
1210        ${EndIf}
1211
1212        # Remove the Salt Project directory in ProgramData
1213        # The Salt Project directory will only ever be in ProgramData
1214        # It is not user selectable
1215        ${GetParent} $RootDir $0  # Get parent directory
1216        ${If} $0 == "$APPDATA\Salt Project"  # Make sure it's not ProgramData
1217            SetOutPath "$SysDrive"  # Can't remove CWD
1218            RMDir /r $0
1219        ${EndIf}
1220
1221    ${EndIf}
1222
1223    finished:
1224
1225FunctionEnd
1226!macroend
1227
1228
1229!insertmacro uninstallSalt ""
1230!insertmacro uninstallSalt "un."
1231
1232
1233Function un.onUninstSuccess
1234    HideWindow
1235    MessageBox MB_OK|MB_USERICON \
1236        "$(^Name) was successfully removed from your computer." \
1237        /SD IDOK
1238FunctionEnd
1239
1240
1241###############################################################################
1242# Helper Functions
1243###############################################################################
1244Function MsiQueryProductState
1245    # Used for detecting VCRedist Installation
1246    !define INSTALLSTATE_DEFAULT "5"
1247
1248    StrCpy $NeedVcRedist "False"
1249    System::Call "msi::MsiQueryProductStateA(t '$VcRedistGuid') i.r0"
1250    StrCmp $0 ${INSTALLSTATE_DEFAULT} +2 0
1251    StrCpy $NeedVcRedist "True"
1252
1253FunctionEnd
1254
1255#------------------------------------------------------------------------------
1256# Trim Function
1257# - Trim whitespace from the beginning and end of a string
1258# - Trims spaces, \r, \n, \t
1259#
1260# Usage:
1261#   Push " some string "  ; String to Trim
1262#   Call Trim
1263#   Pop $0                ; Trimmed String: "some string"
1264#
1265#   or
1266#
1267#   ${Trim} $0 $1   ; Trimmed String, String to Trim
1268#------------------------------------------------------------------------------
1269Function Trim
1270
1271    Exch $R1 # Original string
1272    Push $R2
1273
1274    Loop:
1275        StrCpy $R2 "$R1" 1
1276        StrCmp "$R2" " " TrimLeft
1277        StrCmp "$R2" "$\r" TrimLeft
1278        StrCmp "$R2" "$\n" TrimLeft
1279        StrCmp "$R2" "$\t" TrimLeft
1280        GoTo Loop2
1281    TrimLeft:
1282        StrCpy $R1 "$R1" "" 1
1283        Goto Loop
1284
1285    Loop2:
1286        StrCpy $R2 "$R1" 1 -1
1287        StrCmp "$R2" " " TrimRight
1288        StrCmp "$R2" "$\r" TrimRight
1289        StrCmp "$R2" "$\n" TrimRight
1290        StrCmp "$R2" "$\t" TrimRight
1291        GoTo Done
1292    TrimRight:
1293        StrCpy $R1 "$R1" -1
1294        Goto Loop2
1295
1296    Done:
1297        Pop $R2
1298        Exch $R1
1299
1300FunctionEnd
1301
1302
1303#------------------------------------------------------------------------------
1304# Explode Function
1305# - Splits a string based off the passed separator
1306# - Each item in the string is pushed to the stack
1307# - The last item pushed to the stack is the length of the array
1308#
1309# Usage:
1310#   Push ","                    ; Separator
1311#   Push "string,to,separate"   ; String to explode
1312#   Call Explode
1313#   Pop $0                      ; Number of items in the array
1314#
1315#   or
1316#
1317#   ${Explode} $0 $1 $2         ; Length, Separator, String
1318#------------------------------------------------------------------------------
1319Function Explode
1320    # Initialize variables
1321    Var /GLOBAL explString
1322    Var /GLOBAL explSeparator
1323    Var /GLOBAL explStrLen
1324    Var /GLOBAL explSepLen
1325    Var /GLOBAL explOffset
1326    Var /GLOBAL explTmp
1327    Var /GLOBAL explTmp2
1328    Var /GLOBAL explTmp3
1329    Var /GLOBAL explArrCount
1330
1331    # Get input from user
1332    Pop $explString
1333    Pop $explSeparator
1334
1335    # Calculates initial values
1336    StrLen $explStrLen $explString
1337    StrLen $explSepLen $explSeparator
1338    StrCpy $explArrCount 1
1339
1340    ${If} $explStrLen <= 1             #   If we got a single character
1341    ${OrIf} $explSepLen > $explStrLen  #   or separator is larger than the string,
1342        Push    $explString            #   then we return initial string with no change
1343        Push    1                      #   and set array's length to 1
1344        Return
1345    ${EndIf}
1346
1347    # Set offset to the last symbol of the string
1348    StrCpy $explOffset $explStrLen
1349    IntOp  $explOffset $explOffset - 1
1350
1351    # Clear temp string to exclude the possibility of appearance of occasional data
1352    StrCpy $explTmp   ""
1353    StrCpy $explTmp2  ""
1354    StrCpy $explTmp3  ""
1355
1356    # Loop until the offset becomes negative
1357    ${Do}
1358        # If offset becomes negative, it is time to leave the function
1359        ${IfThen} $explOffset == -1 ${|} ${ExitDo} ${|}
1360
1361        # Remove everything before and after the searched part ("TempStr")
1362        StrCpy $explTmp $explString $explSepLen $explOffset
1363
1364        ${If} $explTmp == $explSeparator
1365            # Calculating offset to start copy from
1366            IntOp   $explTmp2 $explOffset + $explSepLen    # Offset equals to the current offset plus length of separator
1367            StrCpy  $explTmp3 $explString "" $explTmp2
1368
1369            Push    $explTmp3                              # Throwing array item to the stack
1370            IntOp   $explArrCount $explArrCount + 1        # Increasing array's counter
1371
1372            StrCpy  $explString $explString $explOffset 0  # Cutting all characters beginning with the separator entry
1373            StrLen  $explStrLen $explString
1374        ${EndIf}
1375
1376        ${If} $explOffset = 0           # If the beginning of the line met and there is no separator,
1377                                        # copying the rest of the string
1378            ${If} $explSeparator == ""  # Fix for the empty separator
1379                IntOp   $explArrCount   $explArrCount - 1
1380            ${Else}
1381                Push    $explString
1382            ${EndIf}
1383        ${EndIf}
1384
1385        IntOp   $explOffset $explOffset - 1
1386    ${Loop}
1387
1388    Push $explArrCount
1389FunctionEnd
1390
1391
1392#------------------------------------------------------------------------------
1393# StrContains
1394#
1395# This function does a case sensitive searches for an occurrence of a substring in a string.
1396# It returns the substring if it is found.
1397# Otherwise it returns null("").
1398# Written by kenglish_hi
1399# Adapted from StrReplace written by dandaman32
1400#------------------------------------------------------------------------------
1401Function StrContains
1402
1403    # Initialize variables
1404    Var /GLOBAL STR_HAYSTACK
1405    Var /GLOBAL STR_NEEDLE
1406    Var /GLOBAL STR_CONTAINS_VAR_1
1407    Var /GLOBAL STR_CONTAINS_VAR_2
1408    Var /GLOBAL STR_CONTAINS_VAR_3
1409    Var /GLOBAL STR_CONTAINS_VAR_4
1410    Var /GLOBAL STR_RETURN_VAR
1411
1412    Exch $STR_NEEDLE
1413    Exch 1
1414    Exch $STR_HAYSTACK
1415    # Uncomment to debug
1416    #MessageBox MB_OK 'STR_NEEDLE = $STR_NEEDLE STR_HAYSTACK = $STR_HAYSTACK '
1417    StrCpy $STR_RETURN_VAR ""
1418    StrCpy $STR_CONTAINS_VAR_1 -1
1419    StrLen $STR_CONTAINS_VAR_2 $STR_NEEDLE
1420    StrLen $STR_CONTAINS_VAR_4 $STR_HAYSTACK
1421
1422    loop:
1423        IntOp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_1 + 1
1424        StrCpy $STR_CONTAINS_VAR_3 $STR_HAYSTACK $STR_CONTAINS_VAR_2 $STR_CONTAINS_VAR_1
1425        StrCmp $STR_CONTAINS_VAR_3 $STR_NEEDLE found
1426        StrCmp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_4 done
1427        Goto loop
1428
1429    found:
1430        StrCpy $STR_RETURN_VAR $STR_NEEDLE
1431        Goto done
1432
1433    done:
1434        Pop $STR_NEEDLE  # Prevent "invalid opcode" errors and keep the stack clean
1435        Exch $STR_RETURN_VAR
1436FunctionEnd
1437
1438
1439#------------------------------------------------------------------------------
1440# UninstallMSI Function
1441# - Uninstalls MSI by product code
1442#
1443# Usage:
1444#   Push product code
1445#   Call UninstallMSI
1446#
1447# Source:
1448#   https://nsis.sourceforge.io/Uninstalling_a_previous_MSI_(Windows_installer_package)
1449#------------------------------------------------------------------------------
1450Function UninstallMSI
1451    ; $R0 === product code
1452    MessageBox MB_OKCANCEL|MB_ICONINFORMATION \
1453        "${PRODUCT_NAME} is already installed via MSI.$\n$\n\
1454        Click `OK` to remove the existing installation." \
1455        /SD IDOK IDOK UninstallMSI
1456    Abort
1457
1458    UninstallMSI:
1459        ExecWait '"msiexec.exe" /x $R0 /qb /quiet /norestart'
1460
1461FunctionEnd
1462
1463
1464###############################################################################
1465# Specialty Functions
1466###############################################################################
1467Function getExistingMinionConfig
1468
1469    # Set Config Found Default Value
1470    StrCpy $ExistingConfigFound 0
1471
1472    # Find config, should be in $RootDir\conf\minion
1473    # Root dir is usually ProgramData\Salt Project\Salt\conf though it may be
1474    # C:\salt\conf if Salt was installed the old way
1475    IfFileExists "$RootDir\conf\minion" check_owner
1476    IfFileExists "C:\salt\conf\minion" old_location confNotFound
1477
1478    old_location:
1479    StrCpy $RootDir "C:\salt"
1480
1481    check_owner:
1482        # We need to verify the owner of the config directory (C:\salt\conf) to
1483        # ensure the config has not been modified by an unknown user. The
1484        # permissions and ownership of the directories is determined by the
1485        # installer used to install Salt. The NullSoft installer requests Admin
1486        # privileges so all directories are created with the Administrators
1487        # Group (S-1-5-32-544) as the owner. The MSI installer, however, runs in
1488        # the context of the Windows Installer service (msiserver), therefore
1489        # all directories are created with the Local System account (S-1-5-18)
1490        # as the owner.
1491        #
1492        # When Salt is launched it sets the root_dir (C:\salt) permissions as
1493        # follows:
1494        # - Owner: Administrators
1495        # - Allow Perms:
1496        #   - Owner: Full Control
1497        #   - System: Full Control
1498        #   - Administrators: Full Control
1499        #
1500        # The conf_dir (C:\salt\conf) inherits Allow/Deny permissions from the
1501        # parent, but NOT Ownership. The owner will be the Administrators Group
1502        # if it was installed via NullSoft or the Local System account if it was
1503        # installed via the MSI. Therefore valid owners for the conf_dir are
1504        # both the Administrators group and the Local System account.
1505        #
1506        # An unprivileged account cannot change the owner of a directory by
1507        # default. So, if the owner of the conf_dir is either the Administrators
1508        # group or the Local System account, then we will trust it. Otherwise,
1509        # we will display an option to abort the installation or to backup the
1510        # untrusted config directory and continue with the default config. If
1511        # running the install with the silent option (/S) it will backup the
1512        # untrusted config directory and continue with the default config.
1513
1514        AccessControl::GetFileOwner /SID "$RootDir\conf"
1515        Pop $0
1516
1517        # Check for valid SIDs
1518        StrCmp $0 "S-1-5-32-544" correct_owner  # Administrators Group (NullSoft)
1519        StrCmp $0 "S-1-5-18" correct_owner      # Local System (MSI)
1520        MessageBox MB_YESNO "Insecure config found at $RootDir\conf. If you \
1521            continue, the config directory will be renamed to \
1522            $RootDir\conf.insecure and the default config will be used. \
1523            Continue?" /SD IDYES IDYES insecure_config
1524            Abort
1525
1526    insecure_config:
1527        # Backing up insecure config
1528        Rename "$RootDir\conf" "$RootDir\conf.insecure-${TIME_STAMP}"
1529        Goto confNotFound
1530
1531    correct_owner:
1532        StrCpy $ExistingConfigFound 1
1533        FileOpen $0 "$RootDir\conf\minion" r
1534
1535    confLoop:
1536        ClearErrors                                             # clear Errors
1537        FileRead $0 $1                                          # read the next line
1538        IfErrors EndOfFile                                      # error is probably EOF
1539        ${StrLoc} $2 $1 "master:" ">"                           # find `master:` starting at the beginning
1540        ${If} $2 == 0                                           # if it found it in the first position, then it is defined
1541            ${StrStrAdv} $2 $1 "master: " ">" ">" "0" "0" "0"   # read everything after `master: `
1542            ${Trim} $2 $2                                       # trim white space
1543            ${If} $2 == ""                                      # if it's empty, it's probably a list of masters
1544                masterLoop:
1545                ClearErrors                                     # clear Errors
1546                FileRead $0 $1                                  # read the next line
1547                IfErrors EndOfFile                              # error is probably EOF
1548                ${StrStrAdv} $2 $1 "- " ">" ">" "0" "0" "0"     # read everything after `- `
1549                ${Trim} $2 $2                                   # trim white space
1550                ${IfNot} $2 == ""                               # if the line is not empty, we found something
1551                    ${If} $MasterHost_Cfg == ""                 # if the config setting is empty
1552                        StrCpy $MasterHost_Cfg $2               # make the first item the new entry
1553                    ${Else}
1554                        StrCpy $MasterHost_Cfg "$MasterHost_Cfg,$2"  # Append the new master, comma separated
1555                    ${EndIf}
1556                    Goto masterLoop                             # check the next one
1557                ${EndIf}
1558            ${Else}
1559                StrCpy $MasterHost_Cfg $2                       # a single master entry
1560            ${EndIf}
1561        ${EndIf}
1562
1563        ${StrLoc} $2 $1 "id:" ">"
1564        ${If} $2 == 0
1565            ${StrStrAdv} $2 $1 "id: " ">" ">" "0" "0" "0"
1566            ${Trim} $2 $2
1567            StrCpy $MinionName_Cfg $2
1568        ${EndIf}
1569
1570    Goto confLoop
1571
1572    EndOfFile:
1573    FileClose $0
1574
1575    confNotFound:
1576
1577    # Set Default Config Values if not found
1578    ${If} $MasterHost_Cfg == ""
1579        StrCpy $MasterHost_Cfg "salt"
1580    ${EndIf}
1581    ${If} $MinionName_Cfg == ""
1582        StrCpy $MinionName_Cfg "hostname"
1583    ${EndIf}
1584
1585FunctionEnd
1586
1587
1588Var cfg_line
1589Var chk_line
1590Var lst_check
1591Function updateMinionConfig
1592
1593    ClearErrors
1594    FileOpen $0 "$RootDir\conf\minion" "r"              # open target file for reading
1595    GetTempFileName $R0                                 # get new temp file name
1596    FileOpen $1 $R0 "w"                                 # open temp file for writing
1597
1598    StrCpy $ConfigWriteMaster 1                         # write the master config value
1599    StrCpy $ConfigWriteMinion 1                         # write the minion config value
1600
1601    loop:                                               # loop through each line
1602        FileRead $0 $cfg_line                           # read line from target file
1603        IfErrors done                                   # end if errors are encountered (end of line)
1604
1605        loop_after_read:
1606        StrCpy $lst_check 0                             # list check not performed
1607
1608        ${If} $MasterHost == ""                         # if master is empty
1609        ${OrIf} $MasterHost == "salt"                   # or if master is 'salt'
1610            StrCpy $ConfigWriteMaster 0                 # no need to write master config
1611        ${EndIf}                                        # close if statement
1612        ${If} $MinionName == ""                         # if minion is empty
1613        ${OrIf} $MinionName == "hostname"               # and if minion is not 'hostname'
1614            StrCpy $ConfigWriteMinion 0                 # no need to write minion config
1615        ${EndIf}                                        # close if statement
1616
1617        ${If} $ConfigWriteMaster == 1                   # if we need to write master config
1618
1619            ${StrLoc} $3 $cfg_line "master:" ">"        # where is 'master:' in this line
1620            ${If} $3 == 0                               # is it in the first...
1621            ${OrIf} $3 == 1                             # or second position (account for comments)
1622
1623                ${Explode} $9 "," $MasterHost_state     # Split the hostname on commas, $9 is the number of items found
1624                ${If} $9 == 1                           # 1 means only a single master was passed
1625                    StrCpy $cfg_line "master: $MasterHost$\r$\n"  # write the master
1626                ${Else}                                 # make a multi-master entry
1627                    StrCpy $cfg_line "master:"          # make the first line "master:"
1628
1629                    loop_explode:                       # start a loop to go through the list in the config
1630                    pop $8                              # pop the next item off the stack
1631                    ${Trim} $8 $8                       # trim any whitespace
1632                    StrCpy $cfg_line "$cfg_line$\r$\n  - $8"  # add it to the master variable ($2)
1633                    IntOp $9 $9 - 1                     # decrement the list count
1634                    ${If} $9 >= 1                       # if it's not 0
1635                        Goto loop_explode               # do it again
1636                    ${EndIf}                            # close if statement
1637                    StrCpy $cfg_line "$cfg_line$\r$\n"  # Make sure there's a new line at the end
1638
1639                    # Remove remaining items in list
1640                    ${While} $lst_check == 0            # while list item found
1641                        FileRead $0 $chk_line           # read line from target file
1642                        IfErrors done                   # end if errors are encountered (end of line)
1643                        ${StrLoc} $3 $chk_line "  - " ">"  # where is 'master:' in this line
1644                        ${If} $3 == ""                  # is it in the first...
1645                            StrCpy $lst_check 1         # list check performed and finished
1646                        ${EndIf}
1647                    ${EndWhile}
1648
1649                ${EndIf}                                # close if statement
1650
1651                StrCpy $ConfigWriteMaster 0             # master value written to config
1652
1653            ${EndIf}                                    # close if statement
1654        ${EndIf}                                        # close if statement
1655
1656        ${If} $ConfigWriteMinion == 1                   # if we need to write minion config
1657            ${StrLoc} $3 $cfg_line "id:" ">"            # where is 'id:' in this line
1658            ${If} $3 == 0                               # is it in the first...
1659            ${OrIf} $3 == 1                             # or the second position (account for comments)
1660                StrCpy $cfg_line "id: $MinionName$\r$\n"  # write the minion config setting
1661                StrCpy $ConfigWriteMinion 0             # minion value written to config
1662            ${EndIf}                                    # close if statement
1663        ${EndIf}                                        # close if statement
1664
1665        FileWrite $1 $cfg_line                          # write changed or unchanged line to temp file
1666
1667    ${If} $lst_check == 1                               # master not written to the config
1668        StrCpy $cfg_line $chk_line
1669        Goto loop_after_read                            # A loop was performed, skip the next read
1670    ${EndIf}                                            # close if statement
1671
1672    Goto loop                                           # check the next line in the config file
1673
1674    done:
1675    ClearErrors
1676    # Does master config still need to be written
1677    ${If} $ConfigWriteMaster == 1                       # master not written to the config
1678
1679        ${Explode} $9 "," $MasterHost_state             # split the hostname on commas, $9 is the number of items found
1680        ${If} $9 == 1                                   # 1 means only a single master was passed
1681            StrCpy $cfg_line "master: $MasterHost"      # write the master
1682        ${Else}                                         # make a multi-master entry
1683            StrCpy $cfg_line "master:"                  # make the first line "master:"
1684
1685            loop_explode_2:                             # start a loop to go through the list in the config
1686            pop $8                                      # pop the next item off the stack
1687            ${Trim} $8 $8                               # trim any whitespace
1688            StrCpy $cfg_line "$cfg_line$\r$\n  - $8"    # add it to the master variable ($2)
1689            IntOp $9 $9 - 1                             # decrement the list count
1690            ${If} $9 >= 1                               # if it's not 0
1691                Goto loop_explode_2                     # do it again
1692            ${EndIf}                                    # close if statement
1693        ${EndIf}                                        # close if statement
1694        FileWrite $1 $cfg_line                          # write changed or unchanged line to temp file
1695
1696    ${EndIf}                                            # close if statement
1697
1698    ${If} $ConfigWriteMinion == 1                       # minion ID not written to the config
1699        StrCpy $cfg_line "$\r$\nid: $MinionName"        # write the minion config setting
1700        FileWrite $1 $cfg_line                          # write changed or unchanged line to temp file
1701    ${EndIf}                                            # close if statement
1702
1703    FileClose $0                                        # close target file
1704    FileClose $1                                        # close temp file
1705    Delete "$RootDir\conf\minion"                       # delete target file
1706    CopyFiles /SILENT $R0 "$RootDir\conf\minion"        # copy temp file to target file
1707    Delete $R0                                          # delete temp file
1708
1709FunctionEnd
1710
1711
1712Function un.parseUninstallerCommandLineSwitches
1713
1714    # Load the parameters
1715    ${GetParameters} $R0
1716
1717    # Display Help
1718    ClearErrors
1719    ${GetOptions} $R0 "/?" $R1
1720    IfErrors display_un_help_not_found
1721
1722        # Using a message box here
1723        # I couldn't get the console output to work with the uninstaller
1724        MessageBox MB_OK "\
1725            Help for Salt Minion Uninstallation\
1726            $\n\
1727            $\n==============================================\
1728            $\n\
1729            $\n/delete-install-dir$\tDelete the installation directory that contains the\
1730            $\n$\t$\tconfig and pki directories. Default is to not delete\
1731            $\n$\t$\tthe installation directory\
1732            $\n\
1733            $\n$\t$\tThis applies to old method installations where\
1734            $\n$\t$\tthe root directory and the installation directory\
1735            $\n$\t$\tare the same (C:\salt)\
1736            $\n\
1737            $\n/delete-root-dir$\tDelete the root directory that contains the config\
1738            $\n$\t$\tand pki directories. Default is to not delete the root\
1739            $\n$\t$\tdirectory\
1740            $\n\
1741            $\n$\t$\tThis applies to new method installations where the\
1742            $\n$\t$\troot directory is in ProgramData and the installation\
1743            $\n$\t$\tdirectory is user defined, usually Program Files\
1744            $\n\
1745            $\n/S$\t$\tUninstall Salt silently\
1746            $\n\
1747            $\n/?$\t$\tDisplay this help screen\
1748            $\n\
1749            $\n--------------------------------------------------------------------------------------------\
1750            $\n\
1751            $\nExamples:\
1752            $\n\
1753            $\n$\tuninst.exe /S\
1754            $\n\
1755            $\n$\tuninst.exe /S /delete-root-dir\
1756            $\n\
1757            $\n=============================================="
1758
1759            Abort
1760
1761    display_un_help_not_found:
1762
1763    # Load the parameters
1764    ${GetParameters} $R0
1765
1766    # Uninstaller: Remove Installation Directory
1767    ClearErrors
1768    ${GetOptions} $R0 "/delete-install-dir" $R1
1769    IfErrors delete_install_dir_not_found
1770        StrCpy $DeleteInstallDir 1
1771    delete_install_dir_not_found:
1772
1773    # Uninstaller: Remove Root Directory
1774    ClearErrors
1775    ${GetOptions} $R0 "/delete-root-dir" $R1
1776    IfErrors delete_root_dir_not_found
1777        StrCpy $DeleteRootDir 1
1778    delete_root_dir_not_found:
1779
1780FunctionEnd
1781
1782
1783Function parseInstallerCommandLineSwitches
1784
1785    # Load the parameters
1786    ${GetParameters} $R0
1787
1788    # Display Help
1789    ClearErrors
1790    ${GetOptions} $R0 "/?" $R1
1791    IfErrors display_help_not_found
1792
1793        System::Call 'kernel32::GetStdHandle(i -11)i.r0'
1794        System::Call 'kernel32::AttachConsole(i -1)i.r1'
1795        ${If} $0 = 0
1796        ${OrIf} $1 = 0
1797            System::Call 'kernel32::AllocConsole()'
1798            System::Call 'kernel32::GetStdHandle(i -11)i.r0'
1799        ${EndIf}
1800        FileWrite $0 "$\n"
1801        FileWrite $0 "$\n"
1802        FileWrite $0 "Help for Salt Minion installation$\n"
1803        FileWrite $0 "===============================================================================$\n"
1804        FileWrite $0 "$\n"
1805        FileWrite $0 "/minion-name=$\t$\tA string value to set the minion name. Default value is$\n"
1806        FileWrite $0 "$\t$\t$\t'hostname'. Setting the minion name causes the installer$\n"
1807        FileWrite $0 "$\t$\t$\tto use the default config or a custom config if defined$\n"
1808        FileWrite $0 "$\n"
1809        FileWrite $0 "/master=$\t$\tA string value to set the IP address or hostname of the$\n"
1810        FileWrite $0 "$\t$\t$\tmaster. Default value is 'salt'. You may pass a single$\n"
1811        FileWrite $0 "$\t$\t$\tmaster or a comma-separated list of masters. Setting$\n"
1812        FileWrite $0 "$\t$\t$\tthe master will cause the installer to use the default$\n"
1813        FileWrite $0 "$\t$\t$\tconfig or a custom config if defined$\n"
1814        FileWrite $0 "$\n"
1815        FileWrite $0 "/start-minion=$\t$\t1 will start the minion service, 0 will not.$\n"
1816        FileWrite $0 "$\t$\t$\tDefault is 1$\n"
1817        FileWrite $0 "$\n"
1818        FileWrite $0 "/start-minion-delayed$\tSet the minion start type to 'Automatic (Delayed Start)'$\n"
1819        FileWrite $0 "$\n"
1820        FileWrite $0 "/default-config$\t$\tOverwrite the existing config if present with the$\n"
1821        FileWrite $0 "$\t$\t$\tdefault config for salt. Default is to use the existing$\n"
1822        FileWrite $0 "$\t$\t$\tconfig if present. If /master and/or /minion-name is$\n"
1823        FileWrite $0 "$\t$\t$\tpassed, those values will be used to update the new$\n"
1824        FileWrite $0 "$\t$\t$\tdefault config$\n"
1825        FileWrite $0 "$\n"
1826        FileWrite $0 "$\t$\t$\tAny existing config will be backed up by appending$\n"
1827        FileWrite $0 "$\t$\t$\ta timestamp and a .bak extension. That includes$\n"
1828        FileWrite $0 "$\t$\t$\tthe minion file and the minion.d directory$\n"
1829        FileWrite $0 "$\n"
1830        FileWrite $0 "/custom-config=$\t$\tA string value specifying the name of a custom config$\n"
1831        FileWrite $0 "$\t$\t$\tfile in the same path as the installer or the full path$\n"
1832        FileWrite $0 "$\t$\t$\tto a custom config file. If /master and/or /minion-name$\n"
1833        FileWrite $0 "$\t$\t$\tis passed, those values will be used to update the new$\n"
1834        FileWrite $0 "$\t$\t$\tcustom config$\n"
1835        FileWrite $0 "$\n"
1836        FileWrite $0 "$\t$\t$\tAny existing config will be backed up by appending$\n"
1837        FileWrite $0 "$\t$\t$\ta timestamp and a .bak extension. That includes$\n"
1838        FileWrite $0 "$\t$\t$\tthe minion file and the minion.d directory$\n"
1839        FileWrite $0 "$\n"
1840        FileWrite $0 "/install-dir=$\tSpecify the installation location for the Salt binaries.$\n"
1841        FileWrite $0 "$\t$\t$\tThis will be ignored for existing installations.$\n"
1842        FileWrite $0 "$\n"
1843        FileWrite $0 "/move-config$\t$\tIf config is found at C:\salt it will be moved to %ProgramData%$\n"
1844        FileWrite $0 "$\n"
1845        FileWrite $0 "/S$\t$\t$\tInstall Salt silently$\n"
1846        FileWrite $0 "$\n"
1847        FileWrite $0 "/?$\t$\t$\tDisplay this help screen$\n"
1848        FileWrite $0 "$\n"
1849        FileWrite $0 "-------------------------------------------------------------------------------$\n"
1850        FileWrite $0 "$\n"
1851        FileWrite $0 "Examples:$\n"
1852        FileWrite $0 "$\n"
1853        FileWrite $0 "$\t$EXEFILE /S$\n"
1854        FileWrite $0 "$\n"
1855        FileWrite $0 "$\t$EXEFILE /S /minion-name=myminion /master=master.mydomain.com /start-minion-delayed$\n"
1856        FileWrite $0 "$\n"
1857        FileWrite $0 "$\t$EXEFILE /S /minion-name=myminion /master=master.mydomain.com /install-dir=$\"C:\Software\salt$\"$\n"
1858        FileWrite $0 "$\n"
1859        FileWrite $0 "===============================================================================$\n"
1860        FileWrite $0 "$\n"
1861        System::Free $0
1862        System::Free $1
1863        System::Call 'kernel32::FreeConsole()'
1864
1865        # Give the user back the prompt
1866        !define VK_RETURN 0x0D ; Enter Key
1867        !define KEYEVENTF_EXTENDEDKEY 0x0001
1868        !define KEYEVENTF_KEYUP 0x0002
1869        System::Call "user32::keybd_event(i${VK_RETURN}, i0x45, i${KEYEVENTF_EXTENDEDKEY}|0, i0)"
1870        System::Call "user32::keybd_event(i${VK_RETURN}, i0x45, i${KEYEVENTF_EXTENDEDKEY}|${KEYEVENTF_KEYUP}, i0)"
1871        Abort
1872
1873    display_help_not_found:
1874
1875    # Set default value for Use Existing Config
1876    StrCpy $ConfigType "Existing Config"
1877
1878    # Check for start-minion switches
1879    # /start-service is to be deprecated, so we must check for both
1880    ${GetOptions} $R0 "/start-service=" $R1
1881    ${GetOptions} $R0 "/start-minion=" $R2
1882
1883    # Service: Start Salt Minion
1884    ${IfNot} $R2 == ""
1885        # If start-minion was passed something, then set it
1886        StrCpy $StartMinion $R2
1887    ${ElseIfNot} $R1 == ""
1888        # If start-service was passed something, then set StartMinion to that
1889        StrCpy $StartMinion $R1
1890        MessageBox MB_OK|MB_ICONINFORMATION "`/start-service` is being deprecated. Please use `/start-minion` instead." /SD IDOK
1891    ${Else}
1892        # Otherwise default to 1
1893        StrCpy $StartMinion 1
1894    ${EndIf}
1895
1896    # Service: Minion Startup Type Delayed
1897    ClearErrors
1898    ${GetOptions} $R0 "/start-minion-delayed" $R1
1899    IfErrors start_minion_delayed_not_found
1900        StrCpy $StartMinionDelayed 1
1901    start_minion_delayed_not_found:
1902
1903    # Minion Config: Master IP/Name
1904    # If setting master, we don't want to use existing config
1905    ${GetOptions} $R0 "/master=" $R1
1906    ${IfNot} $R1 == ""
1907        StrCpy $MasterHost $R1
1908        StrCpy $ConfigType "Default Config"
1909    ${ElseIf} $MasterHost == ""
1910        StrCpy $MasterHost "salt"
1911    ${EndIf}
1912
1913    # Minion Config: Minion ID
1914    # If setting minion id, we don't want to use existing config
1915    ${GetOptions} $R0 "/minion-name=" $R1
1916    ${IfNot} $R1 == ""
1917        StrCpy $MinionName $R1
1918        StrCpy $ConfigType "Default Config"
1919    ${ElseIf} $MinionName == ""
1920        StrCpy $MinionName "hostname"
1921    ${EndIf}
1922
1923    # Use Default Config
1924    ClearErrors
1925    ${GetOptions} $R0 "/default-config" $R1
1926    IfErrors default_config_not_found
1927        StrCpy $ConfigType "Default Config"
1928    default_config_not_found:
1929
1930    # Use Custom Config
1931    # Set default value for Use Custom Config
1932    StrCpy $CustomConfig ""
1933    # Existing config will get a `.bak` extension
1934    ${GetOptions} $R0 "/custom-config=" $R1
1935    ${IfNot} $R1 == ""
1936        # A Custom Config was passed, set it
1937        StrCpy $CustomConfig $R1
1938        StrCpy $ConfigType "Custom Config"
1939    ${EndIf}
1940
1941    # Set Install Location
1942    ClearErrors
1943    ${GetOptions} $R0 "/install-dir=" $R1
1944    ${IfNot} $R1 == ""
1945        # A Custom Location was passed, set it
1946        StrCpy $CustomLocation $R1
1947    ${EndIf}
1948
1949    # Set Move Config Option
1950    ClearErrors
1951    ${GetOptions} $R0 "/move-config" $R1
1952    IfErrors move_config_not_found
1953        StrCpy $MoveExistingConfig 1
1954    move_config_not_found:
1955
1956FunctionEnd
1957