Public/Start-LissWindowsFocusedCapture.ps1

function Start-LissWindowsFocusedCapture {
    <#
    .SYNOPSIS
    Creates a bounded, focused pktmon ETL and PCAPNG capture.
    .DESCRIPTION
    Requires elevation, an approved destination IP, an existing output directory, explicit approval, and explicit confirmation that pktmon is idle with no filters. The command independently checks pktmon status and filters before adding its complete filter set. It captures at most 128 bytes per packet to a 64 MB circular ETL for 5 through 120 seconds, always attempts stop in finally, converts to PCAPNG, and removes filters only when this invocation created them from a verified empty state. Captures can contain sensitive network data and are never uploaded.
    .PARAMETER DestinationIp
    Destination IPv4 or IPv6 address. Hostnames are not accepted.
    .PARAMETER Protocol
    TCP, UDP, ICMP, or ICMPv6.
    .PARAMETER Port
    Optional TCP or UDP source-or-destination port filter.
    .PARAMETER DurationSeconds
    Capture duration, from 5 through 120 seconds.
    .PARAMETER OutputDirectory
    Existing directory for ETL and PCAPNG artifacts.
    .PARAMETER FilePrefix
    Safe artifact filename prefix.
    .PARAMETER Approved
    Confirms capture authorization and artifact sensitivity.
    .PARAMETER ConfirmPktmonIdleAndNoFilters
    Explicitly confirms that the operator expects pktmon to be idle with no existing filters. The module still verifies state and refuses to proceed if activity or filters are detected.
    .EXAMPLE
    Start-LissWindowsFocusedCapture -DestinationIp 203.0.113.10 -Protocol TCP -Port 443 -DurationSeconds 15 -OutputDirectory C:\Diagnostics -Approved -ConfirmPktmonIdleAndNoFilters -Confirm:$false
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType('LISSTech.WindowsNetworkDiagnostics.CaptureResult')]
    param(
        [Parameter(Mandatory)][ipaddress]$DestinationIp,
        [Parameter(Mandatory)][ValidateSet('TCP', 'UDP', 'ICMP', 'ICMPv6')][string]$Protocol,
        [ValidateRange(1, 65535)][int]$Port,
        [ValidateRange(5, 120)][int]$DurationSeconds = 15,
        [Parameter(Mandatory)][ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })][string]$OutputDirectory,
        [ValidatePattern('^[a-zA-Z0-9._-]+$')][string]$FilePrefix = 'LissNetworkCapture',
        [Parameter(Mandatory)][switch]$Approved,
        [Parameter(Mandatory)][switch]$ConfirmPktmonIdleAndNoFilters
    )

    if (-not $Approved) {
        throw 'Explicit -Approved confirmation is required for packet capture.'
    }
    if (-not $ConfirmPktmonIdleAndNoFilters) {
        throw 'Explicit -ConfirmPktmonIdleAndNoFilters confirmation is required.'
    }
    if ($Port -and $Protocol -notin @('TCP', 'UDP')) {
        throw '-Port is supported only with TCP or UDP.'
    }
    if (-not (Test-LissAdministrator)) {
        throw 'An elevated Windows administrator session is required for pktmon capture.'
    }
    if (-not (Get-Command pktmon.exe -CommandType Application -ErrorAction SilentlyContinue)) {
        throw 'pktmon.exe is unavailable on this Windows version.'
    }
    $capability = Get-LissPktmonCapability
    if (-not $capability.Supported) {
        throw "This pktmon version lacks required capture capabilities: $($capability.MissingCapabilities -join ', ')."
    }
    $state = Get-LissPktmonState
    if ($state.IsCaptureActive) {
        throw 'pktmon is already capturing. Refusing to disrupt the existing capture.'
    }
    if ($state.HasFilters) {
        throw 'pktmon already has filters. Refusing to call filter remove or alter the existing filter set.'
    }
    $destination = $DestinationIp.IPAddressToString
    $target = "$Protocol traffic involving $destination for $DurationSeconds seconds in '$OutputDirectory'"
    if (-not $PSCmdlet.ShouldProcess($target, 'Create sensitive ETL and PCAPNG capture artifacts')) {
        return
    }

    $stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
    $etlPath = Join-Path $OutputDirectory "$FilePrefix-$stamp.etl"
    $pcapngPath = Join-Path $OutputDirectory "$FilePrefix-$stamp.pcapng"
    $filterCreated = $false
    $startAttempted = $false
    $stopError = $null
    $cleanupError = $null
    $started = Get-Date
    try {
        $filterArguments = @('filter', 'add', 'LISSTechFocusedCapture', '-i', $destination, '-t', $Protocol)
        if ($Port) {
            $filterArguments += @('-p', [string]$Port)
        }
        $filterResult = Invoke-LissPktmonCommand -ArgumentList $filterArguments
        if ($filterResult.ExitCode -ne 0) {
            throw "pktmon filter add failed: $($filterResult.Output -join ' ')"
        }
        $filterCreated = $true
        $startAttempted = $true
        $startResult = Invoke-LissPktmonCommand -ArgumentList @('start', '--capture', '--pkt-size', '128', '--file-name', $etlPath, '--file-size', '64', '--log-mode', 'circular')
        if ($startResult.ExitCode -ne 0) {
            throw "pktmon start failed: $($startResult.Output -join ' ')"
        }
        Start-Sleep -Seconds $DurationSeconds
    }
    finally {
        if ($startAttempted) {
            $stopResult = Invoke-LissPktmonCommand -ArgumentList @('stop')
            if ($stopResult.ExitCode -ne 0) {
                $stopError = $stopResult.Output -join ' '
                Write-Warning "pktmon stop failed: $stopError"
            }
        }
        if ($filterCreated) {
            $removeResult = Invoke-LissPktmonCommand -ArgumentList @('filter', 'remove')
            if ($removeResult.ExitCode -ne 0) {
                $cleanupError = $removeResult.Output -join ' '
                Write-Warning "pktmon filter cleanup failed: $cleanupError"
            }
        }
    }
    if ($stopError) {
        throw "Capture stop failed; ETL state may be incomplete: $stopError"
    }
    if (-not (Test-Path -LiteralPath $etlPath -PathType Leaf)) {
        throw "pktmon did not create the expected ETL artifact '$etlPath'."
    }
    $convertResult = Invoke-LissPktmonCommand -ArgumentList @('etl2pcap', $etlPath, '--out', $pcapngPath)
    if ($convertResult.ExitCode -ne 0 -or -not (Test-Path -LiteralPath $pcapngPath -PathType Leaf)) {
        throw "pktmon ETL to PCAPNG conversion failed: $($convertResult.Output -join ' ')"
    }
    [pscustomobject]@{
        PSTypeName       = 'LISSTech.WindowsNetworkDiagnostics.CaptureResult'
        DestinationIp    = $destination
        Protocol         = $Protocol
        Port             = if ($Port) { $Port } else { $null }
        DurationSeconds  = $DurationSeconds
        StartedAt        = $started
        CompletedAt      = Get-Date
        PacketBytes      = 128
        CircularSizeMB   = 64
        EtlPath          = $etlPath
        PcapngPath       = $pcapngPath
        FilterRemoved    = -not [bool]$cleanupError
        CleanupError     = $cleanupError
        ArtifactWarning  = 'ETL and PCAPNG files can contain sensitive endpoint network data. Store and share them only through approved channels.'
    }
}