Archive: problem with icons, CreateShortcut on 64-bit Windows and "C:\Program Files (x86)"


problem with icons, CreateShortcut on 64-bit Windows and "C:\Program Files (x86)"
  I've creating an NSIS package and noticed that when a program is installed to the legacy 32-bit folder on 64-bit Windows 7 Ultimate SP1, that "C:\Program Files (x86)" is being translated either by NSIS or Windows to the environmental variable "%ProgramFiles%" which resolves to the 64-bit version of Program Files. Where I'm running into a problem is with CreateShortcut and the custom icon setting. Here's my code for starters:

CreateShortcut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\online.exe" "" "$INSTDIR\online.exe" "" "" "" "Launches PSOBB"
CreateShortcut "$SMPROGRAMS\${APP_NAME}\Visit schtserv.com.lnk" "http://www.schtserv.com/" "" "$INSTDIR\online.exe" "" "" "" "Visit the SCHTHACK website"
CreateShortcut "$SMPROGRAMS\${APP_NAME}\Register a game account.lnk" "http://www.schtserv.com/bbregister.php" "" "$INSTDIR\online.exe" "" "" "" "You need an account to play PSOBB"
CreateShortcut "$SMPROGRAMS\${APP_NAME}\Community forums and support.lnk" "http://schtserv.com/forum/" "" "$INSTDIR\online.exe" "" "" "" "Join the community"
CreateShortcut "$SMPROGRAMS\${APP_NAME}\Readme.txt.lnk" "$INSTDIR\readme.txt" "" "" 0 "" "" "o_o"
Now if I install the program anywhere inside "C:\Program Files (x86)" and inspect the shortcuts above (right-click->Properties), and choose "Change Icon" it will prompt an error that says "Windows cannot find the file %ProgramFiles%\<installfolder>\online.exe." I did a test and found that if I placed a copy of online.exe in the 64-bit Program Files folder that the shortcut icon was found by Windows Explorer, and I did another test and found that the field was properly filled in if any folder other than C:\Program Files (x86) (such as C:\newtest) was used. What I think is NSIS has some type of code that is converting Program Files path by default to the environmental variable %ProgramFiles%. This might work on 32-bit Windows, but 64-bit Windows uses different folders for 32-bit and 64-bit and this causes the icon path at least for CreateShortcut to be resolved improperly in Windows Explorer (64-bit version).

What I also found is that in some cases the icon on the shortcuts will default back to the missing shortcut icon and you have to reset the icon manually on the shortcut for it to work again, which is how I discovered this problem. It does this every time after a reboot on the first shortcut in the code, the other ones are a bit different (URLs/Internet shortcuts) that don't seem to reset as often (so far). I'm still trying to fix this problem but thought I'd report about my findings, it's obviously a bug that needs fixing somewhere.

NSIS 2.46...

:eek: Removed public display of stupid...


These are shortcuts being installed to the Start Menu Programs folder, ala internal NSIS Constant $SMPROGRAMS. I also found that it makes no difference what InstallDir is, the user can change directories to C:\Program Files (x86) during the installation, or manually select C:\Program Files (x86) and it will convert the shortcut icon path to %ProgramFiles%. As I said I believe NSIS detects "C:\Program Files (x86)" in $INSTDIR and converts it to %ProgramFiles%... unless it is some internal calls of Windows doing it (which NSIS executes), which is definitely possible.


Edit, scratch part of what I said before.

This was my InstallDir:

InstallDir "$PROGRAMFILES32\${APP_NAME}"

I changed it to

InstallDir "C:\PSOBB-installer\${APP_NAME}"

Rebuild...


Actually, I'll just post my entire script for you to test with some test files. I think the behavior I described with %ProgramFiles% variable only occurs if $PROGRAMFILES is initially used in InstallDir, this doesn't happen after changing InstallDir like I did above, and then manually select "C:\Program Files (x86)" as the installation location.

Here's my script & test files to make it work if you wanna take a look and try it: http://strags.com/d2/nsis.zip


Kodama,

!define APP_NAME "My App"

>OutFile lnkTest.exe

InstallDir "$PROGRAMFILES\${APP_NAME}"

>Section
SetOutPath $INSTDIR
File/oname=online.exe "C:\WINDOWS\NOTEPAD.EXE"

CreateDirectory "$SMPROGRAMS\${APP_NAME}"
CreateShortcut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\online.exe" "" "$INSTDIR\online.exe" "" "" "" "Launches PSOBB"
>SectionEnd
>
I ran this on my Windows XP x64 system and it works fine.

Did you check the Change Icon function to determine if the path was set correctly? Didn't work on Win7Ult x64.. BUT the icon does show up properly, at least right after install. Problem I have run into, is that it might default back to the broken shortcut icon after awhile, which shows up on the next screen.

http://strags.com/i/myapp.png
http://strags.com/i/myapp.png

http://strags.com/i/myapp2.png
http://strags.com/i/myapp2.png


If I go and put a copy of online.exe (for your example) in C:\Program Files\My App, it will find it without changing the shortcut. It won't work ever without being manually updated if it's in C:\Program Files (x86) under these exact circumstances. I used the same code as you...


AH! not I see the problem... I'm going to perform some tests to see what can be done about this.


Let me know what you find...


NSIS does not touch your strings, blame the windows shortcut API. At least for you main apps shortcut, try not setting the icon directly and just use "" as the icon path

Edit: Another option would be to remove the SLDF_HAS_EXP_ICON_SZ flag and datablock after the shortcut has been created:


CreateShortcut "$temp\test.lnk" "c:\windows\system32\calc.exe" "" "c:\windows\explorer.exe" 1


System
::Call 'OLE32::CoCreateInstance(g "{00021401-0000-0000-c000-000000000046}",i 0,i 1,g "{000214ee-0000-0000-c000-000000000046}",*i.r1)i'
>${If} $1 <> 0
System::Call '$1->0(g "{0000010b-0000-0000-C000-000000000046}",*i.r2)'
${If} $2 <> 0
System::Call '$2->5(w "$temp\test.lnk",i 2)i.r0'
${If} $0 = 0
System::Call '$1->0(g "{45e2b4ae-b1c3-11d0-b92f-00a0c90312e1}",*i.r3)i.r0'
${If} $3 <> 0
System::Call '$3->5(i 0xA0000007)i.r0'
System::Call '$3->6(*i.r4)i.r0'
${If} $0 = 0
IntOp$4 $4 & 0xffffBFFF
System::Call '$3->7(ir4)i.r0'
${If} $0 = 0
System::Call '$2->6(i0,i0)'
${EndIf}
${EndIf}
System::Call $3->2()
${EndIf}
${EndIf}
System::Call $2->2()
${EndIf}
System::Call $1->2()
${EndIf}
After I do this, (on XP) the change shortcut icon dialog displays c:\windows\explorer.exe and not %SystemRoot%\explorer.exe

As usual... Awesome work Anders :up:
I Wrapped your code up into a function/macro for easy implementation so I can add it to my own packages. I have been packaging for 64bit XP for some time but never noticed the issue. Worse yet no one has complained yet. :eek:

/******************************************************************************

WORKAROUND - FixShortcut
This snippet was developed to address an issue with Windows
x64 incorectly redirecting the shortcuts icon from $PROGRAMFILES32
to $PROGRAMFILES64.

See Forum post: http://forums.winamp.com/newreply.php?do=postreply&t=327806

Example:
CreateShortcut "$SMPROGRAMS\My App\My App.lnk" "$INSTDIR\My App.exe" "" "$INSTDIR\My App.exe"
${lnkX64IconFix} "$SMPROGRAMS\My App\My App.lnk"

Original Code by Anders - http://forums.winamp.com/member.php?u=70852
******************************************************************************/
>!ifndef ___lnkX64IconFix___
!verbose push
!verbose 0

!include "LogicLib.nsh"
!include "x64.nsh"

!define ___lnkX64IconFix___
!define lnkX64IconFix `!insertmacro _lnkX64IconFix`
!macro _lnkX64IconFix _lnkPath
!verbose push
!verbose 0
${If} ${RunningX64}
DetailPrint "WORKAROUND: 64bit OS Detected, Attempting to apply lnkX64IconFix"
Push "${_lnkPath}"
Call lnkX64IconFix
${EndIf}
!verbose pop
!macroend

Function lnkX64IconFix ; _lnkPath
Exch$5
Push$0
Push$1
Push$2
Push$3
Push$4
System
::Call 'OLE32::CoCreateInstance(g "{00021401-0000-0000-c000-000000000046}",i 0,i 1,g "{000214ee-0000-0000-c000-000000000046}",*i.r1)i'
${If} $1 <> 0
System::Call '$1->0(g "{0000010b-0000-0000-C000-000000000046}",*i.r2)'
${If} $2 <> 0
System::Call '$2->5(w r5,i 2)i.r0'
${If} $0 = 0
System::Call '$1->0(g "{45e2b4ae-b1c3-11d0-b92f-00a0c90312e1}",*i.r3)i.r0'
${If} $3 <> 0
System::Call '$3->5(i 0xA0000007)i.r0'
System::Call '$3->6(*i.r4)i.r0'
${If} $0 = 0
IntOp$4 $4 & 0xffffBFFF
System::Call '$3->7(ir4)i.r0'
${If} $0 = 0
System::Call '$2->6(i0,i0)'
DetailPrint "WORKAROUND: lnkX64IconFix Applied successfully"
${EndIf}
${EndIf}
System::Call $3->2()
${EndIf}
${EndIf}
System::Call $2->2()
${EndIf}
System::Call $1->2()
${EndIf}
Pop $4
Pop$3
Pop$2
Pop$1
Pop$0
FunctionEnd
!verbose pop
>!endif

FYI: Wiki Article Created


That URL in the comment is broken. You should probably point to: http://forums.winamp.com/showthread.php?t=327806


Originally posted by Anders
NSIS does not touch your strings, blame the windows shortcut API. At least for you main apps shortcut, try not setting the icon directly and just use "" as the icon path

Edit: Another option would be to remove the SLDF_HAS_EXP_ICON_SZ flag and datablock after the shortcut has been created:

After I do this, (on XP) the change shortcut icon dialog displays c:\windows\explorer.exe and not %SystemRoot%\explorer.exe
Neither of these are suitable alternatives, though. The shortcuts I want to specify the icon on are Internet links, the default icon is just ugly and doesn't go with the program. Secondly, NSIS must be involved in this process somehow, because if InstallDir is set to "C:\${APP_NAME}" by default rather than "$PROGRAMFILES32\${APP_NAME}" - one can manually select the "C:\Program Files (x86)" folder during installation and not have this problem (shortcut icon path is correct, icons never vanish). I think the problem has to do with how NSIS is resolving $PROGRAMFILES or handling these Constants and the environmental variables internally (might be specific to InstallDir directive or when or how these variables are used)... Give it another look.

http://nsis.sourceforge.net/CreateIn...o_%26_function

Stu


Internet shortcuts are even easier to tweak as they are just ini files.

[InternetShortcut]
URL=http://download.microsoft.com/download/8/8/8/888f34b7-4f54-4f06-8dac-fa29b19f33dd/msxml3.msi
IDList=
IconFile=C:\WINDOWS\system32\accwiz.exe
HotKey=0
IconIndex=5
[{000214A0-0000-0000-C000-000000000046}]
Prop3=19,2
Do somthing like this..

WriteINIStr "$SMPROGRAMS\${APP_NAME}\My Web Site.url" "InternetShortcut" "IconFile" "$INSTDIR\online.exe"

>WriteINIStr "$SMPROGRAMS\${APP_NAME}\My Web Site.url" "InternetShortcut" "IconIndex" 5
>
...and it "Should" fix it.


EDIT: Or just read the Wiki article Stu Posted.. :D

Originally posted by o___O
I think the problem has to do with how NSIS is resolving $PROGRAMFILES or handling these Constants and the environmental variables internally (might be specific to InstallDir directive or when or how these variables are used)... Give it another look.
InstallDir does not do anything special, $PROGRAMFILES is resolved using a documented shell function and nsis itself does not use environment variables IIRC.

I found more information out about this issue. From the manual:

4.8.1.21 InstallDir
definstdir
Sets the default installation directory. See the variables section for variables that can be used to make this string (especially $PROGRAMFILES). Note that the part of this string following the last \ will be used if the user selects 'browse', and may be appended back on to the string at install time (to disable this, end the directory with a \ (which will require the entire parameter to be enclosed with quotes). If this doesn't make any sense, play around with the browse button a bit.
So the problem is with this behavior in NSIS when InstallDir doesn't end with a \. Disable this feature and the icons and shortcuts are fine.

So..

InstallDir "$PROGRAMFILES32\SCHTHACK PSOBB" has the problem.
InstallDir "$PROGRAMFILES32\SCHTHACK PSOBB\" (disables behavior) doesn't have the problem.

It seems to be something going wrong in this feature when it enumerates and appends "SCHTHACK PSOBB" to the directory selection. For example, a user will choose "C:\Program Files (x86)" as the directory rather than a sub directory, and NSIS will fill in the sub directory to match the last string of InstallDir after the last \, in this case "SCHTHACK PSOBB," making it auto to C:\Program Files (x86)\SCHTHACK PSOBB\. For some reason this breaks CreateShortcut icon association and perhaps other functions in NSIS. What it looks like is Windows is looking in the 64-bit Program Files directory for the icon when this function is enabled, even though the program is really in the 32-bit Program Files folder, which I think is the result of this features functionality mixing up the 64-bit and 32-bit Program Files folders or how it resolves them.

Originally posted by o___O
making it auto to C:\Program Files (x86)\SCHTHACK PSOBB\.
Are you sure about this? $instdir will normally strip off the \ at the end.

Try:

strcpy $instdir "c:\temp\"

MessageBox mb_ok $instdir
>

I'm going to have to confirm with Anders.

I modified the test script and the issue remains.
Can you provide a script that shows the working vs non?


"My App"

>OutFile lnkTest.exe
ShowInstDetails show

InstallDir "$PROGRAMFILES32\${APP_NAME}\"

Page directory
Page instfiles

Section
SetOutPath "
$INSTDIR"
File /oname=online.exe "C:WINDOWSNOTEPAD.EXE"

CreateDirectory "$SMPROGRAMS${APP_NAME}"
CreateShortcut "$SMPROGRAMS${APP_NAME}${APP_NAME}.lnk" "$INSTDIRonline.exe" "" "$INSTDIRonline.exe" "" "" "" "Launches PSOBB"
SectionEnd
>