Change Local user settings from within c# - c#

I need to write a program that will take a existing local windows user, change the "Start the following program at logon field" in their environment tab and change the password to a new password.
I am using local users instead of AD, will System.DirectoryServices work? Also any tutorials on using System.DirectoryServices would be very helpful.
EDIT --
Ok, so I can find the existing user and I know how to update it. However I do not know what to put in the ???. What AD property represents "Start the following program at logon field" in their environment tab on the user settings page?
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(SAMAccountName={0})", prams.user);
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (result == null)
{
Console.Error.WriteLine("No user of name {0} in the system", prams.user);
return -2;
}
DirectoryEntry entry = result.GetDirectoryEntry();
entry.InvokeSet("???", new object[] { newProgramLognField });
EDIT two---
I now know that the field I need is "Parameters" however I this term is very hard to search for in the MSDN because it is such a generic term. I think I have the correct one because when i run the code
DirectoryEntry entry = new DirectoryEntry("WinNT://testComputer/testUser");
Console.WriteLine("connected");
string value = (string)entry.Properties["Parameters"].Value;
Console.WriteLine(value);
I get the result
P☺CtxCfgPresent????☺CtxCfgFlags1
????☺CtxShadow????*☻☺CtxMinEncryptionLevel? #☺CtxWorkDirectory??????????????????
??????????????"C☺CtxInitialProgram??????????????????????????????????????????????
????????????????????????????????????????????????????????????????????????????????
????????
I am guessing that InitalProgram portion is close to what I want, most likely the ??? after is is the existing path that is there but has be mucked up.
Now the question is how do I write correctly to it?
Edit-3
I try changing the casting from a string to a PropertyValueCollection however that just gives me the error at run time
Unhandled Exception: System.InvalidCastException: Unable to cast object of type
'System.String' to type 'System.DirectoryServices.PropertyValueCollection'.
at UserUpdater.Program.Main(String[] args) in e:\Visual Studio 2008\Projects\
UserUpdater\UserUpdater\Program.cs:line 91

My copy of the TechNet Script Repository says that this WSH script can be used to change the password.
Set objComputer = GetObject _
("LDAP://CN=atl-dc-01,CN=Computers,DC=Reskit,DC=COM")
objComputer.SetPassword "whatever"

Related

AD UserPrincipal ChangePassword method creates user profile folders in C:\Users

We have a .NET Framework application used for changing user passwords. It uses the System.DirectoryServices.AccountManagement assembly.
Process goes as follows:
A user fills out a simple form (username, old pw, new pw, new pw repeated).
UserPrincipal.FindByIdentity() gets called to find the user
UserPrincipal.ChangePassword() gets called to change the password
Note: Principal context type is set to DOMAIN
Note2: everything works, the issue is with the user folders.
So apparently ChangePassword() creates a user profile in C:\Users folder in the application's machine and I cannot find any information why that happens. I would understand in the context was set to Machine, but in this case it's not.
There are over 6k folders now, one for each user, it's taking up a lot of space and slowing the machine down.
I tried recreating the problem locally and apparently the ChangePassword() creates a TEMP user profile in C:\Users in my computer, but then it disappears.
Code
Configuration
container.RegisterType<PasswordChangeService>(new InjectionFactory(m => new PasswordChangeService(
new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["LdapDomain"], ConfigurationManager.AppSettings["LdapQuery"]),
new LogService(new Persistence.LogDbContext())
)));
Code for finding user, changing password
using (var user = UserPrincipal.FindByIdentity(_principalContext, IdentityType.SamAccountName, passwordChange.Username)) {
if (user == null) {
Fail(passwordChange, "User not found");
}
user.ChangePassword(passwordChange.CurrentPassword, passwordChange.NewPassword);
LogSuccess(passwordChange);
}
Can anyone tell me why the user profiles get created?
How to fix this issue? Is this a configuration or permission problem?
(optional) I've seen examples where UserPrincipal.Save() gets called after, say, ChangePassword(), but it has always worked without it, so in what situation can it be used?
Thank you.
Update
after some unsuccessful searching I found out a few things that might be worth mentioning:
1. Someone from 2009 had the same problem technet link
The last comment "Since all the code snippets utilize the ChangePasword implementation of ADSI (which causes the profile generation) the simplest way to accomplish this programmatically would be to not use ADSI but System.DirectoryServices.Protocols instead and perform an attribute modification against unicodePwd".
2. When calling ChangePassword(), user directory was sometimes not created and at at those times Event Viewer showed a couple of errors
"Windows has backed up this user profile. Windows will automatically try to use the backup profile the next time this user logs on."
"Windows cannot find the local profile and is logging you on with a temporary profile. Changes you make to this profile will be lost when you log off."
3. Every "fix" I've seen resulted in fixing the consequence, but not the cause. I still have no idea why the profiles get created, but if the technet comment is legit, I guess I need to use another implementation.
I ended up using an example from SO link and this is my result:
string ldapPath = ConfigurationManager.AppSettings["LdapPath"];
string domainWithUserName = string.Format(ConfigurationManager.AppSettings["LdapDomain"], "\\", passwordChange.Username);
DirectoryEntry directoryEntry = new DirectoryEntry(ldapPath, domainWithUserName, passwordChange.CurrentPassword);
if (directoryEntry != null) {
DirectorySearcher search = new DirectorySearcher(directoryEntry);
search.Filter = "(SAMAccountName=" + passwordChange.Username + ")";
SearchResult result = search.FindOne();
if (result != null) {
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry == null) {
Fail(passwordChange, "User not found.");
}
userEntry.Invoke("ChangePassword", new object[] { passwordChange.CurrentPassword, passwordChange.NewPassword});
userEntry.CommitChanges();
LogSuccess(passwordChange);
}
}
I still need to narrow down the AD search to a specific OU, etc.
This implementation works, no user profile gets created, Event Viewer shows nothing. It's a shame, really, because I'd rather call a few methods instead of using 15 lines of code that do the same thing.

C# active directory update unknown error for 2 people

I have a C# console application that updates Active Directory properties using data from the HR system.
Below is a sample of the code I'm using to update the manager property:-
if (ADManagerPath != adManager)
{
if (enableActiveDirectoryUpdates)
{
if (ADManagerPath.Length > 0)
{
deLDAPUpdate.Properties["manager"].Value = ADManagerPath;
managerUpdated++;
}
else
{
deLDAPUpdate.Properties["manager"].Clear();
managerBlanked++;
}
}
managerChanged = true;
}
Obviously, there are other properties that I am updating and finally if I have made any changes, there is a CommitChanges statement.
My issue is that the line will I am setting the manager, fails but only for 2 active directory accounts (which may well be for the same person).
The error codes I am receiving are (0x80005000) -2147463168. I have searched the site, and others, however most answers appear to be cases where LDAP has not been capitalized or the property/attribute you're trying to update does not exist. Both of which I do not believe apply to my situation, as I can update other accounts.
Can anybody help?
Okay I have fixed the above problem but still have a problem with these 2 accounts. The value I was reading from a database, somehow had an extra backslash in the manager path and that was the problem for setting the manager. My program now successfully can update a person's manager to 1 of these 2 accounts.
However I still believe there is a problem with the 2 accounts in question. I cannot set any property for the 2 accounts in question. Interestingly, I have developed a program, only for my use, where you enter a samAccountName and it will display and iterate through all the properties. However, this program also falls over when I attempt to use it on either of these accounts.
I write any errors to an exception table and all that error message says is "Unknown error (0x80005000)".
This program has been running for a couple of months, although it has been developed further over the past few weeks, and it is only these 2 accounts that will not update?
The code that I am using to create the various active directory connection objects is:-
string gcConnection = "GC://" + guidBindRootPath + "/<GUID=" + adID + ">";
deGUIDBind.Username = username;
deGUIDBind.Password = password;
deGUIDBind.AuthenticationType = AuthenticationTypes.Secure;
deGUIDBind.Path = gcConnection;
deLDAPUpdate.Username = username;
deLDAPUpdate.Password = password;
deLDAPUpdate.AuthenticationType = AuthenticationTypes.Secure;
string distinguishedName = deGUIDBind.Properties["distinguishedName"].Value.ToString();
string ldapConnection = "LDAP://" + getDomainNameFromDistinguishedName(distinguishedName) + "/" + distinguishedName;
deLDAPUpdate.Path = ldapConnection;
`
To reiterate, I have solved the problem of setting, the accounts in question, as the manager of other accounts, however, I can still not change any of the properties of the 2 problematic accounts. My program successfully updates other accounts.
The other program that I have written, in C#, which iterates through and displays all the properties, for a given samAccountName, also falls over for these 2 accounts.
string adDivision = convertNullToString(deLDAPUpdate.Properties["Division"].Value);
propertyDetails = propertyDetails + "Division" + "," + adDivision + Environment.NewLine;
foreach (System.DirectoryServices.PropertyValueCollection p in deLDAPUpdate.Properties)
With regard to the 2 problem accounts, this program falls over both on attempt to retrieve the division property and also when it attains to evaluate deLDAPUpdate.Properties.
I am not asking for help with the 2nd application, which is only for my own use, I only mention it since it seems to indicate that there might be a problem with the 2 accounts. This test application successfully displays the properties of other accounts.

'Access Denied' when attempting DirSync with S.DS

I am trying to set up a DirSync control. Previously I have [successfully] used the methods found in the System.DirectoryServices.Protocols, but I found that the results it returned were only partial objects - I wasn't able to have it return the homeDrive attribute from a user even if I defined it in the SearchRequest's Attributes property.
Thus, I'm attempting to set up DirSync following some of the documentation and examples using System.DirectoryServices instead. I was successful in connecting to my test server (only accessible by IP), and I was successful in targeting just one OU and searching for a user, as such:
byte[] cookie = null;
root = new DirectoryEntry(
"LDAP://[MyIPHere]/OU=test ou,DC=company,DC=com", "username", "password");
//Section A - Use this section for a regular search
DirectorySearcher src = new DirectorySearcher(root);
src.SearchScope = SearchScope.Subtree;
src.Filter = "(&(objectClass=user)(sAMAccountName=myuserhere)";
//Section B - Use this section for a DirSync
//src.DirectorySynchronization = new DirectorySynchronization(
DirectorySynchronizationOptions.IncrementalValues, cookie);
//src.Filter = "(&(objectCategory=person)(objectClass=user))";
//Execute the code whichever section is used
SearchResultCollection result = src.FindAll();
int count = result.Count;
Console.WriteLine(count.ToString());
foreach (SearchResult res in result)
{
//do things
}
However, when I try to use section B instead of section A, I get an error on the line where I'm setting the int count. (I have tried passing no parameters to the constructor of src.DirectorySynchronization as in the example, same result):
COMException was unhandled
Access is denied.
I only get the error when I try to access the properties of the result object, or try to iterate. If I set a breakpoint on the int count line and look at the result object, I see the following in the value column of the result's Count:
'result.Count' threw an exception of type
'System.Runtime.InteropServices.COMException'
I have ensured that my account has the Replicating Directory Changes security access both for the specified OU and the test domain at large (and all other security access possible). I have also tried using a separate domain admin account.
I have the same issue if I try running this on our production domain, passing no credentials when constructing the DirectoryEntry object.
Considering that I can successfully retrieve other search results, what is it about this DirectorySynchronization that is causing the access issues, and why isn't it happening when I call src.FindAll()?
(I'm open to other options, but I'd like to avoid the USNChanged tracking method for now due to it returning the full objects back, and requiring additional coding on my end.)
The base of a DirSync search must be the root of a directory partition, i.e "DC=company,DC=com" in your case.
See http://msdn.microsoft.com/en-us/library/ms677626(v=vs.85).aspx
"Base of the search" in the table.
Some more good C# example for DirSync:
http://msdn.microsoft.com/en-us/magazine/cc188700.aspx#S1
See section "Finding Your Way with DirectorySearcher".
If you want to track changes only on a OU/container, yes, you will have to use USNChange.

c# against Active Directory over LDAP

I'm coding some c# against Active Directory and have tried endlessly to get this to work to no avail. The following code works and the code that follows it does not:
The code below is using "WinNT://" + Environment.MachineName + ",Computer" to make the connection and works fine.
DirectoryEntry localMachine = new DirectoryEntry
("WinNT://" + Environment.MachineName + ",Computer");
DirectoryEntry admGroup = localMachine.Children.Find
("Administrators", "group");
object members = admGroup.Invoke("members", null);
foreach (object groupMember in (IEnumerable)members)
{
DirectoryEntry member = new DirectoryEntry(groupMember);
output.RenderBeginTag("p");
output.Write(member.Name.ToString());
output.RenderBeginTag("p");
}
base.Render(output);
I'm now trying to change the line:
"WinNT://" + Environment.MachineName + ",Computer"
to
"LDAP://MyDomainControllerName"
but it seems no matter what value I try in place of the value 'MyDomainControllerName' it wont work.
To get the 'MyDomainControllerName' value I right clicked on MyComputer and copied the computer name value as suggested elsewhere but this didn't work.
When I try using the LDAP://RootDSE option above it results in the following error:
The Active Directory object located at the path LDAP://RootDSE is not a container
Is this a problem with the member methods as you mention?
Yes- RootDSE is not a container - but it holds a number of interesting properties which you can query for - e.g. the name of your domain controller(s).
You can check these out by using code like this:
DirectoryEntry deRoot = new DirectoryEntry("LDAP://RootDSE");
if (deRoot != null)
{
Console.WriteLine("Default naming context: " + deRoot.Properties["defaultNamingContext"].Value);
Console.WriteLine("Server name: " + deRoot.Properties["serverName"].Value);
Console.WriteLine("DNS host name: " + deRoot.Properties["dnsHostName"].Value);
Console.WriteLine();
Console.WriteLine("Additional properties:");
foreach (string propName in deRoot.Properties.PropertyNames)
Console.Write(propName + ", ");
Console.WriteLine();
}
Or save yourself the trouble and go grab my "Beavertail ADSI Browser" in C# source code - shows in detail how to connect to RootDSE and what it offers.
When connecting to AD using the .NET Framework, you can use "serverless" binding or you can specify a server to use everytime (server bound).
Here's an example of using both:
// serverless
DirectoryEntry rootConfig = new DirectoryEntry("LDAP://dc=domainname,dc=com");
// server bound
DirectoryEntry rootEntry = new DirectoryEntry("LDAP://domainControllerName/dc=domainName,dc=com");
I think where you were going astray is you forgot to include the FQDN for your domain on the end. Hope this helps.
You need to pass it an authorized Username and password.
try setting: DirectoryEntry.Username and DirectoryEntry.Password
have you tried speciying the port number and other parms?
Our ldap string looks like: LDAP://myserver:1003/cn=admin#xyz.com|1,ou=Members,o=mdhfw2
It looks like you need to get the LDAP connection information. You can call LDAP://RootDSE to get the information as shown in the ASP.NET Wiki.
Please keep in mind that the LDAP objects do not have the same member methods and properties as the WINNT objects, so do not expect the group.Invoke("members") and other functions to work exactly the same. You should read up on the DirectoryServices documentation with LDAP as well.

How can I convert from a SID to an account name in C#

I have a C# application that scans a directory and gathers some information. I would like to display the account name for each file. I can do this on the local system by getting the SID for the FileInfo object, and then doing:
string GetNameFromSID( SecurityIdentifier sid )
{
NTAccount ntAccount = (NTAccount)sid.Translate( typeof( NTAccount ) );
return ntAccount.ToString();
}
However, this does not work for files on a network, presumably because the Translate() function only works with local user accounts. I thought maybe I could do an LDAP lookup on the SID, so I tried the following:
string GetNameFromSID( SecurityIdentifier sid )
{
string str = "LDAP://<SID=" + sid.Value + ">";
DirectoryEntry dirEntry = new DirectoryEntry( str );
return dirEntry.Name;
}
This seems like it will work, in that the access to "dirEntry.Name" hangs for a few seconds, as if it is going off and querying the network, but then it throws a System.Runtime.InteropServices.COMException
Does anyone know how I can get the account name of an arbitrary file or SID? I don't know much about networking or LDAP or anything. There's a class called DirectorySearcher that maybe I'm supposed to use, but it wants a domain name, and I don't know how to get that either - all I have is the path to the directory I'm scanning.
See here for a good answer:
The best way to resolve display username by SID?
The gist of it is this bit:
string sid="S-1-5-21-789336058-507921405-854245398-9938";
string account = new System.Security.Principal.SecurityIdentifier(sid).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
This approach works for me for non-local SID's over the active directory.
The SecurityReference object's Translate method does work on non-local SIDs but only for domain accounts. For accounts local to another machine or in a non-domain setup you would need to PInvoke the function LookupAccountSid specifying the specific machine name on which the look up needs to be performed.
System.DirectoryServices.AccountManagement.UserPrincipal class (msdn link) has a static function FindByIdentity to convert an SID to a User object. It should be able to work both against the local machine or an LDAP/Active Directory server. I have only used it against active directory.
Here is an example that I have used in IIS:
// Set the search context to a specific domain in active directory
var searchContext = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=SomeOU,DC=YourCompany,DC=com");
// get the currently logged in user from IIS
MembershipUser aspUser = Membership.GetUser();
// get the SID of the user (stored in the SecurityIdentifier class)
var sid = aspUser.ProviderUserKey as System.Security.Principal.SecurityIdentifier;
// get the ActiveDirectory user object using the SID (sid.Value returns the SID in string form)
var adUser = UserPrincipal.FindByIdentity(searchContext, IdentityType.Sid, sid.Value);
// do stuff to user, look up group membership, etc.
In C#, get the user SID and assign it to a string variable through:
string strUser = System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString();
You will need to use string because the ability to resolve to the UserName supports string. In other words, using var varUser will result in a namespace error.
string strUserName = new System.Security.Principal.SecurityIdentifier(strUser).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
You can also get account name of special accounts like "Everyone" with code like this that will work regardless of user's language settings:
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
string everyone = everyoneSid.Translate(typeof(System.Security.Principal.NTAccount)).ToString();
Ooh, then it's possible that the LDAP call is not working because you might not be in an Active Directory environment. If this is the case, then each of your machines is responsible for its own identity store. And your first code sample is not working across the network because the machine on which you are executing your code does not know how to resolve the SID that only makes sense on the remote machine.
You really should check if your machines are a part of an Active Directory. You would know this during the logon process. Or you can check by right clicking on "My Computer", select "Properties", the "Computer Name" tab, then see if your computer is part of a domain.
Great. I cribbed some LookupAccountSid() code from here:
http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid
And that worked, though I had to provide the host name myself. In the case of a UNC path I can just take the first component of it. When it's a mapped drive, I use this code to convert the path to a UNC one:
http://www.wiredprairie.us/blog/index.php/archives/22
It seems to work, so that's how I'll do it, unless someone comes up with a situation in which the first component of a UNC path isn't the host name...
Thank you all for your help.
This one is a stumper. You are in an Active Directory environment right? Just checking:)
Anyhow, instead of binding with sid.Value,
string str = "LDAP://<SID=" + sid.Value + ">";
I would try converting the SID's byte array to an Octet String and bind with that instead.
There is a sweet example here on page 78. This will get you closer. To be honest, I've not tried binding with a SID before. But I've had success binding with a user's GUID though :)
Good luck and let me know how it goes.
Get the current domain:
System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
Get a directory entry from ldap and the domain name:
DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://{0}", domain));
Get the sid from an ActiveDirectoryMembershipProvider ActiveDirectoryMembershipUser:
ActiveDirectoryMembershipUser user = (ActiveDirectoryMembershipUser)Membership.GetUser();
var sid = (SecurityIdentifier)user.ProviderUserKey;
Get the username from the SecurityIdentifier:
(NTAccount)sid.Translate(typeof(NTAccount));
Get directory search done on an activedirectory with the domain directory entry and username:
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = string.Format("(SAMAccountName={0})", username);
search.PropertiesToLoad.Add("Name");
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("company");
search.PropertiesToLoad.Add("homePhone");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("lastLogon");
search.PropertiesToLoad.Add("userPrincipalName");
search.PropertiesToLoad.Add("st");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("telephoneNumber");
search.PropertiesToLoad.Add("postalCode");
SearchResult result = search.FindOne();
if (result != null)
{
foreach (string key in result.Properties.PropertyNames)
{
// Each property contains a collection of its own
// that may contain multiple values
foreach (Object propValue in result.Properties[key])
{
outputString += key + " = " + propValue + ".<br/>";
}
}
}
Depending on the data in your active directory, you will get a varied response in the output.
Here is a site that has all the user properties I needed:
For all the Windows developers, the answer is LookupAccountSid
LookupAccountSid(null, Sid, username, userSize, domainName, domainSize, sidType);
I am quite sure you will be able to use the accepted answer from here: Determine the LocalSystem account name using C#
Basically, you can translate an instance of the SecurityIdentifier class to type NTAccount, from which you can get the user name. In code:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);

Categories

Resources