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

时间:2022-10-28 14:52:12

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

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

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

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

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

开始写代码。首先写从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[].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 提供程序”。

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

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

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

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

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

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

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

创建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属性是一个集合,除了一些字符串类型的属性,还有几个我觉得操作比较麻烦的。

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

例如"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了。

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

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

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

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

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

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

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

关于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>

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

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

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

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 = ;
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

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

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

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

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

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

其他参考资料:

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#示例代码(一)——导入用户信息的更多相关文章

  1. AD帐户操作C&num;示例代码(二)——检查密码将过期的用户

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

  2. php操作mysqli&lpar;示例代码&rpar;

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

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

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

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

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

  5. java poi操作excel示例代码

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

  6. 如何在C&num;程序中模拟域帐户进行登录操作 (转载)

    .NET Core .NET Core也支持用PInvoke来调用操作系统底层的Win32函数 首先要在项目中下载Nuget包:System.Security.Principal.Windows 代码 ...

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

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

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

    第五章 帐户(号) 帐户提供正在使用该应用程序的用户的身份(或身份).一个帐户有一个与之相关的SIP统一资源标识符(URI).在SIP术语中,该URI用作该人的记录地址( Address of Rec ...

  9. SharePoint 事件 7363:对象缓存:缓存使用的超级读者帐户没有足够的权限访问SharePoint数据库。

    转自MSND:http://technet.microsoft.com/zh-cn/library/ff758656(v=office.14) 对象缓存存储 Microsoft SharePoint ...

随机推荐

  1. 玩转JavaScript OOP&lbrack;2&rsqb;&mdash&semi;&mdash&semi;类的实现

    概述 当我们在谈论面向对象编程时,我们在谈论什么?我们首先谈论的是一些概念:对象.类.封装.继承.多态.对象和类是面向对象的基础,封装.继承和多态是面向对象编程的三大特性. JavaScript提供了 ...

  2. js数组操作大全&lpar;转载&rpar;

    转载原网址:http://hi.baidu.com/jspboy/item/4923fffb52a28014fe35823a shift:删除原数组第一项,并返回删除元素的值:如果数组为空则返回und ...

  3. 【noiOJ】p8208

    03:切分矩形组 查看 提交 统计 提问 总时间限制:  1000ms 内存限制:  65536kB 描述 给定若干个平行于坐标轴的互不重叠的矩形,矩形的顶点都是整点.要求画一根平行于y轴的直线x=k ...

  4. C&num;之泛型

    泛型是C# 2.0版本才有的语言特性,是具有参数类型占位符的类.结构.接口和方法.这些占位符是类.结构.接口和方法所存储或使用的一个或多个占位符.简单来说,就是变量类型的参数化. 以下是详细demo: ...

  5. Java里面instanceof怎么实现的

    开始完全一头雾水呀,后面看了Java指令集的介绍,逐渐理解了. https://www.zhihu.com/question/21574535/answer/18998914 下面这个答案比较直白 你 ...

  6. 使用DS18B20设计温控系统

    datasheet真的是得看看啊,比如DS18B20,不然程序都不好写,美国DALLAS半导体公司推出的数字化温度传感器DS18B20采用单总线协议,即与单片机接口仅需要一个IO口,无需任何外部原件, ...

  7. struts2-core-2&period;1&period;6&period;jar&excl;&sol;struts-default&period;xml无法加载的问题

    找到合适且匹配的jar包,更改完jar包后要去.metadata---.me_tcat---webapps---项目名----WEB-INF--lib下将多余的jar包去掉,否则还运行时还存在替换掉的 ...

  8. IO之流程与buffer 图

    http://blog.chinaunix.net/uid-29075379-id-3944364.html

  9. DevExpress XtraReports 入门四 创建 Web 报表

    原文:DevExpress XtraReports 入门四 创建 Web 报表 本文只是为了帮助初次接触或是需要DevExpress XtraReports报表的人群使用的,为了帮助更多的人不会像我这 ...

  10. iframe嵌套页面 音频在微信公众号环境无法播放

    在微信公众号中 没有iframe的时候window.WeixinJSBridge为对象,有iframe时为undefined 要使用 window.parent.WeixinJSBridge得到 if ...