Private/Get-FileContentSignature.ps1

function Get-FileContentSignature {
    <#
    .SYNOPSIS
        Returns file size, timestamps, and (optionally) a cryptographic hash for a file path.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter()]
        [switch]$IncludeHash
    )

    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        throw [System.IO.FileNotFoundException]::new(("File '{0}' was not found." -f $Path))
    }

    $resolvedPath = (Resolve-Path -LiteralPath $Path -ErrorAction Stop).ProviderPath
    $fileInfo = Get-Item -LiteralPath $resolvedPath -ErrorAction Stop

    $signature = [pscustomobject]@{
        Path             = $fileInfo.FullName
        Length           = [long]$fileInfo.Length
        LastWriteTime    = $fileInfo.LastWriteTime
        LastWriteTimeUtc = $fileInfo.LastWriteTimeUtc
        Hash             = $null
        HashAlgorithm    = $null
    }

    if ($IncludeHash) {
        $fileStream = $null
        $hashAlgorithm = $null

        try {
            $fileStream = [System.IO.File]::Open($resolvedPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
            $hashAlgorithm = [System.Security.Cryptography.SHA256]::Create()
            $hashBytes = $hashAlgorithm.ComputeHash($fileStream)
            $signature.Hash = ([System.BitConverter]::ToString($hashBytes) -replace '-', '').ToLowerInvariant()
            $signature.HashAlgorithm = 'SHA256'
        }
        finally {
            if ($hashAlgorithm) { $hashAlgorithm.Dispose() }
            if ($fileStream) { $fileStream.Dispose() }
        }
    }

    return $signature
}