AD帐户操作C#示例代码(二)——检查密码将过期的用户

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

本文接着和大家分享AD帐户操作,这次开发一个简单的检查密码将过期用户的小工具。

首先,新建一个用户实体类,属性是我们要取的用户信息。

publicclassUserInfo

{///<summary>///sAM帐户名称///</summary>publicstringSamAccountName {get;set; }///<summary>///名称///</summary>publicstringName {get;set; }///<summary>///邮箱///</summary>publicstringMail {get;set; }///<summary>///已禁用///</summary>publicboolIsDisabled {get;set; }///<summary>///设置为密码永不过期///</summary>publicboolIsPassWordNeverExpire {get;set; }///<summary>///设置为不需要密码///</summary>publicboolIsNoPasswordRequired {get;set; }///<summary>///系统密码过期设置天数///</summary>publiclongMaxPasswordAge {get;set; }///<summary>///剩余过期天数///</summary>publicdouble?SurplusPasswordExpirationDays {get{if(!PasswordExpirationDate.HasValue)

{returndefault(double?);

}doubledays =PasswordExpirationDate.Value.Subtract(DateTime.Now).TotalDays;if(days <=0)

{return0;

}returnMath.Round(days,2);

}

}///<summary>///最近密码修改时间///</summary>publicDateTime PasswordLastSet {get;set; }///<summary>///密码过期时间///</summary>publicDateTime? PasswordExpirationDate {get;set; }

}

View Code

然后是搜索用户信息的方法。

PRivateIEnumerable<UserInfo> SearchUsers(stringpath,stringusername,stringpassword,stringsAMAccountName,stringdisplayName,boolisDisabled,boolIsPasswordNeverExpire,long[] surplusExpirationDays)

{using(DirectoryEntry directoryEntry =newDirectoryEntry(path, username, password))

{using(DirectorySearcher directorySearcher =newDirectorySearcher(directoryEntry,@"&(objectCategory=person)(objectClass=user))",newstring[] {"name","sAMAccountName","userAccountcontrol","pwdLastSet","mail"}, SearchScope.Subtree) { PageSize =1000})

{using(SearchResultCollection userResultCollection =directorySearcher.FindAll())

{foreach(SearchResult userResultinuserResultCollection)

{

UserInfo userInfo=newUserInfo();//TODO: 赋值yieldreturnuserInfo;

}

}

}

}

}

这次我们主要用DirectorySearcher类:SearchRoot是搜索的DirectoryEntry根节点;SearchScope属性是搜索的范围,是个SearchScope枚举:Base(限于基对象)、OneLevel(搜索基对象的直接子对象,但不搜索基对象)、Subtree(搜索整个子树,包括基对象及其所有子对象)。我们要在指定的OU下搜索用户,所以选择子树Subtree。

DirectorySearcher类的Filter属性是过滤条件,搜索用户就是“&(objectCategory=person)(objectClass=user))"。注意:表示与或非的“&”、“|”、“!”要放在这些条件表达式前面而不是它们之间;如果要做模糊查询用通配符*;可以用“=”、“>=”、“<=”、“~=”(约等于),但“>”和”<“是不行的;”pwdLastSet“要转为Windows文件时间,因为存的是个long,还有处理”userAccountControl"的并运算,这里用“:1.2.840.113556.1.4.803:=”。我们可以把一些查询条件放在Filter里,减少搜索结果的返回行数:

directorySearcher.SearchScope =SearchScope.Subtree;

List<string> filterItems =newList<string>();if(!string.IsNullOrEmpty(sAMAccountName))

{

filterItems.Add(string.Format(@"(sAMAccountName={0})", sAMAccountName));

}if(!string.IsNullOrEmpty(displayName))

{

filterItems.Add(string.Format(@"(name={0})", displayName));

}if(!containsDisabled)

{

filterItems.Add(@"(!(userAccountControl:1.2.840.113556.1.4.803:=2))");

}if(!containsPasswordNeverExpire)

{

filterItems.Add(@"(!(userAccountControl:1.2.840.113556.1.4.803:=65536))");

}if(!containsNoPasswordRequired)

{

filterItems.Add(@"(!(userAccountControl:1.2.840.113556.1.4.803:=32))");

}if(surplusExpirationDays !=null&& surplusExpirationDays.Length >0)

{

StringBuilder surplusExpirationDaysFilter=newStringBuilder(@"(|");

DateTime now=DateTime.Now;foreach(longsurplusExpirationDayinsurplusExpirationDays)

{

DateTime passwordExpirationDate=now.AddDays(surplusExpirationDay);

DateTime passwordLastSet= passwordExpirationDate.AddDays(-1*maxPwdAge);if(surplusExpirationDay !=0)

{

surplusExpirationDaysFilter.AppendFormat("(&(pwdLastSet>={0})(pwdLastSet<={1}))", passwordLastSet.ToFileTime().ToString(), passwordLastSet.AddDays(1).AddSeconds(-1).ToFileTime().ToString());

}else{

surplusExpirationDaysFilter.AppendFormat("(pwdLastSet<={0})(pwdLastSet=0)", passwordLastSet.AddDays(1).AddSeconds(-1).ToFileTime().ToString());

} }

surplusExpirationDaysFilter.Append(@")");

filterItems.Add(surplusExpirationDaysFilter.ToString());

}

directorySearcher.Filter=string.Format(@"(&{0}(objectCategory=person)(objectClass=user))",string.Concat(filterItems));

Filter语法请参考:http://msdn.microsoft.com/en-us/library/aa746475.aspx、http://www.ldapexplorer.com/en/manual/109010000-ldap-filter-syntax.htm。

userAccountControl标志请参考:http://support.microsoft.com/kb/305144/zh-cn、http://msdn.microsoft.com/zh-cn/library/ms680832(VS.85).aspx、http://technet.microsoft.com/library/ee198831.aspx。

DirectorySearcher类的PropertiesToLoad属性是要检索的属性列表,这个就相当于我们访问数据库时写SQL语句里SELECT后面的东西,最好按需指定,尽量不写“SELECT *”; 注意DirectorySearcher类的PageSize属性,如果要返回所有数据可以设为1000,默认是只返回1000条的。

directorySearcher.PropertiesToLoad.AddRange(newstring[] {"name","sAMAccountName","userAccountcontrol","pwdLastSet","mail"});

directorySearcher.PageSize=1000;

更多DirectorySearcher类属性请参考:http://msdn.microsoft.com/zh-cn/library/System.DirectoryServices.DirectorySearcher_properties(v=vs.80).aspx

用户密码的过期日期可以通过DirectoryEntry对象的InvokeGet方法获得,不过要加载一次DirectoryEntry的话,总觉得很浪费!

using(DirectoryEntry resultDirectoryEntry =userResult.GetDirectoryEntry())

{

userInfo.PasswordExpirationDate= DateTime.Parse(resultDirectoryEntry.InvokeGet("PasswordExpirationDate").ToString());

}

所以我还是愿意自己算一下,用最近密码设置时间+系统设置的密码过期天数。最近密码设置时间对应“pwdLastSet”,如果用DirectoryEntry对象的Properties取,那是个“System.__ComObject”类型值,幸好SearchResult对象的“pwdLastSet”可以直接取为long,这个值是Windows文件时间,可以再转为本地时间。

longfileTime = (userResult.Properties["pwdLastSet"][0]aslong?).GetValueOrDefault();

userInfo.PasswordLastSet= DateTime.FromFileTime(fileTime);

系统密码过期天数是通过组策略设置的,可以在OU路径下通过“maxPwdAge”属性获取,SearchResult对象的“maxPwdAge”也可以直接取为long。

directorySearcher.SearchScope =SearchScope.Base;

directorySearcher.Filter=@"(objectClass=*)";

directorySearcher.PropertiesToLoad.Add("maxPwdAge");

SearchResult ouResult=directorySearcher.FindOne();longmaxPwdAge =0;if(ouResult.Properties.Contains("maxPwdAge"))

{

maxPwdAge= TimeSpan.FromTicks((long)ouResult.Properties["maxPwdAge"][0]).Days * -1;

}

最后,用户的密码过期就可以这么求了!

userInfo.MaxPasswordAge =maxPwdAge;if(!userInfo.IsPasswordNeverExpire)

{

userInfo.PasswordExpirationDate=userInfo.PasswordLastSet.AddDays(userInfo.MaxPasswordAge);

}

查询用户信息OK,剩下贴段将用户信息导出Excel的代码:

stringconnectionString =string.Format("Provider = Microsoft.ACE.OLEDB.12.0;Data Source ={0};Extended Properties='Excel 12.0xml;HDR=YES'", fileName);using(OleDbConnection oleDbConnection =newOleDbConnection(connectionString))

{

oleDbConnection.Open();using(OleDbCommand oleDbCommand =newOleDbCommand())

{

oleDbCommand.Connection=oleDbConnection;//const string sqlCreateTable = @"CREATE TABLE [Sheet1$] ([登录名] TEXT,[显示名] TEXT,[邮箱] TEXT,[已禁用] TEXT,[密码永不过期] TEXT,[密码过期设置天数] TEXT,[密码最近设置时间] TEXT,[密码过期时间] TEXT,[剩余密码过期天数] TEXT)";//oleDbCommand.CommandText = sqlCreateTable;//oleDbCommand.ExecuteNonQuery();foreach(varuserinusers)

{

oleDbCommand.CommandText=string.Format(@"INSERT INTO [Sheet1$]([登录名], [显示名], [邮箱],[已禁用], [密码永不过期], [密码过期设置天数],[密码最近设置时间],[密码过期时间],[剩余密码过期天数]) VALUES ('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}');", user.SamAccountName, user.Name, user.Mail, user.IsDisabled ?"是":"否", user.IsPasswordNeverExpire ?"是":"否", user.MaxPasswordAge.ToString(), user.PasswordLastSet.ToString(), user.PasswordExpirationDate.ToString(), user.SurplusPasswordExpirationDays.ToString());

oleDbCommand.ExecuteNonQuery();

}

}

}

View Code

还有使用SmtpClient发送邮件的代码,可以自定义个HTML文件做模版内容:

using(SmtpClient smtpClient =newSmtpClient())

{if(!string.IsNullOrEmpty(mailMessageInfo.Host))

{

smtpClient.Host=mailMessageInfo.Host;

}if(!string.IsNullOrEmpty(mailMessageInfo.Port))

{

smtpClient.Port=int.Parse(mailMessageInfo.Port);

}

smtpClient.Credentials=newSystem.Net.NetworkCredential();if(!string.IsNullOrEmpty(mailMessageInfo.UserName))

{

NetworkCredential networkCredential=newNetworkCredential { UserName =mailMessageInfo.UserName };if(!string.IsNullOrEmpty(mailMessageInfo.PassWord))

{

networkCredential.Password=mailMessageInfo.PassWord;

}

smtpClient.Credentials=networkCredential;

}

MailMessage mailMessage=newMailMessage();if(!string.IsNullOrEmpty(mailMessageInfo.From))

{

mailMessage.From=newMailAddress(mailMessageInfo.From);

}foreach(stringtoinmailMessageInfo.ToList)

{if(!string.IsNullOrWhiteSpace(to))

{

mailMessage.To.Add(to);

}

}if(mailMessageInfo.CcList !=null&& mailMessageInfo.CcList.Length >0)

{foreach(stringccinmailMessageInfo.CcList)

{if(!string.IsNullOrWhiteSpace(cc))

{

mailMessage.To.Add(cc);

}

}

}

mailMessage.IsBodyHtml=true;stringpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"templates", mailMessageInfo.TemplateFileName);stringbody =File.ReadAllText(path);

Regex regexImg=newRegex(@"<img\s[^>]*>", RegexOptions.IgnoreCase);

Regex regexSrc=newRegex(@"src=(?:(['""])(?<src>(?:(?!\1).)*)\1|(?<src>[^\s>]+))",

RegexOptions.IgnoreCase|RegexOptions.Singleline);

MatchCollection matchCollection=regexImg.Matches(body);

Dictionary<string,string> contentIds =newDictionary<string,string>();foreach(Match matchImginmatchCollection)

{if(regexSrc.IsMatch(matchImg.Groups[0].Value))

{

Match matchSrc= regexSrc.Match(matchImg.Groups[0].Value);stringsrcValue = matchSrc.Groups["src"].Value;if(!srcValue.StartsWith("http:", System.StringComparison.OrdinalIgnoreCase)&& !srcValue.StartsWith("file:", System.StringComparison.OrdinalIgnoreCase))

{if(srcValue.IndexOf("/") ==0)

{

srcValue= srcValue.Substring(1);

}stringattachmentContentId = Path.GetFileName(srcValue).Replace(".",string.Empty);

body= body.Replace(matchSrc.Groups["src"].Value,"cid:"+attachmentContentId);if(!contentIds.ContainsKey(attachmentContentId))

{stringinlinePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"templates", srcValue.Replace(@"/",@"\"));

Attachment inline=newAttachment(inlinePath);

inline.ContentDisposition.Inline=true;

inline.ContentDisposition.DispositionType=DispositionTypeNames.Inline;

inline.ContentId=attachmentContentId;if(srcValue.EndsWith("gif", StringComparison.OrdinalIgnoreCase))

{

inline.ContentType.MediaType=MediaTypeNames.Image.Gif;

}else{

inline.ContentType.MediaType=MediaTypeNames.Image.Jpeg;

}

inline.ContentType.Name=Path.GetFileName(inlinePath);

mailMessage.Attachments.Add(inline);

contentIds.Add(attachmentContentId,null);

}

}

}

}

mailMessage.Body=body; ;

mailMessage.BodyEncoding=Encoding.UTF8;

mailMessage.Subject=mailMessageInfo.Subject;

mailMessage.SubjectEncoding=Encoding.UTF8;

smtpClient.Send(mailMessage);

}

View Code

最后,将这些代码整合起来,就是检查用户密码过期的小工具了!由于笔者水平有限,文中难免会有些疏漏和错误,代码也有待不断优化,欢迎各位高手提出宝贵的建议!

参考资料:

DirectoryEntry 类使用 http://msdn.microsoft.com/zh-cn/library/z9cddzaa(v=vs.110).aspx

DirectorySearcher 类使用 http://msdn.microsoft.com/zh-cn/library/System.DirectoryServices.DirectorySearcher(v=vs.90).aspx

轻量目录访问协议 (LDAP) http://msdn.microsoft.com/en-us/library/aa367008(VS.85).aspx

检查密码将过期用户小工具下载地址:http://files.cnblogs.com/CSharpDevelopers/UserPasswordSetChecker.zip

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航