views:

407

answers:

1

--Update #2--

Here is the final code that worked for me. This uses WNetAddConnection2 to establish a connection first, before using DirectoryEntry.

Imports System.Runtime.InteropServices
Imports System.Net
Imports System.DirectoryServices
Imports System.IO

Module Module1

    Sub Main()
        Dim Computername As String = "SomeComputer"
        'Create connection to remote computer'
        Using nc As New NetworkConnection("\\" + Computername + "", New NetworkCredential("Domain\Login", "Password"))
            'try connecting using DirectoryEntry to the same machine and add me as a user'
            Dim strUserPath As String = String.Format("WinNT://{0}/{1},user", "DOMAIN", "USER")
            Dim deGroup As New DirectoryEntry("WinNT://" + Computername + "/Administrators")
            deGroup.RefreshCache()

            'add and remove the user from the group'
            deGroup.Invoke("Add", strUserPath)
            deGroup.CommitChanges()
            Console.WriteLine("User Added to computer " + Computername)

            deGroup.Invoke("Remove", strUserPath)
            deGroup.CommitChanges()
            Console.WriteLine("User Removed from computer " + Computername)

            deGroup.Close()
        End Using
        Console.ReadLine()
    End Sub

    Public Class NetworkConnection
        Implements IDisposable


        Private _networkName As String

        Public Sub New(ByVal networkName As String, ByVal credentials As NetworkCredential)
            _networkName = networkName

            Dim netResource = New NetResource() With { _
             .Scope = ResourceScope.GlobalNetwork, _
             .ResourceType = ResourceType.Disk, _
             .DisplayType = ResourceDisplaytype.Share, _
             .RemoteName = networkName _
            }

            Dim result = WNetAddConnection2(netResource, credentials.Password, credentials.UserName, 0)

            If result <> 0 Then
                Throw New IOException("Error connecting to remote share", result)
            End If
        End Sub

        Protected Overrides Sub Finalize()
            Try
                Dispose(False)
            Finally
                MyBase.Finalize()
            End Try
        End Sub

        Public Sub Dispose() Implements System.IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub

        Protected Sub Dispose(ByVal disposing As Boolean)
            WNetCancelConnection2(_networkName, 0, True)
        End Sub

        <DllImport("mpr.dll")> _
        Private Shared Function WNetAddConnection2(ByVal netResource As NetResource, ByVal password As String, ByVal username As String, ByVal flags As Integer) As Integer
        End Function

        <DllImport("mpr.dll")> _
        Private Shared Function WNetCancelConnection2(ByVal name As String, ByVal flags As Integer, ByVal force As Boolean) As Integer
        End Function
    End Class

    <StructLayout(LayoutKind.Sequential)> _
    Public Class NetResource
        Public Scope As ResourceScope
        Public ResourceType As ResourceType
        Public DisplayType As ResourceDisplaytype
        Public Usage As Integer
        Public LocalName As String
        Public RemoteName As String
        Public Comment As String
        Public Provider As String
    End Class

    Public Enum ResourceScope As Integer
        Connected = 1
        GlobalNetwork
        Remembered
        Recent
        Context
    End Enum

    Public Enum ResourceType As Integer
        Any = 0
        Disk = 1
        Print = 2
        Reserved = 8
    End Enum

    Public Enum ResourceDisplaytype As Integer
        Generic = &H0
        Domain = &H1
        Server = &H2
        Share = &H3
        File = &H4
        Group = &H5
        Network = &H6
        Root = &H7
        Shareadmin = &H8
        Directory = &H9
        Tree = &HA
        Ndscontainer = &HB
    End Enum
End Module

--Update--

To work around the issue we created a windows service that runs under the local admin account and thus doesn't have any issues running the code. We push all of our updates to a table in a SQL database and the service picks them up and processes them. BUT, I still really would like to know why this doesn't work, and it would be nice to push updates straight from the web site.

--Original Post--

I am using DirectoryServices and the WinNT:// provider to connect to a remote computer. I then check some group membership information and possibly add or remove a domain user from a specified local group.

I have been able to get all of this code working without a hitch using a vb.net console application and when communicating with my local box, or with any box where the account I am logged in under has administrative rights.

Code:

Dim strUserPath as string = "WinNT://DomainName/someuser,user"
Dim deComputer As New DirectoryEntry("WinNT://" + Computername + ",computer")
deComputer.RefreshCache()
Dim deGroup As DirectoryEntry = deComputer.Children.Find("administrators", "group")

Dim members As IEnumerable = deGroup.Invoke("members", Nothing)
Dim r As New List(Of DirectoryEntry)()

For Each o As Object In members
    Dim deMember As DirectoryEntry = New DirectoryEntry(o)

    r.Add(deMember)
Next

 deGroup.Invoke("Add", strUserPath)
 deGroup.CommitChanges()

 deGroup.Invoke("Remove", strUserPath)
 deGroup.CommitChanges()

So I moved the code to an ASP.Net web app, which is impersonating a service account through the Impersonate section of web.config. The account I am impersonating does not have admin rights on any of the workstations so I put in a username/password into the constructor for the computer entry like so:

Dim deComputer As New DirectoryEntry("WinNT://" + Computername + ",computer", username, password)

The username is that of a domain account which has local admin rights on every workstation. If I look at the Username property of the resulting deComputer object I can see that the username matches what I entered. Also if I enter in an invalid password it throws an error, so it is authenticating in some fashion.

However if I now try and add or remove a user from a remote workstation I get a general access denied error. If I add the service account that ASP.Net is using as a local admin on that workstation it will add and remove no problem.

So next I tried using the LogonAPI (advapi32.dll ->LogonUser call) to login as the user account that is a local admin on all workstations, impersonated the resulting WindowsIdentitiy and tried running just the original deComputer instantiation. When I do this every property, excepty Path, returns an OLE exception...

I'm pretty lost here on what to try next. Any help would be greatly appreciated.

A: 

Do you tried to use AuthenticationTypes.Secure as an additional parameter of DirectoryEntry after the username and the password?

By the way if you want connect to remote computer you should not use LogonUser. Correct API are WNetAddConnection2 (see http://msdn.microsoft.com/en-us/library/aa385413.aspx) or NetUseAdd (see http://msdn.microsoft.com/en-us/library/aa370645.aspx)

Oleg
I have tried AuthenticationTypes.Secure, but without any success. I read through the main descriptions on MSDN and it doesn't sound like WNetAddConnection2 and NetUseAdd will work for all situations. Also, how would I use them with a DirectoryEntry? I didn't see any easy way in the MSDN article, not like how easy it is when you use Logonuser.
Patricker
Function `LogonUser` makes local login, but you need to have remote login. It you want for example access a bank with HTTPS you will not make a `LogonUser` call with your bank account, but you do only remote login on the destination computer. The same is working here. If destination computer has no trust to the source computer or you try connect with a user account existing (locally) only on the destination computer `LogonUser` with impersonation CAN NOT WORK! You can use `WNetAddConnection2` or `NetUseAdd` without any problem.
Oleg
If you make a connection to a destination computer with respect of `WNetAddConnection2` or `NetUseAdd` it will be used on different other access attempts to the remote computer. Also `DirectoryEntry` with `WinNT` provider will use it. To disconnect you can use `WNetCancelConnection2` or `NetUseDel`. Some more explanation you can find under http://stackoverflow.com/questions/3282927/manage-remote-service-using-alternate-credentials/3348967#3348967. I recommend you just try `WNetAddConnection2` and `WNetCancelConnection2` and you will see that they do exactly what you need.
Oleg
Along with the other question you helped me with, this worked for this one too. I've posted the code in my question since you didn't provide any.
Patricker