Archive: Question regarding an installer that can also upgrade


Question regarding an installer that can also upgrade
Hi,
I am creating an installer using NSIS 2.0 beta 3, Modern UI.
My requirement is to have the installer check first whether the application is currently running and/or previously installed - this I managed to implement successfully via a DLL that contains some helper functions (in my app there's more to it than just searching for files or Windows that can be scripted).
Next, if the application is running, I ask the user to close it and loop until it really is closed. OK so far. However, if the application is installed but not running, I don't want to proceed as a normal install
(Welcome, EULA, Components, Directory, Install Files, Finish);
Instead, I want to provide a custom page that informs that user that I have detected a previous version x.xx installed at path yyy, and that by clicking Next the application will be *upgraded*.
That's because I don't want to potentially end up with 2 or more installations side-by-side. The new version *must* upgrade the previous one in its current location.
I also want to simplify the upgrade for the user by removing pages that an upgrade doesn't need. The upgrade should simply read the previous installation folder from the Registry and use it and should *not* display the Welcome, EULA, Components and Directory pages. After the custom page, file installation should take place and then the Finish page. That's all.
So the requirement is to have a dual purpose installer that either does a full install or an upgrade that displays fewer pages, based on a check conducted at runtime (currently in Function .onInit).
No matter what I did, I couldn't implement this with NSIS Modern UI. It seems that the pages need to be declared up front, losing flexibility on whether to omit a particular page later on.
There doesn't seem to be functionality to skip a page or to go from one page to another page.
Even when using MUI_CUSTOMPAGECOMMANDS and insertmacro to declare the pages, the compiler complained when I did !insertmacro MUI_PAGECOMMAND_INSTFILES from a function. Without this, I couldn't get the upgrade to be functional. Regarding some other pages like EULA, Components and Directory, even without an !insertmacro they still got displayed! Because I had LicenseData for the install, the compiler insisted that I have !define MUI_LICENSEPAGE and would not allow me to add a license page dynamically (practically forcing me to show a license page even in the upgrade).
To prevent this email from growing too long I'll stop discussing the many dead ends I reached at every step and will leave the stage for the NSIS experts to try to solve this problem. I would appreciate any solid tips regarding the implementation of such an install/upgrade. Thank you.


Page commands only define which pages are being included in the installer.

To skip a certain page, define a custom page pre function (check the Modern UI Readme) and use the Abort command in the function.

offtopic: I replied to your bugreport too


To create your "upgrade page" I suggest you define a welcome page pre-function (MUI_CUSTOMFUNCTION_WELCOME_PRE) and write your own strings to ioSpecial.ini inside $PLUGINSDIR. For example:

!define MUI_CUSTOMFUNCTION_WELCOME_PRE welcomePreFunc
#...
Function welcomePreFunc
# if upgrading....
!insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" \
"Field 2" "Text" "upgrading title"
!insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" \
"Field 3" "Text" "upgrading text"
FunctionEnd


Then just skip the other pages using Abort from their pre-function as Joost said.

Thanks! Your suggestions worked superbly and I have the entire upgrade up and running. Two new, smaller issues were encountered and I hope you can help me with those:

1. I am writing a path contained in a variable to the .ini file of InstallOptions (as the text of a label control). However, InstallOptions expects all backslashes to be escaped with another backslash, which they aren't. Therefore they are not displayed at all. Is there an NSIS trick to fix this, or must I resort to my DLL and write a helper function to manipulate the path?

2. I want to change (dynamically) one of the strings in the Finish page, at the point where I know that this is an upgrade, to say "The application has been successfully upgraded" (instead of installed). When I want to do this, the strings have already been loaded. I can't seem to find a way to modify them at that point. The Modern UI readme only mentions a way to brand a string before the strings are loaded. Please let me know if there is a fix: I'm probably missing something very basic here. If not, what can be done about this?
Thanks.

PS: greets to kichik from the nearby town of Tel Aviv.


1: You will need to convert all \ to \\ and then write that to the ini file in order for them to be written as \ (for labels only)
I'll nock together a function if ya want in a sec.

2:

!define MUI_TEXT_FINISHPAGE_SUBTITLE "[Place your text here]"


-Stu

Here we are...
Usage:
Push C:\program~1\nsis
Call ConvertBStoDBS
Pop $R0

$R0 is now C:\\program~1\\nsis


Function ConvertBStoDBS
Exch $R0
Push $R1
Push $R2
Push $R3
Push $R4
StrCpy $R1 -1
StrCpy $R3 0
StrCpy $R4 ""
StrCpy $R5 ""
loop:
StrCpy $R2 $R0 1 $R1
IntOp $R1 $R1 - 1
StrCmp $R2 "" done
StrCmp $R2 "\" 0 +3
StrCpy $R4 "$R2\$R4"
Goto loop
StrCpy $R4 "$R2$R4"
Goto loop
done:
Pop $R3
Pop $R2
Pop $R1
Exch $R4
FunctionEnd


(ConvertBStoDBS stands for Convert Back Slash to Double Back Slash :p)

I'll put it up oin the archive too.

-Stu

G_J, any chance you could post your installer source at the Archive? Even if u have to strip most of it out for coporate reasons the page structure and code to make it work would be a great example to many.


Afrow UK: many thanks for your backslash-doubler function.
Your tip regarding !define MUI_TEXT_FINISHPAGE_SUBTITLE "[Place your text here]" didn't work though. I am using a multi-lingual Modern UI installer. The message I want to change is defined as follows in English.nsh:

!insertmacro MUI_LANGUAGEFILE_STRING MUI_TEXT_FINISH_INFO_TEXT "${MUI_PRODUCT} has been installed on your computer.\r\n\r\nClick Finish to close this wizard."

I need to change it well after the strings have been loaded, i.e. after !insertmacro MUI_LANGUAGE "English" is performed. Any ideas?

Sunjammer: I think I will indeed post the installer source (with some minor censorship) in a few days once it's done. Since I've never done this I may ask you for help. Cheers.


G_J, no worries, ask away :)


I'm thinking, you could get the dialog item number, and then insert text using a macro.


!macro CUSTOM_FINISHPAGE_TEXT TEXT
GetDlgItem $2 $HWNDPARENT 1037
SendMessage $2 ${WM_SETTEXT} 0 "STR:${TEXT}"
!macroend


Just change the number (currently 1037) until you get the right item on the page.

On the finish page, use:

Function CustomText
!insertmacro MUI_HEADER_TEXT "${MUI_PRODUCT} has been installed on your computer.\r\n\r\nClick Finish to close this wizard."
FunctionEnd

This function is called using this:
!define MUI_CUSTOMFUNCTION_FINISHPAGE_SHOW "CustomText"

This might work, if I understand what you need.

I'd try and get the dialog number for you now, but I gota go!

-Stu

!define MUI_TEXT_FINISH_INFO_TEXT "${MUI_PRODUCT} has been installed on your computer.\r\n\r\nClick Finish to close this wizard."

add that before the MUI_LANGUAGE macro's. If you have multiple languages, put 'em before each macro.

Joost, as I mentioned I can't specify the alternative text before the MUI_LANGUAGE macros because I need to use it *conditionally*. In normal circumstances I leave this text with its default phrasing. But if it's an upgrade, which I detect during .onInit, only then do I want to change the text and by that time the MUI_LANGUAGE macros have taken place.

Afrow, thanks for your idea on using GetDlgItem. It appears to be in the right direction. Unfortunately, it fails. The reason is that if I define a Show function for the Finish page, I found that it gets called while the File Installation page is still displayed. So looking then for the internal #32770 dialog of $HWNDPARENT gets me the one for the File Installation page (and *not* the Finish page). I tried using the weird ${MUI_TEMP1} mentioned in the Modern UI readme but I get the same result. To summarize, I can't get hold of the HWND of the internal dialog of the Finish page. Once that is possible, GetDlgItem using the ID as obtained by Spy++ for the label that I want to change, will probably conclude the affair. Once again, I'd appreciate your ideas.


To change one of the strings in the finish page dynamically do the same as you have done for the welcome page, define a pre-function (MUI_CUSTOMFUNCTION_FINISH_PRE) and write to the same file in the same place ($PLUGINSDIR\ioSpecial.ini) to the same INI fields.

Greetings from Jerusalem :)


Kichik, thanks for your extreme responsiveness. Unfortunately, your suggestion didn't work. Almost, but no cigar... In the Pre function of the Finish page, ioSpecial.ini still has the definition of the Welcome page. Any changes I make there to the strings are lost the moment the function end and the Finish page really gets displayed (unlike changes I made to the [Settings] section that did take effect). I verified this by adding dummy MessageBox calls in my Pre function and I manually examined the said ioSpecial.ini at that point in a temp location on my disk. The problem seems to be that the Pre function of the Finish page is called, perhaps, too early, not giving the scripter a real chance to tweak this page either through GetDlgItem or through the ioSpecial.ini. Am I making any sense? Regards, GJ


Oops, it's show you're supposed to use in both welcome and finish, MUI_CUSTOMFUNCTION_FINISH_SHOW and MUI_CUSTOMFUNCTION_WELCOME_SHOW. Pre is to skip the page, show is to change it, and finish is for preventing the user from leaving the page.


Sorry, still not working! In the Show function of the Finish page, I indeed see the ioSpecial.ini of the Finish page. My modifications in it take place successfully. I verified this. But the new labels I set are ignored, and the default ones are always displayed.

I suspect that the labels are already read into memory *before* the Show function and are not read back. Hence, it is impossible to change them in the Show function, by writing to ioSpecial.ini.
Try modifying any of the Text labels in Field 2 through 4 and you'll see. I believe this may be an NSIS buglet.


OK, here's the deal - on the show function the page has already been created but it's not shown to let the user (and the MUI macros) change background colors and fonts used in the dialog. Therefore the INI file, as you said, has already been read. What you'll need to do is use SendMessage (hwnd) WM_SETTEXT 0 "STR:text" to change the text. To get the HWND use GetDlgItem using ${MUI_TEMP1} as the HWND of the newly created dialog.

MUI_CUSTOMFUNCTION_FINISH_SHOW - ${MUI_TEMP1} contains HWND of Finish dialog
The dialog item ID is 1200 + field number - 1. For example, this code (taken from the MUI) sets the header background color and sets its font:

GetDlgItem ${MUI_TEMP2} ${MUI_TEMP1} 1201
SetBkColor ${MUI_TEMP2} "${MUI_BGCOLOR}"
CreateFont ${MUI_TEMP3} "$(MUI_FONT_TITLE)" "$(MUI_FONTSIZE_TITLE)" "$(MUI_FONTSTYLE_TITLE)"
SendMessage ${MUI_TEMP2} ${WM_SETFONT} ${MUI_TEMP3} 0


I am sorry for the confusion.

I have updated the Modern UI. Grab the latest CVS version and you can write to the INI file in the pre function (the fields have been created).


Thanks Kichik. I finally got this to work.

Somewhere at the top I define:
LangString TEXT_UPGRADE_SUCCESS ${LANG_ENGLISH} "${MUI_PRODUCT} has been successfully upgraded!$\r$\n$\r$\nClick Finish to close this wizard."
... and for the other languages.

Then in the Show function of the Finish page, the following code, though can be optimized a little, seems to work well:

Function MyFinishShow
; Change the text on the Finish page conditionally
; if it's an upgrade:
StrCmp $5 "Upgrade" 0 Leave
Push $R0
Push $R1
StrCpy $R0 ${MUI_TEMP1}
GetDlgItem $R1 $R0 1202
SendMessage $R1 ${WM_SETTEXT} 0 "STR:$(TEXT_UPGRADE_SUCCESS)"
Pop $R1
Pop $R0
Leave:
FunctionEnd

Thanks again!


Good :D You can also write to the INI file in the pre func. It's up to you what you think is better.