Microsoft Graph PowerShell 入門 ── AzureAD・MSOnline 廢止後的 Microsoft 365 維運
· Go Komura · PowerShell, Microsoft 365, Microsoft Entra ID, Microsoft Graph, 資訊系統, 自動化, 維運改善, 資安
對於將 Microsoft 365 維運自動化的現場而言,2024 年到 2025 年間最大的變化就是AzureAD 模組與 MSOnline 模組的廢止。應該有不少資訊系統人員曾用 Get-MsolUser 或 Get-AzureADUser 撰寫「離職人員處理」「授權盤點」「新進員工帳號建立」等制式化流程,而這些命令將陸續無法使用。
遷移目標是 Microsoft Graph PowerShell SDK。不過,這並不只是單純的命令名稱替換。從驗證的思維(範圍與同意)、資料的取得方式(OData 的篩選與分頁),到無人執行的建構方式(應用程式註冊與憑證),設計上的前提都改變了。若不理解這些差異就機械式地替換,將會背上「雖然能動,但用了過度的權限」「帳號數量多的租戶只能取到一部分」之類的另一種問題。
本文以透過 PowerShell 將公司內部 Microsoft 365 維運自動化的資訊系統負責人為對象,整理廢止的來龍去脈、連線與範圍設計、無人執行的架構,以及盤點・授權彙總・離職人員處理這三個經典範例,並整理成可直接用於實務的形式。
1. 先講結論
- MSOnline 與 AzureAD 已於 2024 年 3 月 30 日被列為不建議使用,MSOnline 已於 2025 年 5 月 30 日終止提供,AzureAD 也已於 2025 年 3 月 30 日結束支援後遭到廢止。1
- 遷移目標是 Microsoft Graph PowerShell SDK,或建構於其上的 Microsoft Entra PowerShell(於 2025 年 3 月正式發行)。後者採情境導向設計,也提供協助從 AzureAD 遷移的相容選項。2
Microsoft.Graph是元模組。整包安裝會很耗時,實務上只安裝Microsoft.Graph.Authentication加上實際會用到的工作負載子模組。3- 連線從
Connect-MgGraph -Scopes開始。範圍應採最小權限原則。所需權限可用Find-MgGraphPermission查詢,命令所屬的模組則可用Find-MgGraphCommand查詢。45 - 無人執行採用應用程式註冊+憑證的應用程式專用驗證。以
-ClientId-TenantId-CertificateThumbprint進行不需互動的連線。相較於用戶端密碼,建議採用憑證。46 - 委任(Delegated)與應用程式專用(Application)所需的權限並不相同。即使是同一項操作,所要求的範圍也會不同,因此切換為無人執行時,必須重新設定權限。6
- 清單取得用
-All、篩選用-Filter、欄位用-Property。若改在用戶端以Where-Object篩選,會導致多餘的取得量並招致節流。7 - 大量存取會被調整(節流)。收到 429 回應時,依
Retry-After等待後重試,是官方的指引。8 - 應用程式註冊依用途分開建立。「一個包山包海的應用程式」會讓權限不斷膨脹,一旦出事,影響範圍也會擴大。
2. 廢止的來龍去脈與現在該如何選擇
首先整理事實關係。本文內容為 2026 年 7 月當下的狀態。以下日期皆為 Microsoft 公布的廢止時程,且皆已經過。1
| 模組 | 狀態 |
|---|---|
MSOnline(Get-MsolUser 等) |
2024 年 3 月 30 日列為不建議使用。2025 年 5 月 30 日廢止 |
AzureAD(Get-AzureADUser 等) |
2024 年 3 月 30 日列為不建議使用。2025 年 3 月 30 日結束支援,其後廢止 |
| Microsoft Graph PowerShell SDK | 現行版本。將 Graph API 直接命令組化的產物 |
| Microsoft Entra PowerShell | 於 2025 年 3 月正式發行。建構於 Graph SDK 之上的情境導向模組2 |
該選哪一個,判斷標準如下。若想直接操作 Graph API 的結構,或需要涉及廣泛的工作負載(Exchange、Teams、Intune 等),就選 Graph PowerShell SDK。若主要以 Entra ID(舊稱 Azure AD)的識別身分管理為中心,並希望盡可能輕鬆地從 AzureAD 模組遷移,則選Microsoft Entra PowerShell。後者可與 Graph PowerShell SDK 互通,也提供協助從 AzureAD 模組遷移的向下相容選項。2
本文以泛用性高、文件也較豐富的 Graph PowerShell SDK 為主軸進行說明。
3. 安裝 ── 不要整包安裝元模組(Meta Module)
Microsoft.Graph 是集合了大量子模組的元模組。若整包安裝,不論安裝或載入都會很耗時,依執行環境不同,光是載入就可能需要數十秒。3
# 【耗時】安裝所有工作負載
Install-Module Microsoft.Graph -Scope CurrentUser
# 【實務】只安裝驗證 + 實際會用到的工作負載
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser # 必要
Install-Module Microsoft.Graph.Users -Scope CurrentUser # 使用者
Install-Module Microsoft.Graph.Groups -Scope CurrentUser # 群組
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser # 授權等
Install-Module Microsoft.Graph.Users.Actions -Scope CurrentUser # 針對使用者的操作
# (Revoke-MgUserSignInSession 等)
# 查詢某個命令屬於哪個模組、需要哪些權限
Find-MgGraphCommand -Command Get-MgUser | Select-Object Module, Permissions -First 1
Find-MgGraphPermission user.read -PermissionType Delegated
建議在 PowerShell 7 中使用。雖然在 Windows PowerShell 5.1 上也能運作,但無論在效能還是未來性上,選擇 7 的理由都相當充分(「Windows PowerShell 5.1 與 PowerShell 7 的差異」)。3
4. 連線與範圍(Scope) ── 停止「先套用 ReadWrite.All 再說」
4.1 先統一用語
以下先簡短定義後續會反覆出現的用語。
| 用語 | 意義 |
|---|---|
| 委任(Delegated) | 以登入者本人身分執行的形態。實際能做的事,由「應用程式已獲同意的範圍」與「該使用者所擁有的角色」兩者共同決定6 |
| 應用程式專用(Application) | 不經過使用者,以應用程式本身身分執行的形態。無人批次適用此類型。以憑證或用戶端密碼進行驗證6 |
| 應用程式註冊 / 服務主體 | 應用程式註冊是應用程式的定義。服務主體則是將其實體化到租戶中的產物,權限與角色都綁定在服務主體上 |
| 範圍(存取權限) | 如 User.Read.All 之類的權限名稱。委任用與應用程式用分屬不同類別,即使名稱相同也需要重新設定6 |
| OData | Open Data Protocol。是 Graph 查詢語法的基礎,-Filter -Property 等會被轉換為對應的 OData 查詢選項,在伺服器端處理7 |
| 調整(節流) | 要求過多時,服務端刻意拒絕的機制。以 HTTP 429 與 Retry-After 標頭回應8 |
| 持續存取評估(CAE) | 不等待權杖到期,而是由資源端接收失效等事件,並直接中止存取的機制。僅在支援的應用程式・資源之間有效9 |
這兩種形態的差異,是本文中最容易引發事故的地方。畫成圖如下所示。
flowchart TD
subgraph D["委任 Delegated ── 互動式登入"]
U["系統管理員登入"] --> DS["已同意的委任範圍"]
U --> DR["本人擁有的系統管理員角色"]
DS --> DX["能做的事 =<br/>範圍與角色的交集"]
DR --> DX
end
subgraph A["應用程式專用 Application ── 無人執行"]
C["以憑證驗證"] --> AS["應用程式權限<br/>需要管理員同意"]
AS --> AX["能做的事 =<br/>所授予的權限本身"]
end
DX -.->|"轉為無人執行時<br/>需要重新設定權限"| AS
記憶方式只有一個。委任=以使用者本人身分執行,應用程式專用=無人批次。在委任模式下,「本人做不到的事,應用程式也做不到」;在應用程式專用模式下,「只要授予了應用程式,它就會一直能做到」。
4.2 互動式連線
互動式連線使用 Connect-MgGraph -Scopes。針對指定的範圍會顯示同意畫面,同意結果會被記錄在租戶中。4
# 僅供讀取用途的盤點。不要求寫入權限
Connect-MgGraph -Scopes 'User.Read.All', 'Organization.Read.All' -NoWelcome
Get-MgContext | Format-List Account, TenantId, Scopes, AuthType # 確認目前的連線
Disconnect-MgGraph
這裡是遷移時差異最大的地方。在 Get-MsolUser 的時代,「用管理員帳號登入就什麼都能做」,但在 Graph 中,每項操作都定義了所需的範圍,只能在已同意的範圍內運作。這不是限制,而是安全機制。只要僅同意了 .Read.All,盤點腳本就不可能誤觸寫入操作。
原則有三個。
- 讀取用途不要求寫入範圍(若
User.Read.All已足夠,就不要求User.ReadWrite.All) - 依用途分開應用程式註冊(盤點用、帳號建立用、授權管理用)
- 同意由管理員有意識地執行(一旦給予的同意會持續留存在租戶中)
不確定需要哪個範圍時,可用 Find-MgGraphPermission 尋找候選,並用 Find-MgGraphCommand 確認命令所要求的權限。5
5. 無人執行 ── 應用程式註冊+憑證
若要每晚透過工作排程器執行,就無法使用互動式登入。這時要切換為透過應用程式註冊(服務主體)與憑證進行的應用程式專用驗證。6
步驟共有 4 個階段。這裡是實務上最容易卡關的環節,因此會具體寫出畫面上的位置與命令。6
5.1 註冊應用程式
在 Microsoft Entra 管理中心(https://entra.microsoft.com)中,依序進行以下操作。
識別碼 > 應用程式 > 應用程式註冊 > 新增註冊
輸入名稱(例如:M365-Inventory-Batch),支援的帳戶類型選擇「僅此組織目錄中的帳戶(單一租戶)」,然後按下「註冊」。若只是要從腳本無人執行,則不需要設定重新導向 URI。
註冊完成後,請記下「概觀」頁面中顯示的以下兩項,這是連線時要用的值。
| 概觀頁面顯示名稱 | Connect-MgGraph 的參數 |
|---|---|
| 應用程式 (用戶端) 識別碼 | -ClientId |
| 目錄 (租用戶) 識別碼 | -TenantId |
另外,Entra 管理中心的導覽名稱有時會更改。即使左側選單的文字有所變動,最終抵達的頁面名稱仍是「應用程式註冊」。請以此作為指標。
5.2 新增應用程式權限,並授予管理員同意
在同一個應用程式的畫面中,依序進行以下操作。
管理 > API 權限 > 新增權限 > Microsoft Graph > 應用程式權限
這裡的重點是要選擇「應用程式權限」。若選了旁邊的「委任的權限」,在無人執行時將無法生效。勾選所需的權限(若是盤點,則為 User.Read.All 等),再按「新增權限」確定。
到這個階段還無法使用。請按下同一畫面上方的
「代表 (租用戶名稱) 授與管理員同意」
按鈕確定。按下之後,若清單的「狀態」欄變成「已授與給 (租用戶名稱)」的顯示,即代表完成。忘記這一步而出現「明明加了權限卻收到 403」,是常見的卡關情況。
5.3 建立、上傳、放置憑證
自我簽署憑證已經足夠。可以用 PowerShell 的 New-SelfSignedCertificate 建立。
# 【1】建立憑證(有效期限為 2 年,可依維運方式調整)
$cert = New-SelfSignedCertificate `
-Subject 'CN=M365-Inventory-Batch' `
-CertStoreLocation 'Cert:\CurrentUser\My' `
-KeySpec Signature `
-KeyExportPolicy Exportable `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-HashAlgorithm SHA256 `
-NotAfter (Get-Date).AddYears(2)
# 【2】記下連線時要用的拇印(Thumbprint)
$cert.Thumbprint
# 【3】為了上傳,只將公開金鑰匯出為 .cer(不含私密金鑰)
Export-Certificate -Cert $cert -FilePath 'C:\temp\M365-Inventory-Batch.cer'
將匯出的 .cer 檔案,在 Entra 管理中心的應用程式畫面中透過
管理 > 憑證與密碼 > 憑證 索引標籤 > 上傳憑證
上傳。只上傳 .cer(公開金鑰)。絕對不要上傳含有私密金鑰的 .pfx。
私密金鑰要放在哪裡,取決於工作排程器的執行帳戶。
| 執行帳戶 | 憑證存放區 | 連線時的傳遞方式 | 補充說明 |
|---|---|---|---|
| 特定使用者/服務帳戶 | Cert:\CurrentUser\My(以該帳戶建立・匯入) |
-CertificateThumbprint |
除了建立時使用的帳戶之外看不到 |
| SYSTEM,或供多個帳戶使用 | Cert:\LocalMachine\My |
-Certificate(自行讀取後傳入) |
建立時需要系統管理員權限。須授予執行帳戶私密金鑰的讀取權限 |
這裡容易被忽略的一點是,-CertificateThumbprint 與 -CertificateSubjectName 只會搜尋目前使用者的憑證存放區。4 若將憑證放在 Cert:\LocalMachine\My,即使傳入拇印也找不到。若要使用本機電腦的存放區,必須自行讀取後傳給 -Certificate。
# 放在 LocalMachine 時的寫法
$cert = Get-ChildItem -Path 'Cert:\LocalMachine\My\A1B2C3D4E5F6...'
Connect-MgGraph -ClientId $clientId -TenantId $tenantId -Certificate $cert -NoWelcome
「明明在自己手邊能動,卻只有在工作排程器上找不到憑證」這類問題,幾乎都是放置位置與傳遞方式不一致所導致。
5.4 從腳本連線
# 供無人執行使用的連線(不進行互動)
$connect = @{
ClientId = '11111111-2222-3333-4444-555555555555'
TenantId = '66666666-7777-8888-9999-000000000000'
CertificateThumbprint = 'A1B2C3D4E5F6...' # 位於執行帳戶 CurrentUser 存放區的憑證
NoWelcome = $true
}
Connect-MgGraph @connect
# 確認連線形態:AuthType 為 AppOnly,即代表已以應用程式專用形式連線
Get-MgContext | Format-List AppName, ClientId, TenantId, AuthType, Scopes
try {
# 業務處理
}
finally {
Disconnect-MgGraph
}
第一次使用時,請在實際登錄工作之前,先用工作排程器的執行帳戶執行上面的腳本確認。工作排程器端的常見陷阱(執行帳戶、「不管使用者是否已登入均執行」的設定、工作目錄等)已整理在「工作排程器的工作不執行、以 0x1 結束」中。
5.5 注意事項
有兩個注意事項。
(1) 委任與應用程式專用所需的權限並不相同。若要將原本以 -Scopes 'User.Read.All' 互動式執行的腳本改為無人化,必須在應用程式註冊端以應用程式權限重新設定同種類的權限,並取得管理員同意。6
(2) 憑證有到期日。「到期當天夜間批次全數失敗」是實際上經常發生的事故。請務必將有效期限登記在行事曆上,並將更新程序文件化。關於憑證資訊的保管,亦請一併參閱「PowerShell 中安全處理憑證資訊」。雖然也能用用戶端密碼連線,但由於明文攜帶的風險以及到期管理的繁瑣,建議採用憑證。
6. 資料的取得方式 ── -All / -Filter / -Property
Graph 是以分頁為前提的 API。預設只會回傳一頁的資料,若需要全部資料,就加上 -All。7
# 【NG】不考慮分頁,在用戶端篩選(速度慢・取得過多・造成節流的原因)
Get-MgUser | Where-Object { $_.Department -eq '業務部' }
# 【OK】在伺服器端篩選,只取必要欄位,取得所有頁面
Get-MgUser -All -Filter "department eq '業務部'" `
-Property Id, DisplayName, UserPrincipalName, AccountEnabled, Department |
Select-Object DisplayName, UserPrincipalName, AccountEnabled
重點有三個。
-Filter是 OData 運算式,會在伺服器端進行篩選。Where-Object則是在本機篩選,因此會先取得全部資料再捨棄- 用
-Property篩選欄位能讓回應變輕量。有些預設不會回傳的屬性,只要明確指定就會回傳 - 用
-Property取得的項目,也要寫進Select-Object。取得與顯示是分開的,只寫一邊會出現空白欄位
若要使用 startsWith、endsWith 之類的進階查詢,或只想取得件數,則要併用 -ConsistencyLevel eventual 與 -CountVariable。7
# 只計算有效使用者的數量(不取得全部資料,只取得計數)
Get-MgUser -Filter 'accountEnabled eq true' -ConsistencyLevel eventual -CountVariable total -Top 1 | Out-Null
"有效使用者: $total 筆"
-Top 1 | Out-Null 是不常見的寫法,這裡補充說明。-CountVariable 取得的是符合條件的全部件數,不受 -Top 的值影響。-Top 決定的只是單次回應會回傳的物件數量。也就是說,-Top 1 並非「只計算 1 筆」的指定,而是因為要取得計數,勢必得發出一次請求,而這個指定是為了讓實際回傳的資料量降到最低。若省略,會回傳預設一頁份量的使用者物件,再以 Out-Null 捨棄。另外,-ConsistencyLevel eventual 是使用 -CountVariable 或 startsWith 等進階查詢時的必要指定。7
大量存取會被調整(節流),並回傳 HTTP 429。官方的指引是「依照 Retry-After 標頭指定的秒數等待後再重試」。8 SDK 的命令組在內部會進行一定程度的重試,但當要跑數千筆規模的迴圈時,從根本減少取得次數(以 -Filter 與 -Property 篩選、一次取得所需資訊)才是更確實的做法。重試設計本身請參閱「PowerShell 的錯誤處理與重試設計」。
7. 三個經典範例
(1) 將使用者盤點結果輸出為 CSV
# SignInActivity(最後登入時間)無法只靠 User.Read.All 取得,
# 另外還需要 AuditLog.Read.All。若不需要,請從 -Property 中移除
Connect-MgGraph -Scopes 'User.Read.All', 'AuditLog.Read.All' -NoWelcome
$users = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, AccountEnabled,
Department, JobTitle, CreatedDateTime, SignInActivity |
Select-Object DisplayName, UserPrincipalName, Department, JobTitle, AccountEnabled,
@{ n = '建立日期'; e = { $_.CreatedDateTime } },
# 判定休眠時要用「成功的登入」。LastSignInDateTime 是
# 互動式登入的「嘗試」,包含失敗,也不包含非互動式
@{ n = '最後成功登入'; e = { $_.SignInActivity.LastSuccessfulSignInDateTime } },
@{ n = '最後互動式登入嘗試'; e = { $_.SignInActivity.LastSignInDateTime } },
@{ n = '最後非互動式登入'; e = { $_.SignInActivity.LastNonInteractiveSignInDateTime } }
# 含有中文的 CSV,若設為 UTF-8(含 BOM),用 Excel 開啟也不會亂碼。
# 編碼名稱依版本而異:7 以後為 utf8BOM,5.1 為 UTF8(含 BOM)
$enc = if ($PSVersionTable.PSVersion.Major -ge 6) { 'utf8BOM' } else { 'UTF8' }
$users | Export-Csv -Path "D:\盤點\users_$(Get-Date -f yyyyMMdd).csv" -Encoding $enc -NoTypeInformation
輸出的 CSV 欄位結構,會直接以 Select-Object 中撰寫的順序與名稱作為標頭列。Export-Csv 預設會將所有欄位以引號括起,因此第一行會是以下形式。
"DisplayName","UserPrincipalName","Department","JobTitle","AccountEnabled","建立日期","最後成功登入","最後互動式登入嘗試","最後非互動式登入"
第二行以後,會依相同順序排列每位使用者的值。若預期的欄位出現空白,可能是 -Property 或 Select-Object 其中一處漏寫,或是權限不足。特別是 最後成功登入 之後的三欄若全部空白,請懷疑是接下來會提到的 AuditLog.Read.All 權限不足。
SignInActivity 對於篩選出休眠帳號很有效。不過看哪個欄位很重要。LastSignInDateTime 記錄的是互動式登入的嘗試(包含失敗),不包含應用程式或服務的非互動式登入。若只靠這個欄位判定,可能會因為攻擊者的登入失敗而看起來像「有在使用」,也可能讓實際運作中的服務帳戶看起來像「休眠」。判定休眠時,請使用反映成功的互動式・非互動式登入的 LastSuccessfulSignInDateTime。10不過只有這個屬性無法透過 User.Read.All 取得,還需要額外的 AuditLog.Read.All(此外租戶端也有授權要求)。權限不足時會出現錯誤或值會是空白,若不使用就請從 -Property 中移除。所需的權限可用 Find-MgGraphPermission 確認。10CSV 文字編碼的處理方式,整理在「以 PowerShell 自動化 Excel・CSV 業務處理」中。
(2) 彙總授權的使用狀況
Connect-MgGraph -Scopes 'Organization.Read.All' -NoWelcome
Get-MgSubscribedSku | Select-Object `
SkuPartNumber,
@{ n = '購買數'; e = { $_.PrepaidUnits.Enabled } },
@{ n = '已分配'; e = { $_.ConsumedUnits } },
@{ n = '剩餘'; e = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } } |
Sort-Object 剩餘
「明明還有剩餘授權卻又額外採購」「離職人員的授權沒有釋放」這類問題,只要每月執行一次就能預防。
(3) 離職人員處理(封鎖登入與工作階段失效)
以下程式碼是「委任(互動式登入)」的執行範例。指定了 -Scopes 就是其標記,執行時會要求在瀏覽器中登入。若要改為無人執行,必須依第 5 章的方式改寫為應用程式專用形態。先整理兩者的差異如下。
| 委任(以下程式碼範例) | 應用程式專用(無人執行) | |
|---|---|---|
| 以誰的身分執行 | 登入的負責人本人 | 應用程式(服務主體)本身 |
Connect-MgGraph 的指定 |
-ClientId + -Scopes |
-ClientId + -TenantId + -CertificateThumbprint |
| 憑證 | 不需要 | 必須(或用戶端密碼) |
| 權限的種類 | 委任的存取權限 | 應用程式權限(需要・管理員同意)6 |
| 額外需要的東西 | 登入者本人的 Entra 系統管理員角色(後述)11 | 若對象是管理員,需要為應用程式本身指派更高階角色11 |
| 適合場景 | 現場的個別處理、以 -WhatIf 事前確認 |
夜間批次、與人資系統整合 |
# ── 委任(互動式登入)的執行範例 ──
# 執行後會出現登入畫面。不需要憑證
#
# 由於涉及寫入,請使用專用的應用程式註冊・專用範圍執行。
# 省略 -ClientId 會以 SDK 預設的共用應用程式連線,已同意的權限會
# 累積在該應用程式(於組織內共用)上。像離職人員處理這類
# 破壞性操作,更應該將權限隔離到專用的應用程式註冊中
#
# 每項操作所需的權限不同,因此一次要求兩種操作所需的權限
# 變更 accountEnabled → User.EnableDisableAccount.All(最小權限)
# 使登入工作階段失效 → User.RevokeSessions.All(最小權限)
# 兩者也都能用 User.ReadWrite.All 執行,但權限範圍會變廣
$connect = @{
ClientId = '99999999-aaaa-bbbb-cccc-dddddddddddd' # 離職人員處理專用的應用程式註冊
TenantId = '66666666-7777-8888-9999-000000000000'
Scopes = 'User.Read.All', 'User.EnableDisableAccount.All', 'User.RevokeSessions.All'
NoWelcome = $true
}
Connect-MgGraph @connect
# Revoke-MgUserSignInSession 需要 Microsoft.Graph.Users.Actions
Import-Module Microsoft.Graph.Users.Actions
$upn = 'taro.yamada@example.co.jp'
$user = Get-MgUser -UserId $upn -Property Id, DisplayName, AccountEnabled
# 1. 封鎖登入(刪除則留待緩衝期後再進行)
Update-MgUser -UserId $user.Id -AccountEnabled:$false
# 2. 使更新權杖與瀏覽器的工作階段 Cookie 失效
# 注意:已核發的存取權杖,在到期前可能仍可使用(詳見後述)
Revoke-MgUserSignInSession -UserId $user.Id
Write-Host "$($user.DisplayName) 的登入已停止"
這裡有三個要點需要掌握。第一個是連線目標的應用程式註冊。省略 -ClientId 會以 Microsoft Graph PowerShell SDK 的預設應用程式連線,已同意的權限會被記錄在該組織共用的應用程式上。要落實第 4 章「依用途分開應用程式註冊」的原則,必須透過 -ClientId 明確指定自家的應用程式註冊。4
另一個是範圍。在 Graph 中,每項操作都定義了所需的權限,並非「能寫入使用者就什麼都能做」。變更 accountEnabled 與使登入工作階段失效,各自擁有專屬的最小權限,若只同意其中一項就執行,即使連線成功,也會在中途的命令上出現權限不足的錯誤。各項操作所需的權限,請以 Find-MgGraphPermission 及對應 Graph API 參考文件中的權限表確認。119
而第三點是,以委任方式執行時,只有範圍是不夠的。像上面這樣以使用者本人身分登入執行時,變更 accountEnabled 還需要 Microsoft Entra 的系統管理員角色。這是因為委任的權限只決定「應用程式能代替該使用者做什麼」,並不會提升登入使用者本人的權限。即使已同意權限,若沒有角色,執行時仍會以 403 失敗。文件將可對租戶內所有管理員更新此屬性的最小角色定為 Privileged Authentication Administrator,並一般性地規定「需要比對象更高階的系統管理員角色」。11 由於離職人員處理的對象是一般使用者還是管理員,所需的角色會不同,請事先決定要為維運負責人的帳戶指派哪些角色。即使是以憑證進行的應用程式專用執行,若對象是管理員,也需要為應用程式本身指派更高階的角色。11
若要將這項處理改為夜間批次,需要變動的只有連線部分。
# ── 改寫為應用程式專用(無人執行)的情況 ──
# 事前準備:在應用程式註冊的「API 權限 > 應用程式權限」中新增
# User.Read.All / User.EnableDisableAccount.All / User.RevokeSessions.All
# 並授予管理員同意(5.2)。憑證則使用 5.3 建立的那份
$connect = @{
ClientId = '99999999-aaaa-bbbb-cccc-dddddddddddd'
TenantId = '66666666-7777-8888-9999-000000000000'
CertificateThumbprint = 'A1B2C3D4E5F6...' # 不指定 -Scopes
NoWelcome = $true
}
Connect-MgGraph @connect
# 之後(Import-Module 〜 Revoke-MgUserSignInSession)與委任版完全相同
也必須正確理解 Revoke-MgUserSignInSession 的作用範圍。這個命令會停用的是更新權杖與瀏覽器的工作階段 Cookie,而已核發的存取權杖,在到期之前有可能仍然可以使用。9 若要求即時阻斷,前提是應用程式・資源必須支援持續存取評估(CAE)。不要以為「執行了 revoke 就會立即停止所有存取」,在重要情境中,請併用帳號停用,並將反映所需的時間差納入考量。
避免立即刪除帳號,先停止登入是實務上的定式。若在信箱或 OneDrive 的交接尚未完成前就刪除,復原會很費工。這類「無法挽回的操作」,建議以支援 -WhatIf 的自訂函式包裝,先輸出目標清單確認後再執行,會比較安全(「PowerShell 的參數設計與模組化」)。
8. 實務上的定式(判斷表)
| 議題 | 選項 | 判斷標準 |
|---|---|---|
| 模組 | Graph SDK / Entra PowerShell | 以識別身分管理為主・要從 AzureAD 遷移就用 Entra;工作負載廣泛就用 Graph SDK2 |
| 安裝 | 全部安裝 / 僅子模組 | 為了縮短啟動時間與相依性,以子模組為單位安裝3 |
| 驗證(互動式) | 交由管理員帳號自行處理 / 最小範圍 | 盤點只用 .Read. 系列。同意會留存在租戶中4 |
| 驗證(無人) | 用戶端密碼 / 憑證 | 建議使用憑證。事先決定好到期管理與更新程序6 |
| 權限的粒度 | 一個包山包海的應用程式 / 依用途分開應用程式註冊 | 可以限縮出事時的影響範圍 |
| 清單取得 | Where-Object / -Filter + -All + -Property |
在伺服器端篩選。取得量直接影響速度與穩定性7 |
| 429 對策 | 立即重試 / 依循 Retry-After |
官方指引。從根本減少取得次數才是優先事項8 |
| 危險操作 | 直接執行 / -WhatIf + 事前確認目標清單 |
離職人員處理・批次刪除務必以能目視確認目標的方式進行 |
9. 總結
- MSOnline 與 AzureAD 已經廢止。遷移目標是 Microsoft Graph PowerShell SDK,或建構於其上的 Microsoft Entra PowerShell。
- 由於
Microsoft.Graph是元模組,實務上只安裝驗證+實際會用到的工作負載子模組。 - 連線的原則是範圍最小化。盤點用途不要求寫入權限,並依用途分開應用程式註冊。
- 無人執行採用應用程式註冊+憑證。委任與應用程式專用所需的權限不同,以及憑證有到期日,是事故的根源。
- 資料取得是
-All(分頁)、-Filter(伺服器端篩選)、-Property(限定欄位)三件套。429 則依循Retry-After。 - 光是每月執行一次盤點・授權彙總・離職人員處理這三項,就能大幅降低授權浪費與帳號放置不管這兩大常見風險。
範例程式碼下載
本文所提到的程式碼,已整理成可直接執行的形式提供下載。內容包含連線、休眠帳號篩選、授權彙總、離職人員處理。
本文的範例因依賴 Windows 與租戶環境,並未進行實際執行驗證。所有檔案都已完成語法解析與 PSScriptAnalyzer 靜態分析,但實際運作務必請在自己的驗證機上確認。
# 語法解析 + 靜態分析(在 Windows 以外的環境也能執行)
./Invoke-SampleTests.ps1
設定值(路徑、伺服器名稱、租用戶識別碼等)僅為範例。請勿直接在正式環境中執行,務必依自家環境調整後再使用。
相關文章
- PowerShell 中安全處理認證資訊 ── 把明文密碼逐出腳本
- Windows PowerShell 5.1 與 PowerShell 7 的差異 ── 公司內部腳本遷移實務指南
- 用 PowerShell 自動化 Excel・CSV 業務處理 ── 彙總・比對・報表輸出的實務食譜
- PowerShell 的錯誤處理與重新執行設計 ── 從 try/catch 失效的陷阱到 exit code、重試的實務定石
- 在 WinForms/WPF 應用程式中導入 Entra ID 驗證 ── MSAL.NET 與 WAM 代理的實務架構
- 工作排程器的工作不執行、以 0x1 結束 ── 原因排查與安全的維運設計
相關諮詢領域
合同會社小村軟體提供 Microsoft 365 維運腳本的 Graph 遷移、應用程式註冊與權限設計的審查,以及盤點・離職人員處理等制式業務自動化等服務。
參考連結
-
Microsoft Community Hub(Microsoft Entra Blog), Action required: MSOnline and AzureAD PowerShell retirement - 2025 info and resources。關於 MSOnline 與 AzureAD 兩個 PowerShell 模組於 2024 年 3 月 30 日被列為不建議使用、MSOnline 的廢止於 2025 年春季實施並於 2025 年 5 月 30 日終止提供、AzureAD 於 2025 年 3 月 30 日結束支援並隨後廢止,以及遷移目標為 Microsoft Graph PowerShell SDK 與 Microsoft Entra PowerShell 的說明。 ↩ ↩2
-
Microsoft Learn, What is Microsoft Entra PowerShell?。關於 Microsoft Entra PowerShell 是建構於 Microsoft Graph PowerShell SDK 之上的情境導向模組、能與 Graph PowerShell SDK 的命令組互通,以及提供協助從 AzureAD 模組遷移的向下相容選項的說明。GA(正式發行)公告請見 Microsoft Entra PowerShell module now generally available(2025 年 3 月)。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Install the Microsoft Graph PowerShell SDK。關於 Microsoft.Graph 是包含一系列子模組的元模組、可以只個別安裝所需的子模組、Microsoft.Graph.Authentication 是驗證所必須,以及支援的 PowerShell 版本的說明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Connect-MgGraph。關於以 -Scopes 要求委任存取權限、以 -ClientId / -TenantId / -CertificateThumbprint 進行應用程式專用驗證、-CertificateThumbprint 與 -CertificateSubjectName 會從目前使用者的憑證存放區取得憑證(使用本機電腦存放區時須自行讀取後傳給 -Certificate)、以 Get-MgContext 確認目前的連線資訊,以及以 Disconnect-MgGraph 中斷連線的說明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Find Microsoft Graph PowerShell commands and permissions。關於以 Find-MgGraphCommand 查詢命令所屬模組・所需權限・對應 API,以及以 Find-MgGraphPermission 查詢權限名稱的說明。 ↩ ↩2
-
Microsoft Learn, Use app-only authentication with the Microsoft Graph PowerShell SDK。關於應用程式註冊・應用程式權限・管理員同意的步驟、以憑證建構無人驗證,以及委任存取權限與應用程式存取權限差異的說明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
Microsoft Learn, Paging Microsoft Graph data in your app。關於 Microsoft Graph 的回應會分頁、在 PowerShell SDK 中指定 -All 即可取得所有頁面、相當於 $filter 與 $select 的 -Filter / -Property 在伺服器端篩選,以及進階查詢中的 -ConsistencyLevel eventual 與件數取得的說明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Microsoft Graph throttling guidance。關於以資源為單位的節流會回傳 HTTP 429、應等待回應的 Retry-After 標頭中指定的秒數後再重試,以及建議採用能減少要求數本身的設計的說明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, user: revokeSignInSessions (Microsoft Graph API)。關於使登入工作階段失效所需的存取權限中,最小權限為 User.RevokeSessions.All、需要管理員同意、停用的對象是更新權杖與瀏覽器的工作階段 Cookie,以及已核發的存取權杖可能會被使用到有效期限為止(即時反映則與持續存取評估有關)的說明。 ↩ ↩2 ↩3
-
Microsoft Learn, signInActivity resource type。關於取得使用者的 signInActivity 屬性需要 AuditLog.Read.All 與 User.Read.All 兩項存取權限、存在租戶授權要求,以及 lastSignInDateTime 代表互動式登入的嘗試(包含成功・失敗),相對地 lastSuccessfulSignInDateTime 代表成功的互動式・非互動式登入、lastNonInteractiveSignInDateTime 代表非互動式登入的說明。 ↩ ↩2
-
Microsoft Learn, Update user (Microsoft Graph API)。關於更新使用者所需的存取權限是以屬性為單位定義、變更 accountEnabled 的最小權限是 User.EnableDisableAccount.All、也能以範圍更廣的 User.ReadWrite.All 執行的說明。以及委任情境下除了適當的範圍外還需要 Microsoft Entra 的系統管理員角色、租戶內能對所有管理員更新 accountEnabled 的最小角色是 Privileged Authentication Administrator、一般而言需要比對象更高階的系統管理員角色,以及應用程式專用情境下若對象是管理員,也需要為應用程式指派更高階系統管理員角色的說明。 ↩ ↩2 ↩3 ↩4 ↩5
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
用 winget + PowerShell 自動化 PC 配置 ── 讓操作手冊變得可執行
本文整理讓新進員工 PC 的環境建置可重現的方法,涵蓋以 winget 進行應用程式導入與 export/import、WinGet Configuration 的宣告式組態、以 PowerShell 補充的設定,以及無人執行時的注意事項。
PowerShell 模組的公司內部分發與更新 ── PSResourceGet 與內部儲存庫
整理如何告別複製共用資料夾中的 ps1 檔案並重複使用的運作方式。內容涵蓋模組資訊清單(manifest)的撰寫方式、版本管理、用 PSResourceGet 建立內部儲存庫並進行分發與更新,以及與簽章搭配運用的做法。
Windows 安全性稽核原則與事件記錄調查實務 ── 成為看得懂 4625 的資訊系統人員
這是一份實務指南,用來回應「請幫忙查一下登入失敗的記錄」這類需求。內容涵蓋基本與詳細稽核原則的關係、最低限度應啟用的子類別、事件 ID 4624/4625/4688 的判讀方式、Security 記錄檔的容量設計,以及使用 Get-WinEvent 擷取的方法。
Windows LAPS 實務指南 ── 停止在所有電腦共用同一組本機系統管理員密碼
所有電腦共用同一組本機系統管理員密碼,是讓一台遭入侵就波及全部電腦的 Pass-the-Hash 攻擊溫床。本文說明已成為作業系統標準功能的 Windows LAPS 如何自動輪替密碼、AD/Entra ID 的儲存設定,以及實務運用上的陷阱。
Windows 防火牆與業務應用程式 ── 受信規則要在安裝程式中登錄
「開發機上正常運作,但在客戶端卻無法通訊」的典型原因就是 Windows 防火牆。本文說明受信預設封鎖與設定檔的機制、為何不能把正式環境交給通知對話方塊處理,以及在安裝程式中登錄受信規則與問題排查的步驟。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
常見問題
整理諮詢這個主題時常見的問題。
- AzureAD 模組與 MSOnline 模組已經無法使用了嗎?
- 是的,兩者皆已廢止。MSOnline 與 AzureAD 這兩個模組於 2024 年 3 月 30 日被列為不建議使用(deprecated),MSOnline 已於 2025 年 5 月 30 日終止提供,AzureAD 也已於 2025 年 3 月 30 日結束支援後隨即廢止。即使有腳本看起來仍在運作,那也是隨時可能停止的狀態。遷移目標是 Microsoft Graph PowerShell SDK,或建構於其上的 Microsoft Entra PowerShell 模組(於 2025 年 3 月正式發行)。
- 安裝 Microsoft.Graph 模組很耗時,有辦法變輕量嗎?
- 可以。Microsoft.Graph 是元模組(Meta Module),底下包含大量子模組,若整包安裝,不論安裝或載入都會花費相當時間。實務上,較合理的做法是只安裝實際會用到的工作負載子模組:使用者管理用 Microsoft.Graph.Users、群組用 Microsoft.Graph.Groups、驗證則是必要的 Microsoft.Graph.Authentication,依此類推。哪個命令屬於哪個模組,可以用 Find-MgGraphCommand 查詢。
- 想從工作排程器無人執行,卻跳出了互動式登入畫面。
- 請切換為透過應用程式註冊(服務主體)與憑證進行的應用程式專用驗證。在 Microsoft Entra ID 中註冊應用程式,對所需的應用程式權限(Application permissions)授予管理員同意後,將 -ClientId・-TenantId・-CertificateThumbprint 傳給 Connect-MgGraph,即可在不進行互動的情況下連線。相較於用戶端密碼,憑證更為安全,有效期限的管理也更明確。請務必將憑證放在執行帳戶的憑證存放區,並訂出到期前更新的維運方式。
- Connect-MgGraph 的 -Scopes 該指定什麼?
- 只指定想執行的命令所要求的最小權限即可。可以透過 Find-MgGraphPermission 或該命令的文件確認需要哪些權限,若只是讀取,像 User.Read.All 這類 .Read. 系列權限就已足夠。若目的是盤點或稽核,請不要要求寫入權限。由於一旦同意的權限會被記錄在租戶中,「先用 Directory.ReadWrite.All 同意再說」會成為日後的風險。依用途分開應用程式註冊、也分開權限,才是安全的做法。
- 用 Get-MgUser 處理帳號數量多的租戶時,只能取得一部分資料。
- 這是因為 Microsoft Graph 的回應會分頁。加上 -All,就會自動依序取得所有頁面。此外,大量取得時可能會被調整(節流)並收到 429 回應,因此基本做法是只用 -Property 篩選必要欄位,並將篩選條件以 OData(-Filter)推給伺服器端處理,而不是在用戶端使用 Where-Object。如果只想要件數,可以搭配 -ConsistencyLevel eventual 與 -CountVariable,只取得計數。