计划任务执行AD清理脚本异常:误删活跃OU问题排查
问题:计划任务执行AD学生账号清理脚本时误删非目标OU的原因分析
我编写了一个用于删除已离校学生旧AD账号的脚本,学生账号按入学年份创建在对应OU中,逻辑为每年7月停用上一届账号,次年1月删除未申诉的账号(如留级学生需保留账号)。手动测试脚本时完全符合预期,但通过计划任务执行时,脚本未删除最旧OU,反而删除了仍在使用的年份OU及其主文件夹,引发严重问题。
脚本代码
$ParentOU = "OU=ZACI-UCTY,DC=trnkova,DC=local" # Parent OU, where we look for the oldest sub-OU $HomeDirRoot = "\\dc1\zak_home$" # Root path, where home folders are stored # Getting child OUs $OUs = @(Get-ADOrganizationalUnit -Filter "Name -like '20*'" -SearchBase $ParentOU -SearchScope OneLevel | Sort-Object Created) if ($OUs.Count -eq 0) { Write-Host "Žádné podřízené OU nebyly nalezeny pod $ParentOU" -ForegroundColor Yellow exit } # Oldest OU $OldestOU = $OUs[0] $OUName = ($OldestOU.DistinguishedName -split ",")[0] -replace "OU=","" $HomeDirPath = Join-Path -Path $HomeDirRoot -ChildPath $OUName Write-Host "Nejstarší OU k odstranění: $($OldestOU.DistinguishedName)" -ForegroundColor Cyan Write-Host "Smazání odpovídající složky domovských profilů: $HomeDirPath" -ForegroundColor Cyan # Getting users in this OU $Users = Get-ADUser -Filter * -SearchBase $OldestOU.DistinguishedName -SearchScope Subtree # Users' deletion foreach ($User in $Users) { try { Remove-ADUser -Identity $User -Confirm:$false Write-Host "Smazán uživatel: $($User.SamAccountName)" -ForegroundColor Green } catch { Write-Host "Chyba při mazání uživatele $($User.SamAccountName): $_" -ForegroundColor Red } } # Deletion of the relevant home folder if (Test-Path $HomeDirPath) { try { Remove-Item -Path $HomeDirPath -Recurse -Force Write-Host "Smazána složka domovských profilů: $HomeDirPath" -ForegroundColor Green } catch { Write-Host "Chyba při mazání složky $HomeDirPath - $_" -ForegroundColor Red } } else { Write-Host "Složka domovských profilů $HomeDirPath neexistuje." -ForegroundColor Yellow } # OU removal try { Set-ADOrganizationalUnit -Identity $OldestOU.DistinguishedName -ProtectedFromAccidentalDeletion:$false -Confirm:$false Remove-ADOrganizationalUnit -Identity $OldestOU.DistinguishedName -Confirm:$false -Recursive Write-Host "OU $($OldestOU.DistinguishedName) byla úspěšně smazána." -ForegroundColor Green } catch { Write-Host "Chyba při mazání OU $($OldestOU.DistinguishedName): $_" -ForegroundColor Red }
计划任务XML配置
<?xml version="1.0" encoding="UTF-16"?> <Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task" version="1.4"> <RegistrationInfo> <Date>2024-07-17T12:30:29.3805523</Date> <Author>TRNKOVA\synek</Author> <Description>Smaže OU s účty žáků aktuálně nejstaršího ročníku</Description> <URI>\Smazání nejstarší OU s účty žáků</URI> </RegistrationInfo> <Triggers> <CalendarTrigger> <StartBoundary>2024-07-17T12:00:00</StartBoundary> <ExecutionTimeLimit>PT2H</ExecutionTimeLimit> <Enabled>true</Enabled> <ScheduleByMonth> <DaysOfMonth> <Day>Last</Day> </DaysOfMonth> <Months> <January/> </Months> </ScheduleByMonth> </CalendarTrigger> </Triggers> <Principals> <Principal id="Author"> <UserId>S-1-5-18</UserId> <RunLevel>HighestAvailable</RunLevel> </Principal> </Principals> <Settings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>true</StartWhenAvailable> <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> <IdleSettings> <StopOnIdleEnd>true</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <AllowStartOnDemand>true</AllowStartOnDemand> <Enabled>false</Enabled> <Hidden>false</Hidden> <RunOnlyIfIdle>false</RunOnlyIfIdle> <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession> <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine> <WakeToRun>false</WakeToRun> <ExecutionTimeLimit>PT2H</ExecutionTimeLimit> <Priority>7</Priority> <RestartOnFailure> <Interval>PT2H</Interval> <Count>3</Count> </RestartOnFailure> </Settings> <Actions Context="Author"> <Exec> <Command>powershell.exe</Command> <Arguments>-ExecutionPolicy Bypass -File "C:\Skripty\Smazání nejstarší OU v ZACI-UCTY společně s profily.ps1"</Arguments> </Exec> </Actions> </Task>
原因分析与修复方案
核心原因
账号权限差异
计划任务使用的是Local System账号(S-1-5-18),而你手动运行用的是TRNKOVA\synek账号。Local System在AD中的权限可能不足以读取最旧的几个OU,导致Get-ADOrganizationalUnit查询时直接跳过这些OU,只能返回后续的OU,进而误删。Created字段读取权限限制
脚本依赖Sort-Object Created排序OU,但如果Local System没有读取部分OU的Created属性权限,排序结果会混乱,导致选中错误的OU。而你的个人账号有完整权限,所以手动运行正常。意外删除保护属性干扰
最旧的几个OU可能开启了ProtectedFromAccidentalDeletion,虽然脚本在删除前会关闭该属性,但如果查询阶段就因权限看不到这些OU,自然会跳过它们。
修复与验证步骤
- 验证权限差异:用Local System身份启动PowerShell,执行以下命令,对比返回的OU列表和手动运行结果:
Get-ADOrganizationalUnit -Filter "Name -like '20*'" -SearchBase "OU=ZACI-UCTY,DC=trnkova,DC=local" -SearchScope OneLevel - 更换计划任务运行账号:把计划任务的执行账号改成你手动测试用的
TRNKOVA\synek,确保权限一致。 - 优化排序逻辑:放弃依赖
Created字段,改用OU名称的年份数值排序,更可靠:$OUs = @(Get-ADOrganizationalUnit -Filter "Name -like '20*'" -SearchBase $ParentOU -SearchScope OneLevel | Sort-Object { [int]($_.Name -replace '^20','') }) - 添加日志排查:在脚本中加入查询到的所有OU日志,方便定位计划任务执行时的问题:
Write-Host "查询到的所有OU:" -ForegroundColor Cyan $OUs | ForEach-Object { Write-Host " $($_.Name) - 创建时间: $($_.Created)" }
内容的提问来源于stack exchange,提问作者Petr Synek
相关产品推荐
相关产品推荐

