最近写了一个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
在.NET中访问AD服务可以用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",看起来它只是一个整型字段,但是实际上它一个字段包含了很多属性信息,修改它需要做十六进制值的运算——这个我不擅长。(我见过有人设计数据库表的字段时,把几个维度的单据状态都存在一个int字段里,每次读取都要移位.....)还好我们可以直接用几个常用的,我创建的用户帐户不需要禁用,所以userAccountControl给512。
使用userAccountControl标志请参考资料:http://support.microsoft.com/kb/305144/zh-cn。
还有这些“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。
如果您一次性把这几个属性都提交了,还可能会出现一个很有个性的异常:“该服务器不愿意处理该请求”。
要想让“她”愿意,可以这样写:
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的管理员时,需要给它赋予权限。
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用户导入工具下载:
http://files.cnblogs.com/CSharpDevelopers/ADUserImportTool.zip