zoukankan      html  css  js  c++  java
  • AD帐户操作C#示例代码(二)——检查密码将过期的用户

        本文接着和大家分享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 <= 0)
                    {
                        return 0;
                    }
                    return Math.Round(days, 2); 
                }
            }
            /// <summary>
            /// 最近密码修改时间
            /// </summary>
            public DateTime PasswordLastSet { get; set; }
            /// <summary>
            /// 密码过期时间
            /// </summary>
            public DateTime? PasswordExpirationDate { get; set; }
        }
    View Code

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

    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 = 1000 })
                    {
                        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 > 0)
                        {
                            StringBuilder surplusExpirationDaysFilter = new StringBuilder(@"(|");
                            DateTime now = DateTime.Now;
                            foreach (long surplusExpirationDay in surplusExpirationDays)
                            {
                                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.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 = 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文件时间,可以再转为本地时间。

    long fileTime = (userResult.Properties["pwdLastSet"][0] 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 = 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的代码:

                       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();
                                }
                            }
                        }
    View Code

          还有使用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 > 0)
                    {
                        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(@"<imgs[^>]*>", 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[0].Value))
                        {
                            Match matchSrc = regexSrc.Match(matchImg.Groups[0].Value);
                            string srcValue = 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);
                                }
                                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);
                }
    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

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

  • 相关阅读:
    leetcode—Swap Nodes in Pairs
    leetcode--Merge k Sorted Lists
    leetcode—Valid Parentheses
    leetcode—3sum
    编程工具(保持更新)
    QT下调用摄像头(opencv2.4.4)
    qtcreator 与 opencv
    在线公式编辑器
    QT学习(对话框)codeblock版本
    QT-opencv小结
  • 原文地址:https://www.cnblogs.com/CSharpDevelopers/p/3634640.html
Copyright © 2011-2022 走看看