NSIS basics go wrong - nsis

So this is a really basic question, but I can't seem to find what I am doing wrong.
So I am fiddling with defines in NSIS and it did not work as I would expect so I have scaled down the problem to its smallest part and I still can not make it work as I would expect.
Script looks as follows:
!ifndef b
!define b ""
!endif
!if $b=="b"
!define a "b"
!else
!define a "c"
!endif
Section
MessageBox MB_OK "a: ${a} b: ${b}"
SectionEnd
I run it with the flag /Db=b.
The output is still:
a: "c" b: "b"
I am missing something trivial here!

b is a define, not a variable:
!ifndef b
!define b ""
!endif
!if "${b}" == "b" # <-- Modify this line.
!define a "b"
!else
!define a "c"
!endif
Section
MessageBox MB_OK "a: ${a} b: ${b}"
SectionEnd
Also, I recommend you quote everything when using if because it'll give an error if define (or value of variable) is empty.

Related

How to fix "CreateDirectory: Relative paths not supported" when using a variable?

As a newbiew, I am still in the stage of experimenting and building little prototypes. The idea is to build a silent installer that has all settings in multiple sections of a .INI and the users calls the setup with parameter /config={NameOfSection}.
My current situation:
FooBar-install.ini
[PROD]
FOOHOME=c:\FooBar
FooBar.nsi
!include FileFunc.nsh
!include LogicLib.nsh
!insertmacro GetParameters
!insertmacro GetOptions
var /GLOBAL config
var /GLOBAL cmdLineParams
var /global REGAPPKEY
var /global FOOHOME
!define TheName "FooBar"
!define OutFileSuffix "-Install."
!define IniFile "$EXEDIR\${TheName}${OutFileSuffix}ini"
Name "${TheName} ${PRODUCT_VERSION}" ; bei 2 Kunden geht's auch kd-spezifisch ;)
OutFile ${TheName}${OutFileSuffix}exe
RequestExecutionLevel admin
Icon "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
UninstallIcon "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
Section "-aInstaller Section"
ReadINIStr $FOOHOME ${IniFile} $config "FOOHOME"
MessageBox MB_OK "ini=${IniFile} , config=$config, FOOHOME=$FOOHOME"
CreateDirectory "SFOOHOME"
SectionEnd
function .onInit
UserInfo::GetAccountType
pop $0
${If} $0 != "admin" ;Require admin rights on NT4+
MessageBox mb_iconstop "Administrator rights required!"
SetErrorLevel 740 ;ERROR_ELEVATION_REQUIRED
${Else}
MessageBox MB_OK "onInit"
${EndIf}
; Get parameters
${GetParameters} $cmdLineParams
; /? param (help)
ClearErrors
${GetOptions} $cmdLineParams '/?' $R0
IfErrors +3 0
MessageBox MB_OK "Befehlszeilenparameter /config={{name}} verweist auf einen Abschnitt aus ${TheName}${OutFileSuffix}ini mit div. Parametern zur Steuerung des Setup"
Abort
Call parseParameters
Pop $R0
FunctionEnd
Function parseParameters
; /config
${GetOptions} $cmdLineParams '/config=' $R0
${If} ${Errors}
StrCpy $config "errPROD"
${Else}
StrCpy $config $R0
${Endif}
FunctionEnd
Problem
If I try to compile this, I get the msg
CreateDirectory: Relative paths not supported
Usage: CreateDirectory directory_name
Questions
I do not understand why this error comes up at compile time. When using a variable (especially in this situation where the variable depends on user-input), it does not seem to make sense to complain about the argument when it is not known.
How can I avoid this probolem?
A little puzzle that messes me up is the syntax to refer to variables.The statement MessageBox MB_OK "ini=${IniFile} , config=$config, FOOHOME=$FOOHOME" shows that. I found that I needed to enclose IniFile in {} in order to display its value (I commented out the CreateDir-line to compile the installer and check my assumptions). When do I have to use {}?
If you see any other "unusual" things in my little script, I'd be happy to know ;)
You have a typo, change CreateDirectory "SFOOHOME" to CreateDirectory "$FOOHOME"
You might want to read the documentation again to learn the basics; ${define}, $(langstring) and $variable.

Creating shortcut to console application in NSIS with particular font

I have an NSIS script which can create a shortcut to an application with CreateShortCut.
The application that the shortcut points to is a console application, but one which works much better if there is something other than the default font chosen. Of course, the user can be told to follow instructions like https://www.isunshare.com/windows-10/change-font-and-font-size-in-windows-10-command-prompt.html to change to a different font on the shortcut, but my quetsion is whether that can be automated in NSIS? That is, check if a particular font is available and then have the shortcut start a console with that font.
If that is impossible in NSIS for a particular shortcut, is there a way to give users the option to have a system-wide change to the font used in all terminals?
The CreateShortcut instruction only supports basic shortcut properties, it does not support console properties set by IShellLinkDataList.
Setting the NT_CONSOLE_PROPS data has two issues:
It is all or nothing, you have to set the size, color and edit options in addition to the font.
Ideally you should provide the "index of the font in the system's console font table" but that index is not really documented and I don't know how to map from a font name to the index.
If you still want to do it then you must use the System plug-in:
!include LogicLib.nsh
!include Win\COM.nsh ; NSIS v3
!define /ifndef LF_FACESIZE 32
!define /ifndef NT_CONSOLE_PROPS_SIG 0xA0000002
Section
StrCpy $R1 "$Desktop\MyApp.lnk" ; .Lnk path
StrCpy $R3 "Consolas" ; Font name
StrCpy $R5 i0x36 ; tmPitchAndFamily?
StrCpy $R6 400 ; "The weight can range from 100 to 1000, in multiples of 100. For example, the normal weight is 400, while 700 is bold"
StrCpy $R7 0xc0000 ; dwFontSize packed COORD
StrCpy $R8 0x200060 ; dwWindowSize packed COORD
System::Call '*(&l4,i${NT_CONSOLE_PROPS_SIG}, i0xf50007,i0x3e70050,i$R8,i0x0,i0x0,i0x0,i$R7,i$R5,i$R6, &w${LF_FACESIZE}"$R3", i0x19,i0x0,i0x1,i0x1,i0x1,i0x32,i0x4,i0x1,i0x0,i0x800000,i0x8000,i0x808000,i0x80,i0x800080,i0x8080,i0xc0c0c0,i0x808080,i0xff0000,i0xff00,i0xffff00,i0xff,i0xff00ff,i0xffff,i0xffffff)p.R2'
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 ""
${If} $0 P<> 0
${IShellLink::SetPath} $0 '("%COMSPEC%").r1'
${IShellLink::SetArguments} $0 '("/k echo HelloWorld").r2'
${If} $1 = 0
${AndIf} $2 = 0
${IUnknown::QueryInterface} $0 '("${IID_IShellLinkDataList}",.r1)'
${If} $1 P<> 0
${IShellLinkDataList::AddDataBlock} $1 '(pR2).r2'
${IUnknown::Release} $1 ""
${EndIf}
${IUnknown::QueryInterface} $0 '("${IID_IPersistFile}",.r1)'
${If} $1 P<> 0
${IPersistFile::Save} $1 '("$R1",1).r2'
${IUnknown::Release} $1 ""
${EndIf}
${EndIf}
${IUnknown::Release} $0 ""
${EndIf}
System::Free $R2 ; Free NT_CONSOLE_PROPS
SectionEnd

Find a string pattern in a file from an NSIS script

In an NSIS installer script, I'm trying to check if a given httpd.conf file contains the following line :
Include "c:\xxx\yyy.conf"
If so, then my installer script would not append it to the file, otherwise, it would append it.
I've come through {LineFind} but not sure this really makes what i'm trying to achieve.
What could be the simplest way to do a kind of "grep" on a text file from an NSIS script ?
Thank you !
Here is a sample for searching for a given line into a file, using the LogicLib for ease of syntax. The search is stopped as soon as the line is found. This sample works on the sample script itself:
# find.nsi : sample for LineFind from TextFunc.nsh
!include "textfunc.nsh"
!include "logiclib.nsh"
OutFile "find.exe"
!define lookfor `Section` ;will find
;!define lookfor `Sectionn` ;will not find
Var found
Section
StrCpy $found 0
${LineFind} "find.nsi" "/NUL" "1:-1" "GrepFunc"
${if} $found = 1
MessageBox MB_OK "string found"
${else}
MessageBox MB_OK "string NOT found"
${endIf}
SectionEnd
Function GrepFunc
${TrimNewLines} '$R9' $R9
DetailPrint "test for line $R8 `$R9`"
${if} $R9 == "${lookfor}"
StrCpy $found 1 ;set flag
Push "StopLineFind" ;stop find
${else}
Push 0 ;ignore -> continue
${endIf}
FunctionEnd

How LangString not translated can use a default language?

My installation must be available for many languages but I cannot translate absolutely all messages. How can I use the a default language when a message has not been translated?
For example:
!insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE "Spanish"
!insertmacro MUI_LANGUAGE "TradChinese"
!insertmacro MUI_LANGUAGE "SimpChinese"
LangString EMBEDED ${LANG_ENGLISH} "Installation single user"
LangString EMBEDED ${LANG_SPANISH} "Instalación usuario único"
I would like that if installation is in TradChinese, EMBEDED string would use the english translation because there is not EMBEDED for chinese.
I asked the same question on NSIS forum without success. Finally I created an small Perl script that parses my NSIS code and detect included languages and all Langstrings, detect missing translations, and create Langstrings initialized to the default language:
#!/usr/bin/perl
#
# filltranslationgaps.pl
# Get list of included languages
my $tmp = uc(`grep "^!insertmacro MUI_LANGUAGE" installer.nsi |grep -v ";" |cut -f2 -d\\"`);
my #lang = split(/\n/, $tmp);
my $DEFAULTLANGUAGE = $lang[0];
# Get langstrings
open( $fh, "<installer.nsi");
while (<$fh>) {
if (/LangString *(.*?) *\${LANG_(.*?)} *\"(.*)\"/) {
$phrases{$1}{$2}=$3;
}
}
close $fh;
# Foreach Langstring, check if it's defined in all included languages.
foreach my $k (keys %phrases) {
foreach my $j (#lang) {
if (not defined $phrases{$k}{$j}) {
#print "MISSING PAIR: $k\t$j\n";
print "LangString $k \${LANG_$j} \"$phrases{$DEFAULTLANGUAGE}{$j}\"\n";
}
}
}
My compile script:
#/bin/sh
perl filltranslationgaps.pl >missingphrases.nsi
makensis installer.nsi
In installer.nsi I need to include the generated nsi:
!include missingphrases.nsi
Hope it helps.
LangFile.nsh contains helper code to create language files with fallback support (Used by MUI etc) but each language has to be in a separate .nsh file.
If you want to keep all the translations in the .nsi you have to make your own custom macros:
!macro MyLangInit lnam nlf
!ifndef MYLANG
!define MYLANG ${lnam}
!ifndef MYLANG_${lnam}
!define MYLANG_${lnam}
LoadLanguageFile "${NSISDIR}\Contrib\Language files\${nlf}.nlf"
!ifndef LANG_${lnam}
!define LANG_${lnam} ${LANG_${nlf}}
!endif
!endif
!endif
!macroend
!define MyLangInit "!insertmacro MyLangInit "
!macro MyLangSet mode nam str
!ifndef MYLANG_${MYLANG}_${nam}
!define MYLANG_${MYLANG}_${nam} "${str}"
!else
!if ${mode} != 0
!ifdef MYLANG_${MYLANG}_${nam}
LangString ${nam} "" "${MYLANG_${MYLANG}_${nam}}"
!undef MYLANG_${MYLANG}_${nam}
!endif
!endif
!endif
!macroend
!macro MyLangLoad lnam
!define MyLangSet "!insertmacro MyLangSet 0 "
!insertmacro ${lnam}
!insertmacro ${MYLANG_FALLBACK}
!undef MyLangSet
!define MyLangSet "!insertmacro MyLangSet 1 "
!insertmacro ${lnam}
!insertmacro ${MYLANG_FALLBACK}
!undef MyLangSet
!undef MYLANG
!macroend
!macro English
${MyLangInit} English English
${MyLangSet} foo "bar"
${MyLangSet} bar "baz"
!macroend
!macro Bork
${MyLangInit} Bork Swedish ; Based on Swedish
${MyLangSet} foo "bork"
; Missing: ${MyLangSet} bar "barkz"
!macroend
!macro Danish
${MyLangInit} Danish Danish
; All strings are missing!
!macroend
!define MYLANG_FALLBACK English ; Language to use for missing strings
!insertmacro MyLangLoad Bork
!insertmacro MyLangLoad Danish
!insertmacro MyLangLoad English
Function .onInit
StrCpy $LANGUAGE ${LANG_BORK} ; TODO: Use LangDLL or some algorithm to select the language here
FunctionEnd
Section
SetDetailsView show
DetailPrint LangId=$LANGUAGE
DetailPrint "$(foo)"
DetailPrint "$(bar)"
SectionEnd

NSIS - Conditionally Display Components

I want the installer/uninstaller to check if any of the possible components are installed, and display only relevan components (installer should display only components not already installed and uninstaller should display only already-installed components)
I'm using MUI.
The component section of my .nsi looks something like this:
; Section descriptions
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${Component1} "Component1 Description"
!insertmacro MUI_DESCRIPTION_TEXT ${Component2} "Component2 Description."
!insertmacro MUI_DESCRIPTION_TEXT ${Component3} "Component3 Description."
!insertmacro MUI_FUNCTION_DESCRIPTION_END
I've tried a couple of different ways:
First, I tried using this macro for checking if a registry exists:
!insertmacro IfKeyExists HKEY_LOCAL_MACHINE SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall "Component1"
Pop $R0
${If} $R0 == 0 #Not installed yet. Display.
!insertmacro MUI_DESCRIPTION_TEXT ${Component1} "Component1"
${EndIf}
This didn't work.
Then I tried using the Registry Plug-In:
${registry::KeyExists} "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Component1" $R0
${If} $R0 == 0 #Not installed yet. Display.
!insertmacro MUI_DESCRIPTION_TEXT ${Component1} "Component1"
${EndIf}
This didn't work either.
So then I thought maybe there was some other more basic problem, and I tried this:
StrCpy $0 "0"
${If} $0 == "1"
!insertmacro MUI_DESCRIPTION_TEXT ${Component1} "Component1 Description."
${EndIf}
But even then I still saw "Component1" as a possible component during installation!
What am I doing wrong and how can I go about achieving my goal?
Thanks in advance!
To select a section (= a component) at runtime you can use SectionGetFlag / SectionSetFlag and some bit manipulation :
SectionGetFlags ${test_section_id} $0
IntOp $0 $0 | ${SF_SELECTED}
SectionSetFlags ${test_section_id} $0
To deselect it, just toogle its selected bit, with
SectionGetFlags ${test_section_id} $0
IntOp $0 $0 ^ ${SF_SELECTED}
SectionSetFlags ${test_section_id} $0
These are simpler if you include the Sections.nsh header and use the SelectSection and UnselectSection macros.
If you want to hide a section, you just need to set its text to "" (empty string)
SectionSetText ${test_section_id} ""
You are modifying the wrong text. MUI_DESCRIPTION_TEXT is the long description that is typically displayed when hovering the mouse over a component. But the visibility of a section depends on its name to be empty or not empty.
Therefore you have to use this instead to hide your component1:
SectionSetText ${Component1} ""

Resources