Public/Get-OffsetPEInfo.ps1

function Get-OffsetPEInfo {
    <#
    .SYNOPSIS
        Parses a PE file's headers and sections, optionally mapping a byte offset to its section.

    .DESCRIPTION
        Reads the PE (Portable Executable) header region and returns the machine type, bitness,
        entry-point RVA, image base, and the section table (name, virtual address/size, raw
        pointer/size). With -Offset, it also reports which section that file offset falls in -
        useful for placing a detection boundary or a high-entropy region (.text, .rsrc, ...).
        Cross-platform; reads only the header region.

    .PARAMETER FilePath
        The PE file to parse.

    .PARAMETER Offset
        Optional file offset to map to a section.

    .EXAMPLE
        Get-OffsetPEInfo .\sample.exe | Select-Object -ExpandProperty Sections

    .EXAMPLE
        (Get-OffsetPEInfo .\sample.exe -Offset 0x1234).MappedSection
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$FilePath,

        [ValidateScript({ $_ -ge 0 })]
        [int64]$Offset = -1
    )

    $resolvedPath = (Resolve-Path -LiteralPath $FilePath -ErrorAction Stop).Path
    $item = Get-Item -LiteralPath $resolvedPath -ErrorAction Stop
    if ($item.PSIsContainer) { throw "Path is a directory, not a file: $resolvedPath" }
    $fileSize = $item.Length

    $headerLength = [int][Math]::Min([int64]0x10000, $fileSize)
    $warnings = New-Object 'System.Collections.Generic.List[string]'
    $importInfo = [pscustomobject]@{ Imports = @(); ImpHash = $null }
    $image = $null

    $stream = $null
    try {
        $stream = [System.IO.File]::Open(
            $resolvedPath,
            [System.IO.FileMode]::Open,
            [System.IO.FileAccess]::Read,
            [System.IO.FileShare]::Read
        )
        $headerBytes = Read-OIFileRange -Stream $stream -Start 0 -Length $headerLength
        $image = ConvertTo-OIPEImage -Bytes $headerBytes

        try {
            $importInfo = Get-OIPEImport -Image $image -Stream $stream
        }
        catch {
            $warnings.Add("Import table parse failed: $($_.Exception.Message)")
        }
    }
    finally {
        if ($null -ne $stream) { $stream.Dispose() }
    }

    $overlay = Get-OIPEOverlayRange -Section $image.Sections -FileSize $fileSize
    $mappedSection = if ($Offset -ge 0) { Get-OIPESectionForOffset -Image $image -Offset $Offset } else { $null }

    $result = [pscustomobject]@{
        File             = $resolvedPath
        FileSize         = $fileSize
        Machine          = $image.Machine
        IsPE32Plus       = $image.IsPE32Plus
        EntryPointRva    = $image.EntryPointRva
        EntryPointHex    = '0x{0:X}' -f $image.EntryPointRva
        ImageBase        = $image.ImageBase
        SectionCount     = $image.SectionCount
        Sections         = $image.Sections
        ImportedDllCount = @($importInfo.Imports).Count
        Imports          = $importInfo.Imports
        ImpHash          = $importInfo.ImpHash
        ResourceSize     = $image.ResourceSize
        HasOverlay       = $overlay.HasOverlay
        OverlayOffset    = $overlay.OverlayOffset
        OverlaySize      = $overlay.OverlaySize
        MappedOffset     = if ($Offset -ge 0) { $Offset } else { $null }
        MappedSection    = $mappedSection
        Warnings         = $warnings.ToArray()
    }
    $result.PSObject.TypeNames.Insert(0, 'OffsetInspect.PEInfo')
    return $result
}