Archive: InstallLib: can't make NOREBOOT_PROTECTED to work


InstallLib: can't make NOREBOOT_PROTECTED to work
  Hello everyone,

I've written a bunch a macros to use inside an installer for installing and registering DLLs.
I just adapted some bits of code I grabbed here and there on this forum or in the NSIS wiki.

But it seems I'm not able to make it work properly!

The installer is supposed to install a DLL, which might be in use. To make sure we detect whether it is in use or not, I call InstallLib with NOREBOOT_PROTECTED, as the documentation states: "Warns the user when the library is in use. The user will have to close applications using the library."
I open my application, and I check with "WhoLockMe" utility that my DLL is in use.
I launch the setup and it doesn't warn me.
Several users have complained because their DLL was not updated...

Could you please help me determine what I'm doing wrong?

Note that I'm using UltraModernUI, don't know if it might change something or not, but I prefer to warn about that :-)
I've also tried NOREBOOT_NOTPROTECTED but without success.

Here are some bits of my setup:

1/ The macros

!macro InstallsDLL DLL_PATH DLL_NAME DLL_TYPE
IfFileExists "$SYSDIR\${DLL_NAME}" 0 +2
StrCpy $ALREADY_INSTALLED 1

!insertmacro InstallLib ${DLL_TYPE} $ALREADY_INSTALLED NOREBOOT_PROTECTED "${DLL_PATH}\${DLL_NAME}" "$SYSDIR\${DLL_NAME}" "$SYSDIR"
WriteINIStr "$INSTDIR\install.log" "DLLs" "DLL ${DLL_NAME} was installed" ""
!macroend

!macro AddsOnlyNewerDLL DLL_PATH DLL_NAME DLL_TYPE
GetDLLVersionLocal "${DLL_PATH}\${DLL_NAME}" $R0 $R1
IntOp $R2 $R0 / 0x00010000
IntOp $R3 $R0 & 0x0000FFFF
IntOp $R4 $R1 / 0x00010000
IntOp $R5 $R1 & 0x0000FFFF
StrCpy $0 "$R2.$R3.$R4.$R5"

GetDllVersion "$SYSDIR\${DLL_NAME}" $R0 $R1
IntOp $R2 $R0 / 0x00010000
IntOp $R3 $R0 & 0x0000FFFF
IntOp $R4 $R1 / 0x00010000
IntOp $R5 $R1 & 0x0000FFFF
StrCpy $1 "$R2.$R3.$R4.$R5"

${VersionCompare} $0 $1 $R0

StrCmp $R0 "1" +2 0
Goto "Skip${DLL_NAME}"
!insertmacro InstallsDLL "${DLL_PATH}" "${DLL_NAME}" ${DLL_TYPE}
Goto "Fin${DLL_NAME}"
Skip${DLL_NAME}:
WriteINIStr "$INSTDIR\install.log" "DLLs" "A newer or same version of ${DLL_NAME} is already installed, skipped" ""
Fin${DLL_NAME}:
!macroend

2/ The offending call

Section /o InstSystemDLL InstSystemDLL
[...]
!insertmacro InstallsDLL "<mypath>\MSXML4" "msxml4.dll" REGDLL
!insertmacro InstallsDLL "<mypath>\MSXML4" "msxml4r.dll" DLL
SectionEnd

Whenever MSXML4 is in use, I don't get warned about it being locked!
Yet the output window tells me the DLL was registered: "Registering: C:\WINDOWS\system32\msxml4.dll"


Alter code:
iffileexists "$SYSDIR\msxml4.dll" exists notexists
exists:
get the version and compare, if need update then,
loop:
clearerrors
unregdll "$SYSDIR\msxml4.dll"
iferrors inuse notinuse
inuse:
messagebox mb_ok "please close applications bla bla" idok loop
notinuse:
delete "$SYSDIR\msxml4.dll"
notexists:
setoutpath "$SYSDIR"
file "local_path\"msxml4.dll"
regdll "$SYSDIR\msxml4.dll"


Your answer seemed to me a bit odd in the first place, as many people here recommend using InstallLib and not directly calling regdll and unregdll.
So I decided to dive within InstallLib's code, and in my understanding it looks like it handles error cases silently instead of Warning the user about file lock, which is in contradiction with NSIS's documentation.
Furthermore it already includes the comparison of DLL versions and skips DLL file copy when the version already installed is the same or newer (it's going to save me some macros in my installer :P).
In your example you first call unregdll to check whether the DLL is locked or not, if it succeeds I expect a DLL registering counter was decremented somewhere, so calling regdll once afterwards means in the end the DLL registering counter remained unchanged, while I expect it should be 1 more than at the beginning, if that analysis is correct, would calling regdll twice do the trick?
Right now I'm playing with C:\Program Files\NSIS\Include\Library.nsh contents, hoping I'll find a workaround that I could inject in a macro duplicated from InstallLib's code. I wish to keep most of InstallLib's features but want to be able to skip version check and want to warn the user when the file is locked.


When a file is locked, it will be queued for replacement after reboot. If reboot is disabled, the user will be warned through the SetOverwrite setting which will tell him the file can't be opened for writing and that he'd have to abort, retry or ignore this. It's not supposed to be ignored, at least code-wise. There might a problem elsewhere in the code.

If the DLL is registered and not replaced, it's probably in a good enough version. Are you sure the DLL versions are really older than what you're installing and that those DLL files are not protected?


@fmt_jsc: you are right, the NSIS documentation contains an error.
it claims the NOREBOOT_PROTECTED flag causes the user to get warned in case the lib-file is in use. this is not true! the "library.nsh", even that file included with the latest NSIS distro, only skips installation when it detects the file is in use currently. this state gets determined by calling "SfcIsFileProtected". (anyway, this method always returns 0 on my vista, even if the dll is currently loaded, who knows why :confused: ) if it would return a NZ-number instead, no user warning will occure at all, because the macro will leave! this means: don't use the NOREBOOT_PROTECTED flags, instead use NOREBOOT_NOTPROTECTED only. in this case we will receive a user warning if we try to overwrite the file, since "SetOverwrite" get's turned on, but this happens only on version updates. if the install-file dll-version is older and the flag LIBRARY_IGNORE_VERSION isn't defined, the file operation get's skipped correctly.

another real problem with the "library" is the "UnInstallLib" macro. if you want to delete loaded librarys after reboot it works fine, but if not (using a NOREBOOT_xxx flag), the file gets deleted without any verification. a small patch inside the "library.nsh" will fix this behaviour:

just replace...

;------------------------

;Delete

Delete $R1

>!ifdef UNINSTALLLIB_UNINSTALL_REBOOT_PROTECTED | UNINSTALLLIB_UNINSTALL_REBOOT_NOTPROTECTED

>...
with something like...
;------------------------

;Delete

>"uninstalllib.delete_${UNINSTALLLIB_UNIQUE}:"

>Delete $R1

>!ifndef UNINSTALLLIB_UNINSTALL_REBOOT_PROTECTED | UNINSTALLLIB_UNINSTALL_REBOOT_NOTPROTECTED

${If} ${FileExists} $R1
# File is in use, can't just delete. Give user the chance to retry.

MessageBox MB_RETRYCANCEL|MB_ICONSTOP "\
Failed to uninstall library:$\n$\n$R1$\n$\n\
File may still be in use by some other application.$\n\
Please close all applications before you Retry,$\n\
or press Cancel to keep this file.\
"IDRETRY "uninstalllib.delete_${UNINSTALLLIB_UNIQUE}"

${EndIf}

!endif

!
ifdef UNINSTALLLIB_UNINSTALL_REBOOT_PROTECTED | UNINSTALLLIB_UNINSTALL_REBOOT_NOTPROTECTED

>...
now it should give users the chance to close any application, and give any loaded library free for deletion.

PROTECTED doesn't mean in-use. It means protected as in Windows File Protection. Indeed you shouldn't use this for unprotected files.

The behavior of UninstallLib is intentional. If you don't want to let it do its work and remove the file at reboot, you can implement the logic around UninstallLib with the same piece of code you've added here. If you think that's a common usage, you can create a Wiki page for it so everyone can find it.