Archive: PRODUCT_DIR_REGKEY on 64-bit


PRODUCT_DIR_REGKEY on 64-bit
I have a few defines in my nsi script. I am making a universal installer so it installs if there is a 64-bit system/32-bit system, it would do so dynamically.

For the most part, it works, except for the registry part.

I already did the setregview 64 in the appropiate spots, but it is failing because of:

!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"

How can I get it so the define changes based on 64-bit?

SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${PROGRAM_NAME}" 


So essentially, I want to do this


${If} ${RunningX64}
!define PRODUCT_UNINST_KEY "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${PROGRAM_NAME}"
${Else}
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
${End}


And that is obviously an illegal command. How can I follow the law and make this legal?

Thanks

Make a (global) variable for it.


Originally posted by jpderuiter
Make a (global) variable for it.
In other words, comment out the two defines, and use a global variable elsewhere in the code?

What I also don't get is, in

!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\foo.EXE"
already has it in the Wow64 folder, and I did not knowingly put it there, so why would it not do that for the uninstall stuff?

essentially...


Section -Post
# lalala
${If} ${RunningX64}
SetRegView 64
WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\foo.EXE"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\foo.EXE"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
${Else}
SetRegView 32
WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\foo.EXE"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\foo.EXE"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
${EndIf}
SectionEnd

Yes, create a global variable (e.g. $Product_Uninst_Key), replace "!define" with "StrCpy $Product_Uninst_Key", and use $Product_Uninst_Key elsewhere in your code.


can I just put it here at the end oft he defines?

;  Reserve files
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS

Var /GLOBAL PRODUCT_UNINST_KEY64 "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${PROGRAM_NAME}"
Var /GLOBAL PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"

BrandingText "${PRODUCT_PUBLISHER}"
; MUI end ------

Function .onInit

Yes, but you cannot assign values there.

So:

;  Reserve files
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
Var /GLOBAL Product_Uninst_Key
BrandingText "${PRODUCT_PUBLISHER}"
; MUI end ------
Function .onInit
...
Section -Post
# lalala
${If} ${RunningX64}
SetRegView 64
StrCpy $Product_Uninst_Key "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${PROGRAM_NAME}"
${Else}
SetRegView 32
StrCpy $Product_Uninst_Key "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
${EndIf}
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} $Product_Uninst_Key" "DisplayVersion" "${PRODUCT_VERSION}"
;etc
SectionEnd

Yeah.. after i saw what you wrote and reread the documentation i now realise why the strcpy was necessary. so i did


Section -Post
# lalala

# define the variables
var /GLOBAL unkey64
StrCpy $unkey64 "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
var /GLOBAL unkey32
StrCpy $unkey32 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
var /GLOBAL regkey64
StrCpy $regkey64 "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\Foo.exe"
var /GLOBAL regkey32
StrCpy $regkey32 "Software\Microsoft\Windows\CurrentVersion\App Paths\Foo.exe"


${If} ${RunningX64}
SetRegView 64
WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "$regkey64" "" "$INSTDIR\Foo.exe"
WriteRegStr HKLM "$unkey64" "DisplayName" "$(^Name)"
WriteRegStr HKLM "$unkey64" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr HKLM "$unkey64" "DisplayIcon" "$INSTDIR\Foo.exe"
WriteRegStr HKLM "$unkey64" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "$unkey64" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr HKLM "$unkey64" "Publisher" "${PRODUCT_PUBLISHER}"
${Else}
SetRegView 32
WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "$regkey32" "" "$INSTDIR\Foo.exe"
WriteRegStr HKLM "$unkey32" "DisplayName" "$(^Name)"
WriteRegStr HKLM "$unkey32" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr HKLM "$unkey32" "DisplayIcon" "$INSTDIR\Foo.exe"
WriteRegStr HKLM "$unkey32" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "$unkey32" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr HKLM "$unkey32" "Publisher" "${PRODUCT_PUBLISHER}"
${EndIf}
SectionEnd


should this be the only other area that this is needed?

the only other area where i explicitly mention the 64 thing was right before the main function, so i guess the end of oninit function was


# foofoo
install:
${If} ${RunningX64}
SetRegView 64
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCT_NAME}"
SetOutPath "$PROGRAMFILES64\${PRODUCT_NAME}"
${Else}
SetRegView 32
StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCT_NAME}"
SetOutPath "$PROGRAMFILES\${PRODUCT_NAME}"
${EndIf}


mainly, i had issues with it actually putting the 64-bit code into program files, as it kept putting it into x86. originally we did that, bc our code wasn't 64-bit compliant yet, but eventually we did it, but in the past we did just programfiles32 or something. i had thought forcing it on 64 would force it to go program files regardless of architecture.

anyway, the above does what i want, with at least to the registry, thanks again for all your help...just wanting to make sure that if you can think of other areas that i may had missed off the top of hte head w/o seeing the entire project, but any other areas i should look out for you think?

The reason your registry went under Wow6432Node is because NSIS is 32-bit and Windows 64 automatically redirects registry key operations for 32-bit processes to that key. There is no need to hard-code the Wow6432Node registry key path specially for Windows 64-bit, you are just over-complicating things. Just use the regular key path and do not change SetRegView.

Stu


I know it has been awhile but I am back at it. So to clarify, I don't need to use any setregview, no wow6432node, just use the regular HKLM and it should automagically go into the correct spots within the registry? i think its working fine in Win 7 and Win 8 x86/x64, but not Win XP / Vista x64.


Originally posted by jweinraub
i think its working fine in Win 7 and Win 8 x86/x64, but not Win XP / Vista x64.
What do you mean? Does it not show up in the control panel?

You should only use SetRegView 64 and $programfiles64 when installing a 64bit application, if your application is always 32bit you should just pretend that 64bit Windows does not exist...


No no, I mean its a universal installer, so if its on 64 bit it does 64-bit install, if on 32-bit, it does 32-bit install...


${If} ${RunningX64}
SetRegView 64
# the strcpy is illegal outside of scope
var /GLOBAL unkey64
var /GLOBAL regkey64

StrCpy $unkey64 "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
StrCpy $regkey64 "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\foo.exe"

WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "$regkey64" "" "$INSTDIR\foo.EXE"
WriteRegStr HKLM "$unkey64" "DisplayName" "$(^Name)"
WriteRegStr HKLM "$unkey64" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr HKLM "$unkey64" "DisplayIcon" "$INSTDIR\foo.EXE"
WriteRegStr HKLM "$unkey64" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "$unkey64" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr HKLM "$unkey64" "Publisher" "${PRODUCT_PUBLISHER}"

${Else}
SetRegView 32
var /GLOBAL unkey32
var /GLOBAL regkey32

StrCpy $unkey32 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
StrCpy $regkey32 "Software\Microsoft\Windows\CurrentVersion\App Paths\foo.exe"

WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "$regkey32" "" "$INSTDIR\foo.EXE"
WriteRegStr HKLM "$unkey32" "DisplayName" "$(^Name)"
WriteRegStr HKLM "$unkey32" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr HKLM "$unkey32" "DisplayIcon" "$INSTDIR\foo.EXE"
WriteRegStr HKLM "$unkey32" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "$unkey32" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr HKLM "$unkey32" "Publisher" "${PRODUCT_PUBLISHER}"
${EndIf}

So...

XP x32 - OK
XP x64 -No good
Vista x32 - OK
Vista x64 - No good
Windows 7 x32 - OK
Windows 7 x64 - OK
Windows 8 x64 - not tested but it worked before

Meaning that it isnt going into the registry at all


Don't use Wow6432Node, only SetRegView?


So, just use setregview and for the HKLM location use Software\Microsoft\Windows\CurrentVersion\App Paths for both?
it should use node automatically by redirection? why would it work for the newer stuff but not the older stuff?


SetRegView 64 turns off registry redirection so you are writing to the real 64 bit registry, normally a 32bit application on 64bit Windows writes to a 32bit part of the registry (but some keys are redirected and/or reflected depending on the Windows version)


Weird.

It is something def with vista then

somehting like this


${If} ${RunningX64}
SetRegView 64
${Else}
SetRegView 32
${EndIf}
MessageBox MB_OK "inst: $INSTDIR" # debugging jw


in win 7 x64 instdir is populated. on vista x64 it is not.
so it is writing to the registry properly now, it is detecting what i need to detect, but the part where it tries to find the unist in instdir to rm the previous version if detected, is detecting it, but isnt running the uninst.exe bc instdir seems to be blank. and not sure why.

You can use Process Monitor to view registry operations...


not too sure why but this now works, but uninst.exe is now orphaned after it uninstalls previous prior to install new version.


; Run the uninstaller
uninst:
${If} ${RunningX64}
SetRegView 64
ReadRegStr $R0 HKLM \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"\
"UninstallString"

${GetParent} $R0 $R1
strcpy $INSTDIR $R1
ExecWait '"$INSTDIR\uninst.exe" _?=$INSTDIR' $R1

StrCmp $R1 0 no_remove_uninstaller ; Success? If so we are done...
Abort ; Uninstaller was canceled or failed, we cannot continue
${Else}

Running with _?= does not delete the uninstaller, you must delete manually...


Well, I have delete the uinst.exe in the uninst section, it orphans it when it is run beforehand, during the installer, it checks for previous version, it uninstalls it, then continues. so if i run uininstall manuall there is no orphan. my understanding the _? just doesnt copy the uninstall. this is where i am confused. as the execwait $R1 bit i thought fixed it as my previous version of software i had no orphans in that circumstance and now it is back again for some strange reason.


Normally the uninstaller will copy itself to %temp% and run that copy with _?=, it basically does Exec (not wait) on that copy. The reason is so that the uninstaller in $instdir can be deleted. _?= sets $instdir to that path so code in the uninstaller like RMDir $instdir will work correctly.

To fully uninstall and use ExecWait you also need Delete "$INSTDIR\uninst.exe"


problem is, i did that, but it will then delete the uninstaller after installing and when it needs to be uninstalled later, it cant because the file has been deleted. where do i put the delete uninst so it doesnt do so? perhaps my logic was poor.


Right after execwait...



uninst:
; Exit codes are documented in Appendix D in the help file.
ExecWait '"$INSTDIR\uninst.exe" _?=$INSTDIR' $R2
; Delete "$INSTDIR\uninst.exe"
StrCmp $R2 0 no_remove_uninstaller ; Success? If so we are done...
Abort ; Uninstaller was canceled or failed, we cannot continue


I did do that, and I had to comment it out because it deleted the uinstaller after uninstalling it if it was previously installed so the new uninstaller i guess never generates it?

WriteUninstaller "$INSTDIR\uninst.exe" will create the new uninstaller and this of course has to happen later in your script...


WriteUninstaller is there much further down in the code.
As mentioned, maybe I wasn't being clear enough and for that I apologise.

1. If the software detects a previous version, it asks do you want to uninstall previous version first. It is mandatory. If they say yes, it will uninstall. When that uninstall is done, it keeps itself behind. If they say no, obviously it aborts.

2. After the software is installed and the user runs the uininstaller, the same one thats in the $instdir\${product_name} folder nothing is orphaned.

3. If I actually run the orphaned file that got left behind from step one, it will erase itself.

4. If I add the delete instdir right after execute, it will erase the uinstaller and not show up again, though I have not recently tested this, this is what occurred last time when I first brought up this thread. since I never changed nsis I don't see why anything is different this time. I should however try again anyway, but still doesn't explain why it gets orphaned as the uinstaller does in fact erase itself under normal circumstances, it just doesn't do it when it is doing it from the execwait rather than be initiated by control panel/explorer.


I don't know how many times I can try to explain this but I'll try one more time.

Running the uninstaller with _?= is never going to delete the uninstaller .exe.

When running the uninstaller without _?= what happens is:

  1. uninst.exe is copied to %temp%\~un.exe
  2. uninst.exe does Exec %temp%\~un.exe _?=instdir and quits
  3. %temp%\~un.exe performs uninstall including deleting uninst.exe in $instdir

Anders,

Thank you for this explanation now I am more understanding what is going on. Thank you for your patience and apologise for not being clear for understanding what was going on.