Skip to content

Commit fa55c33

Browse files
authored
Merge pull request #922 from Adam-it/new-script-spo-revoke-app-site-permission
Adds CLI for Microsoft 365 script for spo revoke app site permission sample
2 parents b2b4c1c + 6de71da commit fa55c33

File tree

3 files changed

+249
-4
lines changed

3 files changed

+249
-4
lines changed

scripts/spo-revoke-app-site-permission/README.md

Lines changed: 228 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,233 @@
44

55
This script demonstrates how to audit and revoke Entra ID app permissions across SharePoint sites. The script automates the process of scanning all tenant sites, generating CSV reports of app permissions, and revoking access while implementing verification steps to ensure successful removal.
66

7+
## Usage examples
8+
9+
Usage example of the CLI for Microsoft 365 version:
10+
11+
![CLI for Microsoft 365 Example](assets/example-cli.png)
12+
13+
Usage example of the PnP PowerShell version:
14+
15+
![PnP PowerShell Example](assets/example.png)
16+
717
## Summary
818

19+
# [CLI for Microsoft 365](#tab/cli-m365-ps)
20+
21+
```powershell
22+
[CmdletBinding(SupportsShouldProcess)]
23+
param(
24+
[Parameter(Mandatory = $true, HelpMessage = "SharePoint admin center URL (e.g., https://contoso-admin.sharepoint.com)")]
25+
[ValidatePattern('^https://')]
26+
[string]$TenantAdminUrl,
27+
28+
[Parameter(Mandatory = $true, HelpMessage = "Display name of the Entra ID application to search for")]
29+
[string]$AppDisplayName,
30+
31+
[Parameter(Mandatory = $false, HelpMessage = "Path for CSV export (optional, defaults to current directory)")]
32+
[string]$OutputPath,
33+
34+
[Parameter(Mandatory = $false, HelpMessage = "Switch to revoke permissions (requires confirmation unless -Force is used)")]
35+
[switch]$RevokePermissions
36+
)
37+
38+
begin {
39+
# Start transcript logging
40+
$dateTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
41+
$transcriptPath = "RevokeAppPermissions_$dateTime.log"
42+
Start-Transcript -Path $transcriptPath | Out-Null
43+
Write-Host "Transcript started: $transcriptPath" -ForegroundColor Cyan
44+
45+
# Set OutputPath if not specified
46+
if ([string]::IsNullOrEmpty($OutputPath)) {
47+
$OutputPath = Join-Path -Path (Get-Location) -ChildPath "EntraIDAppPermissions_$dateTime.csv"
48+
}
49+
else {
50+
# Validate parent folder exists when user specifies path
51+
$parentFolder = Split-Path -Path $OutputPath -Parent
52+
if (-not (Test-Path -Path $parentFolder)) {
53+
Stop-Transcript
54+
throw "Output path parent folder does not exist: $parentFolder"
55+
}
56+
}
57+
58+
Write-Host "Output CSV will be saved to: $OutputPath" -ForegroundColor Cyan
59+
60+
# Ensure user is signed in to CLI for Microsoft 365
61+
Write-Host "Ensuring CLI for Microsoft 365 authentication..." -ForegroundColor Yellow
62+
m365 login --ensure
63+
if ($LASTEXITCODE -ne 0) {
64+
Stop-Transcript
65+
throw "Failed to authenticate with CLI for Microsoft 365. Please run 'm365 login' manually."
66+
}
67+
Write-Host "Successfully authenticated" -ForegroundColor Green
68+
69+
# Initialize script-level variables
70+
$script:ReportCollection = [System.Collections.Generic.List[PSCustomObject]]::new()
71+
$script:TotalSites = 0
72+
$script:PermissionsFound = 0
73+
$script:PermissionsRevoked = 0
74+
$script:Failures = 0
75+
}
76+
77+
process {
78+
# Get all SharePoint sites
79+
Write-Host "Retrieving all SharePoint sites..." -ForegroundColor Yellow
80+
$sitesJson = m365 spo site list --output json 2>&1
81+
if ($LASTEXITCODE -ne 0) {
82+
Write-Warning "Failed to retrieve sites: $sitesJson"
83+
return
84+
}
85+
86+
$sites = @($sitesJson | ConvertFrom-Json)
87+
$script:TotalSites = $sites.Count
88+
Write-Host "Found $($script:TotalSites) sites to scan" -ForegroundColor Green
89+
90+
if ($script:TotalSites -eq 0) {
91+
Write-Host "No sites found to process" -ForegroundColor Yellow
92+
return
93+
}
94+
95+
# Process each site
96+
$siteCounter = 0
97+
foreach ($site in $sites) {
98+
$siteCounter++
99+
Write-Progress -Activity "Scanning sites for app permissions" -Status "Processing site $siteCounter of $($script:TotalSites): $($site.Url)" -PercentComplete (($siteCounter / $script:TotalSites) * 100)
100+
101+
Write-Verbose "Processing site: $($site.Url)"
102+
103+
# Get app permissions for the specified app
104+
try {
105+
$permissionsJson = m365 spo site apppermission list --siteUrl $site.Url --appDisplayName $AppDisplayName --output json 2>&1
106+
if ($LASTEXITCODE -ne 0) {
107+
Write-Verbose "No permissions found or error retrieving permissions for site: $($site.Url)"
108+
continue
109+
}
110+
111+
$permissions = @($permissionsJson | ConvertFrom-Json)
112+
if ($permissions.Count -eq 0) {
113+
Write-Verbose "No permissions found for app '$AppDisplayName' on site: $($site.Url)"
114+
continue
115+
}
116+
117+
# Process each permission
118+
foreach ($permission in $permissions) {
119+
$script:PermissionsFound++
120+
121+
$reportItem = [PSCustomObject]@{
122+
PermissionId = $permission.permissionId
123+
SiteUrl = $site.Url
124+
SiteTitle = $site.Title
125+
AppDisplayName = $permission.appDisplayName
126+
AppId = $permission.appId
127+
Roles = ($permission.roles -join '|')
128+
RevokedDate = ""
129+
Status = "Not Revoked"
130+
}
131+
132+
# Revoke permission if switch is enabled
133+
if ($RevokePermissions) {
134+
if ($PSCmdlet.ShouldProcess($site.Url, "Revoke permission for app '$AppDisplayName' (ID: $($permission.permissionId))")) {
135+
try {
136+
Write-Host " Revoking permission ID: $($permission.permissionId) on $($site.Url)" -ForegroundColor Yellow
137+
m365 spo site apppermission remove --siteUrl $site.Url --id $permission.permissionId --force 2>&1 | Out-Null
138+
if ($LASTEXITCODE -eq 0) {
139+
$script:PermissionsRevoked++
140+
$reportItem.RevokedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
141+
$reportItem.Status = "Success"
142+
Write-Host " Successfully revoked permission" -ForegroundColor Green
143+
}
144+
else {
145+
$script:Failures++
146+
$reportItem.Status = "Failed"
147+
Write-Warning " Failed to revoke permission ID: $($permission.permissionId)"
148+
}
149+
}
150+
catch {
151+
$script:Failures++
152+
$reportItem.Status = "Failed"
153+
Write-Warning " Error revoking permission: $($_.Exception.Message)"
154+
}
155+
}
156+
else {
157+
$reportItem.Status = "Skipped (WhatIf)"
158+
}
159+
}
160+
else {
161+
Write-Host " Found permission ID: $($permission.permissionId) (report only mode)" -ForegroundColor Cyan
162+
}
163+
164+
$script:ReportCollection.Add($reportItem)
165+
}
166+
}
167+
catch {
168+
Write-Warning "Error processing site $($site.Url): $($_.Exception.Message)"
169+
continue
170+
}
171+
}
172+
173+
Write-Progress -Activity "Scanning sites for app permissions" -Completed
174+
}
175+
176+
end {
177+
# Export CSV report
178+
if ($script:ReportCollection.Count -gt 0) {
179+
Write-Host "Exporting report to CSV..." -ForegroundColor Yellow
180+
$script:ReportCollection | Sort-Object SiteUrl | Export-Csv -Path $OutputPath -NoTypeInformation -Force
181+
Write-Host "Report exported to: $OutputPath" -ForegroundColor Green
182+
}
183+
else {
184+
Write-Host "No app permissions found for '$AppDisplayName'" -ForegroundColor Yellow
185+
}
186+
187+
# Display summary
188+
Write-Host "" -NoNewline
189+
Write-Host "========================================" -ForegroundColor Cyan
190+
Write-Host " SUMMARY" -ForegroundColor Cyan
191+
Write-Host "========================================" -ForegroundColor Cyan
192+
Write-Host "Total sites scanned : " -NoNewline
193+
Write-Host $script:TotalSites -ForegroundColor White
194+
Write-Host "Permissions found : " -NoNewline
195+
Write-Host $script:PermissionsFound -ForegroundColor White
196+
197+
if ($RevokePermissions) {
198+
Write-Host "Permissions revoked : " -NoNewline
199+
Write-Host $script:PermissionsRevoked -ForegroundColor Green
200+
Write-Host "Failures : " -NoNewline
201+
if ($script:Failures -gt 0) {
202+
Write-Host $script:Failures -ForegroundColor Red
203+
}
204+
else {
205+
Write-Host $script:Failures -ForegroundColor Green
206+
}
207+
}
208+
Write-Host "========================================" -ForegroundColor Cyan
209+
210+
if ($RevokePermissions -and $script:PermissionsRevoked -gt 0) {
211+
Write-Host "Permissions have been revoked. Please verify in your Entra ID admin center." -ForegroundColor Yellow
212+
}
213+
214+
# Stop transcript
215+
Write-Host "Transcript saved to: $transcriptPath" -ForegroundColor Cyan
216+
Stop-Transcript | Out-Null
217+
}
218+
219+
# Report only mode - scan all sites for app permissions
220+
# ./Revoke-AppSitePermissions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -AppDisplayName "MyApp"
221+
222+
# Revoke permissions with confirmation prompt
223+
# ./Revoke-AppSitePermissions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -AppDisplayName "MyApp" -RevokePermissions
224+
225+
# Revoke permissions without confirmation (use with caution)
226+
# ./Revoke-AppSitePermissions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -AppDisplayName "MyApp" -RevokePermissions -Force
227+
228+
# Preview what would be revoked (WhatIf mode)
229+
# ./Revoke-AppSitePermissions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -AppDisplayName "MyApp" -RevokePermissions -WhatIf
230+
```
231+
[!INCLUDE [More about CLI for Microsoft 365](../../docfx/includes/MORE-CLIM365.md)]
232+
233+
9234
# [PnP PowerShell](#tab/pnpps)
10235

11236
```powershell
@@ -102,7 +327,7 @@ else {
102327
}
103328
```
104329
[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)]
105-
330+
***
106331

107332
## Source Credit
108333

@@ -113,7 +338,8 @@ Sample idea first appeared on [Revoke Entra ID App Permissions from SharePoint S
113338
| Author(s) |
114339
|-----------|
115340
| [Reshmee Auckloo](https://github.com/reshmee011) |
341+
| [Adam Wójcik](https://github.com/Adam-it) |
116342

117343

118344
[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]
119-
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-revoke-app-site-permission" aria-hidden="true" />
345+
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-revoke-app-site-permission" aria-hidden="true" />
189 KB
Loading

scripts/spo-revoke-app-site-permission/assets/sample.json

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
""
1010
],
1111
"creationDateTime": "2025-10-23",
12-
"updateDateTime": "2025-10-23",
12+
"updateDateTime": "2026-01-17",
1313
"products": [
1414
"SharePoint",
1515
"Entra ID"
@@ -18,6 +18,10 @@
1818
{
1919
"key": "PNP-POWERSHELL",
2020
"value": "3.1.0"
21+
},
22+
{
23+
"key": "CLI-FOR-MICROSOFT365",
24+
"value": "11.3.0"
2125
}
2226
],
2327
"categories": [
@@ -29,7 +33,11 @@
2933
"Get-PnPAzureADApp",
3034
"Get-PnPAzureADAppSitePermission",
3135
"Get-PnPTenantSite",
32-
"Revoke-PnPAzureADAppSitePermission"
36+
"Revoke-PnPAzureADAppSitePermission",
37+
"m365 login",
38+
"m365 spo site list",
39+
"m365 spo site apppermission list",
40+
"m365 spo site apppermission remove"
3341
],
3442
"thumbnails": [
3543
{
@@ -45,13 +53,24 @@
4553
"company": "",
4654
"pictureUrl": "https://github.com/reshmee011.png",
4755
"name": "Reshmee Auckloo"
56+
},
57+
{
58+
"gitHubAccount": "Adam-it",
59+
"company": "",
60+
"pictureUrl": "https://avatars.githubusercontent.com/u/58668583?v=4",
61+
"name": "Adam Wójcik"
4862
}
4963
],
5064
"references": [
5165
{
5266
"name": "Want to learn more about PnP PowerShell and the cmdlets",
5367
"description": "Check out the PnP PowerShell site to get started and for the reference to the cmdlets.",
5468
"url": "https://aka.ms/pnp/powershell"
69+
},
70+
{
71+
"name": "Want to learn more about CLI for Microsoft 365 and the commands",
72+
"description": "Check out the CLI for Microsoft 365 site to get started and for the reference to the commands.",
73+
"url": "https://aka.ms/cli-m365"
5574
}
5675
]
5776
}

0 commit comments

Comments
 (0)