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

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

    public class UserInfo
{
/// <summary>
/// sAM帐户名称
/// </summary>
public string SamAccountName { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 邮箱
/// </summary>
public string Mail { get; set; }
/// <summary>
/// 已禁用
/// </summary>
public bool IsDisabled { get; set; }
/// <summary>
/// 设置为密码永不过期
/// </summary>
public bool IsPasswordNeverExpire { get; set; }
/// <summary>
/// 设置为不需要密码
/// </summary>
public bool IsNoPasswordRequired { get; set; }
/// <summary>
/// 系统密码过期设置天数
/// </summary>
public long MaxPasswordAge { get; set; }
/// <summary>
/// 剩余过期天数
/// </summary>
public double? SurplusPasswordExpirationDays {
get
{
if (!PasswordExpirationDate.HasValue)
{
return default(double?);
}
double days = PasswordExpirationDate.Value.Subtract(DateTime.Now).TotalDays;
if (days <= )
{
return ;
}
return Math.Round(days, );
}
}
/// <summary>
/// 最近密码修改时间
/// </summary>
public DateTime PasswordLastSet { get; set; }
/// <summary>
/// 密码过期时间
/// </summary>
public DateTime? PasswordExpirationDate { get; set; }
}

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

private IEnumerable<UserInfo> SearchUsers(string path, string username, string password, string sAMAccountName, string displayName, bool isDisabled, bool IsPasswordNeverExpire, long[] surplusExpirationDays)
{
using (DirectoryEntry directoryEntry = new DirectoryEntry(path, username, password))
{
using (DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry, @"&(objectCategory=person)(objectClass=user))", new string[] { "name", "sAMAccountName", "userAccountcontrol", "pwdLastSet", "mail" }, SearchScope.Subtree) { PageSize = })
{
using (SearchResultCollection userResultCollection = directorySearcher.FindAll())
{
foreach (SearchResult userResult in userResultCollection)
{
UserInfo userInfo = new UserInfo();
//TODO: 赋值
yield return userInfo;
}
}
}
}
}

这次我们主要用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 = new List<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 > )
{
StringBuilder surplusExpirationDaysFilter = new StringBuilder(@"(|");
DateTime now = DateTime.Now;
foreach (long surplusExpirationDay in surplusExpirationDays)
{
DateTime passwordExpirationDate = now.AddDays(surplusExpirationDay);
DateTime passwordLastSet = passwordExpirationDate.AddDays(- * maxPwdAge);
if (surplusExpirationDay != )
{
surplusExpirationDaysFilter.AppendFormat("(&(pwdLastSet>={0})(pwdLastSet<={1}))", passwordLastSet.ToFileTime().ToString(), passwordLastSet.AddDays().AddSeconds(-).ToFileTime().ToString());
}
else
{
surplusExpirationDaysFilter.AppendFormat("(pwdLastSet<={0})(pwdLastSet=0)", passwordLastSet.AddDays().AddSeconds(-).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.aspxhttp://www.ldapexplorer.com/en/manual/109010000-ldap-filter-syntax.htm

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

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

 directorySearcher.PropertiesToLoad.AddRange(new string[] { "name", "sAMAccountName", "userAccountcontrol", "pwdLastSet", "mail" });
directorySearcher.PageSize = ;

更多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文件时间,可以再转为本地时间。

long fileTime = (userResult.Properties["pwdLastSet"][] as long?).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();
long maxPwdAge = ;
if (ouResult.Properties.Contains("maxPwdAge"))
{
maxPwdAge = TimeSpan.FromTicks((long)ouResult.Properties["maxPwdAge"][]).Days * -;
}

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

 userInfo.MaxPasswordAge = maxPwdAge;
if (!userInfo.IsPasswordNeverExpire)
{
userInfo.PasswordExpirationDate = userInfo.PasswordLastSet.AddDays(userInfo.MaxPasswordAge);
}

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

                   string connectionString = string.Format("Provider = Microsoft.ACE.OLEDB.12.0;Data Source ={0};Extended Properties='Excel 12.0 Xml;HDR=YES'", fileName);
using (OleDbConnection oleDbConnection = new OleDbConnection(connectionString))
{
oleDbConnection.Open();
using (OleDbCommand oleDbCommand = new OleDbCommand())
{
oleDbCommand.Connection = oleDbConnection;
//const string sqlCreateTable = @"CREATE TABLE [Sheet1$] ([登录名] TEXT,[显示名] TEXT,[邮箱] TEXT,[已禁用] TEXT,[密码永不过期] TEXT,[密码过期设置天数] TEXT,[密码最近设置时间] TEXT,[密码过期时间] TEXT,[剩余密码过期天数] TEXT)";
//oleDbCommand.CommandText = sqlCreateTable;
//oleDbCommand.ExecuteNonQuery();
foreach (var user in users)
{
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();
}
}
}

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

            using (SmtpClient smtpClient = new SmtpClient())
{
if (!string.IsNullOrEmpty(mailMessageInfo.Host))
{
smtpClient.Host = mailMessageInfo.Host;
}
if (!string.IsNullOrEmpty(mailMessageInfo.Port))
{
smtpClient.Port = int.Parse(mailMessageInfo.Port);
}
smtpClient.Credentials = new System.Net.NetworkCredential();
if (!string.IsNullOrEmpty(mailMessageInfo.UserName))
{
NetworkCredential networkCredential = new NetworkCredential { UserName = mailMessageInfo.UserName };
if (!string.IsNullOrEmpty(mailMessageInfo.PassWord))
{
networkCredential.Password = mailMessageInfo.PassWord;
}
smtpClient.Credentials = networkCredential;
}
MailMessage mailMessage = new MailMessage();
if (!string.IsNullOrEmpty(mailMessageInfo.From))
{
mailMessage.From = new MailAddress(mailMessageInfo.From);
}
foreach (string to in mailMessageInfo.ToList)
{
if (!string.IsNullOrWhiteSpace(to))
{
mailMessage.To.Add(to);
}
}
if (mailMessageInfo.CcList != null && mailMessageInfo.CcList.Length > )
{
foreach (string cc in mailMessageInfo.CcList)
{
if (!string.IsNullOrWhiteSpace(cc))
{
mailMessage.To.Add(cc);
}
}
}
mailMessage.IsBodyHtml = true;
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "templates", mailMessageInfo.TemplateFileName);
string body = File.ReadAllText(path);
Regex regexImg = new Regex(@"<img\s[^>]*>", RegexOptions.IgnoreCase);
Regex regexSrc = new Regex(
@"src=(?:(['""])(?<src>(?:(?!\1).)*)\1|(?<src>[^\s>]+))",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
MatchCollection matchCollection = regexImg.Matches(body);
Dictionary<string, string> contentIds = new Dictionary<string, string>();
foreach (Match matchImg in matchCollection)
{
if (regexSrc.IsMatch(matchImg.Groups[].Value))
{
Match matchSrc = regexSrc.Match(matchImg.Groups[].Value);
string srcValue = matchSrc.Groups["src"].Value;
if (!srcValue.StartsWith("http:", System.StringComparison.OrdinalIgnoreCase)
&& !srcValue.StartsWith("file:", System.StringComparison.OrdinalIgnoreCase))
{
if (srcValue.IndexOf("/") == )
{
srcValue = srcValue.Substring();
}
string attachmentContentId = Path.GetFileName(srcValue).Replace(".", string.Empty);
body = body.Replace(matchSrc.Groups["src"].Value, "cid:" + attachmentContentId);
if (!contentIds.ContainsKey(attachmentContentId))
{
string inlinePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "templates", srcValue.Replace(@"/", @"\"));
Attachment inline = new Attachment(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);
}

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

参考资料:

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

AD帐户操作C#示例代码(二)——检查密码将过期的用户的更多相关文章

  1. AD帐户操作C#示例代码(一)——导入用户信息

    最近写了一个AD帐户导入的小工具(为啥写作“帐”户呢?),跟大家分享下相关代码,欢迎各位高手指教! 首先,我准备一个这样的Excel文件作为导入模版,并添加了一些测试数据. 然后,我打开Visual ...

  2. pyspider示例代码二:解析JSON数据

    本系列文章主要记录和讲解pyspider的示例代码,希望能抛砖引玉.pyspider示例代码官方网站是http://demo.pyspider.org/.上面的示例代码太多,无从下手.因此本人找出一下 ...

  3. JQuery -- Dom操作, 示例代码

    1.内部插入节点 *   append(content) :向每个匹配的元素的内部的结尾处追加内容 *   appendTo(content) :将每个匹配的元素追加到指定的元素中的内部结尾处 *   ...

  4. php操作mysqli(示例代码)

    <?php define("MYSQL_OPEN_LOGS",true); class mysqliHelp { private $db; public function _ ...

  5. PHP程序中使用PDO对象实现对数据库的增删改查操作的示例代码

    PHP程序中使用PDO对象实现对数据库的增删改查操作(PHP+smarty) dbconn.php <?php //------------------------使用PDO方式连接数据库文件- ...

  6. java poi操作excel示例代码

    import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io ...

  7. Azure AD B2C(二)使用Azure AD B2C为ASP.NET Core 应用设置社交帐户(邮箱)登录/注册

    一,引言 上次关于Azure AD B2C 讲到一些概念,有介绍到,Azure AD B2C 也是一种身份验证的解决方案,但是它运行客户使用其首选的社交,企业或者本地账户标识对应用程序和API进行单一 ...

  8. [App Store Connect帮助]二、 添加、编辑和删除用户(5)创建一个沙盒测试员帐户

    如果您的 App 使用了 App 内购买项目或 Apple Pay,您可以在 App Store Connect 中创建沙盒测试员帐户,以便您向用户提供该 App 前,可以使用该帐户在测试环境中运行您 ...

  9. 解析大型.NET ERP系统 电子邮件系统帐户集成

    为保证ERP系统的信息流准确快速的传递,需要给系统设计一个消息盒子机制.当系统中发生业务操作后,需要提醒下一个环节的操作人员,以保证ERP信息流快速准确传递.比如生产任务单(工作单,加工单,制单)过帐 ...

随机推荐

  1. python学习心得第一章

    初始python 1什么是程序 计算机程序是一组执行某种动作的的指令.和那些电路.芯片.显卡.硬盘等不同,它不是计算机本身可以触摸的部分,而是隐藏在背后运行在硬件上面的东西.程序就是一系列告诉没有知觉 ...

  2. 9.3 js基础总结3

    2.后增量/后减量运算符 ++,-- var i = 10; var a = i++; // i = i + 1; alert(a); 3.比较运算符(>,<,>=,<=,== ...

  3. 初学JQuery笔记

    extend()函数是jQuery的基础函数之一,作用是扩展现有的对象 <script type="text/javascript" src="jquery-1.5 ...

  4. VC++ 简单的打印功能(对话框模式下)

    不多说,直接上代码 void CParamDlg::OnBnClickedButton6() { // TODO: 在此添加控件通知处理程序代码 CDC dc; CPrintDialog dlg(TR ...

  5. 数据库imp导表dmp的方法

    1>sqlplus / as sysdba 进入sqlplus 2>drop user USER cascade 3>create user USER IDENTIFIED BY P ...

  6. 使用Word发布文章到 WordPress 博客

    使用Word发布文章到 WordPress 博客 我们都知道,WordPress 自带的编辑器功能比较弱,而使用 Word 编辑文档却功能强大.其实我们使用 Word 编辑好的文档也是可以直接发布到 ...

  7. Thomas Brinkhoff 基于路网的移动对象生成器的使用

    Thomas Brinkhoff 基于路网的移动对象生成器的使用 网站:http://iapg.jade-hs.de/personen/brinkhoff/generator/ 各个移动对象在路网中进 ...

  8. dataguru(炼数成金)大数据培训基地印象

    dataguru访问地址:http://f.dataguru.cn/?fromuid=99611 课程优惠码:C4B6  这段时间一直在dataguru(炼数成金)上学习<hadoop数据分析平 ...

  9. mysql查询表的数据大小

    在需要备份数据库里面的数据时,我们需要知道数据库占用了多少磁盘大小,可以通过一些sql语句查询到整个数据库的容量,也可以单独查看表所占容量. 1.要查询表所占的容量,就是把表的数据和索引加起来就可以了 ...

  10. I/O流——File类及使用

    I/O框架介绍 I/O是计算机输入/输出的接口.Java的核心库java.io提供了全方面的I/O接口,包括:文件系统的操作,文件读写,标准设备的输出等. File类及使用 ①   一个File类的对 ...