Archive: Library.nsh - problem with readonly dll


Library.nsh - problem with readonly dll
Hi!

InstallLib macro does not remove the readonly attribute from DLLs it could replace (non-WFP). My script runs itself after reboot, so I get an endless loop for people having some older M$ runtime DLL with Read-Only-Attribute set.

I added SetFileAttributes to Library.nsh. Can anybody familar with those macros into this issue and see whether I did it right?

near line 340 I have this:


;------------------------
;Upgrade

!ifdef LIBRARY_DEFINE_UPGRADE_LABEL

!undef LIBRARY_DEFINE_UPGRADE_LABEL

installlib.upgrade_${INSTALLLIB_UNIQUE}:
SetFileAttributes "$R4" FILE_ATTRIBUTE_NORMAL

!endif

Thanks for the report. I've modified the latest version to use SetFileAttributes just above Rename /REBOOTOK. That's the only place where it might fail because of the read-only attribute.


I think you changed the wrong line as that is only executed in _REBOOT_ calls and that execution point is after trying to upgrade the file. The "Rename /REBOOTOK" will fail always (this seems just write the file in Registry-Entries for Startup-Renaming).


I've added it just above the Rename /REBOOTOK that will otherwise fail on reboot. If a reboot is not needed, the change isn't needed anyway. The File command removes the read-only flag on its own.

Actually, on second thought, File should always remove the read-only flag, even if the file is in use. I have attached a script showing this behavior. As File is always tried before calling Rename /REBOOTOK, there must be something more here.

Can you attach a script reproducing the behavior you were talking about in the first post?


Hi kichik,

ok, if File does remove the READONLY flag itself you're solution is correct. I wasn't aware of that. The problem was that my installer rebooted again and again on a customers machine, due to the fact it could not replace an old msvbvm60.dll (which had the ReadOnly flag). So according to the fail of the File statement, the problem wasn't readonly in the first but another program using the dll.

Maybe in future NSIS relaeses Rename should remove the target READONLY attribute itself just like File.

I have another problem related to DLLs/reboot/register:

I install several DLLs I have to register and they depend on another. For now I check the RebootFlag after each install and stop installation if it is set, so the file gets installed and registered before I run the installer again (RunOnce). So the installer runs for each file I need to register in the middle of the chain:

dllA
dllB needs dllA for registering

If dllA could not be installed I can't register dllB. If dllB is writable this is a problem because I would have to register it after reboot.

Question: How can I schedule a RegDll after Reboot? (Ok, I have ideas for this) But more important: how can I chain the RegDll calls into the right order? Just entering them into RunOnce may execute RegDll dllB before RegDll dllA which will cause dllB not to be registered.

Any idea? Can I solve this with LibTool anyway?


As far as removing the read-only flag, File and SetFileAttributes do exactly the same. In InstallLib, File is always called before Rename so the read-only flag should always be removed. It seems the SetFileAttributes shouldn't matter. Also, if SetFileAttributes works when the DLL is in use by another application, so should File. I would still like an example that reproduces your problem so I can test this.

When a DLL is queued to be replaced after reboot by InstallLib, it is also queued to be registered. The order of the registration is currently random. That needs to be fixed.


Hi,

1. reboot problem
2. register queue

ad 1.: in short: I couldn't reproduce the problem and you seem to be right.

I have a log file from a customer and I spoke him on phone. The log showed that msvbvm60.dll could not be installed. The nsis line is:


!insertmacro InstallLib REGDLL NOTSHARED REBOOT_PROTECTED \
"${VBFILESDIR}\Msvbvm60.dll" "$SYSDIR\Msvbvm60.dll" $SYSDIR


(Don't worry about the NOTSHARED -- I increment the dll counter myself for some reason.)

The log said:


IfFileExists: file "C:\WINDOWS\system32\Msvbvm60.dll" exists, jumping 0
Call: 521
File: overwriteflag=2, allowskipfilesflag=0, name="C:\WINDOWS\system32\Msvbvm60.dll"
File: skipped: "C:\WINDOWS\system32\Msvbvm60.dll" (overwriteflag=2)
Call: 521
File: overwriteflag=2, allowskipfilesflag=0, name="C:\WINDOWS\system32\nsp7.tmp"
File: wrote 1386496 to "C:\WINDOWS\system32\nsp7.tmp"
Rename: C:\WINDOWS\system32\nsp7.tmp->C:\WINDOWS\system32\Msvbvm60.dll
Rename on reboot: C:\WINDOWS\system32\nsp7.tmp->C:\WINDOWS\system32\Msvbvm60.dll
IfFileExists: file "" does not exist, jumping 0
File: overwriteflag=2, allowskipfilesflag=0, name="C:\WINDOWS\system32\NSIS.Library.RegTool.exe"
File: wrote 19451 to "C:\WINDOWS\system32\NSIS.Library.RegTool.exe"
WriteRegStr: set -2147483646\Software\Microsoft\Windows\CurrentVersion\RunOnce\NSIS.Library.RegTool to "C:\WINDOWS\system32\NSIS.Library.RegTool.exe"
WriteRegStr: set -2147483646\Software\NSIS.Library.RegTool\C:\WINDOWS\system32\Msvbvm60.dll to D
This occured in endless loop because the installer automatically started after Reboot. The customer could gave me some information:
i) He had Outlook and some other background software running (some systray icons).
ii) His msvbvm60.dll was outdated. It had an older date and the version number was lower than the one within the installer.
iii) His msvbvm60.dll had the ReadOnly flag (after calling the installer which produced the log above!)

He killed Outlook and he removed the ReadOnly flag manually. Then he executed the installer again and it worked without rebooting. I can't exactly say what he did or what really solved the problem.

I tried to reproduce this but as you said the Readonly attribute gets removed before Reboot (if file in use). If file is not in use it gets overwritten regardless of Readonly state.

Very strange.

ad 2. Queuing regdll:

It is not enough to make InstallLib/LibTool stable regarding the queue order. Think about my initial setup:

dllA
dllB (registering this requires a registered and installed dllA)

if dllA cannot be copied it is scheduled for replace after reboot, but the installer continues with dllB. It copies dllB but RegDll will fail because dllA is not copied/registered. But RegDll won't be called after reboot for dllB, so dllA is correctly installed after reboot but dllB never gets installed correctly. The nsis/InstallLib way of installing COM-Dlls which are possibly in use does not work with COM-Dlls which have such dependencies. Unfortunately this is the case for most ActiveX controls as they depend on ole32/oleaut32 which may be outdated on older systems (win98). I thought about building another little "RegDll subinstaller" which I will package into the installer and run after Reboot. But this does not work (queue problem, again) together with LibTool or other unqueued RunOnce actions. Conclusion/Suggestion: Make LibTool beware of the queue order and allow LibTool to be entered easily from outside InstallLib. I think an external macro should do this (!macro AddLibToolAction ...) and could be called by InstallLib macro as submacro and by the installer script itself.

Sorry, much text, hope it got clear..

The only situation I can think of that can cause the first problem is that another application which runs on startup replaces msvnvm60.dll. After the user quit Outlook and installed it successfully with reboot, did the change stick after rebooting?

I think a better solution for the second problem would be registering both dllA and dllB after reboot if one of them is scheduled to be replaced after reboot.


ad 1.: That would be very strange. I don't know whether this was the case. The dll was current enough so the user will not note that it might be replaced again (after install). The installation did run _without_ reboot, so I'm quite sure the dll was replaced then. I think the problem was that the File-command in InstallLib didn't remove the Readonly flag while trying to overwrite the dll. So on reboot the replace maybe failed due to presence of the readonly flag. I don't think it is possible for the dll being in use during replace phase...

ad 2.: Yes, I thought that way (and actually do this in my installer, but due to queue problems I reboot after each problematic dll upgrade error). The problem of the right order persists, so it would be nice to handle this with LibTool and some additional macro. Otherwise you need double rebooting to solve the order problem: first ReplaceFiles and second RegisterAll. You can only be sure to have the correct order if you handle this with 1 call in RunOnce: on reboot first all dlls must be replaced and then all dlls must be registered in the correct order. Whether you do it this way or do it the smart way (replace dllA, register dllA, replace dllB, register dllB, register dllC) is some detail question. I would just add some auto numbering to LibTool entries in registry and LibTool should process the list in this order. You wouldn't need explicit numbers in install scripts if you use some variable the macro could increment itself (some INOUT parameter). For this solution you automatically get the smart way without much work.

Can anybody upgrade LibTool to process some numbered list? I would add the macros to library.nsh.

Macro pseudo code for RegDllCheckReboot(?):
- check reboot flag
- if not set call RegDll
- if set call RegDll (a try worth?) and call the RegDllOnReboot macro

Macro pseudo code for RegDllOnReboot:
- increment INOUT parameter COUNT
- add registry entry to Software\NSIS.Libtool...:
$COUNT file t (just adding $COUNT to those calls)

The InstallLib code would change to not directly make this registry entries but call the macro above (to avoid double code).

This would be compatible to current scripts (versions of LibTool and library.nsh have to much) but solve the problem.


  1. Since the reboot flag wasn't set, the DLL was probably replaced. But what I wanted to know is if the DLL wasn't reverted back to the older version the next unrelated reboot.
  2. File replacement occurs before the RunOnce key is read. The registration is already handled with one RunOnce call. Only the order needs to be sorted out. Just having one integer which will be incremented is not good enough because the LibTool registry key might contain entries from other installers too.

    LibTool is a simple NSIS uninstaller. If you want to make the change yourself, its source code is available in Contrib\Library\RegTool.

ad 1.: Of course. I want to know that, too. And I asked the customer to send me the current dll version. If it is the old one again, your theory is correct. Otherwise we're none the wiser. As I said: I could not reproduce this.

ad 2.: I implemented this in a way that different installers will have different "reg lists". When I have tested this I will post the solution here.

I wonder why RegTool.bin is only 19KB. If I compile it, I get a executable about 37KB in size.


I could reduce the size by 4KB using the non-logging NSIS compiler. But there is still a gap (19KB original vs. 33KB recompiled). Is this the difference between NSIS 2.06 and that older released used on 2004-09-24?

If nobody has an idea I will forget this issue as for me those 14KB are quite peanuts...


Hi kichik,

could you think about a builtin NSIS Session-ID? I realized the Library ordered reg solution using some session variable. The variable needs a unique value and I fill it in .onInit using CreateGUID. It would make the interface much smaller (the GUID has to be passed to all 3 macros) if NSIS had an builtin session id. The ID has to be unique for each call of the installer. Eventually a session id could be useful in other cases. Think about $SESSION...

Stefan


SOLUTION
Hi,

here comes the tested solution. The design may be improvable (see last point) but it works. There's no help yet.

1. The modified version of Library.nsh is in the file Library2.nsh, the modified RegTool has the new name "RegTool2".

2. You have to copy those files where the original ones are. Compile RegTool2.nsi, run RegTool2Generator.exe.

3. The InstallLib macro has a new obligatory parameter (the first one). You have to pass some unique identifier here. The identifier has to be unique per installer run. I mean unique for all vendors, applications AND runs. I use "CreateGUID" macro for this (macro is attached, too). You should create your GUID in .onInit and store it in a VAR. All Dlls that are registered (using the supplied macros) after the RebootFlag got set will be scheduled after reboot. On next reboot they will be registered exactly in the order of the installation process. This only works if the GUID is the same for all those calls in one run. So don't create a GUID for each InstallLib call.

4. some example code snapshots

Function .onInit
!insertmacro CreateGuid
Pop $SESSIONID
FunctionEnd

Section MySec
!insertmacro InstallLib "$SESSIONID" REGDLL SHARED REBOOT_PROTECTED "DLL_REPOSITORY\Oleaut32.dll" "$SYSDIR\Oleaut32.dll" $SYSDIR
SectionEnd

5. There is a macro RegDllCheckReboot which should be called instead of RegDll. The macro calls RegDll if the RebootFlag is not set, otherwise RegDll is scheduled using RegTool2. REGTYPE is 'D' for RegDll, 'T' for TypeLib::Register, 'DT' for both.

6. I improved RegTool2 so it does not get executed by accident (on doubleclick). It is no more silent by default but it must be run with /S (silent) to enable it. Running it unsilent won't do anything. Of course macros in library2.nsh install RegTool2 with /S in RunOnce... (Please note: This behaviour was enabled in my file uploaded after 2005-04-04 17:30 GMT!)

7. Improvements possible:
- RegTool and RegTool2 can be joined to be one tool as the new registry entries are stored under subkeys (so a new RegTool could do both to be compatible with older installers). The "File /oname=$R3\NSIS.Library.RegTool2.exe" has to be different from the old one (otherwise the new tool will not get written to disk if the old one is present).
- The extra parameter in InstallLib and the other macros can be avoided if NSIS had a internal SessionID variable.
- The REGTYPE parameter for RegDllCheckReboot and RegDllOnReboot may be replaced by some !define construction as in InstallLib.


RegTool is compiled using a specially compiled makensis.exe with as many options as possible disabled in config.h.

I'll take a look at your changes over the weekend. Thanks.


The read-only issue is still there! I just had the issue with some ocx which is NOT used by any application. I have installed an old version (older than in my nsis package) and it remains there because of the read-only flag. If I run my setup, InstallLib will NOT overwrite the ocx file but does the "rename on reboot" thing. But that fails, too (probably due to read-only?). I tried attrib -r ocx and copy newfile sys32\ocx manually and it worked at once (without rebooting).

I think InstallLib should SetFileAttributes ALWAYS (if the dll is not up to date).

NSIS 2.15


You're correct. I've misread the code the first time. It only removes the read-only flag if SetOverwrite mode is set to "on". The Library macros use "try" mode so no message boxes saying the file can't be overwritten will pop-up.

I've attached the new version so you can test it as anonymous CVS is having trouble right now. It worked in my simple test case.


Hi kichik,

I already did that change for myself :-) and will test it soon.

There is a difference though:

490c464,465
< SetFileAttributes $R0 FILE_ATTRIBUTE_NORMAL
---
> SetFileAttributes "$R0" FILE_ATTRIBUTE_NORMAL
> ClearErrors

I added the ClearErrors because I felt checking with IfErrors could be a bad idea if the file did not exist. Had a second look and saw that IfErrors only checks when the file exists. So you're solution should be fine.

Another question: When do I have to enclose arguments in quotes (what about strings/filenames containing spaces)? Is this never necessary with Vars? I think it is necessary with defines. Correct?


ClearErrors is a good idea, I've added it just in case.

Quotes are only for the parser of the compiler. Variables are translated at runtime and so they don't require quotes. Defines are replaced at compile time and so you need spaces, if that's what you really want. You can also skip the spaces and do something like the following. In this case, quotes will cause a parsing error.

!macro blah
#...
!macroend
!define blah "!insertmacro blah"
${blah}