Public/Get-SharingLink.ps1
|
function Get-SharingLink { <# .SYNOPSIS Audit SharePoint Online sharing links for a site, a set of sites, or a whole tenant, with an oversharing risk rating on each. .DESCRIPTION Sharing links are the quietest oversharing vector in SharePoint - they do not show up as normal permissions, they hide as SharingLinks.<guid> groups, and nobody reads a list of those. This finds them through exactly those groups, so it only touches files that actually have a link rather than walking everything, and reports each link's scope, who it reaches, when it expires, and how risky it is. Read-only. Returns objects, so pipe to Remove-SharingLink to revoke, or to Export-SharingLinkReport / Export-Csv to keep a record. .PARAMETER SiteUrl A single site to audit. .PARAMETER SiteUrls Several sites to audit. .PARAMETER Tenant Audit every site in the tenant. Needs -TenantAdminUrl and SharePoint admin. .PARAMETER TenantAdminUrl e.g. https://contoso-admin.sharepoint.com. Required with -Tenant. .PARAMETER ClientId Entra ID app registration client ID. Not needed with -UseExistingConnection. .PARAMETER IncludeFolders Also report links on folders, not just files. .PARAMETER MinimumRisk Only return links at this risk level or above (Low, Medium, High, Critical). .EXAMPLE Get-SharingLink -SiteUrl "https://contoso.sharepoint.com/sites/Sales" -ClientId $appId -Interactive .EXAMPLE Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/Sales" -ClientId $appId -Interactive Get-SharingLink -SiteUrl "https://contoso.sharepoint.com/sites/Sales" -UseExistingConnection .EXAMPLE Get-SharingLink -Tenant -TenantAdminUrl "https://contoso-admin.sharepoint.com" ` -ClientId $appId -CertificatePath .\audit.pfx -Tenant contoso.onmicrosoft.com ` -MinimumRisk High #> [CmdletBinding(DefaultParameterSetName = 'Site')] param( [Parameter(Mandatory, ParameterSetName = 'Site')] [string] $SiteUrl, [Parameter(Mandatory, ParameterSetName = 'Sites')] [string[]] $SiteUrls, [Parameter(Mandatory, ParameterSetName = 'Tenant')] [switch] $TenantWide, [Parameter(ParameterSetName = 'Tenant')] [string] $TenantAdminUrl, [string] $ClientId, [string] $Tenant, [string] $CertificatePath, [securestring] $CertificatePassword, [string] $Thumbprint, [switch] $Interactive, [switch] $ManagedIdentity, [switch] $UseExistingConnection, [switch] $IncludeFolders, [ValidateSet('Low','Medium','High','Critical')] [string] $MinimumRisk = 'Low' ) $rank = @{ Low = 0; Medium = 1; High = 2; Critical = 3 } $floor = $rank[$MinimumRisk] $connectSplat = @{ ClientId = $ClientId; Tenant = $Tenant CertificatePath = $CertificatePath; CertificatePassword = $CertificatePassword Thumbprint = $Thumbprint; Interactive = $Interactive ManagedIdentity = $ManagedIdentity; UseExistingConnection = $UseExistingConnection } # Work out the list of sites to visit. $targets = switch ($PSCmdlet.ParameterSetName) { 'Site' { @($SiteUrl) } 'Sites' { $SiteUrls } 'Tenant' { # -TenantWide is the switch that selects this set; referencing it keeps # the intent explicit (and satisfies the unused-parameter analyzer). $null = $TenantWide if (-not $TenantAdminUrl) { throw "-TenantWide needs -TenantAdminUrl." } if ($UseExistingConnection) { throw "-UseExistingConnection cannot be combined with -TenantWide: each site is connected to in turn, so real credentials (-ClientId + an auth method) are required." } Connect-IfNeeded -Url $TenantAdminUrl @connectSplat Write-Verbose "Enumerating tenant sites..." (Invoke-WithRetry -Because 'Get-PnPTenantSite' -Action { Get-PnPTenantSite }) | Where-Object { $_.Template -notlike 'REDIRECT*' } | Select-Object -ExpandProperty Url } } $total = @($targets).Count $n = 0 $emitted = 0 # so an empty run can say so, instead of looking broken foreach ($site in $targets) { $n++ Write-Progress -Activity 'Auditing sharing links' -Status "$n of $total : $site" ` -PercentComplete (($n / [Math]::Max($total,1)) * 100) try { Connect-IfNeeded -Url $site @connectSplat # Count as we stream, without collecting everything - keeps it usable # on a large tenant while still knowing whether anything was found. Get-SiteSharingLink -SiteUrl $site -IncludeFolders:$IncludeFolders | Where-Object { $rank[$_.RiskLevel] -ge $floor } | ForEach-Object { $emitted++; $_ } } catch { Write-Warning "Skipped $site : $($_.Exception.Message)" } } Write-Progress -Activity 'Auditing sharing links' -Completed if ($emitted -eq 0) { $where = if ($MinimumRisk -ne 'Low') { " at risk level $MinimumRisk or above" } else { '' } Write-Information "No sharing links found across $total site(s)$where." -InformationAction Continue } else { Write-Verbose "Found $emitted sharing link(s)." } } |