![]() |
Platforms, Frameworks & Libraries »
Vista Security »
User Account Control
Intermediate
High elevation can be bad for your application: How to start a non-elevated process at the end of the installationBy Andrei BelogortseffA reusable DLL that uses code injection to launch a non-elevated application from an InnoSetup script |
VC8.0Win2K, WinXP, Win2003, VistaVS2005, Architect, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
Many installation packages offer an option for the user to launch the application at the end of the installation:

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.
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.
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:
requireAdministrator value in the setup.exe's manifest.
This method would work, but it's messy, since it requires creating a separate helper executable, as well as designing a communication protocol between the setup utility and the helper, which is not a trivial task.
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.
Specifically, this method could work as follows:
FindWindow() API to obtain a handle to this window:
HWND hwndShell = ::FindWindow( _T("Progman"), NULL);
RegisterWindowsMessage() API to register a unique message that we would use to communicate with the shell's Window:
uVEMsg = ::RegisterWindowMessage( _T("VistaElevatorMsg") );
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);
::SendMessage( hwndShell, uVEMsg, 0, 0 );
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 );
}
::UnhookWindowsHookEx( hVEHook );
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.
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:

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.
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.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 25 May 2007 Editor: Deeksha Shenoy |
Copyright 2007 by Andrei Belogortseff Everything else Copyright © CodeProject, 1999-2009 Web13 | Advertise on the Code Project |