2

So I am using a user to run the following code that is a member of the "User" group on a Windows 7, x64 machine. I am trying to use impersonation (by logging in as a user that is part of the Administrator group) to allow the current user to read from the registry. For some reason the login happens successfully but even though WindowsIdentity.GetCurrent() is returning the user that is part of the Administrator group I am still getting an error message saying "Requested registry access is not allowed". What am I doing wrong?

This is the main code:

            Dim ra As RunAs = Nothing
            If UserDomain.Length > 0 AndAlso UserName.Length > 0 AndAlso UserPassword.Length > 0 Then
                ra = New RunAs
                ra.ImpersonateStart(UserDomain, UserName, UserPassword)
            End If
            If Not My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting", "DontShowUI", 0) Is Nothing AndAlso _
            My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting", "DontShowUI", 0) = 0 Then
                    My.Computer.Registry.SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting", "DontShowUI", 1)
            End If

And suppose my RunAs class is the following:

Public Class RunAs
 Public Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Boolean

    Public Declare Auto Function DuplicateToken Lib "advapi32.dll" (ByVal ExistingTokenHandle As IntPtr, _
      ByVal SECURITY_IMPERSONATION_LEVEL As Integer, _
      ByRef DuplicateTokenHandle As IntPtr) As Boolean

    ' Test harness.
    ' If you incorporate this code into a DLL, be sure to demand FullTrust.
    <PermissionSetAttribute(SecurityAction.Demand, Name:="FullTrust")> _
    Public Sub ImpersonateStart(ByVal Domain As String, ByVal userName As String, ByVal Password As String)
            tokenHandle = IntPtr.Zero
            ' Call LogonUser to obtain a handle to an access token.
            Dim returnValue As Boolean = LogonUser(userName, Domain, Password, 2, 0, tokenHandle)

            'check if logon successful
            If returnValue = False Then
                Dim ret As Integer = Marshal.GetLastWin32Error()
                Console.WriteLine("LogonUser failed with error code : {0}", ret)
                Throw New System.ComponentModel.Win32Exception(ret)
                Exit Sub
            End If

            'Logon succeeded

            ' Use the token handle returned by LogonUser.
            Dim newId As New WindowsIdentity(tokenHandle)
            impersonatedUser = newId.Impersonate()
    End Sub
End Class
John Saunders
  • 160,644
  • 26
  • 247
  • 397
Denis
  • 11,796
  • 16
  • 88
  • 150
  • This cannot work, there is no user account on a regular machine that can write to HKLM. UAC prevents this. You'll have to get the process elevated, that requires going through the UAC prompt. Or run this code in an environment where you can use the privileged System account. A service or a scheduled task. – Hans Passant Sep 27 '11 at 17:56
  • Hans, why can't I impersonate a user that is part of the Administrator group and write to HKLM? – Denis Sep 27 '11 at 17:59
  • 1
    Because being an admin doesn't give you enough rights. Everybody always was an admin on Windows, that's why UAC was necessary. – Hans Passant Sep 27 '11 at 18:01
  • Hans, where can I find out more about UAC? I don't understand why an admin doesn't have rights to read/write the registry - I thought an admin could do anything - that's why he was the admin. – Denis Sep 27 '11 at 18:01
  • Google "user account control", the top hits all look good. – Hans Passant Sep 27 '11 at 18:06
  • Thanks for your info but what you are saying doesn't sound right because if I run the same application logged in as an Administrator it works just fine (can read/write registry no problem) so impersonating an administrator SHOULD work. Something else is happening here. – Denis Sep 27 '11 at 18:16
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/3835/discussion-between-denis-and-hans-passant) – Denis Sep 27 '11 at 18:16
  • This code works just fine if I login as the user I am trying to impersonate (the user that is part of the admin group) and run it. I just can't figure out why the impersonation is not working... – Denis Sep 27 '11 at 18:30

1 Answers1

4

I agree with @Hans. With UAC, you need to restart the application with UAC privileges, which will cause the UAC prompt to display. A simple way to accomplish this is as follows:

  1. In normal path of the application, when admin privileges are needed, restart the application with UAC request and a command-line flag, like /admin.
  2. On second run of the application, detect the flag /admin, and do the administrative part of the application.
  3. When the second run finishes (#2), if it was successful, then continue application logic from the first pass. If not successful, display error/perform appropriate exception handling logic.

In our application, I have a helper method called RunElevated that attempts to restart the application with requested administrator privileges, which will cause the UAC prompt to display (I have also included my IsAdmin() helper function):

Private Function RunElevated(commandLine As String, Optional ByVal timeout As Integer = 0) As Boolean
    Dim startInfo As New ProcessStartInfo
    startInfo.UseShellExecute = True
    startInfo.WorkingDirectory = Environment.CurrentDirectory
    Dim uri As New Uri(Assembly.GetEntryAssembly.GetName.CodeBase)
    startInfo.FileName = uri.LocalPath
    startInfo.Verb = "runas"
    startInfo.Arguments = commandLine

    Dim success As Boolean
    Try
        Dim p As Process = Process.Start(startInfo)
        ' wait thirty seconds for completion
        If timeout > 0 Then
            If Not p.WaitForExit(30000) Then
                ' did not complete in thirty seconds, so kill
                p.Kill()
                success = False
            Else
                success = True
            End If
        Else
            p.WaitForExit()
            success = True
        End If
    Catch ex As Win32Exception
        success = False
    Catch ex As Exception
        MsgBox("Error occurred while trying to start application as administrator: " & ex.Message)
        success = False
    End Try
    Return success
End Function

Public Function IsAdmin() As Boolean
    Dim id As WindowsIdentity = WindowsIdentity.GetCurrent
    Dim p As New WindowsPrincipal(id)
    Return p.IsInRole(WindowsBuiltInRole.Administrator)
End Function

To use, I pass a flag and run elevated. In my case, I have a function that sets registry keys, and uses the flag /setregistry to indicate that the instance is started for UAC purposes to just set the registry keys. That code looks something like this:

    Dim success As Boolean
    If Not IsAdmin() Then
        ' try to create the registry keys as administrator
        success = RunElevated("/setregistry", 30000)
        success = success And ValidateKeysSet() ' check if it was successful
        Return success
    End If

    ' If we are Admin (Not IsAdmin() = False), then go ahead and set the keys here

Then in the startup logic (Form_Load, since this is a forms application), I check if that flag is present:

    If Command.ToLower.Contains("/setregistry") Then
        ' if application instance is for sole purpose of setting registry keys as admin
        If IsAdmin() Then
            SetRegistryKeys() ' set the keys, since we are admin
        Else
            MsgBox("ERROR: Application must be run as administrator to set registry keys.")
        End If
    Else
        ' Perform normal startup process
    End If
mellamokb
  • 56,094
  • 12
  • 110
  • 136
  • The application I am working with runs automatically at Startup. Prompting for UAC is not an option. – Denis Sep 27 '11 at 19:28
  • I still don't really understand why impersonation doesn't work to accomplish this... – Denis Sep 27 '11 at 19:29
  • Also, I just changed "User Account Control Settings" to "Never Notify". In MSDN it says this "Disables UAC". That didn't change anything... – Denis Sep 27 '11 at 19:35
  • @Dennis " Prompting for UAC is not an option." Wrong. It's your only option. – David Heffernan Sep 27 '11 at 19:51
  • You can also try using an application manifest to require administrator privileges to run it to begin with. Then windows will take care of showing the UAC prompt it applicable. See here for example: http://community.bartdesmet.net/blogs/bart/archive/2006/10/28/Windows-Vista-_2D00_-Demand-UAC-elevation-for-an-application-by-adding-a-manifest-using-mt.exe.aspx – mellamokb Sep 27 '11 at 20:04
  • @mellamokb In recent Visual Studios that's trivially arranged in the project config. – David Heffernan Sep 27 '11 at 20:09