NSIS : How to cleanup the files on abort installation? - nsis

I have written an NSIS script with the !define MUI_CUSTOMFUNCTION_ABORT _onUserAbort abort call. Inside the _onUserAbort function I want to remove some files.
Here's my function:
Function _onUserAbort
MessageBox MB_YESNO "Are you sure you want to abort the installation?" IDYES true IDNO false
true:
;cleanup function
Abort
false:
FunctionEnd
If i click on “Yes” it does nothing, but if I click on “No” it aborts the installation. Please help me out.

Modern UI already comes with an abort warning, there is no need to replicate this feature. To enable the warning, all you have to do is to define MUI_ABORTWARNING. You can further customize the warning with MUI_ABORTWARNING_TEXT and MUI_ABORTWARNING_CANCEL_DEFAULT (see the documentation for details!)
The _onUserAbort function is meant to run, when the user has already decided to abort installation. In your case, this should only handle the clean-up of files.

Related

NSIS - Run a function after the installer released the files

I'm using NSIS to create a simple installer, and then NsisXML plug-in by Joel to change some things in the config.xml file.
I've created a simple function that does this:
Function ChangeConfig
${nsisXML->OpenXML} "$INSTDIR\configHexd.xml"
${nsisXML->CreateElement} "/config/config_hexd/paramlist" "param" ""
${nsisXML->SetElementAttr} "/config/config_hexd/paramlist/param[10]" "name" "logPath"
${nsisXML->SetElementAttr} "/config[0]/config_hexd/paramlist/param[10]" "value" "$INSTDIR/logs"
${nsisXML->CloseXML}
FunctionEnd
and I'm calling it like this:
; Finish page
!define MUI_FINISHPAGE_RUN "$INSTDIR\run.exe"
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\readme.txt"
!define MUI_PAGE_CUSTOMFUNCTION_PRE ChangeConfig
!insertmacro MUI_PAGE_FINISH
The script compiling goes well and I get my setup.exe. The only problem is, the setup crashes with an 'unknown runtime error' when it's copying the files. This only happens when I'm using this function, if I don't call it everything goes well. Also everything goes well if I just open and close the XML file without trying to write into it. It only crashes when I try writing into the file.
I think that's because the installer tries writing to the file before it's done copying it over, and I'm wondering how I can prevent that.
Thanks!
Ok, I solved it, and it was way easier than I thought.
First of all, my node was not called paramlist but paramList, that was the main problem.
Second, as #Anders said, I shouldn't have used MUI_PAGE_CUSTOMFUNCTION_PRE for my problem, a simple Call of the function after the installer was finished transffering the files was enough.
So, lesson 1: Check for typos before blaming the program
Lesson 2: Read the docs to fully understand what something does.
Lesson 3: In most cases, when you find the answer, you're gonna be baffled as to how easy it was to solve the problem.

NSIS: reading DWORD from the registry?

I'm trying to update an existing installer to read a registry value (if it exists) and change the install settings to match existing installation settings when doing upgrade installations.
But I'm getting hung up on not being able to sucessfully read the registry value.
I write the key like this in the installer:
WriteRegDWORD HKLM "Software\${PRODUCT}" "IniPath" 0x00000026
This part works just fine, the key is created or modified, and is of type DWORD, all is well.
But then if I try to read that value back, I seem to be getting an empty value every time.
ReadRegDWORD $IniPath HKCU "Software\${PRODUCT}" "IniPath"
MessageBox MB_OK "$$IniPath=$IniPath"
Results in printing $IniPath=
I also tried doing ClearErrors before reading and IfErrors after, and it does appear to be getting into IfErrors...but I don't understand why it would be having errors and not seeing the key. I am running 32 bit windows, so I doubt this is 64bit "wrong section of the registry" error.
The line to write the DWORD is working just fine. If I delete the registry key, and examine the registry using regedit immediately after, the key is there. But regardless of whether the key existed.
So what could be going wrong to cause it to fail to read the value, or how to debug this?
You are writing to HKLM and reading from HKCU...
To debug registry issues you can use Process Monitor.

How to Create Custom View in Windows Event Viewer using API?

Is there an API to create a Custom View in Windows Event Viewer?
I would like to do that in the installation using NSIS,
But if I cant and there is any other way then I'll write a plugin for it.
The problem is not NSIS but the way the Event Log works, you need to install a message table DLL somewhere on the system and register it in the registry. This DLL has to stay on the system since it is used by the Event Log Viewer when you view the log and is not used when you log the message! This design is really problematic for a installer.
It is probably better to just log to a plain text file in $temp or $exedir.
If you really want to use the Event Log and you don't mind hacks you could:
Put the message table DLL in $pluginsdir and register/unregister when the installer starts/ends (If it crashes you leave junk in the registry!)
Use a Microsoft/Windows message table (They are not documented, could change from version to version (And service packs?) so you would have to test every Windows version and configuration you support!)
The NSIS code looks something like this:
section
System::Call 'advapi32::RegisterEventSource(i0,t "Software Installation")i.r0'
#TODO: Check if $0 == 0 and display error message
System::Call '*(i,&t1000 "Hello world")i.r1'
IntOp $2 $1 + 4
System::Call '*$1(ir2)'
System::Call 'advapi32::ReportEvent(ir0,i ${EVENTLOG_ERROR_TYPE},i0,i 101,i0,i1,i0,ir1,i0)i'
System::Free $1
System::Call 'advapi32::DeregisterEventSource(ir0)'
sectionend

NSIS callback function crashes installer when file is not present

I'm stuck on this.
I have this callback function at the bottom of a subscript (.nsh) file.
(As you can see I'm using LogicLib):
Function InstallFoo
MessageBox MB_OK "Within InstallFoo function"
${If} ${FileExists} "$EXEDIR\Modules\foo.zip"
MessageBox MB_OK "foo.zip found, do install it!"
nsisunz::Unzip "$EXEDIR\Modules\foo.zip" "$INSTDIR\Foo"
${Else}
MessageBox MB_OK "No foo.zip found. Do nothing"
${EndIf}
MessageBox MB_OK "End reached"
FunctionEnd
Everything works exactly as I want it to when the 'foo.zip' is present, but when it isn't, the installer crashes, and I really can't understand why.
I would expect it to also be able to handle when the 'foo.zip' is not found, that is by doing nothing. What happens now is that the installer crashes with
"setup.exe - Application Error, The instruction at "some-address" referenced memory at "some-other-address". The memory could not be "read".
I have the same type of code within sections in my scripts and it works fine there, whether zip-files present or not. It's just in this callback function it doesn't work, so I'm starting to think it has something to do with it being a callback function.
In the 'foo.zip file not present' - case I get the following MessageBoxes: "Within InstallFoo function" "No foo.zip found. Do nothing" "End reached"
And then the crash. I have a MessageBox printout within the section where the callback was called from also, that is, the first thing to happen after the return from the callback function, but it never gets there.
Note, as soon as the foo.zip is there everything works just fine!
Does anyone have any idea of what I might be doing wrong here?
It seems to me I get the crash whenever nothing's done within the callback function.
Is it that NSIS allocates some memory for the callback function, and if it's not used we get the crash..?? Weird.

Detect if an instance is running with kernel32::CreateMutexA

I'm working on an NSIS installer, and trying to check if a certain application is running before uninstalling. So, I use kernel32::CreateMutexA call. Here is the chunk:
System::Call 'kernel32::CreateMutexA(i 0, i 0, t "cmd.exe") i .r1 ?e'
Pop $R0
StrCmp $R0 0 +3
MessageBox MB_USERICON "The application is already running."
Abort
I put it into un.onInit. Trouble is, the process (cmd.exe here) is never detected.
Did I miss something?
Tx.
I found a simple solution; using FindProcDLL plugin.
So:
FindProcDLL::FindProc "cmd.exe"
Pop $R0
StrCmp $R0 0 +3
MessageBox MB_USERICON "The application is already running." IDOK
Abort
P.S. FindProcDLL.dll must be copied into /Plugins.
All you are doing is creating a mutex with the global name "cmd.exe". From the MSDN article for CreateMutex:
If lpName matches the name of an existing event, semaphore, waitable
timer, job, or file-mapping object, the function fails and the
GetLastError function returns ERROR_INVALID_HANDLE. This occurs
because these objects share the same name space.
So unless cmd.exe creates a handle to one of those types of objects with the name "cmd.exe", this call will simply create a new mutex with that name and return you the (non-erronous) handle.
You're probably using the wrong Win32 API function. Your CreateMutex tries to create a named mutex "something.exe". If there isn't one with that name it will succeed, so unless the process you're trying to check is creating a mutex with that name, you won't get the result you're after.
What you want is probably to enumerate all running processes and see if the one you're after is there. You can do this with ToolHelp32 from Win32 API - see sample here. I don't know how easy it will be to convert it to 'pure' NSIS so you might want to write a DLL plugin, or check if there's an existing solution floating around the NSIS community.
For this kind of thing, I've used either the KillProcess or Find-Close-Terminate plugins for NSIS - see here
Documentation is pretty straightforward, hopefully this does what you need - with a fairly minimal overhead.

Resources