Archive: Vista: Exec does not work as in XP


Vista: Exec does not work as in XP
This is no Vista privilege / UAC problem (see other thread if you have your software run in "wrong" virtualisation/elevation mode).

I have some mode where the installer copies itself to TMP and runs from there. This works in XP but not in Vista (tested with UAC on).

The problem is that the temporary file's name does not end with ".exe". So

Exec '"C:\temp\mytmpfile.tmp" /OPTIONS'

does not execute. You will get a warning saying ".tmp" is not associated.

With XP this works without problems.

I know the CreateProcess API and I guess NSIS' Exec just lets the application parameter empty and supplies the exec-string above as "parameter" which generally works fine.
Furthermore I guess it would be ok to pass the executable file to the first parameter (application). Perhaps XP is here somewhat fuzzy to always assume the first parameter is an executable. So if I'm right the change in Vista seems to make it cleaner.

I personally will check these two options for my installer:
- get a temp name which ends in ".exe"
- use own CreateProcess call using the tmp name as application parameter.


CreateProcess("c:\temp\mytmpfile.tmp", "/OPTIONS", ...) does not work.

This leads to some feature request in NSIS: Please add an option for setting the extension for GetTempFilename.


solution:


; ################################################################
; get temporary filename with a specific extension
; Usage: !insertmacro GetTempFile "exe"
; Pop $0
!macro GetTempFile EXTENSION
; USE ON YOUR OWN RISK! Please report bugs here: http://stefan.bertels.org/
!define Index_GetTempFile 'GetTempFile_${__LINE__}'
Push $0 ; result (bla.tmp_0.EXTENSION)
Push $1 ; filename bla.tmp (GetTempFilename result)
Push $2 ; extension
Push $3 ; counter

StrCpy $2 "${EXTENSION}"
StrCpy $3 0

${Index_GetTempFile}-loop:
GetTempFilename $1
StrCpy $0 "$1_$3.$2"
IfFileExists $0 0 ${Index_GetTempFile}-finished
Delete $1
IntOp $3 $3 + 1
Goto ${Index_GetTempFile}-loop

${Index_GetTempFile}-finished:
Pop $3
Pop $2
Pop $1
Exch $0
!undef Index_GetTempFile
!macroend

Can't you convert that to a Function instead?

Stu


I personally try to make helpers as macro. This makes including it very easy (for installers and uninstallers). I personnally do not like the combined approach (like FileFunc and LogicLib).

But of course you're free to do this. ;-)


Do you even need an internal counter? GetTempFileName already has one.

GetTempFileName $R0
Rename $R0 $R0.ext
StrCpy $R0 $R0.ext

This works under any number of tries (the file name is unique.)

Stu


I think the macro counter is correct because you cannot be sure that NEWFILE.ext is a new file, too.


I did 10000 tests and the file names were all unique (10000 ns*.tmp files were present in the temp folder).
Not sure exactly how GetTempFileName works internally, so maybe kichik can shed some light on this.

Stu


Of course the file names will be unique, that's what GetTempFileName does.

We do not have to know how GetTempFileName really works (you can find details in MSDN) but we should just rely on the fact that GetTempFileName creates a NEW file (not existing before). Microsoft may change the way the function works at random.

If GetTempFileName would never try to create the same filename again we could use its internal counter. If it just checks for existence we could not remove a tmp file before getting what we want (filling up the tmp dir). Of course in practise this will work anyway. But then we just could use "mypersonaltmp.exe" or something like that (fixed string).

Anyway we have to run a loop and check for existence of NEWFILE.ext because GetTempFileName will only guarantee that NEWFILE is a new file (without custom extension, e.g. "exe").


This was the code I used:


Loop:
IntOp $R1 $R1 + 1
GetTempFileName $R0
Rename $R0 $R0.ext
StrCpy $R0 $R0.ext
DetailPrint $R0
StrCmp $R1 10000 0 Loop

It still created 10000 unique files with the .ext file extension.

Either way, here is the code I made before I did the tests.

!macro GetTempFileNameExt Ext Var
Push `${Ext}`
Call GetTempFileNameExt
Pop ${Var}
!macroend

Function GetTempFileNameExt
Exch $R0
Push $R1
Push $R2

StrCpy $R2 0
GetTempFileName $R1
IntOp $R2 $R2 + 1
IfFileExists $R1$R2.$R0 -2

SetDetailsPrint none
Rename $R1 $R1$R2.$R0
SetDetailsPrint both
StrCpy $R0 $R1$R2.$R0

Pop $R2
Pop $R1
Exch $R0
FunctionEnd


!insertmacro GetTempFileNameExt exe $R0

Stu

Using your short code (gettempfilename, rename, strcpy) will cause problems if the file NEWFILE.ext already exists. You cannot be sure this is the fact without testing it yourself. This is what GetTempFileName is for...

Your GetTempFileNameExt function/macro is more suitable but has the problem of not deleting files in the loop. And there is a theoretical chance of a race condition.

My code had a bug, too: The file is not renamed so the filename is not "locked". Here's the fixed code which should work perfectly:


; ################################################################
; get temporary filename with a specific extension
; Usage: !insertmacro GetTempFile "exe"
; Pop $0
!macro GetTempFile EXTENSION
; USE ON YOUR OWN RISK! Please report bugs here: http://stefan.bertels.org/
!define Index_GetTempFile 'GetTempFile_${__LINE__}'
Push $0 ; result (bla.tmp_0.EXTENSION)
Push $1 ; filename bla.tmp (GetTempFilename result)
Push $2 ; extension
Push $3 ; counter

StrCpy $2 "${EXTENSION}"
StrCpy $3 0

${Index_GetTempFile}-loop:
GetTempFilename $1
StrCpy $0 "$1_$3.$2"
ClearErrors
Rename $1 $0
IfErrors 0 ${Index_GetTempFile}-finished
Delete $1
IntOp $3 $3 + 1
Goto ${Index_GetTempFile}-loop

${Index_GetTempFile}-finished:
Pop $3
Pop $2
Pop $1
Exch $0
!undef Index_GetTempFile
!macroend

The following works perfectly fine for me.

GetTempFileName $0
CopyFiles $SYSDIR\cmd.exe $0
Exec '"$0" /K echo hello'
Seems like the real problem here is UAC. CreateProcess fails with error 740 (ERROR_ELEVATION_REQUIRED) when trying to execute a program that requires elevation from a non-elevated context. To pop-up the elevation dialog, you need to use ExecShell, but then .tmp isn't associated, as the error says (though I have no idea how it automatically got the idea to ExecShell it). Is your main installer running elevated or not?

Another possible problem is an anti-virus. I have heard about recent versions that deny running applications from the temporary directory. It's possible some took it a step forward (or actually backward in a sane view) and denied only non-exe running from the temporary directory.

@Kichik: I tested this with Vista 32bit, UAC disabled. The CopyFiles command does not work. The TMP file has still zero size. I tried it with calc.exe instead - same problem. It works with XP SP2.

I then tried my copyfile macro:


; ################################################################
; copy source file to target file (using FileWrite)
!macro CopyFile SOURCEFILE TARGETFILE
; USE ON YOUR OWN RISK! Please report bugs here: http://stefan.bertels.org/
Push "${SOURCEFILE}"
Push "${TARGETFILE}"
Push $R1
Exch
Pop $R1 ; $R1 contains TARGETFILE
Push $R0
Exch 2
Pop $R0 ; $R0 contains SOURCEFILE
; stack order: TOP => old $R1 => old $R0
Push $R2 ; source hdl
Push $R3 ; target hdl
Push $R4 ; buffer address / tmp3
Push $R5 ; buffer len / tmp2
Push $R6 ; tmp1

IfFileExists $R1 0 +2
Delete $R1

FileOpen $R2 $R0 r
FileSeek $R2 0 END $R5
System::Alloc /NOUNLOAD $R5
Pop $R4
FileSeek $R2 0 SET
System::Call /NOUNLOAD 'kernel32::ReadFile(i R2, i R4, i R5, t.,)'

FileOpen $R3 $R1 w
System::Call /NOUNLOAD 'kernel32::WriteFile(i R3, i R4, i R5, t.,)'
System::Free /NOUNLOAD $R4

System::Call /NOUNLOAD 'kernel32::GetFileTime(i, *l, *l, *l) i (R2, .R4, .R5, .R6)'
System::Call 'kernel32::SetFileTime(i, *l, *l, *l) i (R3, R4, R5, R6)'

FileClose $R2
FileClose $R3

Pop $R5
Pop $R4
Pop $R3
Pop $R2
Pop $R1
Pop $R0
!macroend


This works with XP SP2 and Vista (UAC still disabled). So the file extension is no problem here. I will do UAC tests later (and post it here).

UAC test: Vista asked for Elevation (I did not use RequestExecutionLevel in nsi in this example). After that the cmd prompt is shown, too. So no problem here (did use my copyfile macro, did not try CopyFiles).

I don't know why Vista had some problem. I definitely used Exec (not ExecShell or ExecWait) and the file extension definitely made a difference. I cannot reproduce this quickly now :-(

The test machine has McAfee VirusScan Enterprise 8.5 (enabled), but it is not likely that this caused the problem.


So the original problem was with CopyFiles as well?


McAfee is one of those Anti-Viruses that had this option to annoy every existing installer.

http://news.jrsoftware.org/read/arti...nnosetup#65596


@kichik: No, I didn't / don't use CopyFiles at all (discussed this quite some time ago in another thread...)

McAfee has some of those options, I know. But I cannot see a rule which allows executing ".exe" files but denies to run ".tmp" named executables. Such a rule would be braindead (according Murphy's law this rule will exist...)

I just checked the McAfee 8.5 on the test machine: the access control is/was fully deactivated.

The question is: Why did Vista show the "don't know how tmp files are to open" error message when calling just:

Exec '"c:\temp\bla123.tmp" /TMPMOVED'


Very strange.

I have no idea how this can happen. CreateProcess shouldn't know anything about associations. That's for the shell only.


I agree. I personally will just use my GetTempFile macro (see above) to get a temp file with "working" extension.

Here are all necessary details of my setup where I use this (if someone has similar problems or wants to do more debugging...)

- RequestExecutionLevel user
- The installer checks for its name or some cmd line parameter. If this matches some criteria the installer will copy itself to a TMP filename, call it via Exec (adding all original cmd line parameter plus some extra parameter "/TMPMOVED"), make the TMP filename delete on next reboot, and quit immediately.

This way the original installer exe can be deleted or overwritten by the installation process (which now runs from TMP).

In Vista with UAC the outer (and inner) installer will not run with admin privileges (elevation is done later). If the TMP filename ends with ".tmp" (because generated with GetTempFileName) this will cause a error message box of Vista which says that the file ending is not associated with some program. So I use a tmp filename ending in ".exe" and all works fine.