Analisi Condivisioni

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