Private/Resolve-SPMSharingLink.ps1

function Resolve-SPMSharingLink {
    <#
    .SYNOPSIS
        Resolves what a sharing link shares and how it was created.
    .DESCRIPTION
        The item GUID inside a SharingLinks group name is the file/folder UniqueId.
        One ListItemAllFields lookup yields the path, the real type (FileSystemObjectType)
        and the list/item ids; GetSharingInformation then adds creator, creation date and
        expiration. Folders shared anonymously or organization-wide are additionally
        checked for suspicious filenames.

        Shared by the deep scan (Get-SPMSiteScan) and the express scan
        (Get-SPMSharingOverview) so both see identical link forensics.
    .PARAMETER ItemId
        UniqueId of the shared file or folder (from the link group name).
    .PARAMETER Kind
        Link kind from the group name: AnonymousView, OrganizationEdit, Flexible, ...
    .PARAMETER ShareId
        Share GUID from the group name - identifies the individual link on the item.
    .PARAMETER Cache
        Hashtable reused across calls so one item with several links is fetched once.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$ItemId,
        [Parameter(Mandatory)][string]$Kind,
        [string]$ShareId,
        [hashtable]$Cache = @{}
    )

    $out = [ordered]@{
        target          = $null
        targetType      = 'file'
        created         = $null
        createdBy       = $null
        expiration      = $null
        suspiciousFiles = @()
    }

    $li = $null
    try {
        $li = Invoke-SPMWithRetry {
            Invoke-PnPSPRestMethod -Url "/_api/web/GetFileById('$ItemId')/ListItemAllFields?`$select=Id,FileSystemObjectType,FileRef,ParentList/Id&`$expand=ParentList"
        }
    }
    catch {
        try {
            $li = Invoke-PnPSPRestMethod -Url "/_api/web/GetFolderById('$ItemId')/ListItemAllFields?`$select=Id,FileSystemObjectType,FileRef,ParentList/Id&`$expand=ParentList"
        }
        catch { Write-Verbose "Sharing link target $ItemId could not be resolved." }
    }
    if ($li -and [int]$li.FileSystemObjectType -eq 1) { $out.targetType = 'folder' }
    if ($li -and $li.FileRef) { $out.target = [string]$li.FileRef }

    # link metadata: who created it, when, and whether it ever expires
    try {
        if (-not $Cache.ContainsKey($ItemId)) {
            $base = if ($out.targetType -eq 'folder') { "GetFolderById('$ItemId')" } else { "GetFileById('$ItemId')" }
            # the empty JSON body is required: without it SharePoint's OData parser
            # fails with "Cannot handle the data at position 0"
            $Cache[$ItemId] = Invoke-SPMWithRetry {
                try {
                    Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/$base/ListItemAllFields/GetSharingInformation?`$Expand=permissionsInformation" -Content '{}'
                }
                catch {
                    if (-not $li) { throw }
                    # canonical list-item form; some tenants reject the shorthand app-only
                    Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/Lists('$($li.ParentList.Id)')/GetItemById($($li.Id))/GetSharingInformation?`$Expand=permissionsInformation" -Content '{}'
                }
            }
        }
        $si = $Cache[$ItemId]
        $silinks = @($si.permissionsInformation.links)
        $link = @($silinks | Where-Object { [string]$_.linkDetails.ShareId -eq $ShareId }) | Select-Object -First 1
        if ($link) {
            $ld = $link.linkDetails
            # PnP deserializes timestamps into [datetime]; normalize to ISO 8601
            $toIso = { param($v) if ($v -is [datetime]) { $v.ToUniversalTime().ToString('o') } else { [string]$v } }
            if ($ld.Created) { $out.created = & $toIso $ld.Created }
            if ($ld.CreatedBy -and $ld.CreatedBy.name) { $out.createdBy = [string]$ld.CreatedBy.name }
            if ($ld.Expiration) { $out.expiration = & $toIso $ld.Expiration }
        }
        elseif ($silinks.Count) {
            Write-Warning "Sharing link $ShareId not found in sharing information of item $ItemId ($($silinks.Count) links returned)."
        }
    }
    catch { Write-Warning "Sharing information for item $ItemId not available: $($_.Exception.Message)" }

    # suspicious filenames inside anonymously/org-wide shared folders
    if ($out.targetType -eq 'folder' -and $Kind -match '^(Anonymous|Organization)') {
        try {
            $files = @((Invoke-PnPSPRestMethod -Url "/_api/web/GetFolderById('$ItemId')/Files?`$select=Name&`$top=200").value)
            $sus = @($files | Where-Object { $_.Name -match '(?i)passwor|pwd|credential|secret|geheim|vertraulich|confidential|iban|salar|lohn|gehalt|\.pfx$|\.pem$|\.key$|\.ppk$' } |
                    ForEach-Object { [string]$_.Name } | Select-Object -First 5)
            if ($sus.Count) { $out.suspiciousFiles = @($sus) }
        }
        catch { Write-Verbose "File check for shared folder $ItemId failed." }
    }

    return [pscustomobject]$out
}