views:

8875

answers:

6

Within c#, I need to be able to

  • Connect to a remote system, specifying username/password as appropriate
  • List the members of a localgroup on that system
  • Fetch the results back to the executing computer

So for example I would connect to \SOMESYSTEM with appropriate creds, and fetch back a list of local administrators including SOMESYSTEM\Administrator, SOMESYSTEM\Bob, DOMAIN\AlanH, "DOMAIN\Domain Administrators".

I've tried this with system.directoryservices.accountmanagement but am running into problems with authentication. Sometimes I get:

Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. (Exception from HRESULT: 0x800704C3)

The above is trying because there will be situations where I simply cannot unmap existing drives or UNC connections.

Other times my program gets UNKNOWN ERROR and the security log on the remote system reports an error 675, code 0x19 which is KDCERRPREAUTH_REQUIRED.

I need a simpler and less error prone way to do this!

A: 

You should be able to do this with System.DirectoryServices.DirectoryEntry. If you are having trouble running it remotely, maybe you could install something on the remote machines to give you your data via some sort of RPC, like remoting or a web service. But I think what you're trying should be possible remotely without getting too fancy.

Eric Z Beard
A: 

If Windows won't let you connect through it's login mechanism, I think your only option is to run something on the remote machine with an open port (either directly or through remoting or a web service, as mentioned).

Nick
+1  A: 

This should be easy to do using WMI. Here you have a pointer to some docs:

WMI Documentation for Win32_UserAccount

Even if you have no previous experience with WMI, it should be quite easy to turn that VB Script code at the bottom of the page into some .NET code.

Hope this helped!

dguaraglia
+1  A: 

I would recommend using the Win32 API function NetLocalGroupGetMembers. It is much more straight forward than trying to figure out the crazy LDAP syntax, which is necessary for some of the other solutions recommended here. As long as you impersonate the user you want to run the check as by calling "LoginUser", you should not run into any security issues.

You can find sample code for doing the impersonation here.

If you need help figuring out how to call "NetLocalGroupGetMembers" from C#, I reccomend that you checkout Jared Parson's PInvoke assistant, which you can download from codeplex.

If you are running the code in an ASP.NET app running in IIS, and want to impersonate the user accessing the website in order to make the call, then you may need to grant "Trusted for Delegation" permission to the production web server.

If you are running on the desktop, then using the active user's security credentials should not be a problem.

It is possible that you network admin could have revoked access to the "Securable Object" for the particular machine you are trying to access. Unfortunately that access is necessary for all of the network management api functions to work. If that is the case, then you will need to grant access to the "Securable Object" for whatever users you want to execute as. With the default windows security settings all authenticated users should have access, however.

I hope this helps.

-Scott

Scott Wisniewski
A: 

Wow you guys are fast, and StackOverflow is going to rock.

Right now I am learning towards davidg's WMI answer, though I see I will also need to whip up something that can return the groups which are members of the queried group. So of the four members I noted earlier (SOMESYSTEM\Administrator, SOMESYSTEM\Bob, DOMAIN\AlanH, "DOMAIN\Domain Administrators"), davidg's solution returns the first 3 but not the fourth.

Will get back to y'all soon with more info.

quux
+2  A: 

davidg was on the right track, and I am crediting him with the answer.

But the WMI query necessary was a little less than straightfoward, since I needed not just a list of users for the whole machine, but the subset of users and groups, whether local or domain, that were members of the local Administrators group. For the record, that WMI query was:

SELECT PartComponent FROM Win32_GroupUser WHERE GroupComponent = "Win32_Group.Domain='thehostname',Name='thegroupname'"

Here's the full code snippet:

public string GroupMembers(string targethost, string groupname, string targetusername, string targetpassword)
     {
      StringBuilder result = new StringBuilder(); 
      try
      {
       ConnectionOptions Conn = new ConnectionOptions();
       if (targethost != Environment.MachineName) //WMI errors if creds given for localhost
       {
        Conn.Username = targetusername; //can be null
        Conn.Password = targetpassword; //can be null
       }
       Conn.Timeout = TimeSpan.FromSeconds(2);
       ManagementScope scope = new ManagementScope("\\\\" + targethost + "\\root\\cimv2", Conn);
       scope.Connect();
       StringBuilder qs = new StringBuilder();
       qs.Append("SELECT PartComponent FROM Win32_GroupUser WHERE GroupComponent = \"Win32_Group.Domain='");
       qs.Append(targethost);
       qs.Append("',Name='");
       qs.Append(groupname);
       qs.AppendLine("'\"");
       ObjectQuery query = new ObjectQuery(qs.ToString());
       ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
       ManagementObjectCollection queryCollection = searcher.Get();
       foreach (ManagementObject m in queryCollection)
       {
        ManagementPath path = new ManagementPath(m["PartComponent"].ToString());                                        
        { 
         String[] names = path.RelativePath.Split(',');
         result.Append(names[0].Substring(names[0].IndexOf("=") + 1).Replace("\"", " ").Trim() + "\\"); 
         result.AppendLine(names[1].Substring(names[1].IndexOf("=") + 1).Replace("\"", " ").Trim());                    
        }
       }
       return result.ToString();
      }
      catch (Exception e)
      {
       Console.WriteLine("Error. Message: " + e.Message);
       return "fail";
      }
     }

So, if I invoke Groupmembers("Server1", "Administrators", "myusername", "mypassword"); I get a single string returned with:

SERVER1\Administrator
MYDOMAIN\Domain Admins

The actual WMI return is more like this:

\\SERVER1\root\cimv2:Win32_UserAccount.Domain="SERVER1",Name="Administrator"

... so as you can see, I had to do a little string manipulation to pretty it up.

quux