Questo script PowerShell utilizza Microsoft Graph per analizzare tutti i file e le cartelle condivise presenti in SharePoint Online e OneDrive for Business.
Il risultato è un report CSV dettagliato che evidenzia condivisioni, permessi, owner e accessi esterni, utile per audit e governance.
Lo script è pensato per un’unica esecuzione, la pianificazione è sconsigliata in quanto la connessione è in chiaro nello script
#Requires -Version 3.0
<#
Analisi di TUTTI i file/cartelle condivisi nelle document library SharePoint Online
+ OneDrive for Business
===========================================================
PREREQUISITI & PERMESSI (Application) su Entra ID (Graph)
===========================================================
- Sites.Read.All (lettura siti SharePoint)
- Files.Read.All (lettura file e permessi via drive/items)
- Directory.Read.All (consigliato: proprietà complete di oggetti)
- GroupMember.Read.All (MINIMO per leggere membri dei gruppi)
- Member.Read.Hidden (se usi gruppi con membership nascosta)
- TeamMember.Read.All (consigliato per /teams/{id}/members in app-only)
*In alternativa, RSC: TeamMember.Read.Group su singolo Team*
#>
[CmdletBinding()]
Param(
[switch]$ExpandFolders,
[int]$Depth = 3,
[switch]$TraceGroups
)
# ==========================
# VARIABILI GLOBALI
# ==========================
$AccessToken = $null
$GroupCache = @{}
$GroupMembersCache = @{}
$TraceGroupsEnabled = $TraceGroups
$Output = @()
# ==========================
# CARTELLA LOG
# ==========================
$LogPath = "C:\Temp\SPOAuditErrors.log"
if (-not (Test-Path "C:\Temp")) {
New-Item -ItemType Directory -Path "C:\Temp" | Out-Null
}
# ==========================
# TOKEN GRAPH
# ==========================
$body = @{
grant_type = "client_credentials"
client_id = $appID
client_secret = $client_secret
scope = "https://graph.microsoft.com/.default"
}
$tokenResponse = Invoke-RestMethod `
-Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" `
-Method POST -Body $body
$AccessToken = $tokenResponse.access_token
# ==========================
# DOMINI AZIENDALI
# ==========================
$Headers = @{ Authorization = "Bearer $AccessToken" }
$Domains = (Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/domains" `
-Headers $Headers).value |
Where-Object { $_.isVerified } |
Select-Object -ExpandProperty id
# ==========================
# RECUPERO SITI SPO
# ==========================
$Sites = @()
$uriSites = "https://graph.microsoft.com/v1.0/sites?search=*"
do {
try {
$resSites = Invoke-RestMethod -Uri $uriSites -Headers $Headers
$Sites += $resSites.value
$uriSites = $resSites.'@odata.nextLink'
} catch {
Add-Content $LogPath "Errore siti: $($_.Exception.Message)"
break
}
} while ($uriSites)
Write-Host "[Siti trovati: $($Sites.Count)]" -ForegroundColor Yellow
# ==========================
# CICLO SITI SHAREPOINT
# ==========================
foreach ($site in $Sites) {
Write-Host "Analizzo sito: $($site.name)" -ForegroundColor Cyan
# --- OWNER SITO ---
$OwnerName = "N/D"
$OwnerEmail = $null
$OwnerType = "unknown"
try {
$driveInfo = Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/sites/$($site.id)/drive" `
-Headers $Headers
if ($driveInfo.owner.user) {
$OwnerName = $driveInfo.owner.user.displayName
$OwnerEmail = $driveInfo.owner.user.email
$OwnerType = "user"
}
elseif ($driveInfo.owner.group) {
$OwnerName = $driveInfo.owner.group.displayName
$OwnerType = "group"
}
} catch {}
# --- DRIVE ---
$drives = (Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/sites/$($site.id)/drives" `
-Headers $Headers).value
foreach ($drive in $drives) {
$queue = @()
$queue += [PSCustomObject]@{
Uri = "https://graph.microsoft.com/v1.0/drives/$($drive.id)/root"
Level = 0
}
while ($queue.Count -gt 0) {
$current = $queue[0]
$queue = $queue[1..($queue.Count-1)]
try {
$children = Invoke-RestMethod `
-Uri "$($current.Uri)/children" `
-Headers $Headers
} catch {
continue
}
foreach ($item in $children.value) {
if ($item.folder -and $ExpandFolders -and $current.Level -lt $Depth) {
$queue += [PSCustomObject]@{
Uri = "https://graph.microsoft.com/v1.0/drives/$($drive.id)/items/$($item.id)"
Level = $current.Level + 1
}
}
$Shared = if ($item.shared) { "Yes" } else { "No" }
$Permissions = "Non condiviso"
$ExternallyShared = "No"
if ($Shared -eq "Yes") {
try {
$perms = Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/drives/$($drive.id)/items/$($item.id)/permissions" `
-Headers $Headers
$permText = @()
foreach ($p in $perms.value) {
if ($p.grantedToV2.user) {
$permText += "$($p.grantedToV2.user.displayName) <$($p.grantedToV2.user.email)>"
if ($p.grantedToV2.user.email) {
$domain = $p.grantedToV2.user.email.Split("@")[1]
if ($Domains -notcontains $domain) {
$ExternallyShared = "Yes"
}
}
}
elseif ($p.link) {
$permText += "Link ($($p.link.scope))"
if ($p.link.scope -eq "anonymous") {
$ExternallyShared = "Yes"
}
}
}
$Permissions = $permText -join "; "
} catch {
$Permissions = "Errore permessi"
}
}
$Output += [PSCustomObject]@{
SiteName = $site.name
SiteUrl = $site.webUrl
OwnerName = $OwnerName
OwnerEmail = $OwnerEmail
OwnerType = $OwnerType
GroupMembers = $null
DriveName = $drive.name
ItemName = $item.name
ItemType = if ($item.folder) { "Folder" } else { "File" }
Shared = $Shared
ExternallyShared = $ExternallyShared
Permissions = $Permissions
}
}
}
}
}
# ==========================
# EXPORT CSV
# ==========================
$csvPath = "C:\Temp\SPOSharedItems_{0}.csv" -f (Get-Date -Format "yyyyMMdd_HHmmss")
$Output |
Where-Object { $_.Shared -eq "Yes" } |
Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
Write-Host "CSV creato: $csvPath" -ForegroundColor Green