Skip to content
⌘ NSIS Forum Archive

%ProgramFiles% shortcut icon location on 64-bit Windows

10 posts

unisev#edited

%ProgramFiles% shortcut icon location on 64-bit Windows

Hello,

Here is a part of my code :

ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Patio" Path

CreateShortCut "$DESKTOP\SwitchPatio.lnk" "$0switch.cmd" "" "$0Switch\switch.ico"
Doing that, my customized icon is well used, but with this path :

%programfile%\Patio\Switch\switch.ico

How can I be sure that the path will be the true absolute path without %ProgramFile% ??

UniseV
unisev#
Thank you Anders,

This did not solve my problem, since my var ($0) did not contain the environment variables (%ProgramFiles%)... even if my finally created shortcut, contain this environment variables 🤪.

This is not a big problem since the icon is well shown, the problem just appear when you try to manually change the icon shortcut...



Not a big problem
Anders#
Sorry, I somehow thought your App Paths entry had %ProgramFiles% in it.

If Windows adds it and then later has a problem with it then I'd call that a bug in Windows.

Is this a 64-bit system by any chance? I have seen problems with %programfiles% there in the past...
unisev#
This is Win10 64-bit, the very last version (1511)

I don't know how 'NSIS-CreateShortCut' do the trick but when I do it in VBS the bug do not occurs, so is this really a Windows bug ?

There is in fact 2 bugs :
  • %ProgramFiles% is not expected, the absolute path is enough
  • It takes %ProgramFiles% instead of %ProgramFiles(x86)%
Anders#
Just to be clear, NSIS does not change c:\program files\... to %ProgramFiles%\..., it does some minor checking to make sure the path is valid and then just calls IShellLink::SetIconLocation. VBS has the exact same bug when you execute the script with the 32-bit version of wscript.exe.

This bug exists in the 32-bit IShellLink implementation when running under WOW64. Windows tries to be clever and sees that the path is inside %ProgramFiles% and therefore in addition to storing the path we used in SetIconLocation it also adds a EXP_SZ_ICON_SIG block to the .lnk. This is not a problem when running on 32-bit Windows but it becomes a problem on 64-bit Windows if you view the shortcut properties (created by a 32-bit program) in a 64-bit program! When a 32-bit IShellLink::SetIconLocation references something in the 64-bit Program Files folder then %ProgramFiles% does not match and it falls back to storing %SystemDrive%\Program Files\ which is actually better and works everywhere.




RequestExecutionLevel admin
!include x64.nsh
Section
!if "${NSIS_PTR_SIZE}" > 4
!error "Must be compiled as 32-bit!"
!endif
${IfNot} ${RunningX64}
MessageBox mb_IconStop "Must be 32-bit app on 64-bit Windows!"
Quit
${EndIf}
DetailPrint ProgramFiles32=$ProgramFiles32
DetailPrint ProgramFiles64=$ProgramFiles64
FileOpen $0 "$ProgramFiles32\lnktest32.cmd" a
FileClose $0
File "/oname=$ProgramFiles32\lnktest32.ico" "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
FileOpen $0 "$ProgramFiles64\lnktest64.cmd" a
FileClose $0
File "/oname=$ProgramFiles64\lnktest64.ico" "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
/* If you uncomment these then the icon in lnktestvbs32.lnk will point to 
** "%SystemDrive%\Program Files (x86)\lnktest32.ico" and 
** not (the broken in 64-bit processes) "%ProgramFiles%\lnktest32.ico"
System::Call 'KERNEL32::SetEnvironmentVariable(t "ProgramFiles", t "c:\*invalid*\?path?")i'
System::Call 'KERNEL32::SetEnvironmentVariable(t "ProgramFiles(x86)", t "c:\*invalid*\?path?")i'
System::Call 'KERNEL32::SetEnvironmentVariable(t "ProgramW6432", t "c:\*invalid*\?path?")i'
#*/
InitPluginsDir
FileOpen $0 "$Pluginsdir\lnktest.vbs" w
FileWrite $0 'Set lnk = WScript.CreateObject("WScript.Shell").CreateShortcut("$Temp\lnktestvbs32.lnk")$\r$\n'
FileWrite $0 'lnk.TargetPath = "$ProgramFiles32\lnktest32.cmd"$\r$\n'
FileWrite $0 'lnk.IconLocation = "$ProgramFiles32\lnktest32.ico, 0"$\r$\n'
FileWrite $0 'lnk.Save$\r$\n'
FileWrite $0 'Set lnk = WScript.CreateObject("WScript.Shell").CreateShortcut("$Temp\lnktestvbs64.lnk")$\r$\n'
FileWrite $0 'lnk.TargetPath = "$ProgramFiles64\lnktest64.cmd"$\r$\n'
FileWrite $0 'lnk.IconLocation = "$ProgramFiles64\lnktest64.ico, 0"$\r$\n'
FileWrite $0 'lnk.Save$\r$\n'
FileClose $0
ExecWait '"$SysDir\wscript.exe" "$Pluginsdir\lnktest.vbs"'
MessageBox mb_ok "Done?"
Delete "$ProgramFiles32\lnktest32.cmd"
Delete "$ProgramFiles32\lnktest32.ico"
Delete "$ProgramFiles64\lnktest64.cmd"
Delete "$ProgramFiles64\lnktest64.ico"
Quit
SectionEnd 
Don't ask me why 64-bit Explorer is able to display the icon...
unisev#
Thank you for this complete and precise answer !

So juste to be sure, this can only happen on 64bit systems when .LNK is created by a 32bit process, am I right ?

Is there a simple way to evolve my package to a 64bit one ? is there a real good reason to do this ?
Anders#
It seems like this only happens when you try to change the icon so not a big deal IMHO. If you want to force the icon to never use a environment path when resolving the icon you can try this (NSIS 3+ probably):

!include LogicLib.nsh
!include Win\COM.nsh
!define /ifndef STGM_READWRITE 2
Function Shortcut_RemoveExpandIconPath
Exch $9
Push $0
Push $1
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IPersistFile} r0 ""
${If} $0 <> 0
    ${IPersistFile::Load} $0 '("$9",${STGM_READWRITE})i.r1'
    ${If} $1 = 0
        ${IUnknown::QueryInterface} $0 '("${IID_IShellLinkDataList}",.r1)'
        ${If} $1 <> 0
            ${IShellLinkDataList::RemoveDataBlock} $1 '(${EXP_SZ_ICON_SIG}).r9'
            ${IUnknown::Release} $1 ""
            ${If} $9 = 0
                ${IPersistFile::Save} $0 '(p0,1)'
            ${EndIf}
        ${EndIf}
    ${EndIf}
    ${IUnknown::Release} $0 ""
${EndIf}
Pop $1
Pop $0
Pop $9
FunctionEnd
Section 
Push $Temp\lnktestvbs32.lnk
Call Shortcut_RemoveExpandIconPath
SectionEnd 
Originally Posted by unisev View Post

So juste to be sure, this can only happen on 64bit systems when .LNK is created by a 32bit process, am I right ?
Pretty much. There are probably also some corner cases where it can happen if you change %ProgramFiles% etc.
unisev#
Originally Posted by Anders View Post
It seems like this only happens when you try to change the icon so not a big deal IMHO.
You're totally right, thank you very much.
Anders#
Originally Posted by joewinograd
Hi Folks,
I haven't heard back from Anders on this. Can anyone else help me to get this fixed? Thanks very much!
There is no one true fix.

If you want to try the SetEnvironmentVariable hack then just add those three System::Call lines to the top of your first section. However, this might also change the behavior of any child processed you start from your installer.

If you want to try the Shortcut_RemoveExpandIconPath code then just copy the function and add the Push and Call instructions after your call to CreateShortcut (Change the path used in the Push of course)