Click here to Skip to main content
Email Password   helpLost your password?

Introduction

Many installation packages offer an option for the user to launch the application at the end of the installation:

Screenshot - LaunchApp.jpg

It works, except for one small problem: if installed on a Vista computer, the application started in such a way gets executed at the elevated level, with the full administrator rights. This article discusses why this is bad, and offers a reusable DLL that can be included in the existing installation packages to run the application non-elevated at the end of the installation. A sample setup script for the popular InnoSetup software is provided as well. The included source code also contains several other functions that can be of use when programming for Windows Vista.

Background

You've followed the Microsoft guidelines and carefully updated your application to run well in the context of a restricted user (non-administrator). You have added the asInvoker value to the application manifest, to make sure that when the user launches your application, it is started without the administrative privileges. You've tested it, it works well, and you think your new Vista-compatible version is ready for the release. So you create the setup package and test it, and now you are up for a nasty surprise: if you select the option to launch the application at the end of the installation, the application gets started as administrator, with full administrative privileges.

Why running your application elevated is bad? Because things may break unexpectedly. For example:

Why does your application run elevated when started automatically by the installer? Because the installation process runs with the full administrative rights, and when it creates child processes, such as the one for your application, such processes execute at the same elevated level, just like the setup program itself.

How to solve this problem?

If you've searched the Microsoft SDK documentation for a solution, you've undoubtedly been up for another surprise, even nastier than the first one: Microsoft has not provided for a way to start a non-elevated process from an elevated one. That's right, there is no API call, no special value to specify in the manifest, not even a flag for the ShellExecute() API to allow for that. If you've found yourself stuck in this situation, read on, this article is for you.

Let's consider the options that we have:

  1. We could create a separate helper executable that would help our main application launch a non-elevated task, when necessary. That is, it could work as follows:

  2. A simpler approach could be by making use of the capabilities of the built-in Task Scheduler of Windows Vista: our elevated process could register a task with Task Scheduler to be started at the non-elevated level immediately upon its registration. (I've described this method in detail in my previous article Vista Elevator). This method is much easier to implement than the previous one, and it works rather well when the installation is preformed by the administrator. However, if the application is installed by a standard user (with the administrator authorizing the installation "over the shoulder" of the standard user), the procedure does not work as expected: Task Scheduler is scheduling the application to start in the context of the administrator, rather than in the context of the original standard user. The application would launch not at the end of the installation, but later on, when the administrator logs on to the system, which is far from what one would expect.

    Another problem with the second method is that the target machine could have Task Scheduler disabled. In such a case, this method would fail to start the application at all.

  3. Instead of creating a helper executable to launch our application, we could find an existing non-elevated process already running on the target computer, and make it start a non-elevated process on our behalf by injecting our code into that process. The perfect candidate for the code injection is the Windows shell process: it is running non-elevated, and we can be sure it is always present on a computer running Windows Vista (when was the last time you saw a Windows computer without its shell running?).

The Solution: Code Injection in the shell process

Specifically, this method could work as follows:

  1. The elevated process would find a window that belongs to the shell, and that is guaranteed to be available at any time. A good window for this purpose is "Progman": it is responsible for displaying the user's desktop. We can call the FindWindow() API to obtain a handle to this window:

    HWND hwndShell = ::FindWindow( _T("Progman"), NULL);
    

  2. Our elevated process would call the RegisterWindowsMessage() API to register a unique message that we would use to communicate with the shell's Window:

    uVEMsg = ::RegisterWindowMessage( _T("VistaElevatorMsg") );
    

  3. Our elevated process would call SetWindowsHookEx() API to install a global hook, to be invoked when a Windows message gets processed by any process running on the system:

    hVEHook = ::SetWindowsHookEx( WH_CALLWNDPROCRET,
        (HOOKPROC)VistaElevator_HookProc_MsgRet, hModule, 0);
    

  4. Once the hook is installed we would send our unique message to the shell's window, and that would make our hook procedure get invoked. (That's how we inject our code into the shell process!):

     ::SendMessage( hwndShell, uVEMsg, 0, 0 );
    

  5. When the hook procedure is called (in the context of the shell process), it would call ShellExecute() API to launch the non-elevated process that we need. Such a process would start non-elevated because the shell's process is not elevated, and our process would inherit the shell's elevation level:

    LRESULT CALLBACK
    VistaElevator_HookProc_MsgRet( int code, WPARAM wParam, LPARAM lParam )
    {
        if ( code >= 0 && lParam )
        {
            CWPRETSTRUCT * pwrs = (CWPRETSTRUCT *)lParam;
    
            if (pwrs->message == uVEMsg )
            {
                bVESuccess = VistaTools::MyShellExec(
                                pwrs->hwnd,
                                NULL,
                                szVE_Path,
                                szVE_Parameters,
                                szVE_Directory,
                                bVE_NeedProcessHandle ? &hVE_Process : NULL );
            }
        }
    
        return ::CallNextHookEx( hVEHook, code, wParam, lParam );
    }
    

  6. Finally, we would remove the hook, as we no longer need it:

     ::UnhookWindowsHookEx( hVEHook );

Using the code in C++ projects

The method described above is implemented as the function RunNonElevated(), defined in the file VistaTools.cxx. If you need to start a non-elevated process from your own application, you can add this file to your C++ project and call this function directly. The detailed instructions on how to use the file and this function are provided in the VistaTools.cxx file itself.

Note, however, that in order for the RunNonElevated() function to work, you must compile it in a DLL project. The reason for that is that the global hook code needs to reside in a DLL, it cannot be in an executable file. Also note that if you plan to run the code under the x64 versions of Windows as well, you need to compile a separate 64-bit version of the DLL in order for it to work as expected. The reason for this is that on the x64 versions of Windows, the shell is a native 64-bit process. In order to inject our code into it, your DLL must contain the native 64-bit code too.

Note also that VistaTools.cxx contains several other functions that you may find of use when programming for Windows Vista, such as IsVista(), GetElevationType(), and more. They are described in the file itself.

Using the code in the setup scripts

If you don't use C++, or if all you need is call the RunNonElevated() function from your setup script, then you can use the precompiled DLLs I've included in the file Redist.zip. This package contains the VistaLib32.dll and VistaLib64.dll files which export the RunNonElevated() function in such a way that you can invoke it by running the RunDll32.exe utility (which is part of the standard Windows distributions).

For example, if you use InnoSetup (a popular software installation package), then usually when you want to run your application at the end of the installation, you would include the following lines in your setup script:

[Run]
Filename: "{app}\SampleApp32.EXE"; Description: "Launch application";

The above command launches the SampleApp32.EXE, that would start at the elevated level. To launch the same application at the non-elevated level, you would change the above lines to:

[Run]
Filename: "RunDll32.exe"; Parameters:
"{code:AddQuotes|{app}\VistaLib32.dll},RunNonElevated 
            {code:AddQuotes|{app}\SampleApp32.EXE}";
Description: "Launch application";

Such a command would make the setup utility launch RunDll32.exe at the end of the installation. It, in turn, would load VistaLib32.dll and call its RunNonElevated entry point, passing the path to our application SampleApp32.EXE, enclosed in double quotation marks, to it. (Optional command line parameters for the application can be passed there, as well.) SampleApp32.exe is a very simple application that does nothing except that it calls a few functions exported by the VistaLib32.dll and displays the result in a message box:

Screenshot - SampleApp.jpg

To see it all in action, download the file SampleApp-setup.zip (see the link at the top of this article). It contains a pre-compiled setup utility that installs the sample application and runs it non-elevated at the end of the installation.

What about backward compatibility?

The code in VistaLib32/64.dll is backward compatible down to Windows 2000 (I did not test it with the earlier versions of Windows). If you call RunNonElevated() under Windows XP or 2000, it would simply use the regular ShellExecute() API to launch the application, as if ShellExecute() was called directly. This means that you can use the same setup script with both Vista and pre-Vista versions of Windows.

Note, however, that the setup script shown above is for the 32-bit version of Windows. To make it work with 64-bit versions, you need to modify the script to use the file VistaLib64.dll instead of VistaLib32.dll.

More information

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralWorking directory
chrislong2
13:07 18 Sep '08  
Hi Andrei,

First, thank you so much for your work with this! Smile

I'm using these DLL's for my Setup program which is used by a number of people. However, I just discovered that specifying a working directory is not working. This is important for many programs run at the end of Setup/install because they are looking for files relative to the program, etc. I've looked at the source and see that the problem is that the vistalib dll's don't parse the command-line for working directory....they stop right after looking for arguments. But yet, it looks like the functionality is there in the underlying vistatools.cxx file.

Sadly, I am not really a C++ programmer (and even all I have is VC6, which doesn't seem to work), so I can't really change this. It looks like a really easy change though - just an extra parsing layer on the command-line if I'm not mistaken. Is this something you can quickly just change and either repost here or email to me?

It would be greatly appreciated (by me, and many I'm sure).

Chris
GeneralAnother alternative to this method
FaxedHead
20:04 28 May '08  
Can be found here:

http://www.codeproject.com/KB/vista-security/createprocessexplorerleve.aspx

The advantages of this method is that no external DLLs are required.
NewsYes: It can be done much easier - Here is the Code:
Elmue
16:51 7 Nov '09  
Hello

Thanks for posting this link.
You are right: You can do it without DLLs - much easier.

But the code on the page you link to is very ugly and has SIX bugs!

I completely cleaned the code and fixed all the bugs.
I tested it on Windows 2000, XP, Vista and Windows 7.
It works perfectly.

If you compile and run this code "As Administrator" it will start the Calculator process under the account of the currently logged-in user. In Taskmanager you will see the new Calculator running under the same user account as Explorer. This is important in an installer because the new process must access the correct "Documents and Settings" folder.

Elmü

#include "stdafx.h"

#include "windows.h"

#ifndef SECURITY_MANDATORY_HIGH_RID
#define SECURITY_MANDATORY_UNTRUSTED_RID (0x00000000L)
#define SECURITY_MANDATORY_LOW_RID (0x00001000L)
#define SECURITY_MANDATORY_MEDIUM_RID (0x00002000L)
#define SECURITY_MANDATORY_HIGH_RID (0x00003000L)
#define SECURITY_MANDATORY_SYSTEM_RID (0x00004000L)
#define SECURITY_MANDATORY_PROTECTED_PROCESS_RID (0x00005000L)
#endif

#ifndef TokenIntegrityLevel
#define TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25)
#endif

#ifndef TOKEN_MANDATORY_LABEL
typedef struct
{
SID_AND_ATTRIBUTES Label;
} TOKEN_MANDATORY_LABEL;
#endif

typedef BOOL (WINAPI *defCreateProcessWithTokenW)
(HANDLE,DWORD,LPCWSTR,LPWSTR,DWORD,LPVOID,LPCWSTR,LPSTARTUPINFOW,LPPROCESS_INFORMATION);


// Writes Integration Level of the process with the given ID into pu32_ProcessIL
// returns Win32 API error or 0 if succeeded
DWORD GetProcessIL(DWORD u32_PID, DWORD* pu32_ProcessIL)
{
*pu32_ProcessIL = 0;

HANDLE h_Process = 0;
HANDLE h_Token = 0;
DWORD u32_Size = 0;
BYTE* pu8_Count = 0;
DWORD* pu32_ProcIL = 0;
TOKEN_MANDATORY_LABEL* pk_Label = 0;

h_Process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, u32_PID);
if (!h_Process)
goto _CleanUp;

if (!OpenProcessToken(h_Process, TOKEN_QUERY, &h_Token))
goto _CleanUp;

if (!GetTokenInformation(h_Token, TokenIntegrityLevel, NULL, 0, &u32_Size) &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
goto _CleanUp;

pk_Label = (TOKEN_MANDATORY_LABEL*) HeapAlloc(GetProcessHeap(), 0, u32_Size);
if (!pk_Label)
goto _CleanUp;

if (!GetTokenInformation(h_Token, TokenIntegrityLevel, pk_Label, u32_Size, &u32_Size))
goto _CleanUp;

pu8_Count = GetSidSubAuthorityCount(pk_Label->Label.Sid);
if (!pu8_Count)
goto _CleanUp;

pu32_ProcIL = GetSidSubAuthority(pk_Label->Label.Sid, *pu8_Count-1);
if (!pu32_ProcIL)
goto _CleanUp;

*pu32_ProcessIL = *pu32_ProcIL;
SetLastError(ERROR_SUCCESS);

_CleanUp:
DWORD u32_Error = GetLastError();
if (pk_Label) HeapFree(GetProcessHeap(), 0, pk_Label);
if (h_Token) CloseHandle(h_Token);
if (h_Process) CloseHandle(h_Process);
return u32_Error;
}

// Creates a new process u16_Path with the integration level of the Explorer process (MEDIUM IL)
// If you need this function in a service you must replace FindWindow() with another API to find Explorer process
// The parent process of the new process will be svchost.exe if this EXE was run "As Administrator"
// returns Win32 API error or 0 if succeeded
DWORD CreateProcessMediumIL(WCHAR* u16_Path, WCHAR* u16_CmdLine)
{
HANDLE h_Process = 0;
HANDLE h_Token = 0;
HANDLE h_Token2 = 0;
PROCESS_INFORMATION k_ProcInfo = {0};
STARTUPINFOW k_StartupInfo = {0};

BOOL b_UseToken = FALSE;

// Detect Windows Vista, 2008, Windows 7 and higher
if (GetProcAddress(GetModuleHandleA("Kernel32"), "GetProductInfo"))
{
DWORD u32_CurIL;
DWORD u32_Err = GetProcessIL(GetCurrentProcessId(), &u32_CurIL);
if (u32_Err)
return u32_Err;

if (u32_CurIL > SECURITY_MANDATORY_MEDIUM_RID)
b_UseToken = TRUE;
}

// Create the process normally (before Windows Vista or if current process runs with a medium IL)
if (!b_UseToken)
{
if (!CreateProcessW(u16_Path, u16_CmdLine, 0, 0, FALSE, 0, 0, 0, &k_StartupInfo, &k_ProcInfo))
return GetLastError();

return ERROR_SUCCESS;
}

defCreateProcessWithTokenW f_CreateProcessWithTokenW =
(defCreateProcessWithTokenW) GetProcAddress(GetModuleHandleA("Advapi32"), "CreateProcessWithTokenW");

if (!f_CreateProcessWithTokenW) // This will never happen on Vista!
return ERROR_INVALID_FUNCTION;

HWND h_Progman = ::FindWindow("Progman", NULL);
if (!h_Progman) // This can only happen if Explorer has crashed (User has no desktop and no taskbar)
return ERROR_INVALID_WINDOW_HANDLE;

DWORD u32_ExplorerPID = 0;
GetWindowThreadProcessId(h_Progman, &u32_ExplorerPID);

// ATTENTION:
// If UAC is turned OFF all processes run with SECURITY_MANDATORY_HIGH_RID, also Explorer!
// But this does not matter because to start the new process without UAC no elevation is required.
h_Process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, u32_ExplorerPID);
if (!h_Process)
goto _CleanUp;

if (!OpenProcessToken(h_Process, TOKEN_DUPLICATE, &h_Token))
goto _CleanUp;

if (!DuplicateTokenEx(h_Token, TOKEN_ALL_ACCESS, 0, SecurityImpersonation, TokenPrimary, &h_Token2))
goto _CleanUp;

if (!f_CreateProcessWithTokenW(h_Token2, 0, u16_Path, u16_CmdLine, 0, 0, 0, &k_StartupInfo, &k_ProcInfo))
goto _CleanUp;

SetLastError(ERROR_SUCCESS);

_CleanUp:
DWORD u32_Error = GetLastError();
if (h_Token) CloseHandle(h_Token);
if (h_Token2) CloseHandle(h_Token2);
if (h_Process) CloseHandle(h_Process);
return u32_Error;
}

int main(int argc, char* argv[])
{
DWORD u32_Err = CreateProcessMediumIL(L"C:\\Windows\\System32\\Calc.exe", NULL);

printf("CreateProcessMediumIL() exited with error %d\r\n", u32_Err);
Sleep(2000);
return 0;
}

GeneralVistaLib32.dll and VistaLib64.dll redistribution license
kenny4
4:10 29 Apr '08  
Hi Andrei,

If we ship these 2 dlls in our installer, do we need your written permission ? Thank you.

Regards,
Kenny
GeneralRe: VistaLib32.dll and VistaLib64.dll redistribution license
Andrei Belogortseff
18:54 29 Apr '08  
> If we ship these 2 dlls in our installer, do we need your written permission ? Thank you.

Kenny,

you (or anyone else) don't need a written permission, the libraries are free to use and redistribute, as long as you agree that they come with no warranties, yadda, yadda. (You can find the terms of use in the Readme.txt file included in the source code of the library).

Thanks for asking though.

Good luck!

Andrei Belogortseff
http://www.winability.com

GeneralRunNonElevated is not working on Vista
m-force1
9:18 4 Mar '08  
Hi Andrei,

I am having problem with the RunNonElevated (VE32.dll) method in order to start a process on Vista.

I am having a NSIS installer which is installing an executable file and the calls RunNonElevated in order to run the installed program.

That works all fine on XP (administrator/standard user rights) and on Vista with administrator rights. But when I try to install it on Vista from a standard user account it does not work.

When I start the installer Vista pops up a window to put in credentials of an administrator with user name and password. After entering the information the NSIS installer runs, copies the executable file to the program files folder and then it calls RunNonElevated from the VE32.dll in order to start the program.

After I debugged RunNonElevated it looks like everything works as supposed to be but the hook "VistaElevator_HookProc_MsgRet" is never receiving the message "VistaElevatorMsg".

Would you have an idea what is going wrong or why the hook is never receiving the message.

It would be great if you could give me some help.

Thanks
Mike
GeneralRe: RunNonElevated is not working on Vista
Andrei Belogortseff
9:55 6 Mar '08  
Mike,

If I remember correctly, when I tested this code while writing the article, it used to work with the standard users.

Are you sure you are using the correct DLL? Because you mentioned VE32.DLL, which is form another article of mine. This article comes with a dll named VistaLib32.dll, you may want to try it instead of VE32.DLL.

Hope this helps.

Andrei Belogortseff
http://www.winability.com

GeneralCreative idea
lmueller
13:57 27 Aug '07  
Why are people so offended by this idea? Smile If you haven't installed Windows Vista yet, or you haven't attempted to solve the problem on your own, then you probably shouldn't criticize.

The solution is pretty good actually, because it is low overhead, and the complications with FindWindow (that everyone seems to focusing on) are not the main point of the article. It is fixed easily by (A) finding a different window via heuristic or (B) using another method to find a non-administrative process.

GeneralVistaLib32.dll launches app twice
sharevari
2:00 15 Aug '07  
I have successfully integrated your pre-built VistaLib32 and VistaLib64 DLLs into our Inno Setup-based installer (completely painlessly thanks to your detailed instructions may I add), and it does work in that it launches the app non-elevated after installation is finished on both 32- and 64-bit Vistas. So far so good.

However, on 32-bit versions of Vista, the app is launched twice most of the time. It seems to happen in something like 80% of the installs I've tried so far. On 64-bit Vista, I have never seen it launch twice.

I'm using the following lines in the InnoSetup script:

[Run]
Filename: "RunDll32.exe"; Parameters: "{code:AddQuotes|{app}\VistaLib32.dll},RunNonElevated {code:AddQuotes|{app}\LastFM.exe}"; Description: "{cm:LaunchProgram,Last.fm}"; Flags: nowait postinstall; MinVersion: 0,6

If I revert back to a normal launch, i.e.

Filename: "{app}\LastFM.exe"; Description: "{cm:LaunchProgram,Last.fm}"; Flags: nowait postinstall

it never launches twice so I can only deduce that it's the VistaLib32 DLL that does it. Any ideas why?

Thanks,
Erik

GeneralRe: VistaLib32.dll launches app twice
sharevari
3:06 15 Aug '07  
Just discovered that this only seems to happen when launching two executables after installation. The above was a simplification, the Run section looks like this:

Filename: "RunDll32.exe"; Parameters: "{code:AddQuotes|{app}\VistaLib32.dll},RunNonElevated {code:AddQuotes|{app}\LastFMHelper.exe}"; Flags: nowait runhidden; MinVersion: 0,6
Filename: "RunDll32.exe"; Parameters: "{code:AddQuotes|{app}\VistaLib32.dll},RunNonElevated {code:AddQuotes|{app}\LastFM.exe}"; Description: "{cm:LaunchProgram,Last.fm}"; Flags: nowait postinstall; MinVersion: 0,6

If I remove either line, the remaining exe launches fine, and only once. But if I leave both lines in, it seems either of the two (or both) get launched twice. Is this not a good technique when you need to launch more than one exe?

GeneralWhat about XP & W2K ?
_Olivier_
6:18 2 Aug '07  
I know this article is about Vista security
But I have a similar problem with a limited user account under XP or W2K running an installer with a temporary administrator account. (through the login prompt, using "runas" context menu or when the installer is named "Install.exe" or "Setup.exe")
When the installer has finished its job, how to make it run a non-admin program, using the initial user credentials, not as the temporary administrator account
I couldn't find a good answer on Internet...


GeneralRe: What about XP & W2K ?
Andrei Belogortseff
12:00 2 Aug '07  
Olivier,

I did not try it, but the same method of hooking into the shell process should work with XP just as well. Of course, the code of the DLL should be modified to make the DLL load under XP (that is, all static calls to the Vista-only APIs should be changed to the dynamic ones). If you do try it, please let us know the results here. Thanks!


Andrei Belogortseff
http://www.winability.com

GeneralQuestion about the High elevation solution using Rundll32.exe
Tim Mayert
12:00 20 Jul '07  
I am using InstallShield 12 to install our application and we would like to also have our apps triggered to run in non-elevated mode during the install so I would like to find out more about the command line to trigger it.

In InstallShield we can call an executable and set the parameters that we have to pass to it, but my question is how would you set this up if the app you are calling from the install also requires some parameters?

Can this be done or will the Rundll32.exe see the extra parameters as something passed to it and therefore not work at all, or do the parameter need to be wrapped within the exe command. ie:
RunDll32.exe "app path\VistaLib32.dll,RunNonElevated" "app path\our application.exe,-NOSPU"

If this does not support this then what would the next best thing be?

Thanks,

Tim.
GeneralRe: Question about the High elevation solution using Rundll32.exe
Andrei Belogortseff
12:10 20 Jul '07  
> my question is how would you set this up if the app you are calling from the install also requires some parameters?

You should enter the parameters on the same command line that you pass to RunDLL32, after the application path. The sample setup script included with the article shows how to do that (look for /some_args - it illustrates how you can pass the command line paramenters to your application by calling RunDll32).

HTH


Andrei Belogortseff
http://www.winability.com

QuestionRe: Question about the High elevation solution using Rundll32.exe
FaxedHead
3:04 12 Apr '08  
Thanks your work on this Andrei.

Yes, adding /some_args after the path to the application to execute works, but I can't seem to be able to specify the working directory for starting the application using this method (rundll32). Using:


ProcessStartInfo pi = new ProcessStartInfo("rundll32.exe");
pi.Arguments = "VistaLib32.dll,RunNonElevated \"" + fileName + "\" " + arguments + " " + workingDir;
Process.Start(pi);


The process is launched, but Process explorer shows that the arguments passed to it are {arguments workingdir} and the actual working directory is set to some default (e.g. c:\windows\system32). Is there any way to specify the working directory for the process using a call to rundll32.exe? Perhaps this has to do with RunDLL32 expecting the entrypoint function it is calling to do the parsing (as stated here http://support.microsoft.com/kb/164787)...

As an alternative I tried to call RunNonElevated directly (from C#), however my DLLImport prototype must be wrong, because I just get a messagebox entitled RunNonElevatedA with some garbage characters.


[DllImport("VistaLib32.dll", EntryPoint = "RunNonElevated")]
public static extern bool RunNonElevated(
IntPtr hwnd,
[MarshalAs(UnmanagedType.LPTStr)] string filePath,
[MarshalAs(UnmanagedType.LPTStr)] string arguments,
[MarshalAs(UnmanagedType.LPTStr)] string workingDir);


Can you offer any advice how to achieve this? Thanks.
GeneralRe: Question about the High elevation solution using Rundll32.exe
FaxedHead
19:18 12 Apr '08  
Ok I figured this out - firstly I was looking at the source to VC32.dll which was confusing me. Once I found the correct source for VistaLib32.dll, I saw that only RunNonElevatedA and RunNonElevatedW are exported as C functions in the .def file. And these two functions do not support passing a working directory for the process to be created. Additionally, there is a bug in RunNonElevatedA caused by the following two lines:


TCHAR szCmdLineW[ MAX_PATH ];
MessageBox( hwnd, szCmdLineW, _T("RunNonElevatedA"), MB_OK );


which shows the message box I was seeing with garbage characters. This should be converting the ANSI string to wide like RunElevatedA.

So the solution for me was adding RunNonElevated to DLL.def. Then I can use the following DLLImport:


[DllImport("VistaLib32.dll", EntryPoint = "RunNonElevated")]
public static extern int RunNonElevated(
IntPtr hwnd,
[MarshalAs(UnmanagedType.LPTStr)] string filePath,
[MarshalAs(UnmanagedType.LPTStr)] string arguments,
[MarshalAs(UnmanagedType.LPTStr)] string workingDir,
IntPtr hProcess);


Once again thanks for writing an article about this and providing source code. I find it incredible that MS didn't include a API for doing this. But then that's consistent with the mess that is Vista WTF
QuestionCreating Desktop icons for standard users. Can this be done?
nagarsoft
8:26 30 Jun '07  
I wonder if your library can be used to solve the problem of elevation privileges in Windows Vista when the setup is run by a standard (non admin) user. The desktop icons and all the Current User registry settings are saved to the admin account and not the standard user account. Any idea on how to fix that?
Thanks.
Andrea
AnswerRe: Creating Desktop icons for standard users. Can this be done?
Andrei Belogortseff
12:00 30 Jun '07  
> I wonder if your library can be used to solve the problem of elevation privileges in Windows Vista when the setup is run by a standard (non admin) user. The desktop icons and all the Current User registry settings are saved to the admin account and not the standard user account. Any idea on how to fix that?

Andrea,

I see that you have asked this question in the InnoSetup newsgroup as well and Jordan has already answered it.

No, this DLL will not solve such a problem directly. If you must create the user-specific items during the installation, then you can solve the problem by creating a separate executable that does only the user-specific customizations, and you can launch it non-elevated from the main installer using my DLL. However, an easier way would be to do what Jordan suggested: do not create any user-specifc items during the install. Instead, let your program detect when it's being run for the first time, and if so, create those items from within the program itself.

However, that would not remove such items during uninstall... It may be better not to create any user-specific items at all...

Good luck!

Andrei Belogortseff
http://www.winability.com

GeneralRe: Creating Desktop icons for standard users. Can this be done?
nagarsoft
1:49 8 Jul '07  
Thank you.
I'm thinking about creating an /uninstall parameter for my application so that when the user runs the Uninstall, it can be called using your library (with non elevated privileges).

I'm wondering what happens in this scenario.
User Standard actually uses the application but he's not an administrator.

When Standard uninstall the application, Vista requires admin privileges so user Admin is the one who actually runs the setup. If I launch an application using your library who's the active user? The admin or the standard one? If it's the admin, there's no way I can remove any setting from the registry who are specific to the user who's actually running the application.

Thanks.

GeneralNot able to build
email2venki
21:39 3 Jun '07  
Hey,

I am not able to build VistaLib solution. Following error is reported,

1>e:\study\vista\vistalib\source\vistatools.cxx(179) : error C2065: 'TOKEN_ELEVATION_TYPE' : undeclared identifier
1>e:\study\vista\vistalib\source\vistatools.cxx(179) : error C2065: 'ptet' : undeclared identifier
1>e:\study\vista\vistalib\source\vistatools.cxx(179) : fatal error C1903: unable to recover from previous error(s); stopping compilation

I know that these errors are due to not having proper header file (Platform SDK). So, downloaded the latest SDK from followint site:

http://www.microsoft.com/downloads/details.aspx?FamilyId=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en[^]

and downloaded PSDK-x86.exe

Do I have to download anything else or am I missing something here.

Please let me know on this.



Thanks,
Venki
GeneralRe: Not able to build
icnocop
2:30 13 Jun '07  
Hello Venki,

I was able to resolve the issue you indicated by configuring Visual Studio 2005 SP1 in the following manner:

Tools > Options > Projects and Solutions > VC++ Directories > Platform: Win32

> Executable Files
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\bin

> Include Files
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Include
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Include\gl

> Library files
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\lib
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Lib\x64

> Exclude Directories
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Include
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Include\gl

I had to place these directories first (at the very top) because they are searched in that order (top to bottom)

Hope that works out for you.
-Rami

GeneralRe: Not able to build
email2venki
2:40 13 Jun '07  
Thanks Rami,

I'll check this out.



Regards,
Venki
GeneralRe: Not able to build
Shashank K Danej
20:01 17 Dec '08  
Thanks Rami for the tips you given i was also stuck there for a week
Thank you very much
Smile Big Grin

Regards
Shashank
To gain Knowledge you must share it

GeneralAnother way
Leo Davidson
6:22 31 May '07  
I also find the idea of injecting code into another process, especially a process that you didn't write and have no control over, scary.

Here's an alternative idea:

The setup program's process runs unelevated but, through the UAC API, creates an elevated COM object to use for those parts of the install which require administrator rights. The COM object only has to be created once and can then be re-used, so there would still only be a single UAC prompt. Since the main process is still running unelevated it can launch other unelevated applications directly, and since it has its COM object it can perform whatever elevated actions it needs to through the course of the installation.

I believe this is a far better way to do the job although it does, of course, require modification of the installer itself. So it's something that should be built into InnoSetup (etc.) rather than something we can easily do as users of the installer creators.

BTW, creating a single elevated COM object and then keeping it around for several operations is a perfectly legal use of the UAC model. It's been tried and tested in Directory Opus 9 (see my guide here: http://nudel.dopus.com/opus9/page4.html#vistauac ). The only reason that Explorer and many installers pop up multiple UAC prompts for the same logical operation is lazy programming.

GeneralRe: Another way
Andrei Belogortseff
9:53 31 May '07  
> I believe this is a far better way to do the job

I agree!

> although it does, of course, require modification of the installer itself.

And that's the drawback. If you are willing to rewrite the installer, then it's much better to redesign it and make it work like you described, or maybe split it in two parts, one to run elevated and perform the real administrative operations, and another one, to run non-elevated, to perform the user-specific configuration (sort of like the helper and the main modules I described in in the article).

However, if rewriting the installer is not an option, then the code injection method should get the job done.



Andrei Belogortseff
http://www.winability.com


Last Updated 25 May 2007 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010