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

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

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

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

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

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

privatestaticasyncTask<DataTable> GetTableFromExcelAsync(stringfileName)

{returnawaitTask.Factory.StartNew<DataTable>(() =>GetTableFromExcel(fileName));

}privatestaticDataTable GetTableFromExcel(stringfileName)

{

DataTable dataTable=newDataTable();stringconnectionString =string.Format("Provider = Microsoft.ACE.OLEDB.12.0;Data Source ={0};Extended Properties='Excel 12.0xml;HDR=YES'", fileName);using(OleDbConnection oleDbConnection =newOleDbConnection(connectionString))

{

oleDbConnection.Open();

DataTable schemaTable= oleDbConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,newObject[] {null,null,null,"TABLE"});stringsheetName = schemaTable.Rows[0].Field<string>("TABLE_NAME");stringcommandText =string.Format("select * from [{0}]", sheetName);using(OleDbDataAdapter adapter =newOleDbDataAdapter(commandText, oleDbConnection))

{

adapter.Fill(dataTable);

}

}returndataTable;

}

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

privateasyncvoidbtnImport_Click(objectsender, EventArgs e)

{

DataTable dataTable=awaitGetTableFromExcelAsync(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和帐户的权限。

stringldapPath =txtLdapPath.Text;stringuserName =txtUserName.Text;stringpassWord= txtPassword.Text;

DirectoryEntry rootDirectoryEntry;if(userName !=string.Empty)

{

rootDirectoryEntry=newDirectoryEntry(ldapPath, userName, password);

}else{

rootDirectoryEntry=newDirectoryEntry(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字段读取或写入状态要做次位运算。

privatevoidSetPropertyInUserAccountControl(DirectoryEntry directoryEntry,boolnewValue,intpropertyflag)

{intuserAccountControl = (int)directoryEntry.Properties["userAccountControl"].Value;boololdValue =GetPropertyFromUserAccountControl(directoryEntry, propertyflag);if(oldValue !=newValue)

{if(newValue)

{

directoryEntry.Properties["userAccountControl"].Value = userAccountControl |propertyflag;

}else{

directoryEntry.Properties["userAccountControl"].Value = userAccountControl & ~propertyflag;

}

}

}privateboolGetPropertyFromUserAccountControl(DirectoryEntry directoryEntry,intpropertyflag)

{returnConvert.ToBoolean((int)directoryEntry.Properties["userAccountControl"].Value &propertyflag);

}

更多userAccountControl属性标志(propertyflag参数)请参考资料:http://support.microsoft.com/kb/305144/zh-cn、http://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 =newLargeInteger() { HighPart =0, LowPart =0};

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

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

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

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

<userProperties><!--常规--><propertyname= "sn"title= "姓"/><propertyname= "givenName"title= "名"/>

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

currentUserDirectoryEntry.CommitChanges();

}

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

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

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

if(string.Equals(currentDataRow[_isAdminColumnName]asstring,@"是"))

{

IdentityReference newOwner=newNTAccount(domainName, sAMAccountName).Translate(typeof(SecurityIdentifier));

ActiveDirectoryAccessRule newRule=newActiveDirectoryAccessRule(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

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航