//******************************************************************************************************
// UserAccountControl.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 02/18/2011 - J. Ritchie Carroll
// Generated original version of source code.
// 09/21/2011 - J. Ritchie Carroll
// Excluded class from Mono deployments due to P/Invoke requirements.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
// These functions are strictly for Windows, so we don't even have the class show up in Mono for now
// TODO: Can test if this works under Mono on Windows then just exit code when not POSIX
#if !MONO
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security.Principal;
using GSF.Interop;
using Microsoft.Win32;
namespace GSF.Identity
{
///
/// Provides facilities for enabling and disabling User Account Control (UAC), determining elevation and virtualization status, and launching a process under elevated credentials.
///
public static class UserAccountControl
{
private const string UacRegistryKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
private const string UacRegistryValue = "EnableLUA";
///
/// Creates a process under the elevated token, regardless of UAC settings or the manifest associated with that process.
///
/// The path to the executable file.
/// The command-line arguments to pass to the process.
/// A object representing the newly created process.
public static Process CreateProcessAsAdmin(string fileName, string arguments = "")
{
ProcessStartInfo startInfo = new ProcessStartInfo(fileName, arguments);
startInfo.UseShellExecute = true;
startInfo.Verb = "runas";
return Process.Start(startInfo);
}
///
/// Creates a process under the standard user if the current process is elevated. The identity of the standard user is
/// determined by retrieving the user token of the currently running Explorer (shell) process.
///
/// The path to the executable file.
/// The command-line arguments to pass to the process.
/// A object representing the newly created process.
///
///
/// This method requires administrative privileges. An exception will be thrown if the current user is not elevated.
///
///
/// This is an especially useful function if you are trying to shell an application from an installation program. With UAC
/// enabled, an application spawned from a setup program will be the "NT AUTHORITY\SYSTEM" user - not the local user that
/// executed the installer; this can wreak havoc if the spawned application needs to authenticate the local user.
///
///
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults")]
public static Process CreateProcessAsStandardUser(string fileName, string arguments = "")
{
// The following implementation is roughly based on Aaron Margosis' post:
// 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
IntPtr hProcessToken = IntPtr.Zero;
IntPtr hShellProcess = IntPtr.Zero;
IntPtr hShellProcessToken = IntPtr.Zero;
IntPtr hPrimaryToken = IntPtr.Zero;
try
{
if (!WindowsApi.OpenProcessToken(WindowsApi.GetCurrentProcess(), WindowsApi.TOKEN_ADJUST_PRIVILEGES, ref hProcessToken))
throw new Win32Exception(WindowsApi.GetLastError());
// Enable SeIncreaseQuotaPrivilege in this process. (This requires administrative privileges.)
WindowsApi.TOKEN_PRIVILEGES tkp = new WindowsApi.TOKEN_PRIVILEGES();
long luid = 0;
int returnLen = 0;
WindowsApi.LookupPrivilegeValue(null, WindowsApi.SE_INCREASE_QUOTA_NAME, ref luid);
tkp.PrivilegeCount = 1;
tkp.Privileges.Luid = luid;
tkp.Privileges.Attributes = WindowsApi.SE_PRIVILEGE_ENABLED;
WindowsApi.AdjustTokenPrivileges(hProcessToken, false, ref tkp, 0, IntPtr.Zero, ref returnLen);
int dwLastErr = WindowsApi.GetLastError();
if (dwLastErr != 0)
throw new Win32Exception(dwLastErr);
// Get window handle representing the desktop shell. This might not work if there is no shell window, or when
// using a custom shell. Also note that we're assuming that the shell is not running elevated.
IntPtr hShellWnd = WindowsApi.GetShellWindow();
if (hShellWnd == IntPtr.Zero)
throw new InvalidOperationException("Unable to locate shell window. System might be using a custom shell.");
// Get the ID of the desktop shell process.
uint dwShellPID;
WindowsApi.GetWindowThreadProcessId(hShellWnd, out dwShellPID);
if (dwShellPID == 0)
throw new Win32Exception(WindowsApi.GetLastError());
// Open the desktop shell process in order to get the process token.
hShellProcess = WindowsApi.OpenProcess(WindowsApi.ProcessAccessTypes.PROCESS_QUERY_INFORMATION, false, dwShellPID);
if (hShellProcess == IntPtr.Zero)
throw new Win32Exception(WindowsApi.GetLastError());
// Get the process token of the desktop shell.
if (!WindowsApi.OpenProcessToken(hShellProcess, WindowsApi.TOKEN_DUPLICATE, ref hShellProcessToken))
throw new Win32Exception(WindowsApi.GetLastError());
// Duplicate the shell's process token to get a primary token.
const uint dwTokenRights = WindowsApi.TOKEN_QUERY | WindowsApi.TOKEN_ASSIGN_PRIMARY | WindowsApi.TOKEN_DUPLICATE | WindowsApi.TOKEN_ADJUST_DEFAULT | WindowsApi.TOKEN_ADJUST_SESSIONID;
if (!WindowsApi.DuplicateTokenEx(hShellProcessToken, dwTokenRights, IntPtr.Zero, WindowsApi.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, WindowsApi.TOKEN_TYPE.TokenPrimary, out hPrimaryToken))
throw new Win32Exception(WindowsApi.GetLastError());
// Start the target process with the new token.
WindowsApi.PROCESS_INFORMATION pi;
WindowsApi.STARTUPINFO si = new WindowsApi.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
if (!WindowsApi.CreateProcessWithTokenW(hPrimaryToken, 0, null, fileName + " " + arguments, 0, IntPtr.Zero, null, ref si, out pi))
throw new Win32Exception(WindowsApi.GetLastError());
WindowsApi.CloseHandle(pi.hProcess);
WindowsApi.CloseHandle(pi.hThread);
return Process.GetProcessById(pi.ProcessId);
}
finally
{
if (hProcessToken != IntPtr.Zero)
WindowsApi.CloseHandle(hProcessToken);
if (hShellProcessToken != IntPtr.Zero)
WindowsApi.CloseHandle(hShellProcessToken);
if (hPrimaryToken != IntPtr.Zero)
WindowsApi.CloseHandle(hPrimaryToken);
if (hShellProcess != IntPtr.Zero)
WindowsApi.CloseHandle(hShellProcess);
}
}
///
/// Returns true if the current user has administrator privileges.
///
///
/// If UAC is on, then this property will return true even if the current process is not running elevated.
/// If UAC is off, then this property will return true if the user is part of the built-in Administrators group.
///
public static bool IsUserAdmin
{
get
{
if (IsUacEnabled)
return GetProcessTokenElevationType() != WindowsApi.TOKEN_ELEVATION_TYPE.TokenElevationTypeDefault; // Split token
// If UAC is off, we can't rely on the token; check for Admin group.
return (new WindowsPrincipal(WindowsIdentity.GetCurrent())).IsInRole("Administrators");
}
}
///
/// Returns true if User Account Control (UAC) is enabled on this machine.
///
///
/// This value is obtained by checking the LUA registry key. It is possible that the user has not restarted the machine after
/// enabling/disabling UAC. In that case, the value of the registry key does not reflect the true state of affairs.
///
public static bool IsUacEnabled
{
// Note: It might be possible to devise a custom solution that would provide a mechanism for tracking whether a restart occurred
// since UAC settings were changed (using the RunOnce mechanism, temporary files, or volatile registry keys).
get
{
// Check the HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA registry value.
RegistryKey key = Registry.LocalMachine.OpenSubKey(UacRegistryKey, false);
if ((object)key != null)
{
object value = key.GetValue(UacRegistryValue);
if ((object)value != null)
return value.Equals(1);
}
return false;
}
}
///
/// Returns true if the current process is using UAC virtualization.
///
///
/// Under UAC virtualization, file system and registry accesses to specific locations performed by an application are redirected to
/// provide backwards compatibility. 64-bit applications or applications that have an associated manifest do not enjoy UAC virtualization
/// because they are assumed to be compatible with Vista and UAC.
///
public static bool IsCurrentProcessVirtualized
{
get
{
IntPtr hToken = IntPtr.Zero;
try
{
if (!WindowsApi.OpenProcessToken(WindowsApi.GetCurrentProcess(), WindowsApi.TOKEN_QUERY, ref hToken))
throw new Win32Exception(WindowsApi.GetLastError());
uint virtualizationEnabled = 0;
uint dwSize;
if (!WindowsApi.GetTokenInformation(hToken, WindowsApi.TOKEN_INFORMATION_CLASS.TokenVirtualizationEnabled, ref virtualizationEnabled, sizeof(uint), out dwSize))
throw new Win32Exception(WindowsApi.GetLastError());
return (virtualizationEnabled != 0);
}
finally
{
if (hToken != IntPtr.Zero)
WindowsApi.CloseHandle(hToken);
}
}
}
///
/// Returns true if the current process is elevated, i.e. if the process went through an elevation consent phase.
///
///
/// This property will return false if UAC is disabled and the process is running as admin. It only determines whether the process
/// went through the elevation procedure.
///
public static bool IsCurrentProcessElevated
{
get
{
return GetProcessTokenElevationType() == WindowsApi.TOKEN_ELEVATION_TYPE.TokenElevationTypeFull; // elevated
}
}
///
/// Disables User Account Control by changing the LUA registry key. The changes do not have effect until the system is restarted.
///
public static void DisableUac()
{
SetUacRegistryValue(false);
}
///
/// Disables User Account Control and restarts the system.
///
public static void DisableUacAndRestartWindows()
{
DisableUac();
RestartWindows();
}
///
/// Enables User Account Control by changing the LUA registry key. The changes do not have effect until the system is restarted.
///
public static void EnableUac()
{
SetUacRegistryValue(true);
}
///
/// Enables User Account Control and restarts the system.
///
public static void EnableUacAndRestartWindows()
{
EnableUac();
RestartWindows();
}
private static WindowsApi.TOKEN_ELEVATION_TYPE GetProcessTokenElevationType()
{
IntPtr hToken = IntPtr.Zero;
try
{
if (!WindowsApi.OpenProcessToken(WindowsApi.GetCurrentProcess(), WindowsApi.TOKEN_QUERY, ref hToken))
throw new Win32Exception(WindowsApi.GetLastError());
WindowsApi.TOKEN_ELEVATION_TYPE elevationType = WindowsApi.TOKEN_ELEVATION_TYPE.TokenElevationTypeDefault;
uint dwSize;
if (!WindowsApi.GetTokenInformation(hToken, WindowsApi.TOKEN_INFORMATION_CLASS.TokenElevationType, ref elevationType, (uint)sizeof(WindowsApi.TOKEN_ELEVATION_TYPE), out dwSize))
throw new Win32Exception(WindowsApi.GetLastError());
return elevationType;
}
finally
{
if (hToken != IntPtr.Zero)
WindowsApi.CloseHandle(hToken);
}
}
private static void SetUacRegistryValue(bool enabled)
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(UacRegistryKey, true);
if ((object)key != null)
key.SetValue(UacRegistryValue, enabled ? 1 : 0);
}
private static void RestartWindows()
{
WindowsApi.InitiateSystemShutdownEx(
null, null, 0 /*Timeout*/,
true /*ForceAppsClosed*/,
true /*RebootAfterShutdown*/,
WindowsApi.ShutdownReason.SHTDN_REASON_MAJOR_OPERATINGSYSTEM |
WindowsApi.ShutdownReason.SHTDN_REASON_MINOR_RECONFIG |
WindowsApi.ShutdownReason.SHTDN_REASON_FLAG_PLANNED);
// This shutdown flag corresponds to: "Operating System: Reconfiguration (Planned)".
}
}
}
#endif