Click here to Skip to main content
Click here to Skip to main content

Riding the Vista UAC elevator, up and down

By , 27 Feb 2007
 

Sample Image - VistaElevator.gif

Introduction

When developing applications for Windows Vista, one of the problems that often arises is how to programmatically control the execution level of a process. When the user starts an application, its elevation level is determined by the value of the requestedExecutionLevel attribute in its manifest, and Vista's User Account Control (UAC) takes appropriate actions depending on it (such as displaying the elevation prompt when needed, etc.) However, what if the application needs to start a new process with a different execution level than that of the application itself? For example:

  • An application that runs at the standard (non-elevated) level determines that an updated version of it is available for download. To be able to update itself, it needs to start a separate process that needs to be elevated in order to perform the upgrade properly. In this case a non-elevated process needs to start a new, elevated process.
  • Most of the installation utilities offer the user the option to run the application automatically at the end of the installation. The installation utility is executing at the elevated level, however, the application must be started at the standard, non-elevated level.

Microsoft has provided a relatively easy way to accomplish the first task (starting a process elevated), by specifying the "runas" verb when calling the ShellExecuteEx API. However, for some reason they have not offered a similarly easy way of going in the opposite direction: to start a non-elevated process from an elevated one. In this article I will show how to solve this and related problems.

Determining the current elevation level

First of all, how can an application determine its current elevation level? The file VistaTools.cxx included in the source code to this article contains two functions that give the answer to this question.

The first such function is GetElevationType() and it uses the Win32 API GetTokenInformation() to obtain the elevation type of the token of the current process. The possible values that it can return are:

  • TokenElevationTypeDefault - User is not using a "split" token. This value indicates that either UAC is disabled, or the process is started by a standard user (not a member of the Administrators group).
  • TokenElevationTypeFull - the process is running elevated.
  • TokenElevationTypeLimited - the process is not running elevated.

Note that the last two values can be returned only if both the UAC is enabled and the user is a member of the Administrator's group (that is, the user has a "split" token).

The second function is IsElevated() that also calls the GetTokenInformation() API, but requests the TokenElevation class of information. It can return one of the following:

  • S_OK - the current process is elevated. This value indicates that either UAC is enabled, and the process was elevated by the administrator, or that UAC is disabled and the process was started by a user who is a member of the Administrators group.
  • S_FALSE - the current process is not elevated (limited). This value indicates that either UAC is enabled, and the process was started normally, without the elevation, or that UAC is disabled and the process was started by a standard user.

Using these two functions, an application can determine the exact circumstances of its execution.

Starting an elevated process

If a non-elevated process needs to start an elevated one, all it has to do is call the ShellExecuteEx() API and supply the "runas" verb as one of its parameters. The source code of this article contains the function RunElevated() that does just that:

BOOL
RunElevated(    HWND hwnd,
        LPCTSTR pszPath,
        LPCTSTR pszParameters = NULL,
        LPCTSTR pszDirectory = NULL )
{
    SHELLEXECUTEINFO shex;

    memset( &shex, 0, sizeof( shex) );

    shex.cbSize        = sizeof( SHELLEXECUTEINFO );
    shex.fMask        = 0;
    shex.hwnd        = hwnd;
    shex.lpVerb        = _T("runas");
    shex.lpFile        = pszPath;
    shex.lpParameters    = pszParameters;
    shex.lpDirectory    = pszDirectory;
    shex.nShow        = SW_NORMAL;

    return ::ShellExecuteEx( &shex );
}

Starting a non-elevated process from an elevated one

Going in the opposite direction (from an elevated process to a non-elevated one) turns out to be much more difficult. If the parent process is elevated, then any process it starts directly becomes elevated too, no matter which value of the requestedExecutionLevel attribute is specified in the application's manifest. For some reason, Microsoft has not provided an API to lower the execution level directly, so we had to come up with an indirect way of achieving the goal.

The trick is to use the built-in Task Scheduler of Windows Vista to set up a task to be executed at the low execution level, and request that the task should start as soon as it is registered with Task Scheduler. The net result is about the same as if the process was started directly.

The source code of this article contains the function RunAsStdUser() that does exactly that. It is based on the MSDN sample "Registration Trigger Example", and it involves calls to more than a dozen COM interfaces to communicate with the Task Scheduler and set up a task to run at the standard (non-elevated) level. I am not including the source code of the function here as it's rather boring; you can find it in the file VistaTools.cxx

Seeing it all in action

The demo application (VistaElevator) illustrates how to perform both the elevation and lowering of the execution level programmatically. When you run it, it displays a dialog box that shows the information about the execution level of the current process obtained by calling GetElevationType() and IsElevated() (see above). It also offers you two choices of how to restart it, elevated or not. Depending on your choice, VistaElevator calls either RunElevated() or RunAsStdUser() functions (see above) to restart itself at the requested execution level.

Note: Make sure you have the latest Windows SDK (see msdn.microsoft.com for more information) if you want to compile the source code on your own.

An update: Vista Elevator 2.0

  • 2007-Feb-27: I have posted an updated version 2.0 of this sample application at http://www.tweak-uac.com. The main improvement is the new function RunNonElevated() that does not rely on Task Scheduler and works well for both the administrators and standard users. Check it out!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Andrei Belogortseff
Web Developer
United States United States
Member
When not busy entertaining my two cats, I run my micro-ISV business at www.winability.com

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionIs there a way to implement temporary elevation?member-273C12 Dec '10 - 3:51 
I have a MFC dialog based application that does not have to be elevated except for one particular user function where I want to temporarily block keyboard and mouse input (for some reason MS has made the standard WINAPI BlockInput function require elevation - it could be that this function is just a cover to keyboard and mouse low level global hooks!).   Is it possible to be elevated just for this one function and then come back down to continue with all other functions?
 
Would getting the code signed (i.e. MS Authenticode) enable the user to be asked only once whether to allow this and then be able to "just do it" thereafter without bothering the user every time?   Something like the "Do you trust programs from XYZ Inc.?". BTW - the application doesn't have to be 'installed' as it is self-contained and could just be unzipped to any directory - even on a USB stick.   Would this impact and solution for temporary elevation?
 
If there is a solution for temporary elevation and MS Authenticode doesn't stop the user being asked every time they do this one function, is there any other way?
 
Thanks
GeneralMethod that doesn't require injection or process token changesmemberLeo Davidson24 Feb '10 - 1:20 
[Andrei, apologies for posting this in two of your articles. Just wanted to make it easier to find for people browsing for info on this topic, since I think the method below isn't well known but is potentially useful in a lot of cases (though not all).]
 
This seems like a nice way to do it, provided you don't care about situations where the Shell is not running (e.g. possibly some Terminal Services application-only setups, perhaps, though I'm not sure):
 
http://brandonlive.com/2008/04/27/getting-the-shell-to-run-an-application-for-you-part-2-how/
 
It gets an interface to Explorer.exe, which should be running in the user's normal context, and asks Explorer to execute a command in its behalf. This is done just using simple, documented COM interfaces and without having to mess around with process tokens or code/DLL injection.
GeneralRun non-elevated, another methodmemberKonkevych16 Mar '09 - 3:31 
I think i have found (another) method to create limited token directly.
Here is the algorythm, proven to be functional:
1. CreateRestrictedToken with corresponding limited prilileges and Flags = 0x4 (LUA_TOKEN)
2. Retrieve linked token over GetTokenInformation from the created restricted token
3. Set "integrity level" via SetTokenInformation for the linked token
4. Return linked token
 
I cann't prove the created token is 100% the as "normal" limited token, but "is elevated", "elevation type" and "integrity level"
properties all returns proper results.
Generalimprovements to the 1.0 codememberTodd C. Gleason5 Mar '09 - 6:17 
I use a command-line tool I wrapped around the Vista Elevator 1.0 code. (I prefer not to use hooks myself so 1.0 really appealed to me.)
 
In any case, I have done a few things over time that make this code more reliable:
 
1. I generate a GUID as part of the task name instead of using a random number. This seems to work better when I run this multiple times in rapid succession (without it, a task may get deleted before it executes due to naming collisions). The basic code is interspersed into RunAsStdUser() roughly as follows:
 
    //  Choose a name for the task.

	// initialize random seed
	RPC_WSTR guidStr = 0x00;
	GUID *pguid = new GUID;
	UuidCreate(pguid);
 
	// Convert the GUID to a string
	UuidToString(pguid, &guidStr);
	delete pguid;
 
	WCHAR pszTaskName[90];
	wsprintf((LPWSTR)pszTaskName, L"RunAsStdUser %s", guidStr);
	::RpcStringFree(&guidStr);
 
...
    //  If the same task exists, remove it.
    iRootFolder.p->DeleteTask( _bstr_t( pszTaskName), 0  ); // ignore error message, if any
...
    DO( iRootFolder.p->RegisterTaskDefinition(
            _bstr_t( pszTaskName ),
            iTask.p,
            TASK_CREATE_OR_UPDATE, 
            _variant_t(), 
            _variant_t(), 
            TASK_LOGON_INTERACTIVE_TOKEN,
            _variant_t(),
            &iRegisteredTask.p) )
...
	//Delete the task when done
	hr = iRootFolder.p->DeleteTask(
		_bstr_t( pszTaskName ),
		NULL);
 
2. I include this code (after put_StartWhenAvailable()) to make the task run even in battery mode (without it, laptops in battery mode won't execute these tasks at all):
 
	// We don't care about battery mode; start always
	DO( iSettings.p->put_DisallowStartIfOnBatteries(VARIANT_BOOL(false)) )

 
--Todd C. Gleason
www.cool-man.org

GeneralImproved Function RunNonElevated() in V2.0memberncalverl15 Jan '09 - 3:43 
Hi
 
You mention you have an updated v2.0 of your app with an improved RunNonElevated() function. Do you have the source available for this?
I did a quick check on the link you provided but could only find the application executable.
 
Cheers
Neil.
GeneralVista user account mode getting wrtting permission for an exe in program files/programdatamemberknareshkumar1 Dec '08 - 18:14 
Hi,
i have an exe installed in program files or in programdata of vista's user account mode.
now i want to write in a xml file located in program files/programdata and in an ini file located in c:windows.
 
as from user account mode in vista i cannot write into any file.
 
can u help me in writing into the xml file(c:program file/c:programdata) and into an ini file(c:windows).
 
I am naresh kavali
intrested in formus of VC++, Com,ATL

GeneralRe: Vista user account mode getting wrtting permission for an exe in program files/programdatamemberknareshkumar1 Dec '08 - 22:59 
I had used manifest in doing so for the exe
 
previously i was using only 'requireAdministrator' level and i was not getting the write permission now i added all three and its working....
Wink | ;)
 
I am naresh kavali
intrested in formus of VC++, Com,ATL

GeneralCan't built projectmembermanh_duc23 Nov '08 - 19:20 
I'm using Vista OS, VS2005 and last SDK for Vista to built project.
When I built it, it return error "TOKEN_ELEVATION_TYPE : undeclared identifier", but when I try built project on Win Server 2008, VS 2010 it run Ok.
Why it return error on Vista OS and how to fix this error?
Thanks!
GeneralRun As standard user with a manifest filemembersrinivas vaithianathan9 Mar '08 - 20:23 
Thanks for the article. I was able to understand how to run a program as standard user programmatically. But can we specify standard user execution levels in the manifest file similiar to RequireAdministrator?
QuestionRegarding UAC and drag and drop and other problems.memberna.nu19 Nov '07 - 17:36 
Hi,
 
Read your article and used "run as" in installer and added manifests to a application executables that I am working on. Am able to get the elevation prompt on launch of the application. I am facing some problems though:
 
1) The application (win32) I am working on uses drag and drop. It a legacy application and I still use an old IDE for its development. The problem is that elevation has worked but drag and drop does not work any more. I read that since explorer and the desktop work at a lower priviledge level (medium) my application cannot receive any messages related to drag and drop. How do I resolve this? I have tried adding drag and drop messages to the application message filter using an API Changewindowmessagefilter(). This caused the application to crash.
 
2) I require to map a drive for my application to use. If I use Explorer to map the drive then my application does not recieve any messages regarding the added network drive. If I map using my application then only does my application becomes aware of the network drive.
 
3) All folders in a drive are set to read only so I cant send files to these drives as I get write protection status on checking the drive before copying.
 
Please help. Thanks.
 
Cry | :((

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 27 Feb 2007
Article Copyright 2006 by Andrei Belogortseff
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid