一个Exchange后台管理程序(WEB)其一 用户检索

 

引言

如果一个Exchange的用户群按照部门的形式分散在各地,而人数又很多,让一个管理员管理工作量实在有点大。一个方式是编写一个WEB程序发布到IIS,然后让各个分部门的管理员管理各自的部门。没错,本系列文章就是围绕这个话题展开的。而这个话题的核心是如何通过.NET管理Exchange。嗯,似乎是这样。当时在实现系统的过程中,我碰到了很多其他的问题,也非常有趣。所以我决定,按照实现的流程来记叙,列出碰到的一些问题和解决方式。解决问题的过程中参考了很多网友的博客,同时得到了很多网友的帮助,会在适当的地方给出引用,方便大家参考。

这个系统应该包括以下功能:查询,查看(指定的一些属性),禁用/启用,解锁,删除,新建,编辑(指定的一些属性),统计各个部门人数,为分部门指派管理员。总的来说就是增删查改。

用户查询

Exchange的用户信息是和AD同步的,符合微软企业级产品的风格。所以查询用户信息可以通过查询AD进行。在.NET下可以通过System.DirectoryServices名称空间(处于System.DirectoryServices程序集中)进行活动目录查询。使用到类型是DirectorySearcher。当然System.DirectoryService.AccountManagement名称空间中的PrincipalSearcher也可以进行用户查询,但是在执行一些操作的时候,还是前者方便。所以,这里使用前一个方案。

查询功能设计成按照用户的UPN(登录名)和姓名(环境中使用CN以方便显示)搜索,在列表中显示以下信息:登录名,CN,显示名称,所属部门,是否禁用。

分页

要进行分页就需要获得搜索结果的总条数,我一开始没多在意DirectorySearcehr的属性,直接指定了Filter属性和根结点,然后指定搜索范围为SubTree,结果当搜索条数为1200条的时候,统计个数就要十来秒。而且我还很2X的调用GetDirectoryEntry来访问其属性,结果查询超慢。经过朋友的指点,参考了这篇博客:http://blog.sina.com.cn/s/blog_683424be0100scuz.html,才有效的缩短了查询时间。

主要是PropertiesToLoad属性,通过向这个集合添加键,可以指定加载哪些DirectoryEntry属性,剩余属性不加载,从而缩短了时间。注意,Filter中涉及的属性是需要加载的。以下是一段脚本,和结果,可以看到差别之大。

可用的属性键有:

msexchumenabledflags2,homemdb,legacyexchangedn,usncreated,msexchhomeservername,m
sexchrecipientdisplaytype,msexchrbacpolicylink,samaccountname,showinaddressbook,
msexchmailboxauditenable,cn,pwdlastset,whencreated,displayname,lastlogon,garbage
collperiod,samaccounttype,countrycode,objectguid,msexchmailboxsecuritydescriptor
,msexchmdbrulesquota,usnchanged,msexcharchivewarnquota,msexchversion,whenchanged
,msexchmoderationflags,name,msexchwhenmailboxcreated,protocolsettings,msexchpoli
ciesincluded,objectsid,logoncount,internetencoding,mailnickname,msexchuseraccoun
tcontrol,msexchtransportrecipientsettingsflags,msexchprovisioningflags,badpasswo
rdtime,accountexpires,msexchaddressbookflags,primarygroupid,objectcategory,userp
rincipalname,proxyaddresses,msexchdumpsterquota,useraccountcontrol,dscorepropaga
tiondata,mdbusedefaults,distinguishedname,msexchmailboxauditlogagelimit,objectcl
ass,badpwdcount,msexchrecipienttypedetails,homemta,mail,adspath,msexcharchivequo
ta,msexchumdtmfmap,msexchmailboxguid,lastlogoff,msexchbypassaudit,instancetype,c
odepage,msexchdumpsterwarningquota,

 在我的例子中,使用cn,displayname,mail,samaccountname(和UPN同名),distinguishedname(通过解析获取OU信息),useraccountcontrol。

这里有一点需要注意,上面截图中,没有指定PageSize,所以就算指定SizeLimit,都只会最多返回1000条。

内存分页?LINQ迭代?

对1000条进行测试,速度上,两个区别不是很大。我最开始是用内存分页的,就是转换了(select)之后转为数组(ToArray),后来改为Skip,和Take的查询。在代码中进行测试的时候,设置好PageSize属性,然后使用foreach迭代输出,可以发现输出结果是一段一段的,输出若干个(页长)停顿一小会儿。所以使用Skip和Take方式最好,网上已经有LINQ to AD查询的实现了(LINQ Provider)。

用户操作

操作用户信息的时候有几个小点,这里直接列出。

#更改CN。使用DirectoryEntry修改对象的CN的属性的时候应该使用以下方式。

DirectoryEntry.Rename("CN=" + cmname),需要注意参数的格式。

#如何判断用户是否禁用。

1.通过DirecotryEntry.Properties["UserAccountControl"]的值和2进行位与(&)操作,然后转为bool,含义是Disabled(512&2的结果为0代表正常)

2.通过获取UserPrincipal,然后访问其Enabled属性。

#判断用户是否锁定。

DirectoryEntry.InvokeGet(“IsAccountLocked”)

#重置密码。重置密码后如何把用户置为“下次登录必须修改密码”。

1.通过DirectoryEntry获取UserPrincipal对象,调用ExpirePasswordNow()方法。

2.通过DirectoryEntry自身来处理应该也可行,但是尚未尝试。

#修改属性的正确的方式(防止异常)。这里把键和值对应了一下,不是必要的。

         foreach (var kv in propertyValues)
            {
                if (!string.IsNullOrEmpty(kv.Value))
                    if (entry.Properties.Contains(kv.Key))
                        entry.Properties[kv.Key][0] = kv.Value;
                    else
                        entry.Properties[kv.Key].Add(kv.Value);
            }
            entry.CommitChanges();    

 分页窗口

由于以前学过一点C(都不好意思说出口),考虑问题的时候总是面向过程。就算知道建立类型,一些思考方向还是没有转过来。这里表现的就很明显。面对的问题是:让分页栏保持定长(如果有足够多页的数据)。

#CStyle

获取当前页,获取窗口长度。计算左右距离,验证左侧,验证右侧。然后在不同的情况下取不同的值。比如:

protected int[] getPagerWindow(int current, int max, int width)
        {
            //获取分页窗口
            int left = (width - 1) / 2;
            int right = width - left - 1;
            //左右索引
            int rindex;
            int lindex;
            List<int> window = new List<int>();
            //计算
            if (current - left <= 0)
            {
                //获取分页窗口左右索引
                lindex = 1;
                rindex = Math.Min(width, max);
            }
            else if (current + right >= max)
            {
                //获取左右索引
                rindex = max;
                lindex = Math.Max(1, max - width + 1);
            }
            else
            {
                rindex = current + right;
                lindex = current - left;
            }
            for (int i = lindex; i < rindex + 1; i++)
                window.Add(i);

            return window.ToArray();
        }

#OOPStyle

将窗口想象成窗口,将页码想象成长胶卷。那么,如果胶卷长度比窗口小,就取所有页码,否则计算窗口中心的位移,然后移动窗口。比如:

        public static IEnumerable<int> ComputePageWindow(int count, int current, int size)
        {
            current = current < 1 ? 1 : current;
            count = count < 1 ? 1 : count;

            var fixedIndex = size%2 == 0 ? size/2 : (size + 1)/2;
            var offset = current - fixedIndex;
            offset = current < fixedIndex ? 0 : offset;
            offset = count - current < fixedIndex ? count - size : offset;
            var windowStart = 1 + offset;
            return Enumerable.Range(windowStart, size).Where(i => i > 0 && i <= count);
        }

#以及数据绑定

<asp:Repeater ID="rptPager" runat="server" 
DataSource='<%#getPagerWindow(searcher.CurrentIndex,searcher.PageCount,5) %>'
OnItemCommand="rptPager_ItemCommand">
</asp:Repeater>

很明显,第二种思路简洁很多。这里记下来提醒自己。各位朋友就当是听一个初学者的喃喃自语吧。

自此,AD信息管理的内容结束,其实主要是为了解决搜索的问题。这里贴一个运行截图。由于操作中有数据转换,所以执行搜索花了2~3秒,但是作为一个管理系统还可以接受。下一篇的内容是.net调用PowerShell组件以达到管理Exchange的目的。

原文地址:https://www.cnblogs.com/lightluomeng/p/2867019.html