Private/Get-NSPScanBridgeDeliveryClassification.ps1

function Get-NSPScanBridgeDeliveryClassification {
    <#
    .SYNOPSIS
        Classifies one delivery lifecycle as DirectExclusive, DirectReadable, TempRename, Hybrid or Unknown.
    .DESCRIPTION
        Evidence comes from samples taken while the file was still changing. A shared-read open that
        the writer refused points to an exclusive writer; one it allowed points to a readable writer.
        Uncertain evidence stays Unknown or Hybrid rather than being forced into a class.
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory)]$Lifecycle)
    $evidence=New-Object System.Collections.Generic.List[string]
    $mid=@($Lifecycle.MidWrite)
    $rename=$Lifecycle.Rename
    $confidenceFor={ param([int]$Count) if($Count -ge 3){'High'}elseif($Count -ge 1){'Medium'}else{'Low'} }
    if ($rename -and $rename.FromExtension -ine '.pdf' -and $rename.ToExtension -ieq '.pdf') {
        $underTemp=@($mid | Where-Object { $_.Extension -ine '.pdf' })
        $afterRename=@($mid | Where-Object { $_.Extension -ieq '.pdf' })
        $evidence.Add(('Renamed {0} -> .pdf ({1}).' -f $rename.FromExtension,$rename.Method))
        if ($afterRename.Count) {
            $evidence.Add(('{0} change sample(s) after the rename.' -f $afterRename.Count))
            return [pscustomobject]@{Class='Hybrid';Confidence='Low';Evidence=@($evidence)}
        }
        if ($underTemp.Count) { $evidence.Add(('{0} sample(s) while growing under the temporary name.' -f $underTemp.Count)) }
        else { $evidence.Add('No growth seen under the temporary name; the write was shorter than one sample.') }
        $confidence=if($rename.Method -eq 'Watcher' -and $underTemp.Count){'High'}elseif($rename.Method -eq 'Watcher' -or $underTemp.Count){'Medium'}else{'Low'}
        return [pscustomobject]@{Class='TempRename';Confidence=$confidence;Evidence=@($evidence)}
    }
    if ($rename) { $evidence.Add(('Renamed {0} -> {1}; not a temporary-to-PDF rename.' -f $rename.FromExtension,$rename.ToExtension)) }
    $direct=@($mid | Where-Object { $_.Extension -ieq '.pdf' -and $_.Shared -in @('Available','Unavailable') })
    if (-not $direct.Count) {
        $evidence.Add('No sample while the file was changing; it appeared at its final size, or the write was shorter than one sample.')
        return [pscustomobject]@{Class='Unknown';Confidence='Low';Evidence=@($evidence)}
    }
    $allowed=@($direct | Where-Object Shared -eq 'Available').Count
    $refused=@($direct | Where-Object { $_.Shared -eq 'Unavailable' -and $_.SharedError -in @('SharingViolation','LockViolation') }).Count
    $other=$direct.Count-$allowed-$refused
    $evidence.Add(('While writing: shared read allowed {0}, refused by sharing {1}, other errors {2}.' -f $allowed,$refused,$other))
    if ($other) {
        $evidence.Add('A probe failed for a reason other than sharing (for example access denied); permissions limit what can be inferred.')
        return [pscustomobject]@{Class='Unknown';Confidence='Low';Evidence=@($evidence)}
    }
    if ($refused -and -not $allowed) { return [pscustomobject]@{Class='DirectExclusive';Confidence=(& $confidenceFor $refused);Evidence=@($evidence)} }
    if ($allowed -and -not $refused) { return [pscustomobject]@{Class='DirectReadable';Confidence=(& $confidenceFor $allowed);Evidence=@($evidence)} }
    $evidence.Add('Shared read alternated between allowed and refused; the writer may close and reopen between writes.')
    [pscustomobject]@{Class='Hybrid';Confidence='Low';Evidence=@($evidence)}
}