You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

计划任务执行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>

原因分析与修复方案

核心原因

  1. 账号权限差异
    计划任务使用的是Local System账号(S-1-5-18),而你手动运行用的是TRNKOVA\synek账号。Local System在AD中的权限可能不足以读取最旧的几个OU,导致Get-ADOrganizationalUnit查询时直接跳过这些OU,只能返回后续的OU,进而误删。

  2. Created字段读取权限限制
    脚本依赖Sort-Object Created排序OU,但如果Local System没有读取部分OU的Created属性权限,排序结果会混乱,导致选中错误的OU。而你的个人账号有完整权限,所以手动运行正常。

  3. 意外删除保护属性干扰
    最旧的几个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:05:58