Saving files with UAC enabled

Log-in or register.

Saving files with UAC enabled

Published by on February 27th 2012.

In the past years, I have received numerous emails from users, who were having trouble saving their files. In most cases, the users have opened one of the stock Windows cursors from C:\Windows\Cursors and were attempting to save a modified cursor into the same folder. Because this folder is protected by UAC in Vista and Win7, the operation failed.

The next version of RWCursorEditor will finally address this problem and users will see the the standard UAC prompt when attempting to save to a protected location.

The rest of this post is aimed at fellow programmers, who are considering adding the same functionality to their applications and want to do it with minimum effort.

A lean solution to your UAC trouble

So, you have created a nifty software tool and your users want to save files to protected folders. What are your options if you want to fulfill the request? According to Microsoft:

  1. Either start another process with elevated permissions and let it save the file for you.
  2. Or create an elevated COM object and use its methods to save the file.

Both of these methods are problematic and require a lot of work.

Starting a new process

Starting an elevated process can be done via a manifest snippet (that may cause a BSOD on WinXP SP2) or by using an undocumented option of the ShellExecuteEx API function.

If you go the manifest way, you need to ship another .exe file with your tool. If your tool is portable (possibly a standalone .exe), having 2 .exe files will confuse the users. If you use ShellExecuteEx API, you must be in a single-threaded apartment or it will not work. Then you can launch the same .exe file (the one that is running) with admin permissions and some kind of command line argument that will allow it to communicate with your original process.

In either case, after you succeed in creating the elevated process, you must transfer the data that needs to be saved to that process and instruct it to actually save the data and report the result. Hooray, you'll have some inter-process-communication fun. I bet working with pipes and synchronization objects is what you like best.

If you want to know more, read http://www.codeproject.com/Articles/19165/Vista-UAC-The-Definitive-Guide

In short, using an elevated process is unwieldy, complicated and work-intensive. It (the saving function) cannot be implemented as an isolated component that you just plug into your tool.

Using an elevated COM object

In order to start a COM object with elevated permissions, it needs to be registered in the HKLM branch of the registry. You need to have admin permissions to modify entries under HKLM (putting the entries to HKCU will not work). So, if your tool is portable (as is mine), this solution is out of the question.

What now?

The recommended ways failed, but fortunately, there is something that can be used. When Microsoft was designing Windows Explorer (the file manager) in Vista, they had to workaround the UAC somehow and created a COM object that can do basic stuff with files.

We can use this Explorer's COM object to our advantage. It cannot save data to files, but it can copy or move files around.

So, here is the plan:

  1. Detect if UAC is enabled and folder is protected.
  2. Create the Explorer's COM object - this shows the UAC prompt.
  3. Save to a temporary file.
  4. Use the COM object to move the temporary file to the requested location.

While this is not an ideal solution (because of the temporary file), it has several advantages:

  • Is just a few lines of code.
  • There are no extra .exe files.
  • There is no complicated (visible) inter-process communication.
  • There is no need to add entries to the registry (the COM class is already registered by Windows).

Detecting if UAC is on

We'll need these functions (some of the code was taken from VistaTools.cxx):

typedef WINGDIAPI BOOL WINAPI fnOpenProcessToken(HANDLE,DWORD,PHANDLE);
typedef WINADVAPI BOOL WINAPI fnGetTokenInformation(HANDLE,TOKEN_INFORMATION_CLASS,LPVOID,DWORD,PDWORD);

HRESULT GetElevationType(TOKEN_ELEVATION_TYPE* ptet)
{
	static TOKEN_ELEVATION_TYPE tTET = TokenElevationTypeDefault;
	static HRESULT hRes = S_FALSE;

	if (hRes == S_FALSE)
	{
		HMODULE hMod = LoadLibrary(_T("Advapi32.dll"));
		fnOpenProcessToken* pfnOpenProcessToken = (fnOpenProcessToken*)GetProcAddress(hMod, "OpenProcessToken");
		fnGetTokenInformation* pfnGetTokenInformation = (fnGetTokenInformation*)GetProcAddress(hMod, "GetTokenInformation");
		hRes = E_FAIL;
		if (pfnOpenProcessToken && pfnGetTokenInformation)
		{
			HANDLE hToken = NULL;
			DWORD dwReturnLength = 0;
			if (pfnOpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken) &&
				pfnGetTokenInformation(hToken, TokenElevationType, &tTET, sizeof tTET, &dwReturnLength) &&
				dwReturnLength == sizeof tTET)
				hRes = S_OK;
			if (hToken)
				::CloseHandle(hToken);
		}
		FreeModule(hMod);
	}

	*ptet = tTET;
	return hRes;
}

bool IsVista()
{
	static OSVERSIONINFO tVersion = { sizeof(OSVERSIONINFO), 0, 0, 0, 0, _T("") };
	if (tVersion.dwMajorVersion == 0)
		GetVersionEx(&tVersion);
	return tVersion.dwMajorVersion >= 6;
}

In your code, you will use:

if (ordinary saving failed)
{
	TOKEN_ELEVATION_TYPE tTET;
	if (IsVista() && S_OK == GetElevationType(&tTET) && tTET == TokenElevationTypeLimited)
	{
		// here goes the saving code discussed next
	}
}

Creating the elevated COM object

BIND_OPTS3 tBO3;
ZeroMemory(&tBO3, sizeof tBO3);
tBO3.cbStruct = sizeof tBO3;
tBO3.dwClassContext = CLSCTX_LOCAL_SERVER;
CComPtr<IFileOperation> pFO;
HRESULT hRes = CoGetObject(L"Elevation:Administrator!new:{3ad05575-8857-4850-9277-11b85bdb8e09}", &tBO3, __uuidof(IFileOperation), reinterpret_cast<void**>(&pFO));

If everything succeeded (user confirmed your UAC prompt), you'll have a valid pointer in pFO.

Saving to a temporary file

I'll leave this part to you...

Moving the file with the Explorer's object

This is a bit trickier, because the object does not accept paths. It wants its special IShellItem objects. We need to convert the paths to these objects with SHCreateItemFromParsingName API function. Since this function is not available on older Windows, I am loading it dynamically.

This piece of code assumes:

TCHAR* pszTarget; // is the path to the file in the UAC-protected location
TCHAR* pszTemp; // is the path to the temporary file, where you have saved the data
LPTSTR pszNewName = _tcsrchr(pszTarget, _T('\\'));
LPTSTR pszNewName2 = _tcsrchr(pszTarget, _T('/'));
if (pszNewName < pszNewName2) pszNewName = pszNewName2;
if (pszNewName)
{
	*pszNewName = _T('\0');
	++pszNewName;
}
if (FAILED(pFO->SetOperationFlags(FOF_NO_UI)))
	return E_FAIL;
HMODULE hMod = LoadLibrary(_T("Shell32.dll"));
fnSHCreateItemFromParsingName* pfnSHCreateItemFromParsingName = (fnSHCreateItemFromParsingName*)GetProcAddress(hMod, "SHCreateItemFromParsingName");
CComPtr<IShellItem> psiFrom;
CComPtr<IShellItem> psiTo;
if (pfnSHCreateItemFromParsingName == NULL ||
	FAILED(pfnSHCreateItemFromParsingName(pszTemp, NULL, IID_PPV_ARGS(&psiFrom))) ||
	FAILED(pfnSHCreateItemFromParsingName(pszTarget, NULL, IID_PPV_ARGS(&psiTo))))
{
	FreeModule(hMod);
	return E_FAIL;
}
FreeModule(hMod);
if (FAILED(pFO->MoveItem(psiFrom, psiTo, pszNewName, NULL)) ||
	FAILED(pFO->PerformOperations()))
{
	DeleteFile(pszTemp);
	return E_FAIL;
}

There you go, that is all you need to make your application capable of saving to an UAC-protected folder. Less than 100 lines of code.

The obligatory Microsoft bashing

Microsoft annoyed quite a lot people with the frequent UAC prompts back in 2007 when Windows Vista was released. Not me, I actually liked the improved security. It was a nice idea, but the implementation sucked. The first half was good, the second needed some work.

What happened in Windows 7 was a disaster. Instead of properly implementing the user-interaction part in Windows tools, Microsoft gave itself an exception from the UAC, and thus rendered UAC completely useless (due to many security holes in Explorer and other Microsoft tools).

From the programmer perspective, UAC is a pain to work with. The worst part is that every software developer has to implement the same (and quite complex) algorithm. Would it be really so hard if Microsoft officially published a couple of elevation-compatible COM objects for the usual tasks like saving a file? That would save countless software developers countless hours of their time... To simply save a file, one should not be forced to resort to hacks or to delve into the intricacies of IPC on Windows.

Vlasta's blog RSS feed

Recent comments

user icon Anonymous on August 9th 2012

Great article, Vlastimil!

-- kyrathaba on DC

user icon Anonymous
Select background
What about ICL files?
Vista & Win 7 icons
I wish there were...