3
votes

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:

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

    IEnumerable members = deGroup.Invoke("members", null);
    List<DirectoryEntry> r = new List<DirectoryEntry>();

    foreach (object o in members)
    {
        DirectoryEntry deMember = new DirectoryEntry(o);

        r.Add(deMember);
    }

    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:

DirectoryEntry deComputer = 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.

--Workaround--

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.

3

3 Answers

1
votes

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)

0
votes

Since this is a popular question, I've separated the answer out and converted the code to C#

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

public static class CredentialSetter
{
    public static void SetCredentials()
    {
        string Computername = "SomeComputer";
        //Create connection to remote computer'
        using (NetworkConnection nc = new NetworkConnection("\\\\" + Computername + "", new NetworkCredential("Domain\\Login", "Password")))
        {
            //try connecting using DirectoryEntry to the same machine and add me as a user'
            string strUserPath = string.Format("WinNT://{0}/{1},user", "DOMAIN", "USER");
            DirectoryEntry deGroup = 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();
        }
        Console.ReadLine();
    }

    public class NetworkConnection : IDisposable
    {
        private string _networkName;
        public NetworkConnection(string networkName, NetworkCredential credentials)
        {
            _networkName = networkName;

            dynamic netResource = new NetResource
            {
                Scope = ResourceScope.GlobalNetwork,
                ResourceType = ResourceType.Disk,
                DisplayType = ResourceDisplaytype.Share,
                RemoteName = networkName
            };

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

            if (result != 0)
            {
                throw new IOException("Error connecting to remote share", result);
            }
        }

        ~NetworkConnection()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            WNetCancelConnection2(_networkName, 0, true);
        }

        [DllImport("mpr.dll")]
        private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags);

        [DllImport("mpr.dll")]
        private static extern int WNetCancelConnection2(string name, int flags, bool force);
    }

    [StructLayout(LayoutKind.Sequential)]
    public class NetResource
    {
        public ResourceScope Scope;
        public ResourceType ResourceType;
        public ResourceDisplaytype DisplayType;
        public int Usage;
        public string LocalName;
        public string RemoteName;
        public string Comment;
        public string Provider;
    }

    public enum ResourceScope : int
    {
        Connected = 1,
        GlobalNetwork,
        Remembered,
        Recent,
        Context
    }

    public enum ResourceType : int
    {
        Any = 0,
        Disk = 1,
        Print = 2,
        Reserved = 8
    }

    public enum ResourceDisplaytype : int
    {
        Generic = 0x0,
        Domain = 0x1,
        Server = 0x2,
        Share = 0x3,
        File = 0x4,
        Group = 0x5,
        Network = 0x6,
        Root = 0x7,
        Shareadmin = 0x8,
        Directory = 0x9,
        Tree = 0xa,
        Ndscontainer = 0xb
    }
}
-1
votes

Error (0x80004005): Unspecified error

I had the some problem connecting to the remote windows with the error Error (0x80004005): Unspecified error. I resolved as follows:

//Define path
//This path uses the full path of user authentication
String path = string.Format("WinNT://{0}/{1},user", server_address, username);
DirectoryEntry deBase = null;
try
{
    //Try to connect with secure connection
    deBase = new DirectoryEntry(path, username, _passwd, AuthenticationTypes.Secure);

    //Connection test
    //After test define the deBase with the parent of user (root container)
    object nativeObject = deBase.NativeObject;
    deBase = deBase.Parent;

}
catch (Exception ex)
{
    //If an error occurred try without Secure Connection
    try
    {
        deBase = new DirectoryEntry(path, username, _passwd);

        //Connection test
        //After test define the deBase with the parent of user (root container)
        object nativeObject = deBase.NativeObject;
        deBase = deBase.Parent;
        nativeObject = deBase.NativeObject;

    }
    catch (Exception ex2)
    {
        //If an error occurred throw the error
        throw ex2;
    }
}

Hope that helps. Helvio Junior www.helviojunior.com.br