Skip to content

Commit 48e281e

Browse files
authored
Merge pull request #898 from ojopiyo/patch-6
Update README.md
2 parents 1ac7e0e + 6277685 commit 48e281e

2 files changed

Lines changed: 156 additions & 6 deletions

File tree

scripts/get-disabled-or-inactive-user-accounts/README.md

Lines changed: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,158 @@
44

55
## Summary
66

7-
In order to keep your tenant clean (Governance), you might want to ensure that disabled or inactive user accounts will be replaced where oppropriate (Think Owners of sites/groups, assignedto user on tasks/planner and so on). This script will help you find those accounts.
7+
Maintaining a clean and well-governed Microsoft 365 tenant requires visibility into disabled and inactive user accounts. These accounts can unintentionally retain ownership or assignments across SharePoint sites, Microsoft Teams, Microsoft 365 Groups, and task-based workloads such as Planner.
8+
9+
This script helps identify disabled and inactive user accounts from multiple sources, enabling administrators to proactively review and replace users where appropriate. By doing so, organizations can reduce operational risk, improve governance, and ensure continued accountability for owned resources and assigned workloads.
810

911
![Example Screenshot](assets/example.png)
1012

13+
### Purpose
1114

12-
# [PnP PowerShell](#tab/pnpps)
15+
The purpose of this script is to support Microsoft 365 governance by identifying user accounts that are disabled or inactive but may still hold ownership, permissions, or assignments across the tenant. The output can be used as an input for remediation activities such as ownership reassignment, access review, or account cleanup.
16+
17+
### Use Cases
18+
19+
- Identifying disabled users who still own SharePoint sites or Microsoft Teams
20+
- Detecting inactive users assigned to Planner tasks or project deliverables
21+
- Supporting periodic access reviews and governance audits
22+
- Preparing for offboarding or tenant cleanup initiatives
23+
24+
# [PnP PowerShell V2](#tab/pnppsv2)
1325

1426
```powershell
1527
28+
# =========================================
29+
# Script: User Status Discovery (PnP + Graph)
30+
# Purpose: Identify disabled and inactive users from multiple sources
31+
# =========================================
32+
33+
function Get-DisabledUsersFromGraph {
34+
param (
35+
[Parameter(Mandatory)]
36+
[PnP.PowerShell.Commands.Base.PnPConnection]$Connection
37+
)
38+
39+
Invoke-PnPGraphMethod `
40+
-Url "users?`$select=displayName,userPrincipalName,mail,accountEnabled" `
41+
-All `
42+
-Connection $Connection |
43+
Where-Object { $_.accountEnabled -eq $false } |
44+
ForEach-Object {
45+
[PSCustomObject]@{
46+
DisplayName = $_.displayName
47+
UserPrincipalName = $_.userPrincipalName
48+
Mail = $_.mail
49+
Reason = "AccountDisabled"
50+
Source = "EntraID"
51+
}
52+
}
53+
}
54+
55+
function Get-DisabledUsersFromSharePointSearch {
56+
param (
57+
[Parameter(Mandatory)]
58+
[PnP.PowerShell.Commands.Base.PnPConnection]$Connection
59+
)
60+
61+
$results = Invoke-PnPSearchQuery `
62+
-Query "*" `
63+
-SourceId "b09a7990-05ea-4af9-81ef-edfab16c4e31" `
64+
-SelectProperties "WorkEmail,SPS-HideFromAddressLists" `
65+
-All `
66+
-Connection $Connection
67+
68+
$results.ResultRows |
69+
Where-Object { $_["SPS-HideFromAddressLists"] -eq $true } |
70+
ForEach-Object {
71+
[PSCustomObject]@{
72+
DisplayName = $null
73+
UserPrincipalName = $_["WorkEmail"]
74+
Mail = $_["WorkEmail"]
75+
Reason = "HiddenFromGAL"
76+
Source = "SharePointSearch"
77+
}
78+
}
79+
}
80+
81+
function Get-InactiveUsersFromGraph {
82+
param (
83+
[Parameter(Mandatory)]
84+
[PnP.PowerShell.Commands.Base.PnPConnection]$Connection,
85+
86+
[int]$InactiveDays = 90
87+
)
88+
89+
$token = Get-PnPGraphAccessToken -Connection $Connection
90+
$headers = @{
91+
Authorization = "Bearer $token"
92+
"Content-Type" = "application/json"
93+
}
94+
95+
$users = Invoke-RestMethod `
96+
-Uri "https://graph.microsoft.com/v1.0/users?`$select=id,displayName,userPrincipalName" `
97+
-Headers $headers `
98+
-Method GET
99+
100+
foreach ($user in $users.value) {
101+
$signInUri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$top=1&`$filter=userPrincipalName eq '$($user.userPrincipalName)'"
102+
$signIn = Invoke-RestMethod -Uri $signInUri -Headers $headers -Method GET
103+
104+
if (
105+
$signIn.value.Count -eq 0 -or
106+
$signIn.value[0].createdDateTime -lt (Get-Date).AddDays(-$InactiveDays)
107+
) {
108+
[PSCustomObject]@{
109+
DisplayName = $user.displayName
110+
UserPrincipalName = $user.userPrincipalName
111+
Mail = $null
112+
Reason = "Inactive > $InactiveDays days"
113+
Source = "AuditLogs"
114+
}
115+
}
116+
}
117+
}
118+
119+
# ---------------------------
120+
# Connection
121+
# ---------------------------
122+
123+
$ClientId = "clientid"
124+
$TenantName = "[domain].onmicrosoft.com"
125+
$AdminUrl = "https://[domain]-admin.sharepoint.com"
126+
127+
$conn = Connect-PnPOnline `
128+
-Url $AdminUrl `
129+
-ClientId $ClientId `
130+
-Tenant $TenantName `
131+
-CertificatePath "C:\Certs\Cert.pfx" `
132+
-CertificatePassword (ConvertTo-SecureString "ThePassword" -AsPlainText -Force) `
133+
-ReturnConnection
134+
135+
# ---------------------------
136+
# Execution
137+
# ---------------------------
138+
139+
$results = @()
140+
$results += Get-DisabledUsersFromGraph -Connection $conn
141+
$results += Get-DisabledUsersFromSharePointSearch -Connection $conn
142+
$results += Get-InactiveUsersFromGraph -Connection $conn -InactiveDays 90
143+
144+
$results |
145+
Sort-Object UserPrincipalName, Reason |
146+
Export-Csv "C:\Temp\UserStatusFindings.csv" -NoTypeInformation -Encoding UTF8
147+
148+
149+
150+
151+
```
152+
[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)]
153+
154+
# [PnP PowerShell](#tab/pnpps)
155+
156+
In order to keep your tenant clean (Governance), you might want to ensure that disabled or inactive user accounts will be replaced where oppropriate (Think Owners of sites/groups, assignedto user on tasks/planner and so on). This script will help you find those accounts.
157+
158+
```powershell
16159
17160
function Get-UserFromGraph
18161
{
@@ -82,8 +225,6 @@ function Get-UserFromGraphThatHasntLoggedInResently($duration = 90)
82225
}
83226
84227
85-
86-
87228
$ClientId = "clientid"
88229
$TenantName = "[domain].onmicrosoft.com"
89230
$SharePointAdminSiteURL = "https://[domain]-admin.sharepoint.com/"
@@ -100,6 +241,8 @@ $userd1 | Export-Csv -Path "C:\temp\disabledusers.csv" -NoTypeInformation
100241
101242
```
102243
[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)]
244+
245+
103246
***
104247

105248

@@ -108,6 +251,7 @@ $userd1 | Export-Csv -Path "C:\temp\disabledusers.csv" -NoTypeInformation
108251
| Author(s) |
109252
|-----------|
110253
| Kasper Larsen |
254+
| [Josiah Opiyo](https://github.com/ojopiyo) |
111255

112256
[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]
113257
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/get-disabled-or-inactive-user-accounts" aria-hidden="true" />

scripts/get-disabled-or-inactive-user-accounts/assets/sample.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"In order to keep your tenant clean (Governance), you might want to ensure that disabled or inactive user accounts will be replaced where oppropriate (Think Owners of sites/groups, assignedto user on tasks/planner and so on). This script will help you find those accounts."
1010
],
1111
"creationDateTime": "2023-10-15",
12-
"updateDateTime": "2023-10-15",
12+
"updateDateTime": "2025-12-18",
1313
"products": [
1414
"SharePoint",
1515
"Graph",
@@ -18,7 +18,7 @@
1818
"metadata": [
1919
{
2020
"key": "PNP-POWERSHELL",
21-
"value": "2.2.0"
21+
"value": "3.1.0"
2222
}
2323
],
2424
"categories": [
@@ -38,6 +38,12 @@
3838
}
3939
],
4040
"authors": [
41+
{
42+
"gitHubAccount": "ojopiyo",
43+
"company": "",
44+
"pictureUrl": "https://github.com/ojopiyo.png",
45+
"name": "Josiah Opiyo"
46+
},
4147
{
4248
"gitHubAccount": "kasperbolarsen",
4349
"company": "",

0 commit comments

Comments
 (0)