I have a system that creates, in realtime, an user in AD and prints a tag for credentials. That user is used by a person to navigate in the internet.
Everything was ok until now. That user cannot log on company's computer, only on the proxy servers.
In class UserPrincipal, we have the "PermittedWorkstations" property, but it is readonly.
Is there a way to set the PermittedWorkstations (or set the computers restrictions os the users, adding the computers that he is able to logon - like this image http://i.stack.imgur.com/tu2Kp.png)?
As DeFirmo already mentioned: it is unlikely that the problem will be solved by setting the "Permitted Workstation", but nevertheless:
Use direct ADSI commands, the following works for me:
using System.DirectoryServices;
....
using (DirectoryEntry de = new DirectoryEntry("LDAP://CN=NewlyCreatedUser,CN=Users,DC=ad,DC=local"))
{
de.Properties["userWorkstations"].Add("WS1,WS2");
de.CommitChanges();
}
You must use the Add method of PermittedWorkstations Of User.Principal
User.PermittedWorkstations.Add("SampleWorkstation"));
Related
I have a C#/WPF application that is used to manipulate local users and groups on a system. We only care about local users and groups, regardless of whether the machine is joined to a domain or not. When we create a user in our application, I want to add the user to the "Users" group. Normally this works fine, but if the machine is domain-joined and NOT connected to the network (e.g. a laptop out of the office), I get "the network path is not found" errors when trying to add a local user to the "Users" group.
I think the reason is because the "Users" group contains domain users, as shown in this screenshot.
And this is essentially my code:
public static void AddUserToGroup(UserPrincipal oUserPrincipal, string groupName)
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Machine))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, groupName);
if (group == null)
{
group = CreateLocalWindowsGroup(groupName);
}
if (!group.Members.Contains(oUserPrincipal)) // this line throws "network path not found" exception if the machine is domain joined, but can't contact the domain controller
group.Members.Add(oUserPrincipal);
group.Save();
}
}
I can't figure out how to approach this with the API, but it seems like it should be possible because I can add the exact same user to the same group manually with the "Local Users and Groups" tool with no issues, regardless of network connectivity. How can I get around this issue?
This is one reason I don't like using the AccountManagement namespace.
The GroupPrincipal.Members property returns a PrincipalCollection, which is just a collection of Principal objects. The actual type will be UserPrincipal or GroupPrincipal depending on what the actual member is.
But, those Principal classes, when they're created, load all the details for that object. So just the act of creating a UserPrincipal for a domain user triggers it to go out to the domain and get all the details for the user.
You're better off using DirectoryEntry directly, which is what the AccountManagement namespace uses in the background anyway. It gives you more control over what's actually happening.
var usersGroup = new DirectoryEntry($"WinNT://{Environment.MachineName}/{groupName}");
usersGroup.Invoke("Add", new object[] { $"WinNT://{Environment.MachineName}/{userName}" });
This assumes a userName variable with the name of the local user. If the user is already in the group, it will throw an exception, so you may want to catch that.
Besides actually working in this case, this will run faster since you're not wasting time collecting details for all the existing members when you have no intention of using any of that data.
Update: To read all the members of a local group, use .Invoke("Members"). Then you have to create a new DirectoryEntry with each member in the collection. For example:
foreach (var member in (IEnumerable) usersGroup.Invoke("Members")) {
using (var memberDe = new DirectoryEntry(member)) {
Console.WriteLine(memberDe.Name);
}
}
The DirectoryEntry class is really a wrapper around the Windows native ADSI Interfaces. For a group, the underlying object will really be IADsGroup. When you call .Invoke on a DirectoryEntry object, that lets you call the IADsGroup methods (you'll see the Members method listed in the documentation there). All of the object-specific classes like IADsGroup and IADsUser all inherit from IADs, so the methods from that are usable too.
This only applies to local groups. With Active Directory groups, you don't have to resort to using the IADs methods.
I have tried to look for the c# code example to see how the AD service account is created but not much luck. Anyone can provide an example code for creating AD service account please?
I have tried UserPrincipal with $ at the end of the name but not much luck. Errors with Access Denied (Cant create under root MyDomain or under a CN)
// Domain Context to use specific LDAP path.
domainContext = new PrincipalContext(ContextType.Domain, domainContext.ConnectedServer, "CN=Managed Service Accounts,dc=mydomain");
UserPrincipal userAccount = new UserPrincipal(domainContext)
{
DisplayName = userName,
SamAccountName = $"{userName}$",
Description = description
};
userAccount.Save();
Little late, but I think I can answer it. You need to use the NetAddServiceAccount function through logoncli.dll. I hadn't been able to get it to work in PowerShell, even with adding what I thought was an appropriate type shim, but I just came across a module that seems to work.
https://github.com/beatcracker/Powershell-Misc/blob/master/Use-ServiceAccount.ps1
The C# code for the type definition in that script should have everything you need to implement it for yourself.
Problem:
We've upgraded the AD server from 2003 to 2008 and due to some "bad code", where developer has coded in such a way that, he directly casts "badPwdCount" property value to INT and it blows up because of NULL value conversion - NULL reference exception - NULL cannot be converted to INT.
Bigger problem:
We cannot do a deployment at this point because there are over 100 individual apps that depended on this change and we're looking for a least involved way of dealing with it for now.
Background:
Now the way this "badPwdCount" property works is, that when user logs on to the domain, it will get set to zero, otherwise it's NULL. The problem is that none of these users are ever going to log on interactively because they're external and we authenticate them via API and they cannot log in using the API either..
Question:
Does anyone know if this value is in the registry or somewhere, where I can get to it and set it to zero? Was also thinking of initiating a log in per user via a script, but wanted to gather other ideas too...
MSDN page for badPwdCount:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms675244(v=vs.85).aspx
Normally this would be easy, all you would need to do is update all the users in active directory and set the value to 0 if it is null. There are various ways you could do this, for example a script or code, or a bulk update tool.
In this case, badPwdCount is a special property that is not replicated (i.e. it is different for each domain controller) and so far as I can tell, there is no way to update it manually or by script, however, I think I have a solution for you.
You should be able to easily trigger a single failed login for every user in active directory against each domain controller, causing the value to be incremented.
Since you tagged your post with C#, here is some C# code that will do the trick for you:
using System.DirectoryServices.AccountManagement;
using System.DirectoryServices.ActiveDirectory;
...
using (Domain domain = Domain.GetComputerDomain())
{
foreach (DomainController domainController in domain.DomainControllers)
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, domainController.Name))
using (UserPrincipal userPrincipal = new UserPrincipal(context))
using (PrincipalSearcher searcher = new PrincipalSearcher(userPrincipal))
using (PrincipalSearchResult<Principal> results = searcher.FindAll())
{
foreach (UserPrincipal user in results.OfType<UserPrincipal>())
{
context.ValidateCredentials(user.SamAccountName, "THEREISNOWAYTHISISTHECORRECTPASSWORD");
}
}
}
}
PS. If this screws up your AD I take no responsibility for it!
I need to reset a user password. to do so, I use the following code:
DirectoryEntry de = ..
de.AuthenticationType = AuthenticationType.Secure
de.Password = txtPassword.text
de.CommitChanges()
When i run the code - nothing happens. The user password does not change, and no exception is thrown.
if i use the following method:
de.Invoke("SetPassword", .. );
when i run the code, I get a message that says: Please insert smartcard ...
I have admin privilages over the user account.
The user does not have UAV set for smart card.
Any ideas ?
The Password property of the DirectoryEntry class isn't what you think it is. You're not changing the user's password, you're changing the password you're using to access further information from the DirectoryEntry object.
From the MSDN documentation:
You can set the Username and Password properties to specify alternate
credentials with which to access the information in Active Directory
Domain Services. Any other DirectoryEntry objects retrieved from this
instance (for example, through Children) are automatically created
with the same alternate credentials.
With your second method, if you're being asked to insert a smartcard I doubt that has anything to do with the user you're modifying - it's more likely it's asking for your smartcard. If you're not set up to use smartcards either, then I'm really not sure why it's asking you for one at all.
Take a look at this related question and see if the answers there help.
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).