Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西
posts - 115, comments - 316, trackbacks - 42, articles - 0
原文:http://www.blogcn.com/User8/flier_lu/blog/4371854.html

    .NET Framework 提供了 System.DirectoryServices 名字空间用于操作 AD 等支持 LDAP 接口的服务器,通过这组类我们能够很容易实现通过 AD 验证用户帐号,以及向 AD 查询域用户及其所在组的信息,是在 Web 应用中集成 AD 以实现企业单点登陆的重要手段之一。
    纯朴的狗熊在其 blog 上有一系列非常出色的文章介绍了这方面的基本知识

    活动目录.NET编程Tips
    使用System.DirectoryServices.Protocols实现对AD的简单操作
    ADHelper 活动目录用户操作类

    虽然他给出的那个例子代码并不完整,但为后来者提供了很好的基础。

    为了让笔者所在公司的基于 SharePoint 的内网门户能够提供一些方便的小功能,如查询自己帐号的密码过期时间等等,笔者对其封装代码做了一些修改,定义了 AdServer/AdGroup/AdUser 分别用于对 AD 服务器/组/用户的封装,让关系更加清晰。回头等封装代码稳定了,再写篇文章详细介绍。

    其中碰到一个讨厌的问题是如何从 AD 获取当前帐号的密码过期时间。对基于域的用户来说,通过 ADSI 接口的 WINNT:// 协议,可以简单的从 IADsUser::PasswordExpirationDate 获得这一信息;但对于 AD 的 LDAP:// 协议接口,这个字段并不存在,需要我们手工从帐号最后登陆时间 (pwdLastSet) 和用户所在域的帐号过期时间 (maxPwdAge) 自行计算。

    ADSI 接口的 User 对象 schema 中定义了这些常用的属性

    User Object Properties

    微软 MSDN 中也专门有一篇文章详细介绍了如何进行这种计算

    How Long Until My Password Expires?

    其核心算法步骤如下:

    1.帐号是否被禁用
    2.帐号密码是否被设置过
    3.帐号所在域是否有密码期限设置
    4.计算密码期限设置的天数
    5.计算密码过期的时间

    算法流程图如下:

    

    对 VBScript 来说只需要一段简单的代码就可以完成任务
On Error Resume Next

Const ADS_UF_DONT_EXPIRE_PASSWD = &h10000
Const E_ADS_PROPERTY_NOT_FOUND  = &h8000500D
Const ONE_HUNDRED_NANOSECOND    = .000000100
Const SECONDS_IN_DAY            = 86400

Set objADSystemInfo = CreateObject("ADSystemInfo")              ' LINE 8
Set objUser = GetObject("LDAP://" & objADSystemInfo.UserName)   ' LINE 9

intUserAccountControl 
= objUser.Get("userAccountControl")
If intUserAccountControl And ADS_UF_DONT_EXPIRE_PASSWD Then
    WScript.Echo 
"The password does not expire."
    WScript.Quit
Else
    dtmValue 
= objUser.PasswordLastChanged
    
If Err.Number = E_ADS_PROPERTY_NOT_FOUND Then
        WScript.Echo 
"The password has never been set."
        WScript.Quit
    
Else
        intTimeInterval 
= Int(Now - dtmValue)
        WScript.Echo 
"The password was last set on " & _
          
DateValue(dtmValue) & " at " & TimeValue(dtmValue)  & vbCrLf & _
          
"The difference between when the password was last" & vbCrLf & _
          
"set and today is " & intTimeInterval & " days"
    End If

    
Set objDomain = GetObject("LDAP://" & objADSystemInfo.DomainDNSName)
    
Set objMaxPwdAge = objDomain.Get("maxPwdAge")

    
If objMaxPwdAge.LowPart = 0 Then
        WScript.Echo 
"The Maximum Password Age is set to 0 in the " & _
                     
"domain. Therefore, the password does not expire."
        WScript.Quit
    
Else
        dblMaxPwdNano 
= _
            
Abs(objMaxPwdAge.HighPart * 2^32 + objMaxPwdAge.LowPart)
        dblMaxPwdSecs 
= dblMaxPwdNano * ONE_HUNDRED_NANOSECOND
        dblMaxPwdDays 
= Int(dblMaxPwdSecs / SECONDS_IN_DAY)
        WScript.Echo 
"Maximum password age is " & dblMaxPwdDays & " days"

        If intTimeInterval >= dblMaxPwdDays Then
            WScript.Echo 
"The password has expired."
        Else
            WScript.Echo 
"The password will expire on " & _
              
DateValue(dtmValue + dblMaxPwdDays) & " (" & _
              
Int((dtmValue + dblMaxPwdDays) - Now& " days from today)."
        End If
    
End If
End If
    但因为 .NET v1.x 中活动目录的简陋封装,使得在 .NET 中要实现上述功能相对较为繁琐。

    首先需要通过 AdUser 对象封装的 DirectoryEntry 的属性获得 userAccountControl 字段的值,并判断是否设置了密码永不过期的标志:
  public class AdUser : AdItem
  
{
    
public enum ADS_USER_FLAG_ENUM
    
{
      
      ADS_UF_DONT_EXPIRE_PASSWD 
= 0X10000,
      
    }


    
public int UserAccountControl
    
{
      
get
      
{
        
return Convert.ToInt32(Properties["userAccountControl"][0]);
      }

    }


    
public bool IsPasswordNotExpire
    
{
      
get
      
{
        
return (UserAccountControl & (int)ADS_USER_FLAG_ENUM.ADS_UF_DONT_EXPIRE_PASSWD) != 0;
      }

    }

  }


    然后需要访问密码最后被重置的时间,判断此帐号是否被使用过。

    这里需要注意的是,密码最后重置时间 (pwdLastSet) 和域密码过期时间 (maxPwdAge) 等字段在 AD 中是 INTEGER8 类型。虽然理论上对应于 C# 中的 long,但通过 System.DirectoryServices 并不能直接访问之。也就是说对于这些 INTEGER8 类型的字段,通过 Convert.ToInt64(Properties["pwdLastSet"][0]) 这样的强制转换调用会直接抛出异常。
    要访问这种字段,必须显式通过 ADSI 规范中的 IADsLargeInteger 接口,手工进行转换。.NET 247 上的一篇文章里面介绍了这个问题的解决方法

    DirectoryEntry __ComObject use.

    而这个例子中的转换代码还可能出现溢出问题,需要小心处理

    Problem with the HighPart and LowPart Property Methods

    完整的转换代码如下:

public abstract class AdEntry : IDisposable
{
  
// 在 .NET 中访问 INTEGER8 类型必须通过 IADsLargeInteger 接口
  
// http://www.dotnet247.com/247reference/msgs/31/159934.aspx
  [ComImport]
  [Guid(
"9068270B-0939-11D1-8BE1-00C04FD8D503")]
  [InterfaceType(ComInterfaceType.InterfaceIsDual)]
  
internal interface IADsLargeInteger
  
{
    [DispId(
0x00000002)] int HighPart{getset;}
    [DispId(
0x00000003)] int LowPart{getset;}
  }


  
internal long GetLongValue(IADsLargeInteger value)
  
{
    
// 将 IADsLargeInteger 内容转换为 long 之前必须小心溢出
    
// http://www.rlmueller.net/Integer8Discussion.htm
    return (long)(((ulong)value.HighPart << 32+ (ulong)value.LowPart);
  }

}

    只有解决了这诸多问题,才能将上面那一小段 VBScript 代码真正移植到 .NET 下:
public class AdUser : AdItem
  
{
    
// http://msdn.microsoft.com/library/en-us/dnclinic/html/scripting09102002.asp
    public DateTime PasswordExpirationDate
    
{
      
get
      
{
        
if(IsPasswordNotExpire)
        
{
          
return DateTime.MaxValue; // 帐号被设置为密码永不过期
        }

        
else
        
{
          
long lastChanged;

          
try
          
{
            lastChanged 
= GetLongValue((IADsLargeInteger)Properties["pwdLastSet"][0]);
          }

          
catch(Exception)
          
{
            
return DateTime.MinValue; // 密码没有被设置过
          }


          IADsLargeInteger maxAge 
= (IADsLargeInteger)Server.Properties["maxPwdAge"][0];

          
if(maxAge.LowPart == 0)
            
return DateTime.MaxValue; // 域中密码没有设置最大有效期限
          else
            
return PasswordLastChanged.AddDays(Server.MaxPasswordDays);
        }

      }

    }

  }

    虽然是个小问题,可里面的阻力一点都不小。 希望如 纯朴的狗熊 所说微软会在下个版本里面真正认真对待目录服务这块企业级应用必备的领域。

Feedback

#1楼  回复 引用 查看   

2004-10-22 13:15 by 灵感之源      
看来你是非低层或者高难度的不研究:)

#2楼[楼主]  回复 引用 查看   

2004-10-22 16:42 by Flier Lu      
    其他的东西倒是也折腾,只不过很少以 blog 形式发表罢了。平时解决一个问题往往花不了多少时间,但要彻底弄明白并写出来,就需要投入几倍的时间。对普通的问题来说,自己弄明白就行了;除非我认为有价值或者重新折腾一次很麻烦,否则我是懒得写 blog 的,呵呵
    此外与工作相关的东西,原则上我是不在 blog 里面提及的,因为这是我的娱乐空间,而不是工作的延伸。所以虽然我现在工作时天天搞安全、用 java,但 blog 上面基本上都是 .NET 和 Windows 的 :P

#3楼  回复 引用 查看   

2005-04-27 14:43 by skyfei      
internal long GetLongValue(IADsLargeInteger value)
{
// 将 IADsLargeInteger 内容转换为 long 之前必须小心溢出
// http://www.rlmueller.net/Integer8Discussion.htm
return (long)(((ulong)value.HighPart << 32) + (ulong)value.LowPart);
}

这个函数应该改为:return (long)(((ulong)value.HighPart << 32) + (uint)value.LowPart);

用户帐户用不过期属性"accountExpires"值为long.MaxValue, 用原先的GetLongValue,得不到正确的值,用我改过的可以的到正确的值

#4楼  回复 引用   

2006-03-09 11:57 by qinmubiao@21cn.com[未注册用户]
IADsLargeInteger 是什么类型,如何应用。
我在调试的时候,
IADsLargeInteger maxAge = (IADsLargeInteger)Server.Properties["maxPwdAge"][0];
改行报错 找不到IADsLargeInteger的命名空间和类型。

#5楼[楼主]  回复 引用 查看   

2006-03-09 21:15 by Flier Lu      
@qinmubiao@21cn.com

IADsLargeInteger 是一个自定义的类型,用来封装 ADSL 的 COM 接口,你可以看看文中引用的一篇文章 DirectoryEntry __ComObject use.

http://www.dotnet247.com/247reference/msgs/31/159934.aspx

//This is the managed definition of this interface also found in
ActiveDs.tlb
[ComImport]
[Guid("9068270B-0939-11D1-8BE1-00C04FD8D503")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
internal interface IADsLargeInteger
{
[DispId(0x00000002)]
int HighPart{get; set;}
[DispId(0x00000003)]
int LowPart{get; set;}
}

#6楼  回复 引用   

2006-03-14 11:08 by qinmubiao@21cn.com[未注册用户]
我在调试的时候,
IADsLargeInteger maxAge = (IADsLargeInteger)Server.Properties["maxPwdAge"][0];


该行报错 找不到Server的命名空间和类型。

#7楼[楼主]  回复 引用 查看   

2006-03-14 12:39 by Flier Lu      
@qinmubiao@21cn.com

Server 是自己封装的属性,不是系统类当然要报错。
我 blog 的内容,主要是希望说明白一个问题的原理,
并通过此过程让读者去了解或思考一些技术性问题。
如果你希望找到直接能抄的代码,请自行 google 现成实现。

#8楼  回复 引用   

2006-03-14 14:54 by qinmubiao@21cn.com[未注册用户]
Flier Lu

您能给些相关的资料吗?
我在网上搜了很久,都找不到合适的代码。
万分感激。

#9楼[楼主]  回复 引用 查看   

2006-03-14 15:24 by Flier Lu      
@qinmubiao@21cn.com

原则上我是不直接贴代码的,下不为例吧 :S

using System;
using System.DirectoryServices;
using System.Runtime.InteropServices;

namespace AdDemo
{
public enum LoginResult
{
OK,
NotExist,
Inactive,
Incorrect
}

public abstract class AdEntry : MarshalByRefObject, IDisposable
{
private readonly DirectoryEntry _entry;

private bool _autoCommit = false;

protected AdEntry(DirectoryEntry entry)
{
_entry = entry;
}

public DirectoryEntry CurrentEntry
{
get
{
return _entry;
}
}

public bool AutoCommit
{
get
{
return _autoCommit;
}
set
{
_autoCommit = value;
}
}

public PropertyCollection Properties
{
get
{
return _entry.Properties;
}
}

public Uri URI
{
get
{
return new Uri(_entry.Path);
}
}

public String Path
{
get
{
return _entry.Path;
}
}

// 在 .NET 中访问 INTEGER8 类型必须通过 IADsLargeInteger 接口
// http://www.dotnet247.com/247reference/msgs/31/159934.aspx
[ComImport]
[Guid("9068270B-0939-11D1-8BE1-00C04FD8D503")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
internal interface IADsLargeInteger
{
[DispId(0x00000002)] int HighPart{get; set;}
[DispId(0x00000003)] int LowPart{get; set;}
}

internal long GetLongValue(IADsLargeInteger value)
{
// 将 IADsLargeInteger 内容转换为 long 之前必须小心溢出
// http://www.rlmueller.net/Integer8Discussion.htm
return (long)(((ulong)value.HighPart << 32) + (ulong)value.LowPart);
}


public void Commit()
{
_entry.CommitChanges();
}

public void Refresh()
{
if(_autoCommit) Commit();

_entry.RefreshCache();
}

public object Invoke(string methodName, params object[] args)
{
return _entry.Invoke(methodName, args);
}

#region IDisposable Members

public void Dispose()
{
if(_autoCommit) Commit();

_entry.Close();
}

#endregion
}

public abstract class AdItem : AdEntry
{
private readonly AdServer _srv;

internal AdItem(AdServer srv, DirectoryEntry entry) : base(entry)
{
_srv = srv;
}

public AdServer Server
{
get
{
return _srv;
}
}
}

public class AdUser : AdItem
{
///
/// 用户属性定义标志
///
public enum ADS_USER_FLAG_ENUM
{
///
/// 登录脚本标志。如果通过 ADSI LDAP 进行读或写操作时,该标志失效。如果通过 ADSI WINNT,该标志为只读。
///
ADS_UF_SCRIPT = 0X0001,

///
/// 用户帐号禁用标志
///
ADS_UF_ACCOUNTDISABLE = 0X0002,

///
/// 主文件夹标志
///
ADS_UF_HOMEDIR_REQUIRED = 0X0008,

///
/// 过期标志
///
ADS_UF_LOCKOUT = 0X0010,

///
/// 用户密码不是必须的
///
ADS_UF_PASSWD_NOTREQD = 0X0020,

///
/// 密码不能更改标志
///
ADS_UF_PASSWD_CANT_CHANGE = 0X0040,

///
/// 使用可逆的加密保存密码
///
ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0X0080,

///
/// 本地帐号标志
///
ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0X0100,

///
/// 普通用户的默认帐号类型
///
ADS_UF_NORMAL_ACCOUNT = 0X0200,

///
/// 跨域的信任帐号标志
///
ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0X0800,

///
/// 工作站信任帐号标志
///
ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0x1000,

///
/// 服务器信任帐号标志
///
ADS_UF_SERVER_TRUST_ACCOUNT = 0X2000,

///
/// 密码永不过期标志
///
ADS_UF_DONT_EXPIRE_PASSWD = 0X10000,

///
/// MNS 帐号标志
///
ADS_UF_MNS_LOGON_ACCOUNT = 0X20000,

///
/// 交互式登录必须使用智能卡
///
ADS_UF_SMARTCARD_REQUIRED = 0X40000,

///
/// 当设置该标志时,服务帐号(用户或计算机帐号)将通过 Kerberos 委托信任
///
ADS_UF_TRUSTED_FOR_DELEGATION = 0X80000,

///
/// 当设置该标志时,即使服务帐号是通过 Kerberos 委托信任的,敏感帐号不能被委托
///
ADS_UF_NOT_DELEGATED = 0X100000,

///
/// 此帐号需要 DES 加密类型
///
ADS_UF_USE_DES_KEY_ONLY = 0X200000,

///
/// 不要进行 Kerberos 预身份验证
///
ADS_UF_DONT_REQUIRE_PREAUTH = 0X4000000,

///
/// 用户密码过期标志
///
ADS_UF_PASSWORD_EXPIRED = 0X800000,

///
/// 用户帐号可委托标志
///
ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0X1000000

}


internal AdUser(AdServer srv, DirectoryEntry entry) : base(srv, entry)
{
}

public int UserAccountControl
{
get
{
return Convert.ToInt32(Properties["userAccountControl"][0]);
}
}

public bool Enabled
{
get
{
return (UserAccountControl & (int)ADS_USER_FLAG_ENUM.ADS_UF_ACCOUNTDISABLE) == 0;
}
}

public bool IsPasswordNotExpire
{
get
{
return (UserAccountControl & (int)ADS_USER_FLAG_ENUM.ADS_UF_DONT_EXPIRE_PASSWD) != 0;
}
}

public string DistinguishedName
{
get
{
return (string)Properties["distinguishedName"].Value;
}
}

public DateTime PasswordLastChanged
{
get
{
return DateTime.FromFileTime(GetLongValue((IADsLargeInteger)Properties["pwdLastSet"][0]));
}
}

// http://msdn.microsoft.com/library/en-us/dnclinic/html/scripting09102002.asp
public DateTime PasswordExpirationDate
{
get
{
if(IsPasswordNotExpire)
{
return DateTime.MaxValue; // 帐号被设置为密码永不过期
}
else
{
long lastChanged;

try
{
lastChanged = GetLongValue((IADsLargeInteger)Properties["pwdLastSet"][0]);
}
catch(Exception)
{
return DateTime.MinValue; // 密码没有被设置过
}

IADsLargeInteger maxAge = (IADsLargeInteger)Server.Properties["maxPwdAge"][0];

if(maxAge.LowPart == 0)
return DateTime.MaxValue; // 域中密码没有设置最大有效期限
else
return PasswordLastChanged.AddDays(Server.MaxPasswordDays);
}
}
}

public void ChangePassword(string oldPassword, string newPassword)
{
CurrentEntry.Invoke("ChangePassword", new Object[] {oldPassword, newPassword});

if(AutoCommit) Commit();
}

public void Attach(AdGroup group)
{
group.Add(this);
}

public void Detach(AdGroup group)
{
group.Remove(this);
}

public AdGroup[] Groups
{
get
{
PropertyValueCollection memberOfs = Properties["memberOf"];

AdGroup[] groups = new AdGroup[memberOfs.Count];

for(int i=0; i<groups.Length; i++)
{
groups[i] = new AdGroup(Server, Server.GetDirectoryEntry(memberOfs[i].ToString()));
}
return groups;
}
}
}

public class AdGroup : AdItem
{
internal AdGroup(AdServer srv, DirectoryEntry entry) : base(srv, entry)
{
}

public void Add(AdUser user)
{
Properties["member"].Add(user.DistinguishedName);

if(AutoCommit) Commit();
if(user.AutoCommit) user.Commit();
}

public void Remove(AdUser user)
{
Properties["member"].Remove(user.DistinguishedName);

if(AutoCommit) Commit();
if(user.AutoCommit) user.Commit();
}

public bool Contain(AdUser user)
{
return (bool)Invoke("IsMember", new Object[] { user.Path });
}
}

public class AdServer : AdEntry
{
public AdServer(string path, string username, string password, AuthenticationTypes authenticationType)
: base(new DirectoryEntry(path, username, password, authenticationType))
{
}

public AdServer(string path, string username, string password)
: base(new DirectoryEntry(path, username, password))
{
}

public AdServer(string path)
: base(new DirectoryEntry(path))
{
}

public String Username
{
get
{
return CurrentEntry.Username;
}
}

public String Password
{
get
{
return CurrentEntry.Password;
}
}

public AuthenticationTypes AuthenticationType
{
get
{
return CurrentEntry.AuthenticationType;
}
}

private static readonly int ONE_HUNDRED_NANOSECOND = 10000000;
private static readonly int SECONDS_IN_DAY = 86400;

public int MaxPasswordDays
{
get
{
long maxAge = GetLongValue((IADsLargeInteger)Properties["maxPwdAge"][0]);

return -(int)(maxAge / ONE_HUNDRED_NANOSECOND / SECONDS_IN_DAY);
}
}

public LoginResult LoginByAccountName(string accountName, string password)
{
AdUser user = FindUserByAccountName(accountName);

if(user == null)
return LoginResult.NotExist;
else if(!user.Enabled)
return LoginResult.Inactive;
else if(GetDirectoryEntry(user.Path, accountName, password) == null)
return LoginResult.Incorrect;
else
return LoginResult.OK;
}

public LoginResult Login()
{
return LoginByAccountName(Username, Password);
}

public DirectoryEntry GetDirectoryEntry(string path, string username, string password)
{
Uri uri = URI;

if(path.StartsWith(uri.Scheme.ToUpper()))
return new DirectoryEntry(path, username, password, AuthenticationType);
else
return new DirectoryEntry(uri.Scheme.ToUpper() + Uri.SchemeDelimiter +
uri.Host + "/" + path, username, password, AuthenticationType);
}

public DirectoryEntry GetDirectoryEntry(string path)
{
return GetDirectoryEntry(path, Username, Password);
}

public AdGroup FindGroup(string groupName, SearchScope scope)
{
DirectorySearcher searcher = new DirectorySearcher(CurrentEntry);

searcher.Filter = string.Format("(&(objectClass=group)(cn={0}))", groupName);
searcher.SearchScope = scope;

SearchResult ret = searcher.FindOne();

return ret == null ? null : new AdGroup(this, GetDirectoryEntry(ret.Path));
}

public AdGroup FindGroup(string groupName)
{
return FindGroup(groupName, SearchScope.Subtree);
}

public AdUser FindUserByCommonName(string commonName, SearchScope scope)
{
DirectorySearcher searcher = new DirectorySearcher(CurrentEntry);

searcher.Filter = string.Format("(&(&(objectCategory=person)(objectClass=user))(cn={0}))", commonName);
searcher.SearchScope = scope;

SearchResult ret = searcher.FindOne();

return ret == null ? null : new AdUser(this, GetDirectoryEntry(ret.Path));
}

public AdUser FindUserByCommonName(string commonName)
{
return FindUserByCommonName(commonName, SearchScope.Subtree);
}

public AdUser FindUserByAccountName(string accountName, SearchScope scope)
{
DirectorySearcher searcher = new DirectorySearcher(CurrentEntry);

searcher.Filter = string.Format("(&(&(objectCategory=person)(objectClass=user))(sAMAccountName={0}))", accountName);
searcher.SearchScope = scope;

SearchResult ret = searcher.FindOne();

return ret == null ? null : new AdUser(this, GetDirectoryEntry(ret.Path));
}

public AdUser FindUserByAccountName(string accountName)
{
return FindUserByAccountName(accountName, SearchScope.Subtree);
}

public bool IsUserExists(string commonName)
{
return FindUserByCommonName(commonName) != null;
}

public bool IsAccountExists(string accountName)
{
return FindUserByAccountName(accountName) != null;
}
}
}

#10楼  回复 引用   

2006-03-14 17:04 by qinmubiao@21cn.com[未注册用户]
谢谢,有问题我在向您请教。

#11楼  回复 引用   

2006-03-20 10:19 by qinmubiao@21cn.com[未注册用户]
请教您一个问题

用户密码尝试3次不成功后,用户被锁定,锁定状态如何取,还有几此可以尝试如何取?
同样的问题

#13楼  回复 引用   

2007-03-21 15:35 by Rex[未注册用户]
Hi,

Is it possible to know user password was expired?
I can check and calculate the expiry date before the password is expired but I cannot separate between incorrect username/password and password expired after the user password was expired.
I am writing code on .net framework 1.1
Anybody can help?

#14楼  回复 引用 查看   

2007-12-29 12:02 by ζ浮云¢惊龙      
谢谢

#15楼  回复 引用   

2008-10-21 20:34 by jingxj[未注册用户]
有一些关于AD同步编程的问题想请教方便给个MAIL沟通吗?我的MAIL是jingxj@qq.com

#16楼  回复 引用   

2008-12-29 14:42 by taohejumei[未注册用户]
请楼主帮帮忙,把类放入Vs2005的时候,
public String Password
{
get
{

return CurrentEntry.Password;
}
}
报错误 1 属性或索引器“System.DirectoryServices.DirectoryEntry.Password”无法用于此上下文中,因为它缺少 get 访问器
请问该怎么解决?

#17楼  回复 引用   

2008-12-29 16:35 by flier[未注册用户]
@taohejumei

password属性本来就是不允许读的,这是他设计上的约束

#18楼  回复 引用 查看   

2011-11-09 16:25 by 染简若      
LZ
ActiveDs.IADsLargeInteger max = (ActiveDs.IADsLargeInteger)domain.Properties["maxPwdAge"][0];
max.LowPart =-18105
max.HighPart=382894080

而实际上的过期实际是90
这是怎么回事呢