Day 14 我們處理的是:
Active Directory User
透過:
Get-ADUser
找出:
長期未登入
從未登入
仍然 Enabled
需要人工 Review
的帳號。
但企業 AD 裡除了 User Account 之外,另外一種非常容易越積越多的物件就是:
Computer Object
實際工作中很容易遇到:
電腦已經報廢
↓
Computer Object 還在 AD
電腦已經換新
↓
舊 Hostname 還存在
員工已經離職
↓
舊設備的 Computer Object 沒清
電腦重灌 / 改名
↓
舊 Object 被留下來
久而久之:
AD 裡有 3000 台 Computer
但實際公司可能只有 2000 台設備
這時候問題就來了:
剩下的 Computer Object 到底哪些還在使用?
所以 Day 15 我們就來做:
Inactive AD Computer Review Report
整個流程會是:
Get-ADComputer
↓
Enabled Computer
↓
LastLogonDate
↓
Created
↓
Operating System
↓
Inactive Days
↓
OU
↓
Review Report
但跟 Day 14 一樣:
今天只找出疑似過期 Computer,不直接 Disable,也不直接 Delete。
Computer Object 是什麼?
Windows 電腦加入 Active Directory Domain 時,AD 裡通常會建立一個:
Computer Account
例如:
PC001$
PC002$
SERVER01$
在 Active Directory Users and Computers 裡,看到的通常是:
PC001
PC002
SERVER01
它不只是「一個電腦名稱」。
Computer Account 本身會參與:
Domain Authentication
Group Policy
Machine Account Password
Kerberos
存取網域資源
所以它其實也是一個 Active Directory Security Principal。
也因此:
Computer Object 不是沒用了就隨便刪掉的資料。
先查一台 Computer
Day 13 已經稍微碰過:
Get-ADComputer
例如:
Get-ADComputer -Identity "PC001"
可能看到:
DistinguishedName : CN=PC001,OU=Computers,DC=contoso,DC=com
DNSHostName : PC001.contoso.com
Enabled : True
Name : PC001
ObjectClass : computer
SamAccountName : PC001$
SID : ...
先整理:
Get-ADComputer -Identity "PC001" | Select-Object
Name,
DNSHostName,
Enabled,
DistinguishedName
這就是最基本的 Computer Query。
Computer 還有哪些值得看的資訊?
做盤點時,我通常會希望知道:
Computer Name
Enabled
DNS Hostname
Operating System
Operating System Version
Created
LastLogonDate
DistinguishedName
所以可以:
Get-ADComputer -Identity "PC001"
-Properties `
OperatingSystem,
OperatingSystemVersion,
Created,
LastLogonDate
再整理:
Get-ADComputer -Identity "PC001"
-Properties OperatingSystem, OperatingSystemVersion, Created, LastLogonDate | Select-Object
Name,
DNSHostName,
Enabled,
OperatingSystem,
OperatingSystemVersion,
Created,
LastLogonDate,
DistinguishedName
可能看到:
Name : PC001
DNSHostName : PC001.contoso.com
Enabled : True
OperatingSystem : Windows 11 Pro
OperatingSystemVersion : 10.0 (26100)
Created : 2024/06/18
LastLogonDate : 2026/09/22
DistinguishedName : CN=PC001,OU=PC,OU=Taipei,...
這些資料就可以開始拿來盤點。
Computer 的 LastLogonDate 代表什麼?
這裡非常重要。
Computer Object 的:
LastLogonDate
不是:
最後一位使用者登入這台電腦的時間。
它比較接近:
這個 Computer Account 最近與 Active Directory 發生網域驗證活動的時間資訊。
所以假設:
PC001 LastLogonDate = 2026/09/22
不能解讀成:
9/22 有一名員工登入 PC001。
這兩件事情不一樣。
Computer 的 LastLogonDate 也不是即時精確資料
跟 Day 14 的 User 一樣:
LastLogonDate
主要是比較方便閱讀 lastLogonTimestamp 的表示。
它適合:
長期 Inactive Computer 盤點
不適合拿來判斷:
這台 PC 今天上午 09:30
到底有沒有登入 Domain
所以今天如果設定:
180 天
這種比較長的盤點區間,很合理。
但如果設定:
1 天
3 天
7 天
就不適合把 LastLogonDate 當成精確即時資訊。
先定義 Inactive Computer
跟昨天一樣,今天先設定:
180 天沒有活動
→ 列入 Review
PowerShell:
$InactiveDays = 180
$CutoffDate =
(Get-Date).AddDays(-$InactiveDays)
如果:
$CutoffDate
得到:
2026/03/27
那意思就是:
LastLogonDate 早於 2026/03/27 的 Computer,需要被列入 Review。
先查 Enabled Computer
先不要把已經 Disabled 的 Computer 混進來。
$Computers = Get-ADComputer -Filter 'Enabled -eq $true'
-Properties `
LastLogonDate,
OperatingSystem,
OperatingSystemVersion,
Created
看看:
$Computers.Count
例如:
2385
代表目前找到:
2385 個 Enabled Computer Object
找出長期沒有活動的 Computer
先只處理:
有 LastLogonDate
但是時間很久以前
例如:
$InactiveComputers = $Computers |
Where-Object {
$null -ne $_.LastLogonDate -and
$_.LastLogonDate -lt $CutoffDate
}
查看:
$InactiveComputers |
Select-Object `
Name,
OperatingSystem,
LastLogonDate
可能看到:
Name OperatingSystem LastLogonDate
PC0032 Windows 10 Pro 2025/11/21
PC0187 Windows 10 Enterprise 2025/07/15
NB0051 Windows 11 Pro 2026/01/05
這三台就值得進一步確認。
計算 InactiveDays
跟 User Account 一樣,比起:
2025/11/21
報表直接顯示:
306 Days
會更容易閱讀。
例如:
$InactiveComputers |
Select-Object `
Name,
LastLogonDate,
@{Name="InactiveDays"; Expression={
((Get-Date) - $_.LastLogonDate).Days
}}
可能:
Name LastLogonDate InactiveDays
PC0032 2025/11/21 306
PC0187 2025/07/15 435
NB0051 2026/01/05 261
現在一眼就能看出:
哪些 Computer 已經很久沒有活動。
LastLogonDate 空白怎麼辦?
我們也可能看到:
PC0999
LastLogonDate =
也就是:
$null
這種 Computer Object 有可能是:
預先建立的 Computer Account
尚未真正加入 Domain
建立後沒有使用
舊流程留下來的 Object
所以跟 User 一樣,可以另外分類。
找出沒有 LastLogonDate 的 Computer
例如:
$NeverActiveComputers = $Computers |
Where-Object {
$null -eq $_.LastLogonDate
}
但不能看到空白就直接說:
可以刪。
因為可能是昨天才預建的新設備。
所以還要搭配:
Created
加入 Grace Period
假設我們規定:
Computer 建立超過 30 天
但仍然沒有 LastLogonDate
→ Review
設定:
$NewComputerGraceDays = 30
$NewComputerCutoff =
(Get-Date).AddDays(
-$NewComputerGraceDays
)
然後:
$NeverActiveComputers = $Computers |
Where-Object {
$null -eq $_.LastLogonDate -and
$_.Created -lt $NewComputerCutoff
}
這樣:
昨天才預建
的 Computer Object 不會被立刻判定為異常。
Operating System 也是非常有用的資訊
例如:
$Computers |
Group-Object OperatingSystem |
Sort-Object Count -Descending
可能得到:
Count Name
1020 Windows 11 Pro
680 Windows 10 Pro
120 Windows Server 2022 Standard
80 Windows Server 2019 Standard
35
這已經可以拿來做另一種很實用的:
OS Inventory。
甚至看到:
Windows 7
Windows Server 2008
這類舊 OS 時,也可以列入另外一種 Review。
不過今天先專注在:
Inactive Computer
不要把範圍一下拉太大。
Workstation 跟 Server 最好分開
這裡是 Computer Object 盤點很重要的一點。
假設找到:
FILESERVER01
LastLogonDate = 200 Days
和:
OLDPC001
LastLogonDate = 200 Days
兩者的處理風險完全不一樣。
Server 可能是:
備援 Server
特殊用途主機
很少與 DC 產生某些活動
已關機但需要保留
DR 設備
所以我不建議直接:
180 天以上
→ 所有 Computer 一起處理
更好的做法是:
Workstation
Server
分開 Review。
最好直接使用 OU 限定 Workstation
如果公司的 AD 架構是:
Taipei
├── Workstations
├── Servers
└── Users
那比起靠:
OperatingSystem -like "Server"
去猜 Server,
我更喜歡直接:
$SearchBase =
"OU=Workstations,OU=Taipei,DC=contoso,DC=com"
然後:
$Computers = Get-ADComputer -Filter 'Enabled -eq $true'
-SearchBase $SearchBase -SearchScope Subtree
-Properties `
LastLogonDate,
OperatingSystem,
OperatingSystemVersion,
Created
這樣一開始查詢範圍就只包含:
Workstation。
這通常比後面再排除 Server 安全。
如果公司 OU 沒有整理怎麼辦?
現實世界不一定這麼漂亮。
可能:
Computers
├── PC001
├── PC002
├── SERVER01
├── NB001
└── SQL01
全部混在一起。
那就只能增加其他條件。
例如:
Where-Object {
$_.OperatingSystem -notlike `
"*Server*"
}
可以先排除明顯 Windows Server。
但這個方法不是百分之百可靠。
因為:
OperatingSystem 可能空白
資料可能過舊
非 Windows 設備可能不符合規則
所以還是要依公司的實際 AD 結構調整。
DistinguishedName 一定要留
跟 User 一樣,我會在報表保留:
DistinguishedName
例如:
CN=PC001,
OU=Notebook,
OU=Taipei,
DC=contoso,
DC=com
因為這可以幫助我們判斷:
設備在哪個 OU?
是不是特殊設備?
是不是 Server?
是不是已經放進 Disabled OU?
如果只輸出:
PC001
很多背景資訊就不見了。
要不要順便 Ping?
這時候可能會想到:
LastLogonDate 很舊,那我 Ping 看看不就知道了?
可以把 Ping 當成另一個觀察點。
例如:
Test-Connection -ComputerName "PC001"
-Count 1 `
-Quiet
如果:
True
代表目前有收到 ICMP Response。
但是如果:
False
不能直接代表:
PC001 已經不存在。
原因跟 Day 5 一樣:
電腦關機
筆電帶回家
Firewall Block ICMP
目前沒連 VPN
DNS 記錄過期
網路隔離
設備確實不存在
所以:
AD Inactive
+
Ping Failed
只能代表:
值得進一步 Review。
而不是:
可以刪除。
把 Ping 當成補充證據
我們可以做:
$PingResult = Test-Connection -ComputerName $_.Name
-Count 1 -Quiet
-ErrorAction SilentlyContinue
然後報表加入:
PingStatus
例如:
PC001 365 Days False
PC002 240 Days True
其中:
PC002
LastLogonDate 240 天前
但現在居然可以 Ping
那就非常值得先調查。
可能代表:
AD LastLogonDate 並不是我們想像的使用狀態
主機名稱 / DNS 有其他問題
設備重新使用
資料判讀需要修正
所以多一個觀察點,會比單純依賴一個欄位安全很多。
但大量 Computer 不要全部先 Ping
假設:
AD 有 5000 個 Computer
不建議:
先 Ping 5000 台
比較合理是:
5000 個 Computer
↓
AD 條件篩選
↓
得到 150 個 Inactive Candidate
↓
才對 150 台做額外確認
也就是:
先用便宜的 Query 縮小範圍,再做比較昂貴的檢查。
這跟系統排錯的思維其實一樣。
建立 ReviewType
今天我們也跟 Day 14 一樣,把 Computer 分成兩類:
Inactive
代表:
曾有活動
但已經超過 180 天
第二種:
NeverActive
代表:
LastLogonDate 空白
+
Computer Object 建立已超過 Grace Period
這樣報表會比全部叫:
Old Computer
清楚很多。
建立 Inactive Computer Report
首先:
$InactiveComputers = $Computers |
Where-Object {
$null -ne $_.LastLogonDate -and
$_.LastLogonDate -lt $CutoffDate
} |
ForEach-Object {
[PSCustomObject]@{
Name =
$_.Name
DNSHostName =
$_.DNSHostName
Enabled =
$_.Enabled
OperatingSystem =
$_.OperatingSystem
OperatingSystemVersion =
$_.OperatingSystemVersion
Created =
$_.Created
LastLogonDate =
$_.LastLogonDate
InactiveDays =
(
(Get-Date) -
$_.LastLogonDate
).Days
ReviewType =
"Inactive"
ReviewReason =
"Last activity older than $InactiveDays days"
DistinguishedName =
$_.DistinguishedName
}
}
建立 NeverActive Report
接著:
$NeverActiveComputers = $Computers |
Where-Object {
$null -eq $_.LastLogonDate -and
$_.Created -lt $NewComputerCutoff
} |
ForEach-Object {
[PSCustomObject]@{
Name =
$_.Name
DNSHostName =
$_.DNSHostName
Enabled =
$_.Enabled
OperatingSystem =
$_.OperatingSystem
OperatingSystemVersion =
$_.OperatingSystemVersion
Created =
$_.Created
LastLogonDate =
$null
InactiveDays =
$null
ReviewType =
"NeverActive"
ReviewReason =
"No logon timestamp and object older than $NewComputerGraceDays days"
DistinguishedName =
$_.DistinguishedName
}
}
把兩份資料合起來
跟昨天一樣:
$ReviewComputers =
@($InactiveComputers) +
@($NeverActiveComputers)
然後:
$ReviewComputers |
Sort-Object `
ReviewType,
InactiveDays -Descending
現在我們就有一份統一的:
AD Computer Review List
加入 Ping Check
如果 Review 數量不大,可以再對 Candidate 做:
$FinalReview = foreach (
$Computer in $ReviewComputers
) {
$PingResult = Test-Connection `
-ComputerName $Computer.Name `
-Count 1 `
-Quiet `
-ErrorAction SilentlyContinue
[PSCustomObject]@{
Name =
$Computer.Name
DNSHostName =
$Computer.DNSHostName
Enabled =
$Computer.Enabled
OperatingSystem =
$Computer.OperatingSystem
OperatingSystemVersion =
$Computer.OperatingSystemVersion
Created =
$Computer.Created
LastLogonDate =
$Computer.LastLogonDate
InactiveDays =
$Computer.InactiveDays
PingResponding =
$PingResult
ReviewType =
$Computer.ReviewType
ReviewReason =
$Computer.ReviewReason
DistinguishedName =
$Computer.DistinguishedName
}
}
最後報表多一個:
PingResponding
但欄位名稱最好不要叫 Online
注意我故意沒有寫:
Online = True / False
而是:
PingResponding
因為:
Ping Failed
並不等於:
Offline
欄位名稱本身應該描述:
我到底測了什麼。
這跟 Day 10 的:
Connection
Health
分開是一樣的概念。
好的 Automation Report 不只是要有資料。
還要避免讓人誤解資料。
建立 Summary
同樣可以做:
$Summary = [PSCustomObject]@{
CheckTime =
Get-Date
InactiveThresholdDays =
$InactiveDays
EnabledComputersChecked =
@($Computers).Count
InactiveComputers =
@($InactiveComputers).Count
NeverActiveComputers =
@($NeverActiveComputers).Count
TotalReviewComputers =
@($ReviewComputers).Count
PingResponding =
@(
$FinalReview |
Where-Object {
$_.PingResponding -eq $true
}
).Count
}
例如:
CheckTime : 2026/09/23 05:00
InactiveThresholdDays : 180
EnabledComputersChecked : 2385
InactiveComputers : 146
NeverActiveComputers : 18
TotalReviewComputers : 164
PingResponding : 7
這時候:
164 台需要 Review
其中 7 台目前還回 Ping
這 7 台我反而會優先確認。
今天完整 Script
下面把 Day 15 整理成一支完整的 Review Script。
Import-Module ActiveDirectory
$InactiveDays = 180
$NewComputerGraceDays = 30
$ReportFolder =
"C:\Temp\ADReports"
$Date =
Get-Date -Format "yyyyMMdd"
$CutoffDate =
(Get-Date).AddDays(
-$InactiveDays
)
$NewComputerCutoff =
(Get-Date).AddDays(
-$NewComputerGraceDays
)
if (-not (Test-Path $ReportFolder)) {
New-Item `
-Path $ReportFolder `
-ItemType Directory |
Out-Null
}
$Computers = Get-ADComputer -Filter 'Enabled -eq $true'
-Properties `
LastLogonDate,
OperatingSystem,
OperatingSystemVersion,
Created
//
// $Computers = Get-ADComputer // -Filter 'Enabled -eq $true'
// -SearchBase $SearchBase // -SearchScope Subtree
// -Properties `
// LastLogonDate,
// OperatingSystem,
// OperatingSystemVersion,
// Created
$InactiveComputers = $Computers |
Where-Object {
$null -ne $_.LastLogonDate -and
$_.LastLogonDate -lt $CutoffDate
} |
ForEach-Object {
[PSCustomObject]@{
Name =
$_.Name
DNSHostName =
$_.DNSHostName
Enabled =
$_.Enabled
OperatingSystem =
$_.OperatingSystem
OperatingSystemVersion =
$_.OperatingSystemVersion
Created =
$_.Created
LastLogonDate =
$_.LastLogonDate
InactiveDays =
(
(Get-Date) -
$_.LastLogonDate
).Days
ReviewType =
"Inactive"
ReviewReason =
"Last activity older than $InactiveDays days"
DistinguishedName =
$_.DistinguishedName
}
}
$NeverActiveComputers = $Computers |
Where-Object {
$null -eq $_.LastLogonDate -and
$_.Created -lt $NewComputerCutoff
} |
ForEach-Object {
[PSCustomObject]@{
Name =
$_.Name
DNSHostName =
$_.DNSHostName
Enabled =
$_.Enabled
OperatingSystem =
$_.OperatingSystem
OperatingSystemVersion =
$_.OperatingSystemVersion
Created =
$_.Created
LastLogonDate =
$null
InactiveDays =
$null
ReviewType =
"NeverActive"
ReviewReason =
"No logon timestamp and object older than $NewComputerGraceDays days"
DistinguishedName =
$_.DistinguishedName
}
}
$ReviewComputers =
@($InactiveComputers) +
@($NeverActiveComputers)
$FinalReview = foreach (
$Computer in $ReviewComputers
) {
$PingResult = Test-Connection `
-ComputerName $Computer.Name `
-Count 1 `
-Quiet `
-ErrorAction SilentlyContinue
[PSCustomObject]@{
Name =
$Computer.Name
DNSHostName =
$Computer.DNSHostName
Enabled =
$Computer.Enabled
OperatingSystem =
$Computer.OperatingSystem
OperatingSystemVersion =
$Computer.OperatingSystemVersion
Created =
$Computer.Created
LastLogonDate =
$Computer.LastLogonDate
InactiveDays =
$Computer.InactiveDays
PingResponding =
$PingResult
ReviewType =
$Computer.ReviewType
ReviewReason =
$Computer.ReviewReason
DistinguishedName =
$Computer.DistinguishedName
}
}
$Summary = [PSCustomObject]@{
CheckTime =
Get-Date
InactiveThresholdDays =
$InactiveDays
EnabledComputersChecked =
@($Computers).Count
InactiveComputers =
@($InactiveComputers).Count
NeverActiveComputers =
@($NeverActiveComputers).Count
TotalReviewComputers =
@($ReviewComputers).Count
PingResponding =
@(
$FinalReview |
Where-Object {
$_.PingResponding -eq $true
}
).Count
}
$FinalReview |
Sort-Object ReviewType, InactiveDays -Descending | Export-Csv
-Path "$ReportFolder\Inactive_AD_Computers_$Date.csv" -NoTypeInformation
-Encoding UTF8
$Summary |
Export-Csv -Path "$ReportFolder\Inactive_AD_Computer_Summary_$Date.csv"
-NoTypeInformation `
-Encoding UTF8
Write-Host ""
Write-Host "===== AD Computer Review ====="
Write-Host ""
$Summary
Write-Host ""
Write-Host "Report:"
Write-Host "$ReportFolder\Inactive_AD_Computers_$Date.csv"
最後會得到兩份報表
C:\Temp\ADReports
│
├── Inactive_AD_Computers_20260923.csv
│
└── Inactive_AD_Computer_Summary_20260923.csv
其中:
Inactive_AD_Computer_Summary
回答:
到底有多少 Computer 需要確認?
而:
Inactive_AD_Computers
則回答:
到底是哪幾台?
報表可以怎麼看?
假設得到:
Computer OS LastLogon Days Ping Type
PC001 Windows 10 2025/05/01 510 False Inactive
PC002 Windows 11 2026/01/01 265 True Inactive
PC003 False NeverActive
我會先把:
PC002
拉出來。
因為:
AD 顯示長期 Inactive
但是:
目前 Ping 有回應
這表示:
兩個證據有矛盾,需要先查清楚。
而不是看到 InactiveDays 就直接刪。
Computer 180 天沒活動,可以直接刪嗎?
我的答案仍然是:
不應該只靠 LastLogonDate 決定。
比較合理的判斷流程可以是:
AD LastLogonDate
↓
Computer Enabled?
↓
在哪個 OU?
↓
Workstation 還是 Server?
↓
Asset Inventory 還有嗎?
↓
DNS Record 還存在嗎?
↓
DHCP Lease?
↓
Endpoint / EDR / MDM 是否還有紀錄?
↓
設備負責人?
↓
Review
↓
Disable / Move OU
↓
觀察期
↓
最後才考慮 Delete
這會比:
180 Days
↓
Delete
安全非常多。
Disable 通常比直接 Delete 更安全
如果真的確認:
這台設備應該已經不用
企業環境中也常會考慮:
第一階段
→ Disable Computer Account
第二階段
→ Move 到 Quarantine / Disabled OU
第三階段
→ 保留一段時間
第四階段
→ 再決定是否刪除
這樣萬一判斷錯誤,還有回復空間。
今天仍然只做到:
Detection
+
Report
真正的處置我們後面再談。
User 與 Computer 的 Inactive 概念其實很像
Day 14:
AD User
↓
Enabled
↓
LastLogonDate
↓
InactiveDays
↓
Review
Day 15:
AD Computer
↓
Enabled
↓
LastLogonDate
↓
InactiveDays
↓
Created / OS / OU
↓
Connectivity Evidence
↓
Review
兩個流程非常相似。
但 Computer 多了一個很重要的觀念:
AD Computer Object 狀態,不等於實體設備資產狀態。
例如:
AD 說還存在
不代表:
那台筆電真的還在公司。
反過來:
AD Activity 很舊
也不代表:
設備一定已報廢。
所以真正成熟的 IT Asset / AD Cleanup,最終一定會需要交叉比對其他資料來源。
Day 15 小結
今天我們完成了第二個 AD Review 工具:
Day 14
Inactive User Review
Day 15
Inactive Computer Review
主要使用:
Get-ADComputer
LastLogonDate
Created
OperatingSystem
Test-Connection
Export-Csv
並建立兩種分類:
Inactive
→ 曾經有活動,但已經超過 Threshold
NeverActive
→ 沒有 LastLogonDate,而且建立超過 Grace Period
最後:
數千個 Computer Object
↓
PowerShell Query
↓
Inactive Filter
↓
Additional Evidence
↓
Review List
把原本大量資料縮成真正值得工程師看的範圍。
今天最重要的觀念則是:
AD Computer 的 LastLogonDate,不是使用者最後登入那台電腦的時間。
以及:
AD 裡長期未活動的 Computer Object,只能先視為清理候選,不代表可以直接刪除。
Day 16 預告
Day 16|AD Group 自動化盤點:誰在哪個群組?權限到底從哪裡來?
User 與 Computer 盤點完之後,下一個企業 AD 很重要的東西就是:
Group
因為實際的權限往往不是:
User
→ 直接給權限
而是:
User
↓
AD Group
↓
File Server / Application / VPN / System
↓
Permission
所以 Day 16 我們會開始使用:
Get-ADGroup
Get-ADGroupMember
Get-ADPrincipalGroupMembership
並處理幾個很實際的問題:
這個 User 到底加入哪些 Group?
這個 Group 裡面到底有哪些人?
Group 裡還有 Group 怎麼辦?
Disabled User 還留在重要群組嗎?
一個群組到底有多少成員?
最後做出第一份:
AD Group Membership Audit Report
從 Day 16 開始,我們會從「帳號是否存在」進一步走到企業 AD 更重要的:
「這個帳號到底擁有哪些權限?」