StephaneLenclud@394: #include "UserAccountControl.h"
StephaneLenclud@394: 
StephaneLenclud@394: #include <windows.h>
StephaneLenclud@394: #pragma comment (lib, "kernel32.lib")
StephaneLenclud@394: #pragma comment (lib, "advapi32.lib")
StephaneLenclud@394: 
StephaneLenclud@394: #include <vcclr.h>
StephaneLenclud@394: #include <msclr\marshal.h>
StephaneLenclud@394: #include <msclr\marshal_windows.h>
StephaneLenclud@394: 
StephaneLenclud@394: using namespace msclr::interop;
StephaneLenclud@394: 
StephaneLenclud@394: using namespace System::ComponentModel;
StephaneLenclud@394: using namespace Microsoft::Win32;
StephaneLenclud@394: 
StephaneLenclud@394: namespace UacHelpers {
StephaneLenclud@394: 
StephaneLenclud@394: 	Process^ UserAccountControl::CreateProcessAsAdmin(System::String^ exePath, System::String^ arguments)
StephaneLenclud@394:     {
StephaneLenclud@394:         ProcessStartInfo^ psi = gcnew ProcessStartInfo(exePath, arguments);
StephaneLenclud@394:         psi->UseShellExecute = true;
StephaneLenclud@394:         psi->Verb = "runas";
StephaneLenclud@394: 		return Process::Start(psi);
StephaneLenclud@394:     }
StephaneLenclud@394: 
StephaneLenclud@394: 	Process^ UserAccountControl::CreateProcessAsStandardUser(System::String^ exePath, System::String^ arguments)
StephaneLenclud@394: 	{
StephaneLenclud@394: 		marshal_context context;
StephaneLenclud@394: 
StephaneLenclud@394: 		//If the current process is not elevated, then there's no reason to go through the hassle --
StephaneLenclud@394: 		//just use the standard System.Diagnostics.Process facilities.
StephaneLenclud@394: 		if (!IsCurrentProcessElevated)
StephaneLenclud@394: 		{
StephaneLenclud@394: 			return Process::Start(exePath, arguments);
StephaneLenclud@394: 		}
StephaneLenclud@394: 
StephaneLenclud@394: 		//The following implementation is roughly based on Aaron Margosis' post:
StephaneLenclud@394: 		//http://blogs.msdn.com/aaron_margosis/archive/2009/06/06/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app.aspx
StephaneLenclud@394: 
StephaneLenclud@394: 		//Enable SeIncreaseQuotaPrivilege in this process.  (This requires administrative privileges.)
StephaneLenclud@394: 		HANDLE hProcessToken = NULL;
StephaneLenclud@394: 		if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken))
StephaneLenclud@394: 		{
StephaneLenclud@394: 			throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 		}
StephaneLenclud@394: 		else
StephaneLenclud@394: 		{
StephaneLenclud@394: 			TOKEN_PRIVILEGES tkp;
StephaneLenclud@394: 			tkp.PrivilegeCount = 1;
StephaneLenclud@394: 			LookupPrivilegeValueW(NULL, SE_INCREASE_QUOTA_NAME, &tkp.Privileges[0].Luid);
StephaneLenclud@394: 			tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
StephaneLenclud@394: 			AdjustTokenPrivileges(hProcessToken, FALSE, &tkp, 0, NULL, NULL);
StephaneLenclud@394: 			DWORD dwLastErr = GetLastError();
StephaneLenclud@394: 			CloseHandle(hProcessToken);
StephaneLenclud@394: 			if (ERROR_SUCCESS != dwLastErr)
StephaneLenclud@394: 			{
StephaneLenclud@394: 				throw gcnew Win32Exception(dwLastErr);
StephaneLenclud@394: 			}
StephaneLenclud@394: 		}
StephaneLenclud@394: 
StephaneLenclud@394: 		//Get window handle representing the desktop shell.  This might not work if there is no shell window, or when
StephaneLenclud@394: 		//using a custom shell.  Also note that we're assuming that the shell is not running elevated.
StephaneLenclud@394: 		HWND hShellWnd = GetShellWindow();
StephaneLenclud@394: 		if (hShellWnd == NULL)
StephaneLenclud@394: 		{
StephaneLenclud@394: 			throw gcnew System::InvalidOperationException("Unable to locate shell window; you might be using a custom shell");
StephaneLenclud@394: 		}
StephaneLenclud@394: 
StephaneLenclud@394: 		//Get the ID of the desktop shell process.
StephaneLenclud@394: 		DWORD dwShellPID;
StephaneLenclud@394: 		GetWindowThreadProcessId(hShellWnd, &dwShellPID);
StephaneLenclud@394: 		if (dwShellPID == 0)
StephaneLenclud@394: 		{
StephaneLenclud@394: 			throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 		}
StephaneLenclud@394: 
StephaneLenclud@394: 		//Open the desktop shell process in order to get the process token.
StephaneLenclud@394: 		HANDLE hShellProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwShellPID);
StephaneLenclud@394: 		if (hShellProcess == NULL)
StephaneLenclud@394: 		{
StephaneLenclud@394: 			throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 		}
StephaneLenclud@394: 
StephaneLenclud@394: 		HANDLE hShellProcessToken = NULL;
StephaneLenclud@394: 		HANDLE hPrimaryToken = NULL;
StephaneLenclud@394: 		try
StephaneLenclud@394: 		{
StephaneLenclud@394: 			//Get the process token of the desktop shell.
StephaneLenclud@394: 			if (!OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, &hShellProcessToken))
StephaneLenclud@394: 			{
StephaneLenclud@394: 				throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 			}
StephaneLenclud@394: 
StephaneLenclud@394: 			//Duplicate the shell's process token to get a primary token.
StephaneLenclud@394: 			const DWORD dwTokenRights = TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID;
StephaneLenclud@394: 			if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, NULL, SecurityImpersonation, TokenPrimary, &hPrimaryToken))
StephaneLenclud@394: 			{
StephaneLenclud@394: 				throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 			}
StephaneLenclud@394: 
StephaneLenclud@394: 			//Start the target process with the new token.
StephaneLenclud@394: 			STARTUPINFO si = {0}; si.cb = sizeof(si);
StephaneLenclud@394: 			PROCESS_INFORMATION pi = {0};
StephaneLenclud@394: 			if (!CreateProcessWithTokenW(hPrimaryToken, 0,
StephaneLenclud@394: 				context.marshal_as<LPCWSTR>(exePath), context.marshal_as<LPWSTR>(exePath + " " + arguments),
StephaneLenclud@394: 				0, NULL, NULL, &si, &pi))
StephaneLenclud@394: 			{
StephaneLenclud@394: 				throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 			}
StephaneLenclud@394: 			CloseHandle(pi.hProcess);
StephaneLenclud@394: 			CloseHandle(pi.hThread);
StephaneLenclud@394: 
StephaneLenclud@394: 			return Process::GetProcessById(pi.dwProcessId);
StephaneLenclud@394: 		}
StephaneLenclud@394: 		finally
StephaneLenclud@394: 		{
StephaneLenclud@394: 			if (hShellProcessToken != NULL)
StephaneLenclud@394: 				CloseHandle(hShellProcessToken);
StephaneLenclud@394: 
StephaneLenclud@394: 			if (hPrimaryToken != NULL)
StephaneLenclud@394: 				CloseHandle(hPrimaryToken);
StephaneLenclud@394: 
StephaneLenclud@394: 			if (hShellProcess != NULL)
StephaneLenclud@394: 				CloseHandle(hShellProcess);
StephaneLenclud@394: 		}
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	bool UserAccountControl::IsUserAdmin::get()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		if (UserAccountControl::IsUacEnabled)
StephaneLenclud@394: 			return GetProcessTokenElevationType() != TokenElevationTypeDefault;	//split token
StephaneLenclud@394: 
StephaneLenclud@394: 		//If UAC is off, we can't rely on the token; check for Admin group.
StephaneLenclud@394: 		return WindowsPrincipal(WindowsIdentity::GetCurrent()).IsInRole("Administrators");
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	bool UserAccountControl::IsUacEnabled::get()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		//Check the HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA registry value.
StephaneLenclud@394: 		RegistryKey^ key = Registry::LocalMachine->OpenSubKey(UacRegistryKey, false);
StephaneLenclud@394: 		return key->GetValue(UacRegistryValue)->Equals(1);
StephaneLenclud@394:     }
StephaneLenclud@394: 
StephaneLenclud@394: 	void UserAccountControl::DisableUac()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		SetUacRegistryValue(false);
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	void UserAccountControl::DisableUacAndRestartWindows()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		DisableUac();
StephaneLenclud@394: 		RestartWindows();
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	void UserAccountControl::EnableUac()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		SetUacRegistryValue(true);
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	void UserAccountControl::EnableUacAndRestartWindows()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		EnableUac();
StephaneLenclud@394: 		RestartWindows();
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	bool UserAccountControl::IsCurrentProcessElevated::get()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		return GetProcessTokenElevationType() == TokenElevationTypeFull;	//elevated
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	bool UserAccountControl::IsCurrentProcessVirtualized::get()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		HANDLE hToken;
StephaneLenclud@394:         try
StephaneLenclud@394:         {
StephaneLenclud@394:             if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
StephaneLenclud@394: 				throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 
StephaneLenclud@394:             DWORD virtualizationEnabled;
StephaneLenclud@394: 			DWORD dwSize;
StephaneLenclud@394: 			if (!GetTokenInformation(hToken, TokenVirtualizationEnabled, &virtualizationEnabled, sizeof(virtualizationEnabled), &dwSize))
StephaneLenclud@394:                 throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 
StephaneLenclud@394: 			return virtualizationEnabled != 0;
StephaneLenclud@394:         }
StephaneLenclud@394:         finally
StephaneLenclud@394:         {
StephaneLenclud@394:             CloseHandle(hToken);
StephaneLenclud@394:         }
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	int UserAccountControl::GetProcessTokenElevationType()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		HANDLE hToken;
StephaneLenclud@394:         try
StephaneLenclud@394:         {
StephaneLenclud@394:             if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
StephaneLenclud@394: 				throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 
StephaneLenclud@394:             TOKEN_ELEVATION_TYPE elevationType;
StephaneLenclud@394: 			DWORD dwSize;
StephaneLenclud@394:             if (!GetTokenInformation(hToken, TokenElevationType, &elevationType, sizeof(elevationType), &dwSize))
StephaneLenclud@394:                 throw gcnew Win32Exception(GetLastError());
StephaneLenclud@394: 
StephaneLenclud@394: 			return elevationType;
StephaneLenclud@394:         }
StephaneLenclud@394:         finally
StephaneLenclud@394:         {
StephaneLenclud@394:             CloseHandle(hToken);
StephaneLenclud@394:         }
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	void UserAccountControl::SetUacRegistryValue(bool enabled)
StephaneLenclud@394: 	{
StephaneLenclud@394: 		RegistryKey^ key = Registry::LocalMachine->OpenSubKey(UacRegistryKey, true);
StephaneLenclud@394: 		key->SetValue(UacRegistryValue, enabled ? 1 : 0);
StephaneLenclud@394: 	}
StephaneLenclud@394: 
StephaneLenclud@394: 	void UserAccountControl::RestartWindows()
StephaneLenclud@394: 	{
StephaneLenclud@394: 		InitiateSystemShutdownEx(NULL, NULL, 0/*Timeout*/,
StephaneLenclud@394: 								 TRUE/*ForceAppsClosed*/, TRUE/*RebootAfterShutdown*/,
StephaneLenclud@394: 								 SHTDN_REASON_MAJOR_OPERATINGSYSTEM | SHTDN_REASON_MINOR_RECONFIG | SHTDN_REASON_FLAG_PLANNED);
StephaneLenclud@394: 		//This shutdown flag corresponds to: "Operating System: Reconfiguration (Planned)".
StephaneLenclud@394: 	}
StephaneLenclud@394: }