//****************************************************************************************************** // 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