C# LDAP/Active Directory/Win2KServer problem

  • Thread starter Thread starter tim (dot) campbell (at) hyperlink (dash) interacti
  • Start date Start date
T

tim (dot) campbell (at) hyperlink (dash) interacti

(Apologies for the crossposting, but I wasn't sure which group would be
best to post this to)

I have a problem with some C#/.NET (v1.1) code that I've inherited as
part of a project that my company won to redevelop an existing website.
The code in question uses directory services and the LDAP provider to
connect to an Active Directory server, and create/update user account
details.

The code runs perfectly on my development PC, which runs Windows XP Pro,
but fails with a COMException when I try to run it on the batch server,
which runs Windows 2000 Server. Further investigation has revealed that
it apparently fails in the same way on any machine running Windows 2000
Server - I've tried it on five other machines with exactly the same results.

The relevant section of code is reproduced below:

DirectoryEntry accountRoot = new DirectoryEntry(config.adRootPath,
config.adConnectionUsername,
config.adConnectionPassword,
AuthenticationTypes.ServerBind);

DirectoryEntry accountGroup = new DirectoryEntry(
config.adServer
+ config.adGroupObject,
config.adConnectionUsername,
config.adConnectionPassword,
AuthenticationTypes.ServerBind);
try
{
accountRoot.RefreshCache();
accountGroup.RefreshCache();
}
catch (Exception ex)
{
WriteErrorLog("Failed to refresh caches:\n\n" + ex.ToString());
return;
}
PropertyValueCollection groupMembers =
accountGroup.Properties["member"];
DirectoryEntry accountEntry = null;
try
{
accountEntry = accountRoot.Children.Find("CN=" + accountName,
"user");
}
catch(COMException)
{
// code ommited for brevity
}
if( accountEntry != null )
{
try
{
// Set mandatory account elements
accountEntry.Properties["sAMAccountName"].Value = accountName;
// Set some additional properties
accountEntry.Properties["userAccountControl"].Value = 0x10240;
accountEntry.Properties["userPrincipalName"].Value = accountName
+ "@" + config.adDomain;

object[] invocationArg = new object[]{accountPwd};

try
{
accountEntry.Invoke("setPassword", invocationArg);
}
catch (Exception e)
{
WriteErrorLog("Failed to set password for account \""
+ accountName + "\":\n\n" + e.ToString());
return;
}
}
catch( COMException commitException )
{
WriteErrorLog("Failed commit changes to Active Directory
server:\n\n" + commitException.ToString());
return;
}
}

The problematic line is the one that calls the Invoke method on the
DirectoryEntry object. At this point, on each of the Windows 2000 Server
machines, it fails with the following exception:


System.Reflection.TargetInvocationException: Exception has been thrown
by the target of an invocation. --->
System.Runtime.InteropServices.COMException (0x8007052E): The handle
specified is invalid

--- End of inner exception stack trace ---
at System.RuntimeType.InvokeDispMethod(String name, BindingFlags
invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers,
Int32 culture, String[] namedParameters)
at System.RuntimeType.InvokeMember(String name, BindingFlags
invokeAttr, Binder binder, Object target, Object[] args,
ParameterModifier[] modifiers, CultureInfo culture, String[]
namedParameters)
at System.Type.InvokeMember(String name, BindingFlags invokeAttr,
Binder binder, Object target, Object[] args)
at System.DirectoryServices.DirectoryEntry.Invoke(String methodName,
Object[] args)
at ADUpdater.ADUpdater.Main(String[] args)


I've been messing with this on and off for a few days now, and I'm not
really getting anywhere. Does anyone have any idea why this would be
failing on the Win2k Server machines? Input data and config is identical
in all cases, and I'm connecting to the Active Directory server using an
admin account's username and password.


Cheers,

Tim
 
SetPassword is a tricky method to get to work and is one of the most popular
questions on microsoft.public.adsi.general. If you do a Google groups
search for ".Invoke("SetPassword"" you should get tons of hits.

Since it works on some machines and not others, my guess is that there is an
operational problem. SetPassword tries to connect first via SSL on port
636, then via Kerberos on the Kerberos set password port, and finally via
NetUserChangePassword.

The best way to go is to make sure you that you have SSL working with your
connection to the AD as that is the preferred method. You can test that in
your normal DirectoryEntry binds by adding
AuthenticationTypes.SecureSocketsLayer to your flags. If that works, then
you should be able connect via SSL.

If you can't do SSL, then you are relying on Kerberos or
NetUserChangePassword. They are harder to debug.

Often a packet sniffer is really helpful for tracking down what's going on
by watching the network traffic go by and checking the ports that are used.

HTH,

Joe K.

"tim (dot) campbell (at) hyperlink (dash) interactive (dot) co (dot) uk"
(Apologies for the crossposting, but I wasn't sure which group would be
best to post this to)

I have a problem with some C#/.NET (v1.1) code that I've inherited as
part of a project that my company won to redevelop an existing website.
The code in question uses directory services and the LDAP provider to
connect to an Active Directory server, and create/update user account
details.

The code runs perfectly on my development PC, which runs Windows XP Pro,
but fails with a COMException when I try to run it on the batch server,
which runs Windows 2000 Server. Further investigation has revealed that
it apparently fails in the same way on any machine running Windows 2000
Server - I've tried it on five other machines with exactly the same results.

The relevant section of code is reproduced below:

DirectoryEntry accountRoot = new DirectoryEntry(config.adRootPath,
config.adConnectionUsername,
config.adConnectionPassword,
AuthenticationTypes.ServerBind);

DirectoryEntry accountGroup = new DirectoryEntry(
config.adServer
+ config.adGroupObject,
config.adConnectionUsername,
config.adConnectionPassword,
AuthenticationTypes.ServerBind);
try
{
accountRoot.RefreshCache();
accountGroup.RefreshCache();
}
catch (Exception ex)
{
WriteErrorLog("Failed to refresh caches:\n\n" + ex.ToString());
return;
}
PropertyValueCollection groupMembers =
accountGroup.Properties["member"];
DirectoryEntry accountEntry = null;
try
{
accountEntry = accountRoot.Children.Find("CN=" + accountName,
"user");
}
catch(COMException)
{
// code ommited for brevity
}
if( accountEntry != null )
{
try
{
// Set mandatory account elements
accountEntry.Properties["sAMAccountName"].Value = accountName;
// Set some additional properties
accountEntry.Properties["userAccountControl"].Value = 0x10240;
accountEntry.Properties["userPrincipalName"].Value = accountName
+ "@" + config.adDomain;

object[] invocationArg = new object[]{accountPwd};

try
{
accountEntry.Invoke("setPassword", invocationArg);
}
catch (Exception e)
{
WriteErrorLog("Failed to set password for account \""
+ accountName + "\":\n\n" + e.ToString());
return;
}
}
catch( COMException commitException )
{
WriteErrorLog("Failed commit changes to Active Directory
server:\n\n" + commitException.ToString());
return;
}
}

The problematic line is the one that calls the Invoke method on the
DirectoryEntry object. At this point, on each of the Windows 2000 Server
machines, it fails with the following exception:


System.Reflection.TargetInvocationException: Exception has been thrown
by the target of an invocation. --->
System.Runtime.InteropServices.COMException (0x8007052E): The handle
specified is invalid

--- End of inner exception stack trace ---
at System.RuntimeType.InvokeDispMethod(String name, BindingFlags
invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers,
Int32 culture, String[] namedParameters)
at System.RuntimeType.InvokeMember(String name, BindingFlags
invokeAttr, Binder binder, Object target, Object[] args,
ParameterModifier[] modifiers, CultureInfo culture, String[]
namedParameters)
at System.Type.InvokeMember(String name, BindingFlags invokeAttr,
Binder binder, Object target, Object[] args)
at System.DirectoryServices.DirectoryEntry.Invoke(String methodName,
Object[] args)
at ADUpdater.ADUpdater.Main(String[] args)


I've been messing with this on and off for a few days now, and I'm not
really getting anywhere. Does anyone have any idea why this would be
failing on the Win2k Server machines? Input data and config is identical
in all cases, and I'm connecting to the Active Directory server using an
admin account's username and password.


Cheers,

Tim
 
Your problem is security related there's nothing wrong with the code as
such, A few questions though:
- Are the W2K servers domain members of the same user accounts domain?
- What kind of application is this (Windows, console, service, web...)
- Did you try to turn on auditing of AD access on the DC?
The reason I'm asking is that in order to SET the password of a user, the
caller needs a secure channel (to send the encrypted password) to connect to
the DC, so the machine account must be stored in the AD (the machine must be
a domain member).
The authentication type you selected needs Kerberos or NTML authentication.
If the servers are not domain members you will have to select/enable SSL
support.

Willy.
"tim (dot) campbell (at) hyperlink (dash) interactive (dot) co (dot) uk"
 
Back
Top