
最近常常跳出這個錯誤,嚴格來說應該不只WSUS提到的22個,因為我其實很少在清理ADUC裏頭的電腦,早期班級或科任教室統一以教室編號命名電腦名稱,後來改為電腦型號+教室編號來命名,因此留下了許多淘汰或換機之後不再使用的電腦名稱。
Import-Module ActiveDirectory
# 設定閾值:100 天
$threshold = (Get-Date).AddDays(-100)
Write-Host "`n=== 超過 100 天未回報的電腦清單 ===" -ForegroundColor Cyan
Search-ADAccount -ComputersOnly -AccountInactive -TimeSpan 100.00:00:00 |
    Get-ADComputer -Properties Name, LastLogonDate |
    Select-Object Name, LastLogonDate |
    Sort-Object LastLogonDate |
    Format-Table -AutoSize
因為久未清理且數量不少,如果一台台確認再刪除恐怕曠日廢時,所以打算透過腳本先列出超過100天未上線的電腦。腳本內容如上…

上圖看出許多以教室編號命名的電腦卻沒有顯示最後登入日期,表示已不再使用該名稱或之前樹系升級後都沒有登入過的電腦,將這類的進行刪除肯定沒問題的。

另外數量比較多的是有最後登入日期但超過100天未登入過的電腦,這類的許多都已經超過3年沒登入了,也是應該列為被刪除的對象。
<#
AD Inactive Computers Cleanup (>1 Year)
- 找出超過 365 天未回報 (AccountInactive >= 365 天) 的電腦
- 直接刪除 (非 DryRun)
- 輸出 CSV 與 Log
#>
Import-Module ActiveDirectory
# ===== 設定區 =====
$Config = [ordered]@{
    DaysThreshold = 365                   # 1 年
    OutputDir     = "C:\Temp\ADCleanup"
    CsvName       = "InactiveComputers_Over1Year.csv"
    LogName       = "InactiveComputers_Over1Year.log"
    OUFilter      = $null                  # 可選:只掃某 OU (例如 "OU=Computers,DC=xxxx,DC=xxx,DC=xxx,DC=xxx")
}
# ===== 設定區結束 =====
# 建立輸出目錄與檔案路徑
New-Item -ItemType Directory -Path $Config.OutputDir -Force | Out-Null
$csvPath = Join-Path $Config.OutputDir $Config.CsvName
$logPath = Join-Path $Config.OutputDir $Config.LogName
# 簡易 logger
function Write-Log {
    param(
        [string]$Message,
        [ValidateSet('INFO','WARN','ERROR','ACTION')]
        [string]$Level = 'INFO'
    )
    $line = "[{0}] [{1}] {2}" -f (Get-Date).ToString("yyyy-MM-dd HH:mm:ss"), $Level, $Message
    Write-Host $line
    Add-Content -Path $logPath -Value $line
}
Write-Log -Message "開始掃描超過 $($Config.DaysThreshold) 天未回報的電腦,並直接清理。" -Level INFO
# 產生 TimeSpan
$ts = New-TimeSpan -Days $Config.DaysThreshold
# 搜尋範圍
$searchParams = @{
    ComputersOnly   = $true
    AccountInactive = $true
    TimeSpan        = $ts
}
if ($Config.OUFilter) { $searchParams['SearchBase'] = $Config.OUFilter }
# 取得超過 1 年未回報的電腦清單
$inactive = Search-ADAccount @searchParams | Get-ADComputer -Properties Name, DistinguishedName, Enabled, LastLogonDate
if (-not $inactive -or $inactive.Count -eq 0) {
    Write-Log -Message "沒有找到超過 $($Config.DaysThreshold) 天未回報的電腦。" -Level INFO
    return
}
# 建立輸出資料
$data = $inactive | ForEach-Object {
    [PSCustomObject]@{
        Name              = $_.Name
        DistinguishedName = $_.DistinguishedName
        Enabled           = $_.Enabled
        LastLogonDate     = $_.LastLogonDate
        DaysSinceLastLogon= if ($_.LastLogonDate) { [int]((Get-Date) - $_.LastLogonDate).TotalDays } else { $null }
    }
}
# 輸出 CSV
$data | Sort-Object DaysSinceLastLogon -Descending | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
Write-Log -Message "已輸出 CSV:$csvPath。共 $($data.Count) 台。" -Level INFO
# 顯示前幾筆供人工確認
Write-Host "`n=== 預覽前 10 筆 ==="
$data | Select-Object Name, LastLogonDate, DaysSinceLastLogon, Enabled | Sort-Object DaysSinceLastLogon -Descending | Select-Object -First 10 | Format-Table -AutoSize
# 刪除流程
Write-Log -Message "正式刪除開始。" -Level ACTION
foreach ($item in $inactive) {
    try {
        Remove-ADComputer -Identity $item.DistinguishedName -Confirm:$false
        Write-Log -Message "已刪除:$($item.Name) | DN=$($item.DistinguishedName)" -Level ACTION
    }
    catch {
        Write-Log -Message "刪除失敗:$($item.Name) | 錯誤=$($_.Exception.Message)" -Level ERROR
    }
}
Write-Log -Message "正式刪除完成。詳見 log:$logPath" -Level INFO
Write-Host "`n完成。"
接著透過腳本將1年未上線的電腦直接清理

清理完成

再跑一次List-LoginOver-100d檢視久未登入的電腦清單,最後剩下3台電腦就是要進行確認是否仍需使用的部分了。
<#
.SYNOPSIS
  建立排程工作:每年 7/31 凌晨 00:00 執行 Del-LoginOver-365d.ps1
  以 NT AUTHORITY\SYSTEM 身分執行
#>
$TaskName   = "AD-Cleanup-Over365d"
$ScriptPath = "C:\Scripts\Del-LoginOver-365d.ps1"
# 建立新的排程工作
schtasks /create /tn $TaskName `
  /tr "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`"" `
  /sc monthly /mo 12 /d 31 /st 00:00 /ru "SYSTEM"
Write-Host "已建立排程工作:$TaskName,每年 7/31 00:00 執行。"
最後將腳本列入排程於每年7/31上午12點自動執行Del-LoginOver-365d.ps1

完成排程設定