Public/Get-LissWindowsNetworkBaseline.ps1

function Get-LissWindowsNetworkBaseline {
    <#
    .SYNOPSIS
    Collects a read-only Windows endpoint network baseline.
    .DESCRIPTION
    Collects system, adapter, addressing, routing, driver, offload, performance, connectivity, event, WLAN, and WinHTTP proxy evidence. Each optional source has Status, Error, and Data fields so an unavailable cmdlet, class, or log does not fail the complete baseline. The command makes no configuration changes.
    .PARAMETER Target
    One or more configurable ping and TCP connectivity targets.
    .PARAMETER TcpPort
    TCP port tested against each target.
    .PARAMETER PingCount
    Number of ICMP samples per target.
    .PARAMETER TimeoutMilliseconds
    Timeout for each ping or TCP connection attempt.
    .PARAMETER IncludeRouteDiscovery
    Includes route discovery through Test-NetConnection when that cmdlet is available.
    .PARAMETER EventLookbackHours
    Network event lookback duration.
    .PARAMETER MaxEvents
    Maximum number of recent network events.
    .EXAMPLE
    Get-LissWindowsNetworkBaseline -Target 1.1.1.1,8.8.8.8 -TcpPort 443
    .EXAMPLE
    Get-LissWindowsNetworkBaseline -Target gateway.example.net -IncludeRouteDiscovery
    #>

    [CmdletBinding()]
    [OutputType('LISSTech.WindowsNetworkDiagnostics.Baseline')]
    param(
        [ValidateNotNullOrEmpty()][ValidateCount(1, 16)][string[]]$Target = @('1.1.1.1', '8.8.8.8'),
        [ValidateRange(1, 65535)][int]$TcpPort = 443,
        [ValidateRange(1, 20)][int]$PingCount = 4,
        [ValidateRange(100, 10000)][int]$TimeoutMilliseconds = 1000,
        [switch]$IncludeRouteDiscovery,
        [ValidateRange(1, 720)][int]$EventLookbackHours = 24,
        [ValidateRange(1, 1000)][int]$MaxEvents = 100
    )

    # Local copies make the intended closure inputs explicit for optional source isolation.
    $targets = @($Target)
    $connectivityPort = $TcpPort
    $connectivityPingCount = $PingCount
    $connectivityTimeout = $TimeoutMilliseconds
    $eventHours = $EventLookbackHours
    $eventLimit = $MaxEvents
    $time = Get-LissTimeContext
    $system = Invoke-LissDataSource {
        $operatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
        [pscustomobject]@{
            ComputerName   = $env:COMPUTERNAME
            Caption        = $operatingSystem.Caption
            Version        = $operatingSystem.Version
            BuildNumber    = $operatingSystem.BuildNumber
            BootTime       = $operatingSystem.LastBootUpTime
            TimeZoneId     = $time.TimeZoneId
            UtcOffsetMinutes = $time.UtcOffsetMinutes
        }
    }
    $adapters = Invoke-LissDataSource {
        if (-not (Get-Command Get-NetAdapter -ErrorAction SilentlyContinue)) {
            throw 'Get-NetAdapter is unavailable.'
        }
        Get-NetAdapter -IncludeHidden -ErrorAction Stop | Select-Object Name, InterfaceDescription, InterfaceIndex, Status, MediaConnectionState, MacAddress, LinkSpeed, MediaType, PhysicalMediaType, DriverInformation, DriverFileName, DriverVersion, DriverDate
    }
    $ipConfiguration = Invoke-LissDataSource {
        if (-not (Get-Command Get-NetIPConfiguration -ErrorAction SilentlyContinue)) {
            throw 'Get-NetIPConfiguration is unavailable.'
        }
        Get-NetIPConfiguration -All -ErrorAction Stop | ForEach-Object {
            [pscustomobject]@{
                InterfaceAlias       = $_.InterfaceAlias
                InterfaceIndex       = $_.InterfaceIndex
                NetProfileName       = $_.NetProfile.Name
                NetProfileCategory   = $_.NetProfile.NetworkCategory
                IPv4Address          = @($_.IPv4Address | Select-Object IPAddress, PrefixLength)
                IPv6Address          = @($_.IPv6Address | Select-Object IPAddress, PrefixLength)
                IPv4DefaultGateway   = @($_.IPv4DefaultGateway | Select-Object NextHop, RouteMetric)
                IPv6DefaultGateway   = @($_.IPv6DefaultGateway | Select-Object NextHop, RouteMetric)
                DnsServer            = @($_.DNSServer.ServerAddresses)
            }
        }
    }
    $routes = Invoke-LissDataSource {
        if (-not (Get-Command Get-NetRoute -ErrorAction SilentlyContinue)) {
            throw 'Get-NetRoute is unavailable.'
        }
        Get-NetRoute -AddressFamily IPv4, IPv6 -ErrorAction Stop | Select-Object AddressFamily, DestinationPrefix, NextHop, InterfaceAlias, InterfaceIndex, RouteMetric, ifMetric, Protocol, State, Store | Sort-Object AddressFamily, DestinationPrefix, RouteMetric
    }
    $interfaceMetrics = Invoke-LissDataSource {
        if (-not (Get-Command Get-NetIPInterface -ErrorAction SilentlyContinue)) {
            throw 'Get-NetIPInterface is unavailable.'
        }
        Get-NetIPInterface -AddressFamily IPv4, IPv6 -ErrorAction Stop | Select-Object InterfaceAlias, InterfaceIndex, AddressFamily, ConnectionState, NlMtu, AutomaticMetric, InterfaceMetric, Dhcp, RouterDiscovery
    }
    $adapterStatistics = Invoke-LissDataSource { Get-LissAdapterStatisticsSnapshot }
    $drivers = Invoke-LissDataSource {
        $networkAdapters = @(Get-CimInstance -ClassName Win32_NetworkAdapter -ErrorAction Stop)
        $signedDrivers = @(Get-CimInstance -ClassName Win32_PnPSignedDriver -Filter "DeviceClass = 'NET'" -ErrorAction Stop)
        foreach ($adapter in $networkAdapters) {
            $driver = $signedDrivers | Where-Object { $_.DeviceID -eq $adapter.PNPDeviceID } | Select-Object -First 1
            if ($driver) {
                [pscustomobject]@{
                    Name          = $adapter.NetConnectionID
                    DeviceName    = $driver.DeviceName
                    DeviceId      = $driver.DeviceID
                    Manufacturer  = $adapter.Manufacturer
                    DriverProvider = $driver.DriverProviderName
                    DriverVersion = $driver.DriverVersion
                    DriverDate    = $driver.DriverDate
                    InfName       = $driver.InfName
                }
            }
        }
    }
    $advancedProperties = Invoke-LissDataSource {
        if (-not (Get-Command Get-NetAdapterAdvancedProperty -ErrorAction SilentlyContinue)) {
            throw 'Get-NetAdapterAdvancedProperty is unavailable.'
        }
        Get-NetAdapterAdvancedProperty -AllProperties -ErrorAction Stop | Where-Object {
            $_.RegistryKeyword -match '(?i)(speed|duplex|flow|rss|rsc|lso|checksum)'
        } | Select-Object Name, InterfaceDescription, RegistryKeyword, RegistryValue, DisplayName, DisplayValue
    }
    $offloadState = Invoke-LissDataSource {
        $result = New-Object 'Collections.Generic.List[object]'
        foreach ($entry in @(
                @{ Name = 'RSS'; Command = 'Get-NetAdapterRss' }
                @{ Name = 'RSC'; Command = 'Get-NetAdapterRsc' }
                @{ Name = 'LSO'; Command = 'Get-NetAdapterLso' }
                @{ Name = 'ChecksumOffload'; Command = 'Get-NetAdapterChecksumOffload' }
            )) {
            $command = Get-Command $entry.Command -ErrorAction SilentlyContinue
            if ($command) {
                try {
                    $data = @(& $entry.Command -ErrorAction Stop)
                    $result.Add([pscustomobject]@{ Feature = $entry.Name; Status = 'Available'; Error = $null; Data = $data })
                }
                catch {
                    $result.Add([pscustomobject]@{ Feature = $entry.Name; Status = 'Unavailable'; Error = $_.Exception.Message; Data = @() })
                }
            }
            else {
                $result.Add([pscustomobject]@{ Feature = $entry.Name; Status = 'Unavailable'; Error = "$($entry.Command) is unavailable."; Data = @() })
            }
        }
        $result
    }
    $performance = Invoke-LissDataSource {
        foreach ($className in @('Win32_PerfFormattedData_Tcpip_TCPv4', 'Win32_PerfFormattedData_Tcpip_TCPv6', 'Win32_PerfFormattedData_Tcpip_NetworkInterface')) {
            try {
                [pscustomobject]@{
                    ClassName = $className
                    Status    = 'Available'
                    Error     = $null
                    Data      = @(Get-CimInstance -ClassName $className -ErrorAction Stop)
                }
            }
            catch {
                [pscustomobject]@{
                    ClassName = $className
                    Status    = 'Unavailable'
                    Error     = $_.Exception.Message
                    Data      = @()
                }
            }
        }
    }
    $connectivity = Invoke-LissDataSource {
        foreach ($targetName in $targets) {
            $samples = for ($index = 0; $index -lt $connectivityPingCount; $index++) {
                Invoke-LissPingSample -Target $targetName -TimeoutMilliseconds $connectivityTimeout
            }
            [pscustomobject]@{
                Target      = $targetName
                Ping        = Get-LissLatencySummary -Sample @($samples) -Target $targetName
                PingSamples = @($samples)
                Tcp         = Test-LissTcpPort -Target $targetName -Port $connectivityPort -TimeoutMilliseconds $connectivityTimeout
            }
        }
    }
    $routeDiscovery = if ($IncludeRouteDiscovery) {
        Invoke-LissDataSource {
            if (-not (Get-Command Test-NetConnection -ErrorAction SilentlyContinue)) {
                throw 'Test-NetConnection is unavailable.'
            }
            foreach ($targetName in $targets) {
                Test-NetConnection -ComputerName $targetName -TraceRoute -Hops 30 -WarningAction SilentlyContinue -ErrorAction Stop | Select-Object ComputerName, RemoteAddress, TraceRoute
            }
        }
    }
    else {
        [pscustomobject]@{ Status = 'NotRequested'; Error = $null; Data = @() }
    }
    $events = Invoke-LissDataSource { Get-LissWindowsNetworkEvent -LookbackHours $eventHours -MaxEvents $eventLimit }
    $wlan = Invoke-LissDataSource {
        if (-not (Get-Command Get-NetAdapter -ErrorAction SilentlyContinue)) {
            throw 'Get-NetAdapter is unavailable.'
        }
        Get-NetAdapter -IncludeHidden -ErrorAction Stop | Where-Object {
            [string]$_.PhysicalMediaType -match '(?i)(802\.11|wireless)' -or [string]$_.NdisPhysicalMedium -match '(?i)(802\.11|wireless)'
        } | Select-Object Name, InterfaceDescription, InterfaceIndex, Status, MediaConnectionState, LinkSpeed, MacAddress, MediaType, PhysicalMediaType
    }
    $winHttpProxy = Invoke-LissDataSource {
        $output = @(& "$env:SystemRoot\System32\netsh.exe" winhttp show proxy 2>&1 | ForEach-Object { [string]$_ })
        if ($LASTEXITCODE -ne 0) {
            throw "netsh winhttp show proxy failed with exit code $LASTEXITCODE."
        }
        [pscustomobject]@{
            CommandOutput = $output
        }
    }

    [pscustomobject]@{
        PSTypeName          = 'LISSTech.WindowsNetworkDiagnostics.Baseline'
        ComputerName        = $env:COMPUTERNAME
        CollectedAt         = $time.Timestamp
        CollectedAtUtc      = $time.TimestampUtc
        TimeZoneId          = $time.TimeZoneId
        System              = $system
        Adapters            = $adapters
        IPConfiguration     = $ipConfiguration
        Routes              = $routes
        InterfaceMetrics    = $interfaceMetrics
        AdapterStatistics   = $adapterStatistics
        Drivers             = $drivers
        AdvancedProperties  = $advancedProperties
        OffloadState        = $offloadState
        Performance         = $performance
        Connectivity        = $connectivity
        RouteDiscovery      = $routeDiscovery
        Events              = $events
        Wlan                = $wlan
        WinHttpProxy        = $winHttpProxy
    }
}