Disable two mutually exclusive Sections when SectionGroup unchecked? - installation

I have an NSIS installer script with two SectionGroups. Each SectionGroup has two Sections, which are mutually exclusive.
I need to have the user able to uncheck the SectionGroup, which unchecks and disables ("grays-out") the Sections. Basically the user can choose to install one of the Sections, or none. On top of that, the second SectionGroup must be disabled if the first SectionGroup is unchecked.
I'm using this script as reference to control the mutually exclusive Sections: http://nsis.sourceforge.net/Mutually_Exclusive_Sections
Thanks in advance.

Your disable/gray requirement does not make sense. If you click the section group again to enable the "child sections", which of the two gets checked? And you would only be able to get out of the disabled state by clicking the group itself so I did not implement it.
In general, anything beyond simple radio button behavior gets complicated quickly. If you are using the 3.0 alpha version of NSIS then .onSelChange will actually tell you which item that changed which would make this less complicated...
Page Components "" SaveState
Page Instfiles
!include sections.nsh
!include logiclib.nsh
SectionGroup /e "Grp1" GRP_1
Section "A" SEC_A
SectionEnd
Section /o "B" SEC_B
SectionEnd
SectionGroupEnd
SectionGroup /e "Grp2" GRP_2
Section /o "C" SEC_C
SectionEnd
Section "D" SEC_D
SectionEnd
SectionGroupEnd
; Remove comments for a different behavior
Function SaveState
!macro SaveSel id var
SectionGetFlags ${id} ${var}
IntOp ${var} ${var} & ${SF_SELECTED}
!macroend
#SectionGetFlags ${GRP_1} $R0
!insertmacro SaveSel ${SEC_A} $R1
!insertmacro SaveSel ${SEC_B} $R2
#SectionGetFlags ${GRP_2} $R3
!insertmacro SaveSel ${SEC_C} $R4
!insertmacro SaveSel ${SEC_D} $R5
FunctionEnd
Function .onSelChange
!macro OneOfTwoItemsInAGroup gid gv i1 v1 i2 v2
#SectionGetFlags ${gid} $0
!insertmacro SaveSel ${i1} $1
!insertmacro SaveSel ${i2} $2
${If} $1 <> 0
${AndIf} $2 <> 0
StrCpy $1 ${i1}
${IfThen} ${v1} = 0 ${|} StrCpy $1 ${i2} ${|}
!insertmacro UnselectSection $1
/*${If} ${gv} = $0
!insertmacro UnselectSection ${gid}
${EndIf}*/
${EndIf}
!macroend
!insertmacro OneOfTwoItemsInAGroup ${GRP_1} $R0 ${SEC_A} $R1 ${SEC_B} $R2
!insertmacro OneOfTwoItemsInAGroup ${GRP_2} $R3 ${SEC_C} $R4 ${SEC_D} $R5
Call SaveState
FunctionEnd

Related

NSIS: How to get a finish- and a next-button?

I use the MUI_PAGE_INSTFILES. And this page has normally a finish-button, but I have a custom page after it. But I want that you could still finish the installer before the custom page and the next page is only optional. I could also show more code if wished.
!include "MUI2.nsh"
!insertmacro MUI_PAGE_LICENSE $(license)
!insertmacro MUI_PAGE_INSTFILES
Page custom TestSettings
At the moment I have a previous-, next- and cancel-button at the instfile page. But I want a prev-, next- and finish-button.
NSIS was never designed to support this but with the ButtonEvent plug-in you can make it work. I'm just not sure if it makes sense because if the last page is important then some users might skip the page by accident because they are not paying attention.
!include MUI2.nsh
!insertmacro MUI_PAGE_LICENSE $(license)
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE InstFilesLeave
!insertmacro MUI_PAGE_INSTFILES
Page custom TestSettings
!insertmacro MUI_LANGUAGE English
!include WinMessages.nsh
!include nsDialogs.nsh
Function InstFilesLeave
GetDlgItem $0 $hWndParent 2
ShowWindow $0 0
System::Call 'USER32::GetWindowRect(pr0,#r1)' ; NSIS 3+
System::Call 'USER32::MapWindowPoints(p0,p $hWndParent, p $1, i 2)'
System::Call '*$1(i.r2,i.r3,i.r4,i.r5)'
IntOp $4 $4 - $2
IntOp $5 $5 - $3
!define IDC_MYFINISHBTN 1337
System::Call 'USER32::CreateWindowEx(i 0, t "BUTTON", t "&Finish", i ${DEFAULT_STYLES}|${WS_TABSTOP}, ir2, ir3, i r4, i r5, p $hWndParent, p ${IDC_MYFINISHBTN}, p 0, p 0)p.r1'
SendMessage $0 ${WM_GETFONT} "" "" $0
SendMessage $1 ${WM_SETFONT} $0 1
GetFunctionAddress $0 OnFinishButton
ButtonEvent::AddEventHandler ${IDC_MYFINISHBTN} $0
FunctionEnd
Var JustFinish
Function OnFinishButton
StrCpy $JustFinish 1
SendMessage $hWndParent ${WM_COMMAND} 1 ""
FunctionEnd
Function TestSettings
StrCmp $JustFinish "" +2
Return
GetDlgItem $0 $hWndParent ${IDC_MYFINISHBTN}
ShowWindow $0 0
GetDlgItem $0 $hWndParent 2
ShowWindow $0 1
!insertmacro MUI_HEADER_TEXT "Configure" "Blah blah blah"
nsDialogs::Create 1018
Pop $0
; ...
nsDialogs::Show
FunctionEnd
If your custom page just contains a couple of checkboxes then you can use the MUI Finish page with custom texts instead.

Getting a path for each component in NSIS

I'm trying to create an installer with NSIS that installs 3 different components to three different paths. I want the users to select/confirm each of them, but they should only be asked if they've selected the relevant component.
Since pages can't appear in sections, I'm at a loss how to do this
Any suggestions?
Thanks in advance :-)
You can use multiple directory pages:
!include LogicLib.nsh
InstallDir $ProgramFiles32\Foo\Bar
Var Comp1Path
Var Comp2Path
Page Components
PageEx Directory
DirText "Blah blah 1"
DirVar $Comp1Path
PageCallbacks Comp1Pre
PageExEnd
PageEx Directory
DirText "Blah blah 2"
DirVar $Comp2Path
PageCallbacks Comp2Pre
PageExEnd
Page InstFiles
Section /o Comp1 SID_C1
DetailPrint "Installing Comp1 to $Comp1Path"
SectionEnd
Section Comp2 SID_C2
DetailPrint "Installing Comp2 to $Comp2Path"
SectionEnd
Function Comp1Pre
StrCpy $Comp1Path $InstDir\Comp1
${IfNot} ${SectionIsSelected} ${SID_C1}
Abort ; Skipping this page
${EndIf}
FunctionEnd
Function Comp2Pre
StrCpy $Comp2Path $InstDir\Comp2
${IfNot} ${SectionIsSelected} ${SID_C2}
Abort
${EndIf}
FunctionEnd
; In this example the next button on the components page might be the last page before InstFiles so we have to update the button text
!include WinMessages.nsh
Function .onSelChange
GetDlgItem $1 $hwndParent 1
${If} ${SectionIsSelected} ${SID_C1}
${OrIf} ${SectionIsSelected} ${SID_C2}
SendMessage $1 ${WM_SETTEXT} 0 "STR:$(^NextBtn)"
${Else}
SendMessage $1 ${WM_SETTEXT} 0 "STR:$(^InstallBtn)"
${EndIf}
FunctionEnd
Another alternative would be to create a custom page with nsDialogs and just disable or hide the text fields the user does not need to confirm...

NSIS Install like in TeamViewer (Portable mode option)

What I basically want is TeamViewer-like first page for my NSIS installer with the following options:
3 radio buttons for: Run Only; Install for current user; Install for all users (requires restart with admin rights).
A label for license like in TeamViewer (i.e. no actual EULA page, only a link to it in the footer).
A button that can change text, i.e. Accept and Run or Accept and Install.
I cannot figure out how to do it easily in terms of UI and in terms of control flow.
Also I need the ability to restart the installer if user decides to install program for all users (i.e. I guess there should be a detectable command line switch, so that if present installer will automatically assume 3rd install type).
A screenshot of a sample UI as requested:
A sample NSIS template would be greatly appreciated.
Thanks.
...
RequestExecutionLevel user
!include LogicLib.nsh
!include nsDialogs.nsh
!include FileFunc.nsh
!include MUI2.nsh
Var mode
Var modeRadioRun
Var modeRadioInstCU
Var modeRadioInstLM
Function OnRadioChange
GetDlgItem $1 $hwndparent 1 ; Find Install/Next button
${NSD_GetState} $modeRadioRun $0
${If} $0 = ${BST_CHECKED}
${NSD_SetText} $1 "Accept && Run"
${Else}
${NSD_SetText} $1 "Accept && Install"
${EndIf}
FunctionEnd
Function ModePageCreate
!insertmacro MUI_HEADER_TEXT "Welcome to blah" "blah blah"
${GetParameters} $0
ClearErrors
${GetOptions} "$0" "/ELEVATEDINSTALL" $0
${IfNot} ${Errors}
UserInfo::GetAccountType
Pop $0
${If} $0 == "Admin"
StrCpy $mode 1
Abort ; Skip page and start installing
${Else}
MessageBox mb_iconstop "Admin rights required!"
${EndIf}
${EndIf}
nsDialogs::Create 1018
Pop $0
${NSD_CreateRadioButton} 30u 20u 50% 12u "Run"
Pop $modeRadioRun
${NSD_OnClick} $modeRadioRun OnRadioChange
${NSD_CreateRadioButton} 30u 40u 50% 12u "Install for current user"
Pop $modeRadioInstCU
${NSD_OnClick} $modeRadioInstCU OnRadioChange
${NSD_CreateRadioButton} 30u 60u 50% 12u "Install for all users"
Pop $modeRadioInstLM
${NSD_OnClick} $modeRadioInstLM OnRadioChange
${NSD_CreateLink} 20u -14u 50% 12u "License"
Pop $0
${NSD_OnClick} $0 ShowLicense
${NSD_Check} $modeRadioRun
call OnRadioChange ; Trigger button change
nsDialogs::Show
FunctionEnd
Function ModePageLeave
${NSD_GetState} $modeRadioRun $0
${NSD_GetState} $modeRadioInstCU $1
${If} $0 = ${BST_CHECKED}
InitPluginsDir
SetOutPath $pluginsdir
File "myapp.exe"
ExecWait '"$pluginsdir\myapp.exe"'
SetOutPath $temp ; Don't lock $pluginsdir
Quit
${ElseIf} $1 = ${BST_CHECKED}
StrCpy $mode 0
${Else}
StrCpy $mode 1
UserInfo::GetAccountType
Pop $0
${If} $0 != "Admin"
ExecShell "runas" '"$exepath"' "/ELEVATEDINSTALL"
Quit
${EndIf}
${EndIf}
FunctionEnd
Function ShowLicense
ExecShell "" "http://example.com/license"
FunctionEnd
Page Custom ModePageCreate ModePageLeave
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Section
${If} $mode > 0
SetShellVarContext all
StrCpy $InstDir "$ProgramFiles\MyApp"
${Else}
SetShellVarContext current
StrCpy $InstDir "$LocalAppData\Programs\MyApp"
${EndIf}
SetOutPath $InstDir
File myapp.exe
CreateShortcut "$SMPrograms\MyApp.lnk" "$InstDir\myapp.exe"
WriteUninstaller "$InstDir\Uninst.exe"
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyAppGuid" "UninstallString" '"$InstDir\Uninst.exe"'
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyAppGuid" "DisplayName" "MyApp blah blah"
SectionEnd
Section Uninstall
; Todo: Remove files and registry entries (You should write to a .ini in $InstDir so you know if it was a per user or machine install)
RMDir "$InstDir"
SectionEnd
You might want to edit the base UI to make the install button larger with Resource Hacker (on one of the files in NSIS\Contrib\UIs) and in the script use ChangeUI to apply.

Compile Error because I call a Macro twice

When I call a macro function twice (inside a section) I get this compile error:
Error: label "CheckForRMSCustomisationLoop:" already declared in section
I understand its because I am defining a label(jump) twice, is that correct?
How can I avoid this problem?
Do I HAVE to convert the macro to a function or is there an easier way? Because converting to a function means I cant pass parameters I have to use the stack.
Outfile "RequireAdmin.exe"
RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on)
!include LogicLib.nsh
!macro Test param1 param2
TestLabel1:
DetailPrint "TestLabel1"
TestLabel2:
DetailPrint "TestLabel2"
!macroend
Section
!insertmacro Test 1 2
!insertmacro Test 3 4
IfErrors 0 +2
MessageBox MB_ICONEXCLAMATION|MB_OK "Unable to write to the registry" IDOK +1
SectionEnd
Function TestFunct # I MISS MY PARAMS :(
Pop $R9 # represents param2: but actually having param2 is SO MUCH more descriptive
Pop $R8
TestLabel1:
DetailPrint "TestLabel1"
TestLabel2:
DetailPrint "TestLabel2"
FunctionEnd
/* Usage
Push 1
Push 2
Call TestFunct
Push 3
Push 4
Call TestFunct
*/
You're right, you're defining labels TestLabel1 and TestLabel2 twice.
You could make your label names unique using this method:
!macro Test param1 param2
!define ID ${__LINE__}
TestLabel1_${ID}:
DetailPrint "TestLabel1"
TestLabel2_${ID}:
DetailPrint "TestLabel2"
!macroend
More information here:
http://nsis.sourceforge.net/Macro_vs_Function#labels
It appears this is a solution and its pretty elegant too:
!macro Test param1 param2 uid
TestLabel1_${uid}:
DetailPrint "TestLabel1"
TestLabel2_${uid}:
DetailPrint "TestLabel2"
!macroend
# Usage:
Section
!insertmacro Test 1 2 ${__LINE__}
!insertmacro Test 3 4 ${__LINE__}
SectionEnd
!macro test p1
!define test_ "test${__LINE__}"
IntCmp ${p1} 3 ${test_}double 0
DetailPrint ${p1}
Goto ${test_}end
${test_}double:
IntOp $0 ${p1} * 2
DetailPrint $0
${test_}end:
!undef test_
!macroend
; If the macro is very large and you call it multiple times it might be better to use a function or a macro/function hybrid:
!include util.nsh
!macro testmacroasfunction
Pop $0
IntCmp $0 3 double 0
DetailPrint $0
Goto end
double:
IntOp $0 $0 * 2
DetailPrint $0
end:
!macroend
!macro test2 p1
Push ${p1}
${CallArtificialFunction} testmacroasfunction
!macroend
section
!insertmacro test 2
!insertmacro test 3
!insertmacro test 4
!insertmacro test2 2
!insertmacro test2 3
!insertmacro test2 4
sectionend

How to show some text on mouse move in NSIS installer

is there any possibility to show some descriptive text on NSIS installer custom page, but only on mouse hover?
I have the prerequisites check at the beginning of the installer and when one (or more) of the tests fail, appropriate warning message is displayed. It is custom page displayed before whole installation. The problem is, that there are too many messages (in the worst case) and installer page small -- it isn't possible to show all of them without overlaying... So I would like to display only some title (briefly describing the problem) and more detailed information somewhere below in the dedicated area, but only when mouse moved over the brief text. Or, other solution is to create some scrollable area...
But I don't know how to do it in NSIS. I know .onMouseOverSection callback, but AFAIK it can be used only in section selection page.
Is it possible to do it in NSIS page?
Thanks in advance.
I don't think doing this on the instfiles page is a good idea. I would go for a custom page. If you decide to go with the custom page idea, you probably have 3 options:
Create a custom nsis plugin that shows a page in any way you want.
Create a custom page with nsDialogs and handle the mouse messages with the WndSubclass plugin.
Use two component pages and tweak one of them.
The following example uses the 3rd option since it uses only official nsis plugins.
OutFile "$%temp%\test.exe"
Name "Prereq desc"
RequestExecutionLevel user
!include MUI2.nsh
!define MUI_PAGE_HEADER_TEXT "Header blablah"
!define MUI_PAGE_HEADER_SUBTEXT "subheader blablah"
!define MUI_COMPONENTSPAGE_TEXT_TOP "blablah 1"
!define MUI_COMPONENTSPAGE_TEXT_INSTTYPE " "
!define MUI_COMPONENTSPAGE_TEXT_COMPLIST " "
!define MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE "blablah 4"
!define MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO "blablah 5"
!define MUI_PAGE_CUSTOMFUNCTION_PRE prereqcreate
!define MUI_PAGE_CUSTOMFUNCTION_SHOW prereqshow
!insertmacro MUI_PAGE_COMPONENTS
!define MUI_PAGE_CUSTOMFUNCTION_PRE componentscreate
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Function .onInit
InitPluginsDir
StrCpy $0 0
loop:
ClearErrors
SectionGetText $0 $1
IfErrors done
WriteIniStr "$Pluginsdir\sec.ini" S $0 $1 ;Save section names...
IntOp $0 $0 + 1
Goto loop
done:
FunctionEnd
!macro ShowSections initial flip
StrCpy $0 0
StrCpy $2 ${initial}
loop:
ClearErrors
SectionGetText $0 $1
IfErrors done
ReadIniStr $1 "$Pluginsdir\sec.ini" S $0
IntCmpU 0 $2 "" +2 +2
StrCpy $1 ""
SectionSetText $0 $1
IntOp $0 $0 + 1
IntCmpU $0 ${flip} "" +2 +2
IntOp $2 $2 !
Goto loop
done:
!macroend
!macro CreatePreReq text name
Section /o "${text}" ${name}
SectionIn RO
SectionEnd
!macroend
!insertmacro CreatePreReq "Windows Installer v4.5" SEC_PRMSI
!insertmacro CreatePreReq ".NET v666" SEC_PRDOTNET
Section "Program files" SEC_PROG
;...
SectionEnd
Section "Desktop shortcut" SEC_DESKLNK
;...
SectionEnd
!define FIRSTREALSECTION ${SEC_PROG}
Function prereqcreate
!insertmacro ShowSections 1 ${FIRSTREALSECTION}
FunctionEnd
Function prereqshow
FindWindow $0 "#32770" "" $HWNDPARENT
GetDlgItem $1 $0 0x3FD
ShowWindow $1 0
GetDlgItem $1 $0 0x3FE
ShowWindow $1 0
GetDlgItem $1 $0 0x3FF
ShowWindow $1 0 ;hide space texts
GetDlgItem $1 $0 0x408
!define TVM_SETIMAGELIST 0x1109
SendMessage $1 ${TVM_SETIMAGELIST} 2 0 ;Remove images (leaking the imagelist in the process, oh well)
System::Call '*(&i24)i.r2'
System::Call 'user32::GetWindowRect(ir1,ir2)'
System::Call 'user32::MapWindowPoints(i0,ir0,ir2,i2)'
System::Call '*$2(i,i.r0,i.r3,i.r4)'
System::Free $2
IntOp $4 $4 - $0
System::Call 'user32::SetWindowPos(ir1,i0,i0,ir0,ir3,ir4,i0)'
FunctionEnd
Function componentscreate
!insertmacro ShowSections 0 ${FIRSTREALSECTION}
FunctionEnd
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_PRMSI} "You need MSI in a NSIS installer? ;)"
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_PRDOTNET} "You need moar bloat!"
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_PROG} "Required program files..."
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_DESKLNK} "Stupid desktop shortcut"
!insertmacro MUI_FUNCTION_DESCRIPTION_END

Resources