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

最近写了一个AD帐户导入的小工具(为啥写作“帐”户呢?),跟大家分享下相关代码,欢迎各位高手指教!

首先,我准备一个这样的Excel文件作为导入模版,并添加了一些测试数据。

然后,我打开Visual Studio 2012,新建一个Windows窗体应用程序。在主窗体界面,我放了一些Label、TextBox、Button控件,还有一个ProgressBar。

开始写代码。首先写从Excel里读取数据的方法。

        private static async Task<DataTable> GetTableFromExcelAsync(string fileName)
        {
            return await Task.Factory.StartNew<DataTable>(() => GetTableFromExcel(fileName));
        }

        private static DataTable GetTableFromExcel(string fileName)
        {
            DataTable dataTable = new DataTable();
            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();
                DataTable schemaTable = oleDbConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new Object[] { null, null, null, "TABLE" });
                string sheetName = schemaTable.Rows[0].Field<string>("TABLE_NAME");
                string commandText = string.Format("select * from [{0}]", sheetName);
                using (OleDbDataAdapter adapter = new OleDbDataAdapter(commandText, oleDbConnection))
                {
                    adapter.Fill(dataTable);
                }
            }
            return dataTable;
        }

这样调用,将结果保存在一个DataTable里:

       private async void btnImport_Click(object sender, EventArgs e)
       {
            DataTable dataTable = await GetTableFromExcelAsync(txtUserListPath.Text);
       }

运行出现异常:“未在本地计算机上注册 Microsoft.ACE.OLEDB.12.0 提供程序”。

我的系统是X64的Windows 8 ,下载AccessDatabaseEngine.exe安装后,成功读取数据。

下载地址是:http://download.microsoft.com/download/7/0/3/703ffbcb-dc0c-4e19-b0da-1463960fdcdb/AccessDatabaseEngine.exe

如果在发布时还发生异常,那么再试试属性设置,把目标平台(G)改成x86或勾选"首选32位(P)"。

在.NET中访问AD服务可以用DirectoryEntry类(引用程序集 :System.DirectoryServices(在 System.DirectoryServices.dll 中)、命名空间:  System.DirectoryServices)。

创建DirectoryEntry对象要提供LDAP地址,作为我们创建用户的根OU,当然还要有在这个OU下创建OU和帐户的权限。

                string ldapPath = txtLdapPath.Text;
                string userName = txtUserName.Text;
                string password = txtPassword.Text; 

                DirectoryEntry rootDirectoryEntry;
                if (userName != string.Empty)
                {
                    rootDirectoryEntry = new DirectoryEntry(ldapPath, userName, password);
                }
                else
                {
                    rootDirectoryEntry = new DirectoryEntry(ldapPath);
                }

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

在创建用户帐户前,要先创建它们依赖的上级OU。创建OU的代码如下:

DirectoryEntry currentOuDirectoryEntry = currentOuDirectoryEntry.Children.Add("OU=" + currentValue, "organizationalUnit");
currentOuDirectoryEntry.Properties["name"].Add(currentValue);
currentOuDirectoryEntry.CommitChanges();

创建用户的代码如下:

DirectoryEntry currentUserDirectoryEntry = currentOuDirectoryEntry.Children.Add("CN=" + displayName, "user");
currentUserDirectoryEntry.Properties["sAMAccountName"].Value = sAMAccountName;
currentUserDirectoryEntry.Properties["userPrincipalName"].Value = string.Format(@"{0}@{1}", sAMAccountName, domainName);
currentUserDirectoryEntry.Properties["displayName"].Value = displayName;
currentUserDirectoryEntry.CommitChanges();

DirectoryEntry类的Properties属性是一个集合,除了一些字符串类型的属性,还有几个我觉得操作比较麻烦的。

例如"userAccountControl",看起来它只是一个整型字段,但是实际上它一个字段包含了很多个的状态信息。每个状态又对应着一个属性标志(例如密码永不过期是65536)。所以我们要从这一个userAccountControl字段读取或写入状态要做次位运算。

        private void SetPropertyInUserAccountControl(DirectoryEntry directoryEntry, bool newValue, int propertyflag)
        {
            int userAccountControl = (int)directoryEntry.Properties["userAccountControl"].Value;
            bool oldValue = GetPropertyFromUserAccountControl(directoryEntry, propertyflag);
            if (oldValue != newValue)
            {
                if (newValue)
                {
                    directoryEntry.Properties["userAccountControl"].Value = userAccountControl | propertyflag;
                }
                else
                {
                    directoryEntry.Properties["userAccountControl"].Value = userAccountControl & ~propertyflag;
                }
            }
        }
        private bool GetPropertyFromUserAccountControl(DirectoryEntry directoryEntry, int propertyflag)
        {
            return Convert.ToBoolean((int)directoryEntry.Properties["userAccountControl"].Value & propertyflag);
        }

更多userAccountControl属性标志(propertyflag参数)请参考资料:http://support.microsoft.com/kb/305144/zh-cnhttp://msdn.microsoft.com/zh-cn/library/ms680832(VS.85).aspx。那么这些标志属性是什么意思呢?为什么将"userAccountControl"值“&”一下属性标志就可以得到对应的状态呢?我把这些属性标志转换为二进制,发现都是只有一个1,其他都是0的。那个1的位置就是状态的标志位,如果“userAccountControl”字段的这个位置是1,那么对应状态就是“True”了。再用并运算(&:参考资料:http://msdn.microsoft.com/zh-cn/library/sbf85k1c.aspx)操作,因为0&0等于0,0&1或1&0也等于0,只有1&1才能等于1,所以“userAccountControl”和“只有一位是1其他全是0”的propertyflag并运算,就可以推断出该状态对应的标志位是不是1了。

不过我十分讨厌这种把多个维度的状态保存在一个字段中的设计,在曾经的项目中我也遇到过有高人在关系数据库中这样设计表字段,但我个人觉得这不符合第一范式的设计(同一列有多个值,应该分为多个IsXX1,IsXX2的bit字段),另外状态是个比较常用的过滤条件,在这个字段做位运算是否还能索引查找?当然有人觉得这样做减少了字段数量(在UI显示给用户的时候还是要分开吧?不然谁看得懂!),还有就是设计这一个状态字段以后想再多添加几个状态就不用修改表结构了。不过最重要的还是这样设计能体现出设计者的高水平,因为初级的程序员、数学不好的程序员以及记忆力不好的程序员看到这样一个整型值是不会马上知道它代表什么——我就是这样的程序员。

不过还好,我们可以直接用几个常用的,我创建的是正常帐户,不需要禁用,所以userAccountControl直接给512。

还有这些“System.__ComObject”类型的属性,操作起来太不方便了。我在网上找了一些资料,通常是引用了一个“Interop.ActiveDs.dll”的文件(不清楚是谁写的)。我这里只是希望新创建的用户下次登录时更改密码就要写:

currentUserDirectoryEntry.Properties["pwdLastSet"].Value = new LargeInteger() { HighPart = 0, LowPart = 0 };

不过后来我不是用的上面代码而是这样写的,也成功了。

currentUserDirectoryEntry.Properties["pwdLastSet"].Value = 0;

关于ADSI 对象属性有个参考资料:http://msdn.microsoft.com/zh-cn/library/ms180868(v=vs.90).aspx

我把几个常用的字符串类型属性写在XML文件里,导入数据时直接赋值即可。

<userProperties>
      <!--常规-->
      <property name = "sn" title = "姓"/>
      <property name = "givenName" title = "名"/>      <property name = "initials" title = "英文缩写"/>
      <property name = "displayName" title = "显示名称"/>
      <property name = "telephoneNumber" title = "电话号码"/>
      <property name = "otherTelephone" title = "其它电话号码"/>
      <property name = "mail" title = "电子邮件"/>
      <property name = "description" title = "描述"/>
      <property name = "physicalDeliveryOfficeName" title = "办公室"/>
      <property name = "wWWHomePage" title = "网页"/>
      <property name = "url" title = "其它网页"/>
      •<!--地址-->
      <property name = "co" title = "国家/地区"/>
      <property name = "st" title = "省/自治区"/>
      <property name = "l" title = "市/县"/>
      <property name = "streetAddress" title = "街道"/>
      <property name = "postOfficeBox" title = "邮政信箱"/>
      <property name = "postalCode" title = "邮政编码"/>
      •<!--电话-->
      <property name = "homePhone" title = "家庭电话"/>
      <property name = "otherHomePhone" title = "其他家庭电话"/>
      <property name = "pager" title = "寻呼机"/>
      <property name = "otherPager" title = "其他寻呼机"/>
      <property name = "mobile" title = "移动电话"/>
      <property name = "otherMobile" title = "其他移动电话"/>
      <property name = "facsimileTelephoneNumber" title = "传真"/>
      <property name = "otherFacsimileTelephoneNumber " title = "其他传真"/>
      <property name = "ipPhone" title = "IP电话"/>
      <property name = "otherIpPhone" title = "其他IP电话"/>
      <property name = "info" title = "注释"/>
      •<!--帐户-->
      <property name = "userPrincipalName" title = "用户登录名"/>
      <property name = "sAMAccountName" title = "用户登录名(Windows 2000 以前版本)"/>
      •<!--组织-->
      <property name = "company" title = "公司"/>
      <property name = "department" title = "部门"/>
      <property name = "title" title = "职务"/>
      <property name = "manager" title = "经理"/>
      <property name = "directReports" title = "直接下属"/>
    </userProperties>

如果您一次性把这几个属性都提交了,还可能会出现一个很有个性的异常:“该服务器不愿意处理该请求”。

要想让“她”愿意,可以这样写:

using (DirectoryEntry currentUserDirectoryEntry = currentOuDirectoryEntry.Children.Add("CN=" + displayName, "user"))
                            {
                                currentUserDirectoryEntry.Properties["sAMAccountName"].Value = sAMAccountName;
                                currentUserDirectoryEntry.Properties["userPrincipalName"].Value = string.Format(@"{0}@{1}", sAMAccountName, domainName);
                                currentUserDirectoryEntry.Properties["displayName"].Value = displayName;
                                currentUserDirectoryEntry.CommitChanges();
                                currentUserDirectoryEntry.Properties["userAccountControl"].Value = userAccountControl;
                                currentUserDirectoryEntry.Properties["pwdLastSet"].Value = 0;
                                currentUserDirectoryEntry.Invoke("SetPassword", new object[] { newUserDefaultPassword });
                                currentUserDirectoryEntry.CommitChanges();
                            }

因为我想给新导入的用户一个初始的密码,修改密码的操作这样写就可以了:

currentUserDirectoryEntry.Invoke("SetPassword", new object[] { newUserDefaultPassword });

当用户是某个OU的管理员时,需要给它赋予权限。代码里的ActiveDirectoryRights是个枚举类型,当然您有时也会用到别的选择。

                           if (string.Equals(currentDataRow[_isAdminColumnName] as string, @"是"))
                            {
                                IdentityReference newOwner = new NTAccount(domainName, sAMAccountName).Translate(typeof(SecurityIdentifier));
                                ActiveDirectoryAccessRule newRule = new ActiveDirectoryAccessRule(newOwner, ActiveDirectoryRights.GenericAll, AccessControlType.Allow);
                                currentOuDirectoryEntry.ObjectSecurity.SetAccessRule(newRule);
                                currentOuDirectoryEntry.CommitChanges();
                            }

如果要导入的用户已经存在,就会出现异常。那么如何判断一个用户是否已存在呢?这时我们需要用到的是.NET的DirectorySearcher类型。这个类型的一个构造方法需要给一个搜索根路径、搜索筛选器、要检索的属性和搜索范围。

 DirectorySearcher userDirectorySearcher = new DirectorySearcher(currentOuDirectoryEntry, string.Format(@"(&(cn={0})(objectCategory=person)(objectClass=user))", displayName), new[] { "adspath" }, SearchScope.OneLevel); SearchResult searchResult = userDirectorySearcher.FindOne(); if (searchResult != null) {      //TODO:...... }

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

最后将这些零散的代码组合起来,就是我要做的工具了!

看看导入的效果,算是成功导入了吧。

当然这只是个很简单的小例子,日后还要继续完善,各位专家、高手如果看到我做的不好的地方也欢迎指正,多给些高大上的建议,非常感谢!

其他参考资料:

http://msdn.microsoft.com/en-us/library/aa367008(VS.85).aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/ms675085(v=vs.85).aspx

AD用户导入工具下载:

http://files.cnblogs.com/CSharpDevelopers/ADUserImportTool.zip

AD帐户操作C#示例代码(一)——导入用户信息,布布扣,bubuko.com

时间: 2024-07-30 13:45:16

AD帐户操作C#示例代码(一)——导入用户信息的相关文章

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

本文接着和大家分享AD帐户操作,这次开发一个简单的检查密码将过期用户的小工具. 首先,新建一个用户实体类,属性是我们要取的用户信息. public class UserInfo { /// <summary> /// sAM帐户名称 /// </summary> public string SamAccountName { get; set; } /// <summary> /// 名称 /// </summary> public string Name {

php操作mysqli(示例代码)

<?php define("MYSQL_OPEN_LOGS",true); class mysqliHelp { private $db; public function __construct()    {     //如果要查询日志log的话,怎么办 } public function __get($name )     {       //echo "__GET:",$name;       if(in_array($name,array("d

如何使用csvde批量创建/导出用户帐户和群组

在日常AD管理和维护中,为用户创建帐户是最常见的工作啦,但是如果要你一下子创建上百个用户及群组,或者某天老板说他想看一下AD里面一共有多少个用户和群组,需要你导一份数据出来,那该怎么办呢?微软给我们提供了csvde这个工具就可以完成这些操作,具体操作如下: ? 指创建用户帐户 新建一个excel表最少包含以下字段,其中UserAccountControl 值为514 ,即新建帐户初始状态是禁用. 将上面的excel表整理后保存为users.csv上传到域控,使用域管理员执行下面的命令导入用户.

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

为保证ERP系统的信息流准确快速的传递,需要给系统设计一个消息盒子机制.当系统中发生业务操作后,需要提醒下一个环节的操作人员,以保证ERP信息流快速准确传递.比如生产任务单(工作单,加工单,制单)过帐完成后,需要通知仓库准备材料供车间领料生产.消息盒子的界面大致如下所示: 消息盒子包含业务通知(Messages)和工作流审批(Workflow).业务通知比如采购人员下达采购订单PO后,需要通知仓库人员准备收货.工作流审批是以审批为基础的单据流程控制. 在实现消息盒子过程中,遇到一个客户需要将消息

电子邮件系统帐户集成

.NET ERP系统 电子邮件系统帐户集成 为保证ERP系统的信息流准确快速的传递,需要给系统设计一个消息盒子机制.当系统中发生业务操作后,需要提醒下一个环节的操作人员,以保证ERP信息流快速准确传递.比如生产任务单(工作单,加工单,制单)过帐完成后,需要通知仓库准备材料供车间领料生产.消息盒子的界面大致如下所示: 消息盒子包含业务通知(Messages)和工作流审批(Workflow).业务通知比如采购人员下达采购订单PO后,需要通知仓库人员准备收货.工作流审批是以审批为基础的单据流程控制.

域用户和组帐户的管理之一次同时添加多个用户帐户篇

如果利用AD图形界面来创建大量用户帐户的话,将浪费很多时间用于重复操作相同的步骤.此时可以利用系统内置的工作csvde.exe.ldifde.exe.dsadd.exe等程序来节省创建用户帐户的时间. csvde.exe: 可以利用它来添加用户帐户(或其他类型的对象),但是不能利用它来修改或删除用户帐户.您需要事先利用文本编辑器将用户帐户数据创建到纯文本文件内,然后利用csvde.exe将文件内的这些用户帐户一次性导入到AD数据库中. ldifde.exe: 可以利用它来添加.删除.修改用户帐户

win10家庭版怎么开启Administrator超级管理员帐户

win10家庭版很多操作跟win10专业版不一样,比如开启内置administrator最高管理员帐户,专业版可以直接通过本地用户和组打开,win10家庭版就不行,而是需要用命令实现,下面系统城小编跟大家介绍win10家庭版开启administrator超级管理员的方法. 一.win10家庭版开启administrator方法: 1.通过Cortana搜索cmd,匹配出“命令提示符”,右键以管理员身份运行: 2.在打开的命令提示符窗口输入net user administrator /activ

PJSUA2开发文档--第五章 帐户(号)Accounts

第五章 帐户(号) 帐户提供正在使用该应用程序的用户身份.一个帐户有一个与之相关的SIP统一资源标识符(URI).在SIP术语中,该URI用作该人的记录地址(Address of Record ,AOR),并且用作传出请求中的From头. 帐户可能有也可能没有与之相关联的客户注册.帐户也与路由集和一些认证凭证相关联,这些凭证在使用该帐户发送SIP请求消息时使用.帐户还具有状态,当它们订阅的该帐户存在时,该状态将被报告给远程peer(对等体),或者如果为该帐户启用了存在发布,则该状态被发布到存在服

修改域用户创建计算机帐户的数量

将计算机加入到公司域环境,相信大家都做过,很多情况都是通过管理员帐户去添加.那么普通的域用户是否可以将计算机加入域呢?答案是肯定的,默认情况下,经过认证的域用户可以将10台计算机加入域中. 经过认证的域帐户都权限在该域中最多创建 10 个计算机帐户,这个相信很多朋友都知道,但这个数量是否可以修改,域管理员是否有权限只允许指定的组或域用户创建计算机帐户这个问题相信有很多朋友打个问号了,这个当然也是可以实现的. 修改经过认证的用户加入域的数量,我们可以通过以下方法实现: 1. 打开ADSI编辑器,右