I want to run this function, or at least the bit that deletes the machine account from AD with different credentials:
public static void DeleteMachineAccount(String MachineName)
{
String MachineLdapPath = LdapPath(MachineName);
String OuLdapPath = MachineLdapPath.Replace("CN=" + MachineName + ",", "");
Console.WriteLine(MachineLdapPath);
Console.WriteLine(OuLdapPath);
if (DirectoryEntry.Exists(MachineLdapPath))
{
try
{
DirectoryEntry MachineOu = new DirectoryEntry(OuLdapPath);
DirectoryEntry MachineToDelete = new DirectoryEntry(MachineLdapPath);
MachineOu.Children.Remove(MachineToDelete);
MachineToDelete.CommitChanges();
}
catch (Exception e)
{
Console.WriteLine(e.Message.ToString());
}
}
}
(The LdapPath function just returns an LDAP path for the machine name specified.)
How / where do I specify some different credentials to allow this to run? At the moment I get access denied, as the account I am using will not have permission to do this.
Thanks,
Ben
You can use the overload of the DirectoryEntry class that provides authentication. This will cause your LDAP query to be run from the DirectoryServices with this particular user's permission. A word of caution, in order to do this you'll need to pass credentials (which would need to be stored or entered by the user), so be careful in how you handle them. Storing them in plain text may cause system security problems.
New DirectoryEntry(ldapRoot, _activeDirectoryUsername, _activeDirectoryPassword);
You need to use impersonation. The easiest way to do this is to actually "borrow" the permission of whoever called this method. e.g., if this is invoked from a named pipe or WCF call, there are built-in ways to impersonate the caller and do this on their behalf.
Related
We are querying Active Directory using the DirectoryEntry method by passing a domain name.
Under this domain, there are 40 DC's, in that 20 of them are DNS configured, and the rest of them are non-DNS configured, which are not maintained well and not reliable(connecting to these non-DNS configured DC's will usually timeout or thread being aborted).
Now, while making an AD call with directoryEntry method, is there a way to query only the DC's which has the DNS configured?
Currently, the code picks the Non-DNS configured DC.
I know picking the DC in a domain is a domain server task, based on the geographical location and other factors. Is there any way we can modify the code to instruct the DirectoryEntry to pick only the DNS configured DC's when we pass the DomainName.
Sample code in c# .net:
DirectoryEntry obEntry = new DirectoryEntry(#"LDAP://" + DomainName + "/<SID=" + new SecurityIdentifier(groupSid, 0).Value + ">", serviceAccountUser, serviceAccountPassword);
if (obEntry.Guid != null)
{
string distinguishedNameObtained = Convert.ToString(obEntry.Properties["distinguishedName"].Value);
}
You can't tell DirectoryEntry to pick a subset of DCs, but you can tell it to use one specific DC. In your code, you would set your DomainName variable to the name of the DC:
var DomainName = "dc1.example.com";
That's the easiest way, but now you've hard-coded one single DC, and if that one goes down, you have to change your code, which isn't ideal.
If you want to chose from the available DCs, you could try using Domain.GetCurrentDomain() or Domain.GetDomain() (if the computer you're running this from is not on the same domain you're connecting to) and then examining the DomainControllers collection. I don't know what you mean by the DCs not being configured for DNS, so I'm not sure if that's something you can determine from the DomainController class. Take a look at the documentation for DomainController and see if there is something you can use. There is a SiteName property if you want to choose a DC from a specific site.
If you are able to do that, then you can use the Name property of the DomainController in your LDAP string.
I'm having a problem with the windows service I work on currently. Basically I store some values in HKCU registry (from a GUI tool run as administrator) and from within that GUI I am starting a service. The service uses SYSTEM account to run and I believe that's my problem - I can't access registry keys stored with my GUI tool inside the service, as it points to a different HKCU!
How can I "redirect" the service to use the HKCU of the user it was stored with? (Actually I can pass a user name to the service or SID if someone will point me how to retrieve it in my GUI, but I don't know what should I use to "change" the user to point to the correct one)
#EDIT
I use a static class to access registry, it is used by both GUI and Service and the function to retrieve the base key is (rootKey is string variable holding the subkey name):
private static RegistryKey GetBaseKey(bool writable = false)
{
try
{
RegistryKey reg = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
RegistryKey rk = reg?.OpenSubKey("SOFTWARE", writable)?.OpenSubKey(rootKey, writable);
return rk;
}
catch (Exception ex)
{
// handle exceptions later
}
return null;
}
I have found WindowsIdentity class which can provide a handle (AccessToken) for current user, should I pass it as an argument to my service and use this handle to impersonate inside the service?
#EDIT2
I have done some stuff but it doesn't work. What I tried:
CurrentUserToken = WindowsIdentity.GetCurrent().Token; // to get current identity token
then with ServiceController.Start I added CurrentUserToken.ToString()
as an argument. Within my service I initialized RegistryUserToken (IntPtr) with the passed value and I'm stuck at:
WindowsIdentity RegUser = new WindowsIdentity(RegistryUserToken)
throwing exception
Invalid token for impersonation - it cannot be duplicated
I tried the same with AccessToken of current instance of WindowsIdentity - same exception thrown
Can I at all go that way? Or should I try something different??
I can give you two options: impersonate that user if you have their credentials or use idea that HKCU is a symbolic link for one of the keys under HKEY_USERS.
For impersonating you can see this link.
If you know the SID of that user, then you can find it in there. You can get the SID as so:
var account = new NTAccount("usernameThatYouNeed");
var identifier = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
var sid = identifier.Value;
I prefer impersonate. The second option is for case if you don't know that user's credentials.
I dislike second option because it requires administrative rights to write in someone else's account.
I needed to read registry of another user from a service. This is how I got it working, but in this case I know exact user name. Other answers here helped a lot. Username and path are whatever you need.
NTAccount f = new NTAccount("yourUserName");
SecurityIdentifier s = (SecurityIdentifier)f.Translate(typeof(SecurityIdentifier));
String sidString = s.ToString();
Microsoft.Win32.RegistryKey OurKey = Microsoft.Win32.Registry.Users;
OurKey = OurKey.OpenSubKey(sidString + "\\SOFTWARE\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Mappings", true);
if (OurKey != null) {
foreach (string Keyname in OurKey.GetSubKeyNames())
{
Alright, I've managed to solve it going a bit different way. I've added SID variable to my Registry class and if it's not null then I open Users Registry Hive instead of HKCU. First I retrieve current's user SID (within my GUI application) using:
WindowsIdentity.GetCurrent().User.ToString();
which I pass as an argument to my service and set it for Registry class.
I am having issues getting the groups from Active Directory via System.DirectoryServices
Originally I started my application on a computer that was registered on the domain, but as it was a live domain I did not want to do any writes to AD what so ever, so I set up a machine with Windows XP as the host operating system, and installed windows server 2003 on a VM.
I've added another Ethernet port in the machine and set up a switch, the 1 Ethernet port is dedicated to the VM and the other port is used for the host.
After configuring the IP addresses to get them communicating I transferred my application onto the host machine and fired it up, but I was getting an DirectoryServicesCOMException.
With the message that the user name and password was invalid :( just to check that it was not active directory I created a 3rd virtual machine and installed Windows XP, which i added to the domain with the credentials tested in the APP, works a treat.
So I thought it must be because the machine where the application is running is not part of the domain.
Heres the block of code that was causing the issue:
public CredentialValidation(String Domain, String Username, String Password, Boolean Secure)
{
//Validate the Domain!
try
{
PrincipalContext Context = new PrincipalContext(ContextType.Domain, Domain); //Throws Exception
_IsValidDomain = true;
//Test the user login
_IsValidLogin = Context.ValidateCredentials(Username, Password);
//Check the Group Admin is within this user
//******HERE
var Results = UserPrincipal.FindByIdentity(Context, Username).GetGroups(Context);
foreach(Principal Result in Results)
{
if (Result.SamAccountName == "Domain Admins")
{
_IsAdminGroup = true;
break;
}
}
Results.Dispose();
Context.Dispose();
}
catch (PrincipalServerDownException)
{
_IsValidDomain = false;
}
}
The information in the login dialogue is being entered like so:
Domain: test.internal
Username: testaccount
Password: Password01
Hope someone can shed some light in this error.
Update:
After checking the Security Logs on the server i can see that my log in attempts was successful, but this is down to:
_IsValidLogin = Context.ValidateCredentials(Username, Password);
The line after where im checking the groups is causing the error, so the main issue is that the lines of code below are not working correctly from a machine thats not joined to the network:
var Results = UserPrincipal.FindByIdentity(Context, Username).GetGroups(Context);
According to your code snippet, you're failing when you attempt to create the PrincipalContext, before calling ValidateCredentials. At that point the thread running your code is still working under either a local identity (if you're in a web process) or the identity you signed onto your machine with (for a windows process). Either of these won't exist on the test.internal domain.
You might want to try the overload of PrincipalContext that includes the username and password in the constructor. See http://msdn.microsoft.com/en-us/library/bb341016.aspx
I used to do quite a bit of user management via C# .NET. I just dug up some methods you can try.
The following two methods will get a DirectoryEntry object for a given SAM account name. It takes a DirectoryEntry that is the root of the OU you want to start searching for the account at.
The other will give you a list of distinguished names of the groups the user is a member of. You can then use those DN's to search AD and get a DirectoryEntry object.
public List<string> GetMemberOf(DirectoryEntry de)
{
List<string> memberof = new List<string>();
foreach (object oMember in de.Properties["memberOf"])
{
memberof.Add(oMember.ToString());
}
return memberof;
}
public DirectoryEntry GetObjectBySAM(string sam, DirectoryEntry root)
{
using (DirectorySearcher searcher = new DirectorySearcher(root, string.Format("(sAMAccountName={0})", sam)))
{
SearchResult sr = searcher.FindOne();
if (!(sr == null)) return sr.GetDirectoryEntry();
else
return null;
}
}
I need to restrict access to my application to only one specific user account. I have found classes under WMI to find users accounts, but I donĀ“t know how to recognize which one is running my app.
There are simpler ways to get the current username than using WMI.
WindowsIdentity.GetCurrent().Name will get you the name of the current Windows user.
Environment.Username will get you the name of the currently logged on user.
The difference between these two is that WindowsIdentity.GetCurrent().Name will also include the domain name as well as the username (ie. MYDOMAIN\adrian instead of adrian). If you need the domain name from Environment, you can use Environment.UserDomainName.
EDIT
If you really want to do it using WMI, you can do this:
ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT UserName FROM Win32_ComputerSystem");
ManagementObjectCollection collection = searcher.Get();
string username = (string) collection.Cast<ManagementBaseObject>().First()["UserName"];
Unfortunately, there is no indexer property on ManagementObjectCollection so you have to enumerate it to get the first (and only) result.
You don't necessarily need to use WMI. Check out WindowsIdentity.
var identity = WindowsIdentity.GetCurrent();
var username = identity.Name;
The simplest approach is through the Environment class:
string user = Environment.UserDomainName + "\\" + Environment.UserName;
There also are several ways to restrict to a certain user (although checking for a role is more common).
Apart from the obvious
if (userName == "domain\\john")
{ }
You can also use the following attribute on an entire class or specific methods:
[PrincipalPermission(SecurityAction.Demand, Name = "domain\\john",
Authenticated = true)]
void MyMethod()
{
}
Which could be a bit more reliable for low-level, reusable methods.
Note that you could use both, checking for a user with if() as part of the normal flow of the program and the attribute as a safeguard on critical methods.
How do I detect if my program runs in an Active Directory environment?
I'm using C# and .Net 2.0
Try getting Environment.UserDomainName and comparing it to Environment.MachineName. If the two are the same then it's likely that the user does not have a domain. If they are not the same then the user is logged into a domain which must have a directory server.
This code will check if the Computer itself is a member of a domain
using System.DirectoryServices.ActiveDirectory;
bool isDomain = false;
try
{
Domain.GetComputerDomain();
isDomain = true;
}
catch (ActiveDirectoryObjectNotFoundException)
{
}
However the computer can be in a domain, but the currently logged in user may be a local user account. If you want to check for this use the Domain.GetCurrentDomain() function
One way might be to query the LOGONSERVER environmental variable. That'll give the server name of your AD controller... Which, as far as I know, will be blank (or match current workstation? Not sure) if it isn't currently logged into a domain.
Example Usage:
string ADServer = Environment.GetEnvironmentVariable("LOGONSERVER");
I found something that works:
using System.Net.NetworkInformation;
IPGlobalProperties.GetIPGlobalProperties().DomainName;
Works with a local user and a domain user.
From http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.path.aspx
To bind to the current domain using LDAP, use the path "LDAP://RootDSE", then get the default naming context and rebind the entry.
So without a domain the binding to "LDAP://RootDSE" should either fail or return nothing. I didn't try it for myself.
use System.DirectoryServices; // add reference to system.directoryservices.dll
...
DirectoryEntry ent = new DirectoryEntry("LDAP://RootDSE");
String str = ent.Properties["defaultNamingContext"][0];
DirectoryEntry domain = new DirectoryEntry("LDAP://" + str);
This is definitely a cleaner way of checking for an Active Directory than relying on an environment variable (which the user could delete or add to spoof the program).