本文接着和大家分享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