我到现在还记得接那个需求时的感受。刚进公司做IT运维的第二周HR发来一个Excel表说下周有58名新员工入职需要把域账号提前建好。我打开AD用户和计算机右键、新建、填名字、设密码、再右键、填部门一套流程下来少说三分钟算了一下58个人要三个小时而且重复劳动最容易出错。我当时就决定必须用PowerShell把这活儿自动化。这篇文章就是冲着“批量创建域用户和修改用户信息”这个主题来的。无论你是刚接触AD域管理的运维新人还是已经在用PowerShell但没摸清批量操作细节的同行看完应该都能直接上手写脚本少踩几个我踩过的坑。1. 手工建号之痛批量脚本是AD管理员的必修课1.1 从“下周入职58人”说起手工创建域用户看着简单实际操作下来全是重复劳动。打开“Active Directory 用户和计算机”dsa.msc定位到目标OU右键新建用户填姓、名、用户登录名设置初始密码勾选“用户下次登录时须更改密码”创建后再右键属性补填部门、职位、办公电话还要把用户加入对应的安全组。一次操作大概3到5分钟中间还有几个特别耗神的细节登录名偶尔和现有用户冲突得换个名字再来一遍部门填错、加组漏掉后面还得回头补。58个人按3分钟一个算是174分钟但加上查重、填错、加组漏项一上午能做完都是快的。我当时干了半截就开始研究怎么用PowerShell把这活儿自动化。1.2 除了入职还有三类场景吃同样的痛批量创建只是最显眼的需求。真正让我把脚本沉淀下来的是后面这几种同样高频的场景批量修改信息公司组织架构调整原来“研发部”改名“技术中心”全部门的人都得改Department属性有的还要调整Title。在GUI里一个个点属性、改字段费时费力还容易漏人。批量启停账号离职集中发生、账号安全排查时要把一批人统一禁用或启用。手工右键“禁用账户”点完一批手指头都酸。信息补录系统刚上线时很多用户的电话、部门、办公地点都没填AD里一片空白需要拿HR的数据往回收。这几个需求的共同点在于操作对象都是同一批人、重复执行同一个动作。而PowerShell的批量处理恰好就是干这个的。2. 开跑之前模块安装、执行策略和权限边界2.1 管理机上装AD模块RSAT的两种装法要在PowerShell里操作域用户首先得有ActiveDirectory模块。这个模块不是PowerShell自带的它属于RSAT远程服务器管理工具。最直接的办法是到“设置→应用→可选功能→添加功能”里搜索“RSAT: Active Directory 域服务和轻型目录服务工具”装上之后模块就有了。如果像我一样懒得点界面可以直接用命令装Get-WindowsCapability -Name Rsat.ActiveDirectory.DS-LDS.Tools* -Online | Add-WindowsCapability -Online装完后重启PowerShell敲Import-Module ActiveDirectory不报错就算成功。注意这是Windows 10/11专业版或企业版才有的选项家庭版基本没戏我自己在家用电脑上就踩过这个坑后来老老实实装了Windows 11专业版。2.2 执行策略为什么脚本跑不起来模块装好了很多人第一个脚本就出师不利提示无法加载文件 xxx.ps1因为在此系统上禁止运行脚本这就是PowerShell执行策略在拦路。Windows默认的Restricted策略不允许运行任何脚本管理AD脚本需要放开一部分限制。我的处理方式是Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserRemoteSigned的含义是本机创建的脚本可以运行从网上下载的脚本必须有签名对日常管理来说足够安全。注意不要图省事直接设Unrestricted把所有脚本无差别放开。见过有管理员把执行策略设为绕过机器中了恶意脚本把域内账号信息枚举了一遍看起来方便实际是给内网攻击者开了一扇门。2.3 权限边界不是谁都能建域用户还有个容易忽略的点执行策略放开并不代表你就有权限建用户。PowerShell操作AD走的底层是LDAP协议最终认的是你当前登录账号在域里的权限。通常有两种方式用Domain Admins组的账号跑脚本最省事但不推荐日常使用在目标OU上做权限委派给运维账号授予“创建/删除用户对象”“修改用户属性”的权限这样最小化授权还方便审计。我实际维护的环境是有个服务账号svc-admgr专门跑这类批量脚本域策略里配置了委派权限就算泄露了也只是管用户碰不到域控上的敏感权限。3. New-ADUser拆解一行命令背后的AD用户模型3.1 必填参数和常用参数PowerShell创建域用户的核心命令是New-ADUser它的参数基本就是AD用户对象的各个属性。常用的几个我整理成了表格参数作用示例值-Name显示名称必填“张三”-SamAccountName登录名必填zhangsan-UserPrincipalNameUPN登录名zhangsancorp.local-GivenName / -Surname名 / 姓三 / 张-AccountPassword初始密码SecureStringConvertTo-SecureString ...-Path所在OU的LDAP路径“OU员工,DCcorp,DClocal”-Department部门研发部-Title职位软件工程师-Enabled是否启用账号$true-ChangePasswordAtLogon下次登录是否强制改密$true一个最小可用的创建命令大概长这样$pwd ConvertTo-SecureString -String Pssw0rd2024 -AsPlainText -Force New-ADUser -Name 张三 -SamAccountName zhangsan -UserPrincipalName zhangsancorp.local -GivenName 三 -Surname 张 -Path OU员工,OU苏州分公司,DCcorp,DClocal -AccountPassword $pwd -ChangePasswordAtLogon $true -Enabled $true还记得前面手工建号要3分钟吗这条命令跑完大概1秒都不到。3.2 密码怎么传进去SecureString和强密码策略刚接触的人很容易犯一个错直接写-AccountPassword Pssw0rd2024然后报错说类型不对。原因是这个参数要的不是普通字符串而是SecureString类型。所以要先用ConvertTo-SecureString转换$pwd ConvertTo-SecureString -String $明文密码 -AsPlainText -Force如果怕密码明文留在脚本或历史记录里更稳妥的方式是运行时输入$pwd Read-Host -Prompt 请输入初始密码 -AsSecureString另外一个高频报错是“密码不满足复杂性要求”。域默认密码策略要求长度至少8位、必须包含大小写字母和数字等如果你给新用户设置的密码太弱创建的时候就会失败。后面排错章节我会专门讲这个。3.3 -Path的意义新用户落在哪个OU-Path决定新用户创建在哪个OU里。很多初学者在这儿栽跟头拿不到正确的OU路径。常见错误是照着ADUC界面里显示的OU名称写比如界面里显示“苏州分公司”就写-Path 苏州分公司这肯定不对。LDAP路径的写法是从下往上、逐级写明比如用户在“苏州分公司→员工→研发部”这个路径下就应该写-Path OU研发部,OU员工,OU苏州分公司,DCcorp,DClocal拿不准时可以先查一遍Get-ADOrganizationalUnit -Filter * | Select-Object Name, DistinguishedName把输出里的DistinguishedName直接复制到-Path参数里绝对不会错。我写脚本时习惯先用这条命令把所有OU结构撸一遍再在脚本里维护一个OU映射表比每次手工拼路径省心很多。4. CSV批量导入从Excel到域控的完整链路4.1 CSV长什么样列名和用户属性的对应关系单条命令再快也只是开胃菜。真正的批量操作要从Excel数据开始。HR提供的数据通常是中文表头一两百行这时候需要把Excel另存为CSV文件。我这里给出一份推荐的表头设计用英文列名对应PowerShell可以识别的字段LoginName,DisplayName,GivenName,Surname,Password,Department,Title,Extension zhangsan,张三,三,张,Pssw0rd2024,研发部,软件工程师,8001 lisi,李四,四,李,Pssw0rd2024,研发部,测试工程师,8002注意列名不一定要和AD属性完全一致Import-Csv读取后每一列会变成对象的属性名脚本里通过$.列名来引用。只要你在脚本里把$.LoginName传给New-ADUser的-SamAccountName参数就行。我习惯在CSV里用LoginName而不是SamAccountName纯粹是为了让HR同事看得懂。4.2 编码问题中文乱码的真相在这里CSV批量导入第一个拦路虎绝对是中文乱码。以前我遇到过PowerShell读出来的姓名变成“寮犱笁”这种错乱字符创建出来的用户Name全是乱的最后只能批量删掉重建。这个问题的根源是编码。Excel直接另存为CSV时默认是GBK/ANSI编码而Windows PowerShell 5.1的Import-Csv默认按系统当前代码页解析如果你用比较新版本的PowerShell7.x默认是UTF-8两边一错位中文就全花了。最稳的方案是把CSV转成“带有BOM的UTF-8”编码。在VS Code或Notepad里把编码改成UTF-8 with BOM再导入Import-Csv -Path .\users.csv -Encoding UTF8为什么强调BOM无BOM的UTF-8文件Windows PowerShell 5.1有可能会按ANSI解析有了BOM之后它就能直接识别为UTF-8。这个细节我调了很久才摸清网上很多教程只说“用UTF8”没说要不要带BOM坑了不少人。4.3 一条完整的批量创建用户脚本把前面所有知识点串起来一个带日志和错误处理的批量创建脚本大概是这样的Import-Module ActiveDirectory Import-Csv -Path .\users.csv -Encoding UTF8 | ForEach-Object { try { $pwd ConvertTo-SecureString -String $_.Password -AsPlainText -Force $upn $($_.LoginName)corp.local New-ADUser -Name $_.DisplayName -SamAccountName $_.LoginName -UserPrincipalName $upn -GivenName $_.GivenName -Surname $_.Surname -Department $_.Department -Title $_.Title -Path OU$($_.Department),OU员工,OU苏州分公司,DCcorp,DClocal -AccountPassword $pwd -ChangePasswordAtLogon $true -Enabled $true Write-Host 已创建: $($_.LoginName) -ForegroundColor Green } catch { Write-Host 创建失败: $($_.LoginName) - $($_.Exception.Message) -ForegroundColor Red $($_.LoginName),$($_.Exception.Message) | Out-File -FilePath .\error.log -Append } }里面有几个设计思路单独说一下foreach里包了try/catch单条失败不会中断整个流程失败的LoginName会写进error.log便于回头处理OU路径是根据CSV里部门动态拼出来的前提是你的OU规划和部门名称一一对应密码统一用强密码模板创建后强制用户下次登录修改这比每个用户单独设一个弱密码安全得多。如果你的OU层级比较复杂建议维护一个“部门到OU路径”的映射哈希表而不是像我示例里那样直接拼接。比如$ouMap { 研发部 OU研发部,OU员工,OU苏州分公司,DCcorp,DClocal 财务部 OU财务部,OU员工,OU苏州分公司,DCcorp,DClocal }然后去掉了硬编码的字符串拼接脚本的健壮性会高很多就算部门名称有别名、有大小写差异也不会因为拼路径出错。4.4 跑完怎么快速验证脚本执行完毕后别急着收工。抽出几条数据在ADUC界面里对应看一下再用命令行抽查一遍更省事Get-ADUser -Filter * -SearchBase OU员工,OU苏州分公司,DCcorp,DClocal | Where-Object { $_.Created -gt (Get-Date).AddMinutes(-10) } | Select-Object SamAccountName, Name, Department, Enabled加个Created时间过滤条件只看最近10分钟新建的用户一眼就能看出数量对不对、属性全不全。我第一次跑完脚本时没在意检查结果CSV里有几行重复的登录名New-ADUser直接抛了“对象已存在”的异常全靠error.log才定位到问题。5. 批量修改用户信息定位、筛选、修改三步法5.1 先把要改的人找出来Get-ADUser筛选技巧批量创建会了之后批量修改其实是更常遇到的场景。修改之前最关键的一步是“把人找对”改错账号可比不找更麻烦。Get-ADUser的-Filter参数支持按属性过滤常用的写法# 按部门筛选 Get-ADUser -Filter Department -eq 研发部 -Properties Department # 按OU范围筛选 Get-ADUser -Filter * -SearchBase OU研发部,OU员工,OU苏州分公司,DCcorp,DClocal # 多条件组合 Get-ADUser -Filter Department -eq 研发部 -and Enabled -eq true -Properties Department注意一个细节Department这类属性默认不会随基础查询结果返回需要加-Properties Department或者直接用-Properties *。我第一次写脚本时没加这个参数后面想输出部门发现全是空白的排查了半天才意识到是属性没加载。还有个稳妥的小技巧如果拿不准Filter语法先不加条件跑Get-ADUser -Filter *限制SearchBase再配合Select-Object查看效果用输出结果验证筛选条件写对了没有通了之后再加修改动作。5.2 Set-ADUser批量刷属性定位到目标用户后批量修改属性的核心命令是Set-ADUser最简单的用法就是管道往下传Get-ADUser -Filter Department -eq 研发部 -Properties Department | Set-ADUser -Department 技术中心这条命令会把所有研发部用户的Department属性改成技术中心。看起来简单但有两个细节值得注意。第一管道里每个用户对象会把现有属性带过去Set-ADUser收到之后只修改你指定的属性其他属性不受影响不会顺手把用户的密码或登录名给改了。第二如果要在同一批用户上改多个属性可以这样写Get-ADUser -Filter Department -eq 研发部 -Properties Department, Title | ForEach-Object { Set-ADUser -Identity $_ -Department 技术中心 -Title 工程师 -Office A栋3层 }实际项目里我遇到过要批量改电话分机号的情况HR给的Excel里有几百行“登录名新分机号”用Import-Csv读进来再逐条Set-ADUser一分钟全部搞定。5.3 改密码、启禁用、加组成员几个高频操作批量修改不只是改属性还经常涉及账号状态和组成员关系。这几个操作我放在一起说# 批量重置密码 $pwd ConvertTo-SecureString -String Temp12345 -AsPlainText -Force Get-ADUser -Filter Department -eq 实习组 | Set-ADAccountPassword -NewPassword $pwd # 批量禁用账号 Get-ADUser -Filter Enabled -eq true -and Department -eq 离职待办 | Disable-ADAccount # 批量加入安全组 Get-ADUser -Filter Department -eq 新员工 | Add-ADGroupMember -Identity VPN办公组注意一点Set-ADAccountPassword不会自动要求用户下次改密如果需要还要配合Set-ADUser -ChangePasswordAtLogon $true或者在重置密码之后追加一条Set-ADUser。5.4 组织架构调整Move-ADObject迁移OU还有一类很隐蔽但同样高频的需求批量把用户从一个OU迁移到另一个OU。比如研发部拆分成“产品研发组”和“技术预研组”这时候就要用Move-ADObject。Get-ADUser -Filter Department -eq 原研发部 | Move-ADObject -TargetPath OU技术预研组,OU员工,OU苏州分公司,DCcorp,DClocalMove-ADObject用的是对象的DistinguishedName管道里Get-ADUser自动带出来了所以直接传就行。迁移完成之后记得再查一遍确认用户没散落到奇怪的位置。6. 实战排错链路四个高频问题的定位过程6.1 “找不到ActiveDirectory模块”的完整排查这个报错我有段时间每周都能在同事的机器上看到一次现象是Import-Module ActiveDirectory直接红字。我抓到过一次典型的现场整个排查链路是这样的。第一步确认模块是不是没装。执行Get-Module -ListAvailable ActiveDirectory如果输出为空说明当前机器没有AD模块往RSAT方向查。第二步确认系统版本。家庭版Windows不提供RSAT企业环境基本是专业版以上没问题跳过。第三步检查RSAT是否被启用。运行Get-WindowsCapability -Name Rsat.ActiveDirectory.DS-LDS.Tools* -Online看State是不是Installed如果是NotPresent就按前面第2章的命令安装。第四步装完之后开个全新的PowerShell窗口再Import-Module因为模块列表在启动时加载老窗口可能缓存了状态。那次最后定位到的问题特别低级用户装了RSAT之后没有重新加载模块列表直接在新开的窗口里Import结果还是报错实际是RSAT安装后要等一小会儿才能被识别。重启PowerShell之后再试就正常了。6.2 中文乱码的完整定位过程乱码问题的排查思路不复杂但需要演示一下怎么从现象倒推根因。现象是CSV导入后创建的用户名变成“寮犱笁”这类错乱字符。第一步先用Get-Content直接读CSV看看PowerShell读到的是不是乱码Get-Content .\users.csv | Select-Object -First 5第二步查看文件的真实编码。有个技巧$bytes [System.IO.File]::ReadAllBytes(.\users.csv) # 查看文件头三个字节EF BB BF 表示UTF-8带BOMFF FE 表示UTF-16没有BOM则可能是ANSI或UTF-8无BOM $bytes[0..2] | ForEach-Object { $_.ToString(X2) }第三步根据编码调整读取方式。如果是UTF-8无BOM改用Import-Csv -Path .\users.csv -Encoding UTF8如果文件是ANSI更保险的做法是先转成UTF-8 with BOM再导入。我一般在VS Code里打开文件右下角选“通过编码保存”选UTF-8 with BOM保存。之后Import-Csv就再没有出过错。这个坑看起来基础但真的坑过很多人尤其是HR发来的Excel里如果混着特殊字符比如全角空格、中文括号稍微不注意就会变成乱码。建议批量操作前先做一次数据清洗。6.3 “密码不满足复杂性要求”的另一种思路创建时遇到“密码不满足复杂性要求”的报错大多数人的第一反应是换个复杂密码。但我遇到过一次奇怪的情况换成了符合常规复杂度的强密码还是报同一个错。后来一查发现是那个OU上挂了独立的细粒度密码策略Fine-Grained Password Policy要求比默认策略更严格比如密码长度最少12位、必须包含特殊字符。这种情况下我的排查思路是第一步查域默认密码策略用Get-ADDefaultDomainPasswordPolicy看当前域策略。第二步查用户所在OU有没有被关联细粒度密码策略这个在AD管理中心ADAC里可以看到或者用Get-ADFineGrainedPasswordPolicy查看全部细粒度策略。第三步确认脚本里的密码模板满足最严格的那个要求。如果确实搞不定密码策略我的经验是不要在脚本里硬编码一堆弱密码凑数而是统一用随机强密码生成器创建后强制用户下次登录修改。这样既满足复杂性又不会让密码长期以明文躺在HR的Excel里。6.4 权限不足和“拒绝访问”的定位还有两类报错都指向权限一类是创建时直接拒绝访问另一类是查询时提示“对域控制器执行此操作时发生错误”。头一回遇到时我反复确认账号没错、密码没错最后才发现是账号加入了Domain Admins组但没注销重新登录令牌没刷新权限没生效。解决方式很简单注销再登录。如果不是令牌问题就要检查委派权限了。被委派了权限的运维账号可以把创建用户、修改属性等权限精确到某个OU。检查方式是在ADUC里右键OU→属性→安全→高级看这个账号到底被授权了哪些操作。我通常会和AD管理员沟通直接要一个能创建用户、能修改属性的委派权限账号比什么都顺手。6.5 创建成功但账号是禁用状态还有一种不算报错但比报错更隐蔽的情况脚本提示创建成功但ADUC界面里看到的账号是禁用状态新员工没法登录。我遇到过一回排查后发现是脚本里的逻辑串了。写“新建用户”和“批量禁用离职账号”两段逻辑时管道混在了一起把新建的用户也带上了禁用的分支。所以写批量脚本的时候绝对要把“新建逻辑”和“批量禁用逻辑”分开跑不要放在同一个ForEach循环里。这种隐形错误排查起来最费时间基本只能靠看日志一点一点倒查。7. 脚本安全与兜底审计、日志和误操作恢复7.1 密码和敏感数据不要躺在脚本里批量脚本跑起来很爽但安全上有个容易被忽视的点脚本和CSV里都是明文密码。如果这份CSV被谁拷走等于把全公司的初始密码都送出去了。我现在的做法是CSV里不放密码列初始密码统一推一个随机强密码创建后强制用户修改或者用Read-Host -AsSecureString在运行时输入脚本历史里看不到密码内容。CSV文件用完就删只留带账号基本信息和日志的结果文件。如果你一定要在CSV里带初始密码至少把文件权限收一下只给需要的人读别放共享盘上让人随便下载。7.2 日志脚本跑完要能说清楚干了什么批量脚本在无人值守场景下跑完事后要能审计。最简单的做法是给脚本套一层Start-TranscriptStart-Transcript -Path .\ad-actions.log -Append # ... 业务代码 ... Stop-Transcript这个会把整个PowerShell会话的输入输出都记录到日志文件里。我还会在关键步骤后面加一行结构化输出方便事后统计$result | Export-Csv .\user-create-result.csv -NoTypeInformation -Encoding UTF8不推荐只用Write-Host看输出因为控制台是临时的窗口一关就没了出问题的时候想复盘没得查。7.3 误操作兜底-WhatIf和AD回收站批量修改最怕误操作这里必须强烈推荐-WhatIf参数它会预演一遍操作而不真正执行Get-ADUser -Filter Department -eq 研发部 | Set-ADUser -Department 技术中心 -WhatIf跑完只会提示“将更新什么”不会真的改动。我接手别人的脚本时第一件事就是看里面有没有加-WhatIf。自己写脚本关键时刻一定先加。跑完预演确认无误再去掉-WhatIf正式执行。另外如果误删了用户还有两道救命稻草。一是启用AD回收站Enable-ADOptionalFeature -Identity CNRecycle Bin Feature,CNOptional Features,CNDirectory Service,CNWindows NT,CNServices,CNConfiguration,DCcorp,DClocal -Scope ForestOrConfigurationSet -Target corp.local启用了回收站之后误删的用户可以从“已删除对象”容器里还原。二是做系统状态备份用wbadmin start systemstatebackup把域控的系统状态定期备份下来。说实话这两样我都靠它们救回过数据但更重要的是在脚本里防止误操作发生。7.4 最小权限和委派日常运维的底线最后说一句关于账号权限的体会处理AD用户这种核心资产不要把高权限管理操作绑在每天的日常脚本上。我手头的批量脚本都是用专门的委派账号跑的权限范围只在用户管理相关的OU上。这样就算脚本被人拿到、账号被滥用影响面也能控住审计时还能区分是哪次操作、哪个账号、改了什么。能创建域用户、能改用户信息这个能力本身就像一把万能钥匙授权容易、收回来难在批量自动化的前提下权限给得越小越安心。我后来把整套脚本做成了带简单参数入口的工具HR给个CSV运维跑一条命令谁也不敢说这个月建号建到怀疑人生了。
阅读完成 · 觉得有帮助?