Question : Windows user authentication in VB.NET 3.5SP1

I have a slightly unusual problem. I am writing a VB.NET Windows Forms application that manages Windows Task Scheduler tasks. As part of my requirements, the user must be able to specify the Windows user under whose privileges the task will run. This won't necessarily be the currently connected user.

The user could be an account on the local system, or an account on the local domain. The software will need to run on Windows XP, Server 2003, Vista, Server 2008 and Windows 7.

Initially, I was using a piece of code that used SSPI to authenticate against LDAP, but that won't seem to authenticate a local user when the system is attached to a domain and won't authenticate a domain user (even the currently logged in user) if you're disconnected from the network.

So, I switched to a function which used LogonUser to authenticate the user which worked great until I tried it on Vista and found that the users need a specific permission before they can be authenticated using this method.

I have attached my existing functions which are modifications of code I found on the internet.

I appreciate any help I can get.
Code Snippet:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
''' 
    ''' Validates whether the passed Windows user Id and password are valid.
    ''' 
    ''' The Windows user name with domain prepended if necessary.
    ''' The user's Windows password.
    ''' The user's Windows password again.
    ''' True if the user's ID and password are correct.
    ''' 
    Public Function ValidateUserIdPassword(ByVal szUserName As String, ByVal szPassword As String, ByVal szPasswordVerify As String) As Boolean
        Dim bRetVal As Boolean = False
        Console.WriteLine("Validating user {0}", szUserName)
        If szPassword.Equals(szPasswordVerify) Then
            Console.WriteLine("Passwords match.")
            AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal) 
            ' Retrieve the computer name
            Dim machName As String = Environment.MachineName
            Dim MyNamespace As IADsOpenDSObject
            Dim oUserValidation As Object
            Dim DN As String = "LDAP://rootDSE"
            Dim ADS_AUTHENTICATION_SECURE As New ADS_AUTHENTICATION_ENUM
            MyNamespace = GetObject("LDAP:")
            'For authentication, pass in a variable for the user name and password that you wish to use for
            'authentication purposes. It is recommended that you use the ADS_AUTHENTICATION_SECURE flag for
            'security reasons. 
            Try
                oUserValidation = MyNamespace.OpenDSObject(DN, szUserName, szPassword, ADS_AUTHENTICATION_SECURE)
                bRetVal = True
            Catch ex As Exception
                ' Do nothing
                Console.WriteLine("Error {0}", ex.Message)
            End Try
        End If
\        Return bRetVal
    End Function 
    ''' 
    ''' Validates a Windows user Id, domain and password combination.
    ''' 
    ''' The Windows user name. This can be in the form of username, domain\username, or username@domain. If the domain is specified like this, leave the szUserDomain parameter blank.
    ''' The Windows machine name or domain. If the domain is specified as part of the user name, pass an empty string in this parameter.
    ''' The user's password.
    ''' A password validation check.
    ''' True if all the information is correct. False if one or more items were incorrect.
    ''' Uses the Windows API LogonUser function to check the security context. The context is restore before the function exits.
    Public Function ValidateUserIdPassword(ByVal szUserName As String, ByVal szUserDomain As String, ByVal szPassword As String, ByVal szPasswordVerify As String) As Boolean
        ' Set default return value.
        Dim bRetVal As Boolean = False
        ' Compare passwords.
        If szPassword.Equals(szPasswordVerify) Then
            ' Password is same in password and password verify variables.
            If szUserDomain = "" Then
                If szUserName.Contains("\") Then
                    ' Pull the domain out of the user name.
                    szUserDomain = szUserName.Substring(0, szUserName.IndexOf("\"))
                    ' Set user name to just user name.
                    szUserName = szUserName.Substring(szUserName.IndexOf("\") + 1)
                ElseIf szUserName.Contains("@") Then
                    ' User Name is in form of "user@domain"
                    ' Pull the domain out of the user name.
                    szUserDomain = szUserName.Substring(szUserName.IndexOf("@") + 1)
                    ' Set user name to just user name.
                    szUserName = szUserName.Substring(0, szUserName.IndexOf("@"))
                Else
                    ' A "." refers to the local system.
                    szUserDomain = "."
                End If 
            End If
            Console.WriteLine("Validating user {0}, Domain {1}", szUserName, szUserDomain) 
            Dim lphToken As IntPtr = IntPtr.Zero 
            ' Attempt to log in to the machine/domain. Function returns True or False depending on the user Id, password and domain work.
            bRetVal = LogonUser(szUserName, szUserDomain, szPassword, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT, lphToken) 
            ' Close the handle received.
            CloseHandle(lphToken)
            ' Revert security to the currently logged in user.
            RevertToSelf()
        End If 
        Return bRetVal
    End Function

Answer : Windows user authentication in VB.NET 3.5SP1

I've come up with a solution. The task scheduler part is immaterial. It was just explaining why I need to get the user credentials and why I needed to validate they were correct. LogonUser, for my purposes, wasn't effective on Vista and newer operating systems.

A co-worker of mine found a potential solution at http://social.msdn.microsoft.com/Forums/fi-FI/netfxnetcom/thread/2a9dd787-74dd-4e15-b866-1502753c30f9 and I modified it to the code which I have attached. I think this should solve my issues, at least until I run into the next problem.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
    Public Function PlainTextValidateUser(ByVal szUserName As String, ByVal szPassword As String) As Boolean
        Dim result As Boolean = False
        Dim szUserDomain As String = ""
        Dim myLDAPPath As String

        ' Determine what the domain name should be.
        If szUserName.Contains("\") Then
            ' Pull the domain out of the user name.
            szUserDomain = szUserName.Substring(0, szUserName.IndexOf("\"))
            ' Set user name to just user name.
            szUserName = szUserName.Substring(szUserName.IndexOf("\") + 1)
        ElseIf szUserName.Contains("@") Then
            ' User Name is in form of "user@domain"
            ' Pull the domain out of the user name.
            szUserDomain = szUserName.Substring(szUserName.IndexOf("@") + 1)
            ' Set user name to just user name.
            szUserName = szUserName.Substring(0, szUserName.IndexOf("@"))
        Else
            ' A "." refers to the local system.
            szUserDomain = Environment.MachineName
            szUserName = szUserName.Substring(szUserName.IndexOf("\") + 1)
        End If

        ' Determine which entry it will need to be.
        If String.Compare(szUserDomain, Environment.MachineName, True) = 0 Then
            myLDAPPath = "WinNT://" & szUserDomain
        Else
            myLDAPPath = "LDAP://" & szUserDomain
        End If

        Try
            Dim entry As DirectoryEntry = New DirectoryEntry(myLDAPPath, szUserName, szPassword)
            Dim nativeObject As Object = entry.NativeObject
            result = True 'no exception thrown, user must exist
            nativeObject = Nothing  'be sure and clean up these object as this service could be used many times
            entry = Nothing
        Catch ex As Exception
            result = False  'exception thrown - no user with that name/pwd combination
        End Try
        Return result
    End Function
Random Solutions  
 
programming4us programming4us