Questo script PowerShell utilizza Microsoft Graph in modalità app-only per analizzare tutti i file e le cartelle condivise presenti nelle document library di SharePoint Online e OneDrive for Business.
Lo script esegue una scansione di tutti i siti SharePoint e dei drive associati, identificando:
- elementi condivisi tramite permessi diretti o link di condivisione
- condivisioni esterne, basate su domini non aziendali o link anonimi
- owner del file ultimo utente che ha svolto la modifica al documento
- tipologia dell’elemento (file o cartella)
- dettagli completi dei permessi applicati
Il risultato è un report CSV dettagliato, utile per attività di audit, security assessment e data governance, che consente di individuare rapidamente esposizioni non intenzionali o potenziali rischi di sicurezza.
Lo script supporta l’analisi ricorsiva delle cartelle fino a una profondità configurabile ed è basato su permessi applicativi Microsoft Entra ID, ben descritti e visibili nell’intestazione dello script.
Nota
Lo script è progettato per un’esecuzione manuale e puntuale.
La pianificazione automatica è sconsigliata, poiché le credenziali di connessione a Microsoft Graph sono dichiarate in chiaro all’interno dello script.
<#
===========================================================
PREREQUISITI PER L’ESECUZIONE DELLO SCRIPT
===========================================================
1) Permessi necessari nell’app registrata in Azure AD
-----------------------------------------------------
Lo script utilizza Microsoft Graph API con autenticazione
client credentials (app registration). L’app deve avere
i seguenti permessi **Application**:
- Sites.Read.All
- Sites.ReadWrite.All (opzionale ma consigliato)
- Files.Read.All
- Files.ReadWrite.All (opzionale ma consigliato)
- User.Read.All
- Directory.Read.All
Dopo aver assegnato i permessi, è necessario:
→ Cliccare su “Grant admin consent”.
2) Requisiti lato PowerShell
----------------------------
- PowerShell 5.1 o PowerShell 7+
- Modulo "Microsoft.Graph" **NON necessario**
(lo script usa solo Invoke-RestMethod)
- Esecuzione script abilitata:
Set-ExecutionPolicy RemoteSigned
3) Requisiti lato tenant
------------------------
- L’account che esegue lo script deve essere:
→ Global Administrator
oppure
→ SharePoint Administrator
===========================================================
FINE PREREQUISITI
===========================================================
#>
# ==========================
# CONFIGURAZIONE
# ==========================
$TenantId = "INSERISCI-TENANT-ID-QUI"
$ClientId = "INSERISCI-APP-ID-QUI"
$ClientSecret = "INSERISCI-CLIENT-SECRET-QUI"
# ==========================
# CONFIGURAZIONE OUTPUT
# ==========================
# Cartella di destinazione
$OutputFolder = "C:\temp"
# Crea la cartella se non esiste
if (-not (Test-Path $OutputFolder)) {
New-Item -ItemType Directory -Path $OutputFolder | Out-Null
}
# Timestamp per nome file
$timestamp = (Get-Date).ToString("yyyy-MM-dd_HH-mm-ss")
# Percorso completo del CSV
$OutputCsvPath = Join-Path $OutputFolder "sharing-data_$timestamp.csv"
# ==========================
# FUNZIONI DI SUPPORTO
# ==========================
function Get-GraphToken {
param(
[string]$TenantId,
[string]$ClientId,
[string]$ClientSecret,
[string]$Scope = "https://graph.microsoft.com/.default"
)
$body = @{
client_id = $ClientId
scope = $Scope
client_secret = $ClientSecret
grant_type = "client_credentials"
}
$tokenEndpoint = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
$response = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body $body -ContentType "application/x-www-form-urlencoded"
return $response.access_token
}
function Invoke-GraphGet {
param(
[string]$Uri,
[string]$AccessToken
)
$headers = @{
Authorization = "Bearer $AccessToken"
}
try {
$response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $headers
} catch {
return @()
}
if ($response.value) { return $response.value }
return @($response)
}
function Get-DriveItemsRecursive {
param(
[string]$DriveId,
[string]$AccessToken
)
$items = @()
$queue = New-Object System.Collections.Queue
$queue.Enqueue("root")
while ($queue.Count -gt 0) {
$itemId = $queue.Dequeue()
if ($itemId -eq "root") {
$uri = "https://graph.microsoft.com/v1.0/drives/$DriveId/root/children"
} else {
$uri = "https://graph.microsoft.com/v1.0/drives/$DriveId/items/$itemId/children"
}
$children = Invoke-GraphGet -Uri $uri -AccessToken $AccessToken
foreach ($child in $children) {
$items += $child
if ($child.folder) {
$queue.Enqueue($child.id)
}
}
}
return $items
}
function Get-Permissions {
param(
[string]$DriveId,
[string]$ItemId,
[string]$AccessToken
)
$uri = "https://graph.microsoft.com/v1.0/drives/$DriveId/items/$ItemId/permissions"
return Invoke-GraphGet -Uri $uri -AccessToken $AccessToken
}
# ==========================
# OTTENIMENTO TOKEN
# ==========================
Write-Host "Ottenimento token..." -ForegroundColor Cyan
$AccessToken = Get-GraphToken -TenantId $TenantId -ClientId $ClientId -ClientSecret $ClientSecret
# ==========================
# RECUPERO DOMINI DEL TENANT
# ==========================
Write-Host "Recupero domini del tenant..." -ForegroundColor Cyan
$domains = Invoke-GraphGet -Uri "https://graph.microsoft.com/v1.0/domains" -AccessToken $AccessToken
$internalDomains = $domains.id | ForEach-Object { $_.ToLower() }
# ==========================
# FUNZIONE: Filtra permessi NON ereditati
# ==========================
function Get-RealSharing {
param(
$filePerms,
$parentPerms
)
$real = @()
foreach ($perm in $filePerms) {
# Link di condivisione = sempre reale
if ($perm.link) {
$real += $perm
continue
}
# Se non esiste nella cartella padre → è reale
$match = $parentPerms | Where-Object { $_.id -eq $perm.id }
if (-not $match) {
$real += $perm
}
}
return $real
}
# ==========================
# ELABORAZIONE
# ==========================
$report = New-Object System.Collections.Generic.List[Object]
# ============================================================
# SHAREPOINT
# ============================================================
Write-Host "Analisi SharePoint..." -ForegroundColor Cyan
$spoSites = Invoke-GraphGet -Uri "https://graph.microsoft.com/v1.0/sites?search=*" -AccessToken $AccessToken
foreach ($site in $spoSites) {
$siteId = $site.id
$siteUrl = $site.webUrl
$sitoNome = $site.name
Write-Host "Sito SPO: $sitoNome" -ForegroundColor Green
$drives = Invoke-GraphGet -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/drives" -AccessToken $AccessToken
foreach ($drive in $drives) {
$driveId = $drive.id
$driveName = $drive.name
$items = Get-DriveItemsRecursive -DriveId $driveId -AccessToken $AccessToken
foreach ($item in $items) {
if (-not $item.file) { continue }
$filePerms = Get-Permissions -DriveId $driveId -ItemId $item.id -AccessToken $AccessToken
$parentPerms = Get-Permissions -DriveId $driveId -ItemId $item.parentReference.id -AccessToken $AccessToken
# 🔥 Filtra permessi non ereditati
$realPerms = Get-RealSharing -filePerms $filePerms -parentPerms $parentPerms
if ($realPerms.Count -eq 0) { continue }
# Aggregazione
$roles = @()
$emails = @()
$scopes = @()
foreach ($perm in $realPerms) {
if ($perm.roles) { $roles += $perm.roles }
if ($perm.link -and $perm.link.scope) {
$scopes += $perm.link.scope
}
if ($perm.grantedTo) {
if ($perm.grantedTo.user.email) { $emails += $perm.grantedTo.user.email }
}
if ($perm.grantedToIdentitiesV2) {
foreach ($g in $perm.grantedToIdentitiesV2) {
if ($g.user.email) { $emails += $g.user.email }
}
}
}
# Livello condivisione
$livello = "Utenti specifici"
if ($scopes -contains "anonymous") { $livello = "Chiunque con il link" }
elseif ($scopes -contains "organization") { $livello = "Tutta l'organizzazione" }
# 🔥 Identificazione esterni
$isExternal = "No"
foreach ($email in $emails) {
$domain = $email.Split("@")[1].ToLower()
if ($internalDomains -notcontains $domain) {
$isExternal = "Si"
}
}
# Owner = ultima persona che ha modificato il file
$owner = if ($item.lastModifiedBy.user.email) {
$item.lastModifiedBy.user.email
} else {
$item.lastModifiedBy.user.displayName
}
# Formato compatto
$dettaglio = $livello
if ($roles.Count -gt 0) {
$dettaglio += " - " + (($roles | Select-Object -Unique) -join ";")
}
if ($emails.Count -gt 0) {
$dettaglio += " - " + (($emails | Select-Object -Unique) -join ";")
}
# Oggetto finale
$obj = [PSCustomObject]@{
Origine = "SharePoint"
SitoNome = $sitoNome
SitoRootUrl = $siteUrl
DriveName = $driveName
FileName = $item.name
Owner = $owner
DettaglioCondivisione = $dettaglio
CondivisioneEsterna = $isExternal
}
$report.Add($obj) | Out-Null
}
}
}
# ============================================================
# ONEDRIVE
# ============================================================
Write-Host "Analisi OneDrive..." -ForegroundColor Cyan
foreach ($user in $users) {
$userId = $user.id
$upn = $user.userPrincipalName
$sitoNome = "OneDrive - $($user.displayName)"
Write-Host "OneDrive utente: $upn" -ForegroundColor Green
try {
$drive = Invoke-GraphGet -Uri "https://graph.microsoft.com/v1.0/users/$userId/drive" -AccessToken $AccessToken
} catch {
continue
}
if (-not $drive -or -not $drive.id) { continue }
$driveId = $drive.id
$driveName = $drive.name
$sitoRootUrl = $drive.webUrl
$items = Get-DriveItemsRecursive -DriveId $driveId -AccessToken $AccessToken
foreach ($item in $items) {
if (-not $item.file) { continue }
$filePerms = Get-Permissions -DriveId $driveId -ItemId $item.id -AccessToken $AccessToken
$parentPerms = Get-Permissions -DriveId $driveId -ItemId $item.parentReference.id -AccessToken $AccessToken
# 🔥 Filtra permessi non ereditati
$realPerms = Get-RealSharing -filePerms $filePerms -parentPerms $parentPerms
if ($realPerms.Count -eq 0) { continue }
# Aggregazione
$roles = @()
$emails = @()
$scopes = @()
foreach ($perm in $realPerms) {
if ($perm.roles) { $roles += $perm.roles }
if ($perm.link -and $perm.link.scope) {
$scopes += $perm.link.scope
}
if ($perm.grantedTo) {
if ($perm.grantedTo.user.email) { $emails += $perm.grantedTo.user.email }
}
if ($perm.grantedToIdentitiesV2) {
foreach ($g in $perm.grantedToIdentitiesV2) {
if ($g.user.email) { $emails += $g.user.email }
}
}
}
# Livello condivisione
$livello = "Utenti specifici"
if ($scopes -contains "anonymous") { $livello = "Chiunque con il link" }
elseif ($scopes -contains "organization") { $livello = "Tutta l'organizzazione" }
# 🔥 Identificazione esterni
$isExternal = "No"
foreach ($email in $emails) {
$domain = $email.Split("@")[1].ToLower()
if ($internalDomains -notcontains $domain) {
$isExternal = "Si"
}
}
# Owner = ultima persona che ha modificato il file
$owner = if ($item.lastModifiedBy.user.email) {
$item.lastModifiedBy.user.email
} else {
$item.lastModifiedBy.user.displayName
}
# Formato compatto
$dettaglio = $livello
if ($roles.Count -gt 0) {
$dettaglio += " - " + (($roles | Select-Object -Unique) -join ";")
}
if ($emails.Count -gt 0) {
$dettaglio += " - " + (($emails | Select-Object -Unique) -join ";")
}
# Oggetto finale
$obj = [PSCustomObject]@{
Origine = "OneDrive"
SitoNome = $sitoNome
SitoRootUrl = $sitoRootUrl
DriveName = $driveName
FileName = $item.name
Owner = $owner
DettaglioCondivisione = $dettaglio
CondivisioneEsterna = $isExternal
}
$report.Add($obj) | Out-Null
}
}
# ==========================
# EXPORT CSV
# ==========================
Write-Host "Esporto CSV..." -ForegroundColor Cyan
$report | Export-Csv -Path $OutputCsvPath -NoTypeInformation -Encoding UTF8
Write-Host "Completato. File generato: $OutputCsvPath" -ForegroundColor Green