win系统环境检查工具,powershell 脚本,一次检查AI全面掌握系统运行环境 ,AI 它写代码更兼容,更少折腾,无需中间来回折腾环境配置

win系统环境检查工具,powershell 脚本,一次检查AI就知道你的开发环境了 ,各种包版本,网络端口状态,连通性,代理情况, 防火墙,各种工具版本,。。。。无需中间来回折腾环境配置

检测过程包括:

模块编号 模块名称 检测内容
1 系统身份(System Identity) 检测主机名、域/工作组、操作系统版本与 Build、系统架构、厂商与型号、内存大小、安装日期、运行时长、系统 UUID。
2 网络环境(Network Environment) 检测活动网卡、IP 地址、子网前缀、网关、DNS、环境变量代理、IE 代理、PAC 脚本、系统代理解析结果,以及公网 IP。
3 端口与防火墙矩阵(Port Firewall Matrix) 检测监听端口、对应 PID、进程名、本地监听地址、暴露级别(如 LocalOnly、LAN、AllInterfaces)、端口风险标签、高风险暴露端口汇总;同时检测防火墙配置和若干目标的出站 TCP 连通性。
4 终端环境(Terminal Environment) 检测 PowerShell 版本、PSHome、执行策略、Windows Terminal 是否安装、WSL 命令与状态、控制台与输出编码。
5 开发工具栈(Development Stack) 检测常见开发工具是否存在及其版本,包括 Git、Node.js、npm、Python、pip、Java、Docker、VSCode、VSCodium、JetBrains、Winget、Chocolatey。
6 企业管控(Enterprise Controls) 检测安全软件/杀毒软件、MDM 注册情况、BitLocker 状态、UAC 状态,以及执行策略列表。
7 外部连通性(External Connectivity) 对多个外部站点做直连 TCP 和代理 HTTP 探测,判断是直连可达、代理可达、需认证、路径异常还是不可达,并给出汇总。
8 日志位置(Log Locations) 检测常见日志目录是否存在,如系统事件日志、PowerShell 日志、临时目录、CrashDumps,并读取最近 3 条系统事件。

使用方法:

在桌面上创建一个 envcheck.ps1 文件,然后把代码粘贴进去。

cd到桌面, 运行如下命令

powershell -ExecutionPolicy Bypass -File envcheck.ps1

即可进行windows开发环境的全面自检,这样把结果交给AI,那么ai写代码,就不会总是不知道系统环境,网络环境,,防火墙,以及各种包工具,版本的问题,反复在工作中折腾了。 一次到位。

运行环境一次性撸出来。
log 文件会保存在,EnvReport-v2.1.4-xxxxx.txt ,文件中,与.ps1在同一目录下。
你可以审阅后,再发给ai,让他记住你的环境。 但是要注意,保护安全隐私。你要自己处理一下,别把IP,端口,安全信息什么的全发给大模型。
如果你是公司环境!

powershell 复制代码
#Requires -Version 5.1
<#
.SYNOPSIS
    Windows Development Environment Full Detection - v2.1.4
.DESCRIPTION
    8 Modules: System, Network, Port Firewall, Terminal, DevStack, Enterprise, External, Logs
    v2.1.4 polish release:
      - Add listening-port risk tags
      - Add exposure classification (LocalOnly / LAN / AllInterfaces / SpecificInterface)
      - Improve external connectivity summary for direct-vs-proxy split
      - Add decision-oriented environment conclusion block
.NOTES
    Version: 2.1.4
    Output: UTF-8 report file, English labels, Unicode-safe content
    Target: PowerShell 5.1
    Compatibility: Best effort in restricted environments
#>

# ============================================
# Preferences
# ============================================
$ErrorActionPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ProgressPreference = 'SilentlyContinue'

# ============================================
# Globals
# ============================================
$script:ReportLines = New-Object System.Collections.Generic.List[string]
$script:StartTime = Get-Date
$script:ExternalResults = New-Object System.Collections.Generic.List[object]
$script:HighRiskPorts = New-Object System.Collections.Generic.List[int]

try {
    [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
} catch {}
try {
    $OutputEncoding = [System.Text.Encoding]::UTF8
} catch {}

# ============================================
# Logging
# ============================================
function Write-Log {
    param(
        [string]$Message = '',
        [ValidateSet('INFO','HEADER','OK','WARN','ERR','SKIP')]
        [string]$Level = 'INFO'
    )

    $prefix = switch ($Level) {
        'HEADER' { '[*] ' }
        'OK'     { '[+] ' }
        'WARN'   { '[!] ' }
        'ERR'    { '[-] ' }
        'SKIP'   { '[~] ' }
        default  { '    ' }
    }

    $line = $prefix + $Message
    [void]$script:ReportLines.Add($line)

    try {
        [Console]::WriteLine($line)
        [Console]::Out.Flush()
    } catch {}
}

function Add-BlankLine {
    Write-Log ''
}

# ============================================
# Helpers
# ============================================
function Convert-ToSafeString {
    param([object]$Value)

    if ($null -eq $Value) { return '' }
    try { return [string]$Value } catch { return '' }
}

function Get-FirstNonEmpty {
    param(
        [object[]]$Values,
        [string]$Default = 'Unknown'
    )

    foreach ($v in $Values) {
        $s = Convert-ToSafeString $v
        if (-not [string]::IsNullOrWhiteSpace($s)) {
            return $s.Trim()
        }
    }
    return $Default
}

function Get-ShortText {
    param(
        [string]$Text,
        [int]$MaxLength = 80
    )

    if ([string]::IsNullOrEmpty($Text)) { return '' }
    $clean = ($Text -replace '\s+', ' ').Trim()
    if ($clean.Length -le $MaxLength) { return $clean }
    return $clean.Substring(0, $MaxLength) + '...'
}

function Test-IsAdmin {
    try {
        $currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
        $principal = New-Object Security.Principal.WindowsPrincipal($currentIdentity)
        return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    } catch {
        return $false
    }
}

function Convert-WmiOrCimDate {
    param([object]$Value)

    if ($null -eq $Value) { return $null }

    try {
        if ($Value -is [datetime]) {
            return [datetime]$Value
        }
    } catch {}

    try {
        $s = [string]$Value
        if (-not [string]::IsNullOrWhiteSpace($s)) {
            return [Management.ManagementDateTimeConverter]::ToDateTime($s)
        }
    } catch {}

    try {
        return [datetime]$Value
    } catch {}

    return $null
}

function Invoke-WithTimeout {
    param(
        [Parameter(Mandatory = $true)]
        [string]$FilePath,

        [string]$Arguments = '',

        [int]$TimeoutSec = 5
    )

    try {
        $psi = New-Object System.Diagnostics.ProcessStartInfo
        $psi.FileName = $FilePath
        $psi.Arguments = $Arguments
        $psi.RedirectStandardOutput = $true
        $psi.RedirectStandardError = $true
        $psi.UseShellExecute = $false
        $psi.CreateNoWindow = $true

        $proc = [System.Diagnostics.Process]::Start($psi)
        if ($null -eq $proc) {
            return @{
                Status   = 'ERROR'
                ExitCode = $null
                Output   = 'Process failed to start'
            }
        }

        $exited = $proc.WaitForExit($TimeoutSec * 1000)

        if (-not $exited) {
            try { $proc.Kill() } catch {}
            try { $proc.Dispose() } catch {}
            return @{
                Status   = 'TIMEOUT'
                ExitCode = $null
                Output   = 'TIMEOUT'
            }
        }

        $stdout = ''
        $stderr = ''
        try { $stdout = $proc.StandardOutput.ReadToEnd() } catch {}
        try { $stderr = $proc.StandardError.ReadToEnd() } catch {}
        $exitCode = $null
        try { $exitCode = $proc.ExitCode } catch {}
        try { $proc.Dispose() } catch {}

        $combined = (($stdout + [Environment]::NewLine + $stderr).Trim())

        return @{
            Status   = 'OK'
            ExitCode = $exitCode
            Output   = $combined
        }
    } catch {
        return @{
            Status   = 'ERROR'
            ExitCode = $null
            Output   = $_.Exception.Message
        }
    }
}

function Invoke-WebHeadOrGet {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Url,

        [string]$HttpHostHeader = '',

        [int]$TimeoutMs = 8000,

        [System.Net.IWebProxy]$Proxy = $null
    )

    $methods = @('HEAD', 'GET')

    foreach ($method in $methods) {
        try {
            $req = [System.Net.WebRequest]::Create($Url)
            $req.Method = $method
            $req.Timeout = $TimeoutMs
            $req.UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) PowerShell/5.1'
            $req.Accept = '*/*'
            if (-not [string]::IsNullOrWhiteSpace($HttpHostHeader)) {
                $req.Host = $HttpHostHeader
            }
            if ($Proxy) {
                $req.Proxy = $Proxy
                try {
                    $req.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
                } catch {}
            }

            $resp = $req.GetResponse()
            $statusCode = $null
            try { $statusCode = [int]$resp.StatusCode } catch { $statusCode = 200 }
            try { $resp.Close() } catch {}

            return @{
                Success = $true
                Method  = $method
                Code    = $statusCode
                Note    = 'HTTP OK'
            }
        } catch [System.Net.WebException] {
            $webEx = $_.Exception
            if ($webEx.Response) {
                $code = $null
                try { $code = [int]$webEx.Response.StatusCode } catch {}
                try { $webEx.Response.Close() } catch {}

                return @{
                    Success = $true
                    Method  = $method
                    Code    = $code
                    Note    = 'HTTP Response'
                }
            }
        } catch {}
    }

    return @{
        Success = $false
        Method  = ''
        Code    = $null
        Note    = 'No Response'
    }
}

function Get-TcpConnectResult {
    param(
        [Parameter(Mandatory = $true)]
        [string]$TargetHost,

        [Parameter(Mandatory = $true)]
        [int]$TargetPort,

        [int]$TimeoutMs = 3000
    )

    $sw = [Diagnostics.Stopwatch]::StartNew()
    $tcpClient = $null

    try {
        if ([string]::IsNullOrWhiteSpace($TargetHost)) {
            throw [System.ArgumentException]::new('TargetHost is empty')
        }
        if ($TargetPort -le 0 -or $TargetPort -gt 65535) {
            throw [System.ArgumentOutOfRangeException]::new('TargetPort', 'Port out of range')
        }

        $tcpClient = New-Object System.Net.Sockets.TcpClient
        $asyncResult = $tcpClient.BeginConnect($TargetHost, $TargetPort, $null, $null)
        $completed = $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false)

        if ($completed) {
            try {
                $tcpClient.EndConnect($asyncResult)
            } catch {
                $sw.Stop()
                try { $tcpClient.Close() } catch {}
                return [PSCustomObject]@{
                    Success   = $false
                    TimeMs    = [int]$sw.ElapsedMilliseconds
                    ErrorType = $_.Exception.GetType().Name
                    Error     = Get-ShortText -Text $_.Exception.Message -MaxLength 80
                }
            }
        }

        if ($completed -and $tcpClient.Connected) {
            $sw.Stop()
            try { $tcpClient.Close() } catch {}
            return [PSCustomObject]@{
                Success   = $true
                TimeMs    = [int]$sw.ElapsedMilliseconds
                ErrorType = ''
                Error     = ''
            }
        } else {
            $sw.Stop()
            try { $tcpClient.Close() } catch {}
            return [PSCustomObject]@{
                Success   = $false
                TimeMs    = [int]$sw.ElapsedMilliseconds
                ErrorType = 'ConnectFailed'
                Error     = 'Timeout or connect failed'
            }
        }
    } catch {
        $sw.Stop()
        try { if ($tcpClient) { $tcpClient.Close() } } catch {}
        return [PSCustomObject]@{
            Success   = $false
            TimeMs    = [int]$sw.ElapsedMilliseconds
            ErrorType = $_.Exception.GetType().Name
            Error     = Get-ShortText -Text $_.Exception.Message -MaxLength 80
        }
    }
}

function Get-CommandVersionLine {
    param(
        [Parameter(Mandatory = $true)]
        [string]$CommandPath,

        [string]$Arguments = '--version',

        [int]$TimeoutSec = 3
    )

    $result = Invoke-WithTimeout -FilePath $CommandPath -Arguments $Arguments -TimeoutSec $TimeoutSec
    if ($result.Status -ne 'OK') {
        return 'Installed (version query failed)'
    }

    $line = ($result.Output -split "`r?`n" | Select-Object -First 1)
    if ([string]::IsNullOrWhiteSpace($line)) {
        return 'Installed (version unavailable)'
    }

    return $line.Trim()
}

function Get-ProcessNameSafe {
    param([int]$ProcessIdValue)

    try {
        $procObj = Get-Process -Id $ProcessIdValue -ErrorAction Stop
        return $procObj.ProcessName
    } catch {
        return ''
    }
}

function Split-LocalEndpoint {
    param([string]$EndpointText)

    if ([string]::IsNullOrWhiteSpace($EndpointText)) { return $null }

    $endpointValue = $EndpointText.Trim()

    if ($endpointValue.StartsWith('[')) {
        $closeIdx = $endpointValue.LastIndexOf(']')
        if ($closeIdx -gt 0 -and $closeIdx -lt ($endpointValue.Length - 1) -and $endpointValue[$closeIdx + 1] -eq ':') {
            $addrPart = $endpointValue.Substring(0, $closeIdx + 1)
            $portText = $endpointValue.Substring($closeIdx + 2)
            if ($portText -match '^\d+$') {
                return [PSCustomObject]@{
                    Address = $addrPart
                    Port    = [int]$portText
                }
            }
        }
        return $null
    }

    $lastColonIndex = $endpointValue.LastIndexOf(':')
    if ($lastColonIndex -lt 1 -or $lastColonIndex -ge ($endpointValue.Length - 1)) { return $null }

    $addrPart = $endpointValue.Substring(0, $lastColonIndex)
    $portPart = $endpointValue.Substring($lastColonIndex + 1)

    if ($portPart -notmatch '^\d+$') { return $null }

    return [PSCustomObject]@{
        Address = $addrPart
        Port    = [int]$portPart
    }
}

function Get-PortRiskTag {
    param([int]$Port)

    switch ($Port) {
        21   { return '[FTP]' }
        22   { return '[SSH]' }
        23   { return '[TELNET]' }
        25   { return '[SMTP]' }
        53   { return '[DNS]' }
        80   { return '[HTTP]' }
        110  { return '[POP3]' }
        135  { return '[RPC]' }
        139  { return '[NETBIOS]' }
        143  { return '[IMAP]' }
        389  { return '[LDAP]' }
        443  { return '[HTTPS]' }
        445  { return '[SMB]' }
        587  { return '[SMTP-SUBMISSION]' }
        636  { return '[LDAPS]' }
        1433 { return '[MSSQL]' }
        1521 { return '[ORACLE]' }
        2049 { return '[NFS]' }
        2375 { return '[DOCKER-API]' }
        2376 { return '[DOCKER-TLS]' }
        3000 { return '[COMMON-DEV]' }
        3306 { return '[MYSQL]' }
        3389 { return '[RDP]' }
        5000 { return '[COMMON-DEV]' }
        5173 { return '[VITE-DEV]' }
        5432 { return '[POSTGRESQL]' }
        5900 { return '[VNC]' }
        5985 { return '[WINRM-HTTP]' }
        5986 { return '[WINRM-HTTPS]' }
        6379 { return '[REDIS]' }
        8000 { return '[COMMON-DEV]' }
        8080 { return '[HTTP-ALT]' }
        8443 { return '[HTTPS-ALT]' }
        9200 { return '[ELASTICSEARCH]' }
        9229 { return '[NODE-DEBUG]' }
        10000 { return '[COMMON-DEV]' }
        27017 { return '[MONGODB]' }
        default { return '' }
    }
}

function Get-ExposureLevel {
    param([string]$LocalAddress)

    $addr = Convert-ToSafeString $LocalAddress

    switch -Regex ($addr) {
        '^127\.0\.0\.1$' { return 'LocalOnly' }
        '^::1$'          { return 'LocalOnly' }
        '^localhost$'    { return 'LocalOnly' }
        '^0\.0\.0\.0$'   { return 'AllInterfaces' }
        '^::$'           { return 'AllInterfaces' }
        '^192\.168\.'    { return 'LAN' }
        '^10\.'          { return 'LAN' }
        '^172\.(1[6-9]|2[0-9]|3[0-1])\.' { return 'LAN' }
        default {
            if ([string]::IsNullOrWhiteSpace($addr)) { return 'Unknown' }
            return 'SpecificInterface'
        }
    }
}

function Test-IsHighRiskExposedPort {
    param(
        [int]$Port,
        [string]$Exposure
    )

    $highRiskPorts = @(21,23,80,135,139,445,1433,1521,2375,3389,5900,5985,5986,27017)
    if ($Port -in $highRiskPorts -and $Exposure -ne 'LocalOnly') {
        return $true
    }
    return $false
}

# ============================================
# Initial setup
# ============================================
$isAdmin = Test-IsAdmin

try {
    [Net.ServicePointManager]::SecurityProtocol =
        [Net.SecurityProtocolType]::Tls12 -bor
        [Net.SecurityProtocolType]::Tls11 -bor
        [Net.SecurityProtocolType]::Tls
} catch {}

Write-Log 'Windows Development Environment Report v2.1.4' 'HEADER'
Write-Log ('Generated: ' + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))
Write-Log ('User: ' + $env:USERNAME + ' | Admin: ' + $isAdmin)
Write-Log ('PowerShell: ' + $PSVersionTable.PSVersion + ' (' + $PSVersionTable.PSEdition + ')')

$consoleEncodingName = 'Unknown'
$outputEncodingName = 'Unknown'
try { $consoleEncodingName = [Console]::OutputEncoding.EncodingName } catch {}
try { $outputEncodingName = $OutputEncoding.EncodingName } catch {}

Write-Log ('Console Encoding: ' + $consoleEncodingName)
Write-Log ('Output Encoding: ' + $outputEncodingName)
Write-Log ('Report File Encoding: UTF-8')
Add-BlankLine

# ============================================
# Module 1: System Identity
# ============================================
Write-Log 'MODULE 1: System Identity' 'HEADER'

$os = $null
$cs = $null
$csp = $null

try { $os = Get-CimInstance Win32_OperatingSystem } catch {}
if (-not $os) { try { $os = Get-WmiObject Win32_OperatingSystem } catch {} }

try { $cs = Get-CimInstance Win32_ComputerSystem } catch {}
if (-not $cs) { try { $cs = Get-WmiObject Win32_ComputerSystem } catch {} }

try { $csp = Get-CimInstance Win32_ComputerSystemProduct } catch {}
if (-not $csp) { try { $csp = Get-WmiObject Win32_ComputerSystemProduct } catch {} }

$computerName = Get-FirstNonEmpty @($cs.Name, $env:COMPUTERNAME)
$domainName = Get-FirstNonEmpty @($cs.Domain, 'Unknown')
$partOfDomain = $false
try { $partOfDomain = [bool]$cs.PartOfDomain } catch {}

$osCaption = Get-FirstNonEmpty @($os.Caption)
$osBuild = Get-FirstNonEmpty @($os.BuildNumber)
$osArch = Get-FirstNonEmpty @($os.OSArchitecture)
$manufacturer = Get-FirstNonEmpty @($cs.Manufacturer)
$model = Get-FirstNonEmpty @($cs.Model)
$uuid = Get-FirstNonEmpty @($csp.UUID)

$installDate = 'Unknown'
$installDt = Convert-WmiOrCimDate $os.InstallDate
if ($installDt) {
    try { $installDate = $installDt.ToString('yyyy-MM-dd') } catch {}
}

$uptime = 'Unknown'
$bootDt = Convert-WmiOrCimDate $os.LastBootUpTime
if ($bootDt) {
    try {
        $span = (Get-Date) - $bootDt
        $uptime = ('{0}d {1}h {2}m' -f $span.Days, $span.Hours, $span.Minutes)
    } catch {}
}

$memoryGB = 'Unknown'
try {
    if ($os.TotalVisibleMemorySize) {
        $memoryGB = [math]::Round(([double]$os.TotalVisibleMemorySize / 1MB), 1).ToString() + ' GB'
    }
} catch {}

Write-Log ('Computer: ' + $computerName)
Write-Log ('Domain: ' + $domainName + ' (' + $(if ($partOfDomain) { 'Domain Joined' } else { 'Workgroup' }) + ')')
Write-Log ('OS: ' + $osCaption + ' Build ' + $osBuild)
Write-Log ('Architecture: ' + $osArch)
Write-Log ('Manufacturer: ' + $manufacturer + ' ' + $model)
Write-Log ('Memory: ' + $memoryGB)
Write-Log ('Install Date: ' + $installDate)
Write-Log ('Uptime: ' + $uptime)
Write-Log ('System UUID: ' + $uuid)
Add-BlankLine

# ============================================
# Module 2: Network Environment
# ============================================
Write-Log 'MODULE 2: Network Environment' 'HEADER'

$netConfigs = @()
try {
    $netConfigs = Get-NetIPConfiguration | Where-Object { $_.NetAdapter.Status -eq 'Up' }
} catch {}

if (-not $netConfigs -or $netConfigs.Count -eq 0) {
    Write-Log 'No active adapters detected via Get-NetIPConfiguration' 'WARN'
} else {
    $preferred = @()
    $secondary = @()

    foreach ($nic in $netConfigs) {
        $ipObj = $nic.IPv4Address | Select-Object -First 1
        $gwObj = $nic.IPv4DefaultGateway | Select-Object -First 1
        $ip = ''
        $prefix = ''
        $gw = ''
        $dns = ''

        try { $ip = $ipObj.IPAddress } catch {}
        try { $prefix = $ipObj.PrefixLength } catch {}
        try { $gw = $gwObj.NextHop } catch {}
        try { $dns = ($nic.DNSServer.ServerAddresses -join ', ') } catch {}

        $item = [PSCustomObject]@{
            Alias       = $nic.InterfaceAlias
            IP          = $ip
            Prefix      = $prefix
            Gateway     = $gw
            DNS         = $dns
            IsApipa     = ($ip -like '169.254.*')
            HasGateway  = -not [string]::IsNullOrWhiteSpace($gw)
            IsVirtual   = ($nic.InterfaceAlias -match 'VMware|Virtual|Hyper-V|vEthernet|VPN|Loopback')
        }

        if ($item.HasGateway -and -not $item.IsApipa) {
            $preferred += $item
        } else {
            $secondary += $item
        }
    }

    $orderedAdapters = @($preferred + $secondary) | Select-Object -First 5

    foreach ($a in $orderedAdapters) {
        $tag = ''
        if ($a.IsApipa) { $tag = ' [APIPA]' }
        elseif ($a.IsVirtual) { $tag = ' [Virtual/Secondary]' }

        Write-Log ('Adapter: ' + $a.Alias + $tag)
        Write-Log ('  IP: ' + $(if ($a.IP) { $a.IP + '/' + $a.Prefix } else { 'Unknown' }))
        Write-Log ('  Gateway: ' + $(if ($a.Gateway) { $a.Gateway } else { 'None' }))
        Write-Log ('  DNS: ' + $(if ($a.DNS) { $a.DNS } else { 'Unknown' }))
    }
}

Add-BlankLine
Write-Log 'Proxy Configuration:'

$envHttp = if ($env:HTTP_PROXY) { $env:HTTP_PROXY } else { 'Not Set' }
$envHttps = if ($env:HTTPS_PROXY) { $env:HTTPS_PROXY } else { 'Not Set' }
Write-Log ('  HTTP_PROXY: ' + $envHttp)
Write-Log ('  HTTPS_PROXY: ' + $envHttps)

$ieProxy = 'Unknown'
$pac = 'Not configured'
try {
    $ieSettings = Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -ErrorAction Stop
    if ($ieSettings.ProxyEnable -eq 1) {
        $ieProxyServer = Get-FirstNonEmpty @($ieSettings.ProxyServer)
        $ieProxyBypass = Get-FirstNonEmpty @($ieSettings.ProxyOverride, '')
        $ieProxy = $ieProxyServer
        if (-not [string]::IsNullOrWhiteSpace($ieProxyBypass)) {
            $ieProxy += ' (Bypass: ' + $ieProxyBypass + ')'
        }
    } else {
        $ieProxy = 'Disabled'
    }
    if ($ieSettings.AutoConfigURL) {
        $pac = $ieSettings.AutoConfigURL
    }
} catch {
    $ieProxy = 'Registry Access Failed'
}

Write-Log ('  IE Proxy: ' + $ieProxy)
Write-Log ('  PAC Script: ' + $pac)

$systemProxySummary = 'Unknown'
try {
    $sysProxy = [System.Net.WebRequest]::GetSystemWebProxy()
    if ($sysProxy) {
        $sampleUri = New-Object System.Uri('https://www.google.com')
        $proxyUri = $sysProxy.GetProxy($sampleUri)
        if ($proxyUri -and $proxyUri.AbsoluteUri -ne $sampleUri.AbsoluteUri) {
            $systemProxySummary = $proxyUri.AbsoluteUri
        } else {
            $systemProxySummary = 'Direct / No explicit proxy for sample target'
        }
    }
} catch {
    $systemProxySummary = 'Unavailable'
}
Write-Log ('  System Proxy Resolution: ' + $systemProxySummary)

$pubIP = 'Detection Failed'
try {
    $pubUrl = 'https://api.ipify.org'
    $wc = New-Object System.Net.WebClient
    $wc.Headers['User-Agent'] = 'PowerShell/5.1'
    $pubRaw = $wc.DownloadString($pubUrl)
    if ($pubRaw -match '^\s*\d{1,3}(\.\d{1,3}){3}\s*$') {
        $pubIP = $pubRaw.Trim()
        Write-Log ('  Public IP: ' + $pubIP) 'OK'
    } else {
        Write-Log ('  Public IP: Unexpected response') 'WARN'
    }
} catch {
    Write-Log ('  Public IP: Detection failed via HTTPS') 'WARN'
}

Add-BlankLine

# ============================================
# Module 3: Port Firewall Matrix
# ============================================
Write-Log 'MODULE 3: Port Firewall Matrix' 'HEADER'

Write-Log 'Listening Ports:'
Write-Log ('Port'.PadRight(8) + 'PID'.PadRight(8) + 'Process'.PadRight(20) + 'LocalAddress'.PadRight(24) + 'Exposure'.PadRight(16) + 'Tag')

$listenEntries = @()
$usedMethod = ''
$listenDiag = New-Object System.Collections.Generic.List[string]

[void]$listenDiag.Add('Listen Detection: Trying Get-NetTCPConnection')
try {
    $tcpListen = Get-NetTCPConnection -State Listen -ErrorAction Stop
    if ($tcpListen -and $tcpListen.Count -gt 0) {
        foreach ($tcpEntry in $tcpListen) {
            try {
                $localPortValue = 0
                $owningProcessId = 0
                $localAddressValue = '0.0.0.0'

                try { $localPortValue = [int]$tcpEntry.LocalPort } catch { $localPortValue = 0 }
                try { $owningProcessId = [int]$tcpEntry.OwningProcess } catch { $owningProcessId = 0 }
                try {
                    if (-not [string]::IsNullOrWhiteSpace([string]$tcpEntry.LocalAddress)) {
                        $localAddressValue = [string]$tcpEntry.LocalAddress
                    }
                } catch {}

                if ($localPortValue -gt 0) {
                    $exposureValue = Get-ExposureLevel -LocalAddress $localAddressValue
                    $riskTag = Get-PortRiskTag -Port $localPortValue
                    $isHighRisk = Test-IsHighRiskExposedPort -Port $localPortValue -Exposure $exposureValue

                    if ($isHighRisk -and -not ($script:HighRiskPorts -contains $localPortValue)) {
                        [void]$script:HighRiskPorts.Add($localPortValue)
                    }

                    $listenEntries += [PSCustomObject]@{
                        Port        = $localPortValue
                        PID         = $owningProcessId
                        ProcessName = if ($owningProcessId -gt 0) { Get-ProcessNameSafe -ProcessIdValue $owningProcessId } else { '' }
                        LocalAddr   = $localAddressValue
                        Exposure    = $exposureValue
                        Tag         = $riskTag
                        HighRisk    = $isHighRisk
                    }
                }
            } catch {}
        }
        if ($listenEntries.Count -gt 0) {
            $usedMethod = 'Get-NetTCPConnection'
            [void]$listenDiag.Add('Listen Detection: Get-NetTCPConnection returned ' + $listenEntries.Count + ' entries')
        } else {
            [void]$listenDiag.Add('Listen Detection: Get-NetTCPConnection returned no usable entries')
        }
    } else {
        [void]$listenDiag.Add('Listen Detection: Get-NetTCPConnection returned no entries')
    }
} catch {
    [void]$listenDiag.Add('Listen Detection: Get-NetTCPConnection failed or unavailable')
}

if (-not $listenEntries -or $listenEntries.Count -eq 0) {
    [void]$listenDiag.Add('Listen Detection: Falling back to netstat -ano')

    $parseFailCount = 0
    $parsedCount = 0
    $listeningCandidateCount = 0
    $sampleFailures = New-Object System.Collections.Generic.List[string]

    try {
        $netstatOutput = netstat -ano | Out-String
        $lines = $netstatOutput -split "`r?`n"
        $rawLineCount = ($lines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }).Count
        [void]$listenDiag.Add('Listen Detection: netstat produced ' + $rawLineCount + ' non-empty lines')

        foreach ($rawLine in $lines) {
            $trimmedLine = ''
            try { $trimmedLine = $rawLine.Trim() } catch { $trimmedLine = '' }
            if ([string]::IsNullOrWhiteSpace($trimmedLine)) { continue }
            if ($trimmedLine -notmatch 'LISTENING') { continue }

            $listeningCandidateCount++

            try {
                $tokens = $trimmedLine -split '\s+'
                if ($tokens.Count -lt 4) {
                    throw [System.FormatException]::new('Too few tokens')
                }

                $protoToken = $tokens[0]
                if ($protoToken -ne 'TCP') {
                    throw [System.FormatException]::new('Non-TCP listening line')
                }

                $localEndpointToken = $tokens[1]
                $processIdToken = $tokens[$tokens.Count - 1]
                $stateToken = $tokens[$tokens.Count - 2]

                if ($stateToken -ne 'LISTENING') {
                    throw [System.FormatException]::new('State token mismatch')
                }
                if ($processIdToken -notmatch '^\d+$') {
                    throw [System.FormatException]::new('PID token not numeric')
                }

                $endpointParts = Split-LocalEndpoint -EndpointText $localEndpointToken
                if ($null -eq $endpointParts) {
                    throw [System.FormatException]::new('Local endpoint parse failed')
                }

                $processIdValue = [int]$processIdToken
                $portValue = [int]$endpointParts.Port
                $addressValue = [string]$endpointParts.Address

                if ($portValue -le 0) {
                    throw [System.FormatException]::new('Port invalid')
                }

                $exposureValue = Get-ExposureLevel -LocalAddress $addressValue
                $riskTag = Get-PortRiskTag -Port $portValue
                $isHighRisk = Test-IsHighRiskExposedPort -Port $portValue -Exposure $exposureValue

                if ($isHighRisk -and -not ($script:HighRiskPorts -contains $portValue)) {
                    [void]$script:HighRiskPorts.Add($portValue)
                }

                $listenEntries += [PSCustomObject]@{
                    Port        = $portValue
                    PID         = $processIdValue
                    ProcessName = if ($processIdValue -gt 0) { Get-ProcessNameSafe -ProcessIdValue $processIdValue } else { '' }
                    LocalAddr   = $addressValue
                    Exposure    = $exposureValue
                    Tag         = $riskTag
                    HighRisk    = $isHighRisk
                }

                $parsedCount++
            } catch {
                $parseFailCount++
                if ($sampleFailures.Count -lt 3) {
                    $errType = $_.Exception.GetType().Name
                    $sampleFailures.Add($errType + ': ' + (Get-ShortText -Text $trimmedLine -MaxLength 100)) | Out-Null
                }
            }
        }

        [void]$listenDiag.Add('Listen Detection: LISTENING candidate lines=' + $listeningCandidateCount)
        [void]$listenDiag.Add('Listen Detection: parsed entries=' + $parsedCount + ', failed lines=' + $parseFailCount)

        foreach ($sf in $sampleFailures) {
            [void]$listenDiag.Add('Listen Parse Sample: ' + $sf)
        }

        if ($listenEntries.Count -gt 0) {
            $usedMethod = 'netstat -ano'
            [void]$listenDiag.Add('Listen Detection: netstat parsed ' + $listenEntries.Count + ' entries')
        } else {
            [void]$listenDiag.Add('Listen Detection: netstat executed but no usable LISTENING entries were parsed')
            $sample = ($lines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 2) -join ' | '
            if (-not [string]::IsNullOrWhiteSpace($sample)) {
                [void]$listenDiag.Add('Listen Detection Sample: ' + (Get-ShortText -Text $sample -MaxLength 120))
            }
        }
    } catch {
        [void]$listenDiag.Add('Listen Detection: netstat fallback failed: ' + $_.Exception.GetType().Name)
        [void]$listenDiag.Add('Listen Detection Detail: ' + (Get-ShortText -Text $_.Exception.Message -MaxLength 100))
    }
}

$listenEntries = $listenEntries | Sort-Object Port, PID -Unique

foreach ($diagLine in $listenDiag) {
    Write-Log $diagLine
}

if (-not $listenEntries -or $listenEntries.Count -eq 0) {
    Write-Log 'No listening ports detected (all methods failed or no listeners present)' 'WARN'
} else {
    $shownEntries = $listenEntries | Sort-Object Port, PID | Select-Object -First 20

    foreach ($entry in $shownEntries) {
        $tagDisplay = ''
        if ($entry.Tag) { $tagDisplay = $entry.Tag }
        if ($entry.HighRisk) {
            if ($tagDisplay) {
                $tagDisplay += '[HIGH-RISK]'
            } else {
                $tagDisplay = '[HIGH-RISK]'
            }
        }

        $procName = if ($entry.ProcessName) { $entry.ProcessName } else { '' }
        $procDisplay = $procName.Substring(0, [Math]::Min(18, $procName.Length))

        Write-Log (
            ($entry.Port.ToString()).PadRight(8) +
            ($entry.PID.ToString()).PadRight(8) +
            $procDisplay.PadRight(20) +
            ($entry.LocalAddr).PadRight(24) +
            ($entry.Exposure).PadRight(16) +
            $tagDisplay
        ) 'OK'
    }

    Write-Log ('Listen Detection Source: ' + $usedMethod)
    Write-Log ('Total listening: ' + $listenEntries.Count + ' (showing first 20)')

    $exposedCounts = $listenEntries | Group-Object Exposure | Sort-Object Name
    Write-Log 'Exposure Summary:'
    foreach ($grp in $exposedCounts) {
        Write-Log ('  ' + $grp.Name + ': ' + $grp.Count)
    }

    if ($script:HighRiskPorts.Count -gt 0) {
        $riskList = ($script:HighRiskPorts | Sort-Object -Unique) -join ', '
        Write-Log ('High-Risk Exposed Ports: ' + $riskList) 'WARN'
    } else {
        Write-Log 'High-Risk Exposed Ports: None detected outside LocalOnly scope' 'OK'
    }
}

Add-BlankLine
Write-Log 'Firewall Profiles:'
try {
    $fwProfiles = Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction
    foreach ($f in $fwProfiles) {
        $status = if ($f.Enabled) { 'ON' } else { 'OFF' }
        Write-Log ('  [' + $f.Name + '] ' + $status + ' | In:' + $f.DefaultInboundAction + ' Out:' + $f.DefaultOutboundAction)
    }
} catch {
    $fwFallback = netsh advfirewall show allprofiles 2>&1 | Out-String
    Write-Log '  Get-NetFirewallProfile failed, using netsh:' 'WARN'
    ($fwFallback -split "`r?`n" | Where-Object { $_ -match 'Profile|State|Policy' }) | ForEach-Object {
        Write-Log ('  ' + $_.Trim())
    }
}

Add-BlankLine
Write-Log 'Outbound TCP Tests (3s timeout each):'
Write-Log ('Target'.PadRight(14) + 'Port'.PadRight(8) + 'Result'.PadRight(12) + 'Time'.PadRight(10) + 'Note')

$outboundTargets = @(
    [PSCustomObject]@{ TargetHost = 'github.com';           TargetPort = 22;  TargetName = 'GitHub-SSH'   },
    [PSCustomObject]@{ TargetHost = 'github.com';           TargetPort = 443; TargetName = 'GitHub-HTTPS' },
    [PSCustomObject]@{ TargetHost = 'baidu.com';            TargetPort = 443; TargetName = 'Baidu'        },
    [PSCustomObject]@{ TargetHost = 'registry-1.docker.io'; TargetPort = 443; TargetName = 'Docker'       }
)

$outboundRowCount = 0
foreach ($targetItem in $outboundTargets) {
    try {
        $targetName = [string]$targetItem.TargetName
        $targetHostName = [string]$targetItem.TargetHost
        $targetPortNumber = [int]$targetItem.TargetPort

        if ([string]::IsNullOrWhiteSpace($targetName)) {
            throw [System.ArgumentException]::new('Target name missing')
        }
        if ([string]::IsNullOrWhiteSpace($targetHostName)) {
            throw [System.ArgumentException]::new('Target host missing')
        }
        if ($targetPortNumber -le 0 -or $targetPortNumber -gt 65535) {
            throw [System.ArgumentOutOfRangeException]::new('Target port invalid')
        }

        $connectResult = Get-TcpConnectResult -TargetHost $targetHostName -TargetPort $targetPortNumber -TimeoutMs 3000

        $resultText = 'FAIL'
        $noteText = ''
        if ($connectResult.Success) {
            $resultText = 'OK'
        } else {
            if (-not [string]::IsNullOrWhiteSpace($connectResult.ErrorType)) {
                $noteText = $connectResult.ErrorType
            } elseif (-not [string]::IsNullOrWhiteSpace($connectResult.Error)) {
                $noteText = $connectResult.Error
            } else {
                $noteText = 'ConnectFailed'
            }
        }

        $timeText = ($connectResult.TimeMs.ToString() + 'ms')

        $row = $targetName.PadRight(14) +
               $targetPortNumber.ToString().PadRight(8) +
               $resultText.PadRight(12) +
               $timeText.PadRight(10) +
               (Get-ShortText -Text $noteText -MaxLength 40)

        if ($connectResult.Success) {
            Write-Log $row 'OK'
        } else {
            Write-Log $row 'WARN'
        }

        $outboundRowCount++
    } catch {
        $fallbackName = Get-FirstNonEmpty @($targetItem.TargetName, 'Unknown')
        $fallbackPort = Get-FirstNonEmpty @($targetItem.TargetPort, '0')
        $errType = $_.Exception.GetType().Name
        $row = $fallbackName.PadRight(14) +
               ([string]$fallbackPort).PadRight(8) +
               'ERROR'.PadRight(12) +
               '0ms'.PadRight(10) +
               $errType
        Write-Log $row 'ERR'
        $outboundRowCount++
    }
}

if ($outboundRowCount -eq 0) {
    Write-Log 'Outbound TCP Tests: no rows produced (unexpected)' 'ERR'
} else {
    Write-Log ('Outbound TCP Tests: ' + $outboundRowCount + ' rows produced')
}

Add-BlankLine

# ============================================
# Module 4: Terminal Environment
# ============================================
Write-Log 'MODULE 4: Terminal Environment' 'HEADER'

Write-Log ('PowerShell Version: ' + $PSVersionTable.PSVersion + ' (' + $PSVersionTable.PSEdition + ')')
Write-Log ('PSHome: ' + $PSHOME)
Write-Log ('Execution Policy: ' + (Get-ExecutionPolicy))

$wt = Get-Command wt -ErrorAction SilentlyContinue
if ($wt) {
    Write-Log ('Windows Terminal: ' + $wt.Source) 'OK'
} else {
    $wtStore = $null
    try { $wtStore = Get-AppxPackage Microsoft.WindowsTerminal } catch {}
    if ($wtStore) {
        Write-Log ('Windows Terminal: Microsoft Store v' + $wtStore.Version) 'OK'
    } else {
        Write-Log 'Windows Terminal: Not Installed'
    }
}

$wslCmd = Get-Command wsl.exe -ErrorAction SilentlyContinue
if ($wslCmd) {
    Write-Log ('WSL Command: ' + $wslCmd.Source) 'OK'

    $wslListV = Invoke-WithTimeout -FilePath $wslCmd.Source -Arguments '-l -v' -TimeoutSec 5
    $wslList = Invoke-WithTimeout -FilePath $wslCmd.Source -Arguments '-l' -TimeoutSec 5
    $wslVersion = Invoke-WithTimeout -FilePath $wslCmd.Source -Arguments '--version' -TimeoutSec 5

    $defaultDistro = ''
    $wslModeNote = 'Unknown'

    if ($wslListV.Status -eq 'OK' -and -not [string]::IsNullOrWhiteSpace($wslListV.Output)) {
        $lines = $wslListV.Output -split "`r?`n"
        foreach ($line in $lines) {
            if ($line -match '^\s*\*\s*(.+)$') {
                $defaultDistro = $matches[1].Trim()
                break
            }
        }
        $wslModeNote = 'Versioned distro listing available'
        Write-Log ('WSL Status: Present (' + $wslModeNote + ')') 'OK'
    } elseif ($wslList.Status -eq 'OK' -and -not [string]::IsNullOrWhiteSpace($wslList.Output)) {
        $lines = $wslList.Output -split "`r?`n"
        foreach ($line in $lines) {
            if ($line -match '^\s*\*\s*(.+)$') {
                $defaultDistro = $matches[1].Trim()
                break
            }
        }
        $wslModeNote = 'Basic distro listing available'
        Write-Log ('WSL Status: Present (' + $wslModeNote + ')') 'OK'
    } elseif ($wslVersion.Status -eq 'OK' -and ($wslVersion.Output -match 'WSL|Windows Subsystem for Linux')) {
        $wslModeNote = 'Version command available'
        Write-Log ('WSL Status: Present (' + $wslModeNote + ')') 'OK'
    } else {
        Write-Log 'WSL Status: Command present but details unavailable' 'WARN'
    }

    if ($defaultDistro) {
        Write-Log ('WSL Default Distro: ' + $defaultDistro)
    }
} else {
    Write-Log 'WSL Status: Not installed or unavailable'
}

Write-Log ('Console Encoding: ' + $consoleEncodingName)
Write-Log ('Output Encoding: ' + $outputEncodingName)
Add-BlankLine

# ============================================
# Module 5: Development Stack
# ============================================
Write-Log 'MODULE 5: Development Stack' 'HEADER'

$tools = New-Object System.Collections.Generic.List[object]

$toolDefs = @(
    @{ Name='Git';     Exe='git.exe';    Arg='--version' },
    @{ Name='Node.js'; Exe='node.exe';   Arg='--version' },
    @{ Name='npm';     Exe='npm.cmd';    Arg='--version' },
    @{ Name='Python';  Exe='python.exe'; Arg='--version' },
    @{ Name='pip';     Exe='pip.exe';    Arg='--version' },
    @{ Name='Java';    Exe='java.exe';   Arg='-version'  },
    @{ Name='Docker';  Exe='docker.exe'; Arg='--version' }
)

foreach ($def in $toolDefs) {
    $cmd = Get-Command $def.Exe -ErrorAction SilentlyContinue
    if ($cmd) {
        $ver = Get-CommandVersionLine -CommandPath $cmd.Source -Arguments $def.Arg -TimeoutSec 3

        if ($def.Name -eq 'Python' -and $cmd.Source -match 'WindowsApps') {
            $ver += ' [Possible App Execution Alias]'
        }

        [void]$tools.Add([PSCustomObject]@{
            Name    = $def.Name
            Version = $ver
            Path    = $cmd.Source
            Status  = 'OK'
        })
    } else {
        [void]$tools.Add([PSCustomObject]@{
            Name    = $def.Name
            Version = 'Not Found'
            Path    = ''
            Status  = 'MISSING'
        })
    }
}

$vscode = [PSCustomObject]@{
    Name    = 'VSCode'
    Version = 'Not Found'
    Path    = ''
    Status  = 'MISSING'
}

$codeCmd = Get-Command 'code.exe' -ErrorAction SilentlyContinue
if (-not $codeCmd) { $codeCmd = Get-Command 'code.cmd' -ErrorAction SilentlyContinue }

if ($codeCmd) {
    $vscode.Version = Get-CommandVersionLine -CommandPath $codeCmd.Source -Arguments '--version' -TimeoutSec 3
    $vscode.Path = $codeCmd.Source
    $vscode.Status = 'OK'
} else {
    $vsPaths = @(
        (Join-Path $env:LOCALAPPDATA 'Programs\Microsoft VS Code\bin\code.cmd'),
        'C:\Program Files\Microsoft VS Code\bin\code.cmd',
        'C:\Program Files (x86)\Microsoft VS Code\bin\code.cmd'
    )

    foreach ($p in $vsPaths) {
        if (Test-Path $p) {
            $vscode.Version = 'Installed (path detected)'
            $vscode.Path = $p
            $vscode.Status = 'OK'
            break
        }
    }
}
[void]$tools.Add($vscode)

$vscodium = [PSCustomObject]@{
    Name    = 'VSCodium'
    Version = 'Not Found'
    Path    = ''
    Status  = 'MISSING'
}

$codiumCmd = Get-Command 'codium.exe' -ErrorAction SilentlyContinue
if (-not $codiumCmd) { $codiumCmd = Get-Command 'codium.cmd' -ErrorAction SilentlyContinue }

if ($codiumCmd) {
    $vscodium.Version = Get-CommandVersionLine -CommandPath $codiumCmd.Source -Arguments '--version' -TimeoutSec 3
    $vscodium.Path = $codiumCmd.Source
    $vscodium.Status = 'OK'
} else {
    $codiumPaths = @(
        (Join-Path $env:LOCALAPPDATA 'Programs\VSCodium\bin\codium.cmd'),
        'C:\Program Files\VSCodium\bin\codium.cmd',
        'C:\Program Files (x86)\VSCodium\bin\codium.cmd'
    )
    foreach ($cp in $codiumPaths) {
        if (Test-Path $cp) {
            $vscodium.Version = 'Installed (path detected)'
            $vscodium.Path = $cp
            $vscodium.Status = 'OK'
            break
        }
    }
}
[void]$tools.Add($vscodium)

$jetbrains = [PSCustomObject]@{
    Name    = 'JetBrains'
    Version = 'Not Found'
    Path    = ''
    Status  = 'MISSING'
}

$jbPaths = @(
    (Join-Path $env:LOCALAPPDATA 'JetBrains\Toolbox\bin\jetbrains-toolbox.exe'),
    (Join-Path $env:PROGRAMFILES 'JetBrains\Toolbox\bin\jetbrains-toolbox.exe'),
    (Join-Path $env:LOCALAPPDATA 'JetBrains\Toolbox\jetbrains-toolbox.exe')
)

foreach ($jb in $jbPaths) {
    if (Test-Path $jb) {
        try {
            $fileInfo = Get-Item $jb
            $ver = $fileInfo.VersionInfo.FileVersion
            $jetbrains = [PSCustomObject]@{
                Name    = 'JetBrains Toolbox'
                Version = 'Installed ' + $ver
                Path    = $jb
                Status  = 'OK'
            }
            break
        } catch {}
    }
}

if ($jetbrains.Status -ne 'OK') {
    $jbApps = @('idea64.exe', 'rider64.exe', 'pycharm64.exe', 'webstorm64.exe', 'clion64.exe')
    foreach ($app in $jbApps) {
        $appCmd = Get-Command $app -ErrorAction SilentlyContinue
        if ($appCmd) {
            $appName = $app -replace '64\.exe$', ''
            $jetbrains = [PSCustomObject]@{
                Name    = 'JetBrains ' + $appName
                Version = 'Found in PATH'
                Path    = $appCmd.Source
                Status  = 'OK'
            }
            break
        }
    }
}
[void]$tools.Add($jetbrains)

$winget = Get-Command winget.exe -ErrorAction SilentlyContinue
if ($winget) {
    [void]$tools.Add([PSCustomObject]@{
        Name    = 'Winget'
        Version = Get-CommandVersionLine -CommandPath $winget.Source -Arguments '--version' -TimeoutSec 3
        Path    = $winget.Source
        Status  = 'OK'
    })
} else {
    [void]$tools.Add([PSCustomObject]@{
        Name    = 'Winget'
        Version = 'Not Found'
        Path    = ''
        Status  = 'MISSING'
    })
}

$choco = Get-Command choco.exe -ErrorAction SilentlyContinue
if ($choco) {
    [void]$tools.Add([PSCustomObject]@{
        Name    = 'Chocolatey'
        Version = Get-CommandVersionLine -CommandPath $choco.Source -Arguments '--version' -TimeoutSec 3
        Path    = $choco.Source
        Status  = 'OK'
    })
} else {
    [void]$tools.Add([PSCustomObject]@{
        Name    = 'Chocolatey'
        Version = 'Not Found'
        Path    = ''
        Status  = 'MISSING'
    })
}

Write-Log ('Tool'.PadRight(20) + 'Version'.PadRight(42) + 'Path')
Write-Log ('-' * 100)

foreach ($t in $tools) {
    $displayVersion = Get-ShortText -Text $t.Version -MaxLength 40
    $shortPath = ''
    if ($t.Path) {
        try { $shortPath = Split-Path $t.Path -Leaf } catch { $shortPath = $t.Path }
    }
    $level = if ($t.Status -eq 'OK') { 'OK' } else { 'WARN' }

    Write-Log (
        ($t.Name).PadRight(20) +
        ($displayVersion).PadRight(42) +
        $shortPath
    ) $level
}

Add-BlankLine

# ============================================
# Module 6: Enterprise Controls
# ============================================
Write-Log 'MODULE 6: Enterprise Controls' 'HEADER'

Write-Log 'Security Software:'
$avList = @()
try {
    $avWmi = Get-WmiObject -Namespace 'root\SecurityCenter2' -Class AntiVirusProduct -ErrorAction Stop
    foreach ($av in $avWmi) {
        $name = Get-FirstNonEmpty @($av.displayName, 'Unknown Product')
        $stateText = 'State Unknown'

        try {
            $productState = [int]$av.productState
            if ($productState -eq 266240) {
                $stateText = 'Realtime ON'
            } elseif ($productState -eq 266000) {
                $stateText = 'Realtime OFF'
            } else {
                $stateText = 'State Code ' + $productState
            }
        } catch {}

        $avList += ('  - ' + $name + ' [' + $stateText + ']')
    }
} catch {
    $avList += '  - WMI query failed (restricted or no AV registered)'
}

foreach ($a in $avList) {
    Write-Log $a
}

$mdmDetected = $false
try {
    $mdm = Get-WmiObject -Namespace 'root\cimv2\mdm\dmmap' -Class MDM_DevDetail_Ext01 -ErrorAction Stop
    if ($mdm) { $mdmDetected = $true }
} catch {}
Write-Log ('MDM Enrollment: ' + $(if ($mdmDetected) { 'Yes (Managed Device)' } else { 'No or Undetected by current method' }))

$blStatus = 'Admin Required'
if ($isAdmin) {
    try {
        $blVol = Get-BitLockerVolume -MountPoint C: -ErrorAction Stop
        $blStatus = $blVol.VolumeStatus.ToString() + ' (' + $blVol.EncryptionMethod + ')'
    } catch {
        $blStatus = 'Query Failed'
    }
}
Write-Log ('BitLocker (C:): ' + $blStatus)

$uacStatus = 'Unknown'
try {
    $uacReg = Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System' -ErrorAction Stop
    $uacStatus = if ($uacReg.EnableLUA -eq 1) { 'Enabled' } else { 'Disabled or Not Configured' }
} catch {}
Write-Log ('UAC: ' + $uacStatus)

Write-Log 'Execution Policy List:'
try {
    $execPol = Get-ExecutionPolicy -List
    foreach ($item in $execPol) {
        Write-Log ('  ' + ($item.Scope.ToString()).PadRight(14) + ' ' + $item.ExecutionPolicy)
    }
} catch {
    Write-Log '  Unable to enumerate execution policy list' 'WARN'
}

Add-BlankLine

# ============================================
# Module 7: External Connectivity
# ============================================
Write-Log 'MODULE 7: External Connectivity' 'HEADER'

Write-Log 'Testing 9 external sites (Direct TCP and Proxy HTTP)...'
Write-Log ('Site'.PadRight(12) + 'Direct'.PadRight(12) + 'ProxyHTTP'.PadRight(14) + 'Assessment'.PadRight(28) + 'Note')
Write-Log ('-' * 90)

$sysProxy = $null
try { $sysProxy = [System.Net.WebRequest]::GetSystemWebProxy() } catch {}

$testSites = @(
    @{ SiteName='AliDNS';      SiteHost='223.5.5.5';            SitePort=53;  SitePath=''           },
    @{ SiteName='Baidu';       SiteHost='baidu.com';            SitePort=443; SitePath='/'          },
    @{ SiteName='GitHub';      SiteHost='github.com';           SitePort=443; SitePath='/'          },
    @{ SiteName='Moonshot';    SiteHost='api.moonshot.cn';      SitePort=443; SitePath='/'          },
    @{ SiteName='Google';      SiteHost='www.google.com';       SitePort=443; SitePath='/'          },
    @{ SiteName='DockerHub';   SiteHost='registry-1.docker.io'; SitePort=443; SitePath='/v2/'       },
    @{ SiteName='OpenAI';      SiteHost='api.openai.com';       SitePort=443; SitePath='/v1/models' },
    @{ SiteName='Anthropic';   SiteHost='api.anthropic.com';    SitePort=443; SitePath='/'          },
    @{ SiteName='BraveSearch'; SiteHost='api.search.brave.com'; SitePort=443; SitePath='/'          }
)

$directReachable = 0
$proxyReachable = 0
$unauthorizedReachable = 0
$unexpectedHttp = 0
$unreachable = 0
$directFailProxyOkNames = New-Object System.Collections.Generic.List[string]

foreach ($site in $testSites) {
    $directResult = Get-TcpConnectResult -TargetHost $site.SiteHost -TargetPort $site.SitePort -TimeoutMs 3000
    $directText = if ($directResult.Success) { 'OK (' + $directResult.TimeMs + 'ms)' } else { 'FAIL' }

    $proxyText = '-'
    $assessment = 'Unreachable'
    $note = ''
    $proxySucceeded = $false

    if ($site.SitePort -eq 443) {
        $url = 'https://' + $site.SiteHost + $site.SitePath
        $httpResult = Invoke-WebHeadOrGet -Url $url -HttpHostHeader $site.SiteHost -TimeoutMs 8000 -Proxy $sysProxy

        if ($httpResult.Success -and $httpResult.Code -ne $null) {
            $proxyText = 'HTTP:' + $httpResult.Code
            $proxySucceeded = $true

            if ($directResult.Success) {
                if ($httpResult.Code -in @(200,204,301,302,307,308)) {
                    $assessment = 'Direct Reachable'
                    $note = 'HTTP success'
                    $directReachable++
                } elseif ($httpResult.Code -in @(401,403)) {
                    $assessment = 'Reachable (Auth Required)'
                    $note = 'Service reachable, authorization required'
                    $unauthorizedReachable++
                } elseif ($httpResult.Code -eq 404) {
                    $assessment = 'Reachable (Path/Route Issue)'
                    $note = 'Service reachable, returned 404'
                    $unexpectedHttp++
                } else {
                    $assessment = 'Reachable (Unexpected HTTP)'
                    $note = 'HTTP ' + $httpResult.Code
                    $unexpectedHttp++
                }
            } else {
                if ($httpResult.Code -in @(200,204,301,302,307,308)) {
                    $assessment = 'Proxy Reachable'
                    $note = 'HTTP success via proxy'
                    $proxyReachable++
                    [void]$directFailProxyOkNames.Add($site.SiteName)
                } elseif ($httpResult.Code -in @(401,403)) {
                    $assessment = 'Reachable via Proxy (Auth)'
                    $note = 'Service reachable via proxy, authorization required'
                    $unauthorizedReachable++
                    [void]$directFailProxyOkNames.Add($site.SiteName)
                } elseif ($httpResult.Code -eq 404) {
                    $assessment = 'Reachable via Proxy (404)'
                    $note = 'Service reachable via proxy, returned 404'
                    $unexpectedHttp++
                    [void]$directFailProxyOkNames.Add($site.SiteName)
                } else {
                    $assessment = 'Reachable via Proxy (Unexpected HTTP)'
                    $note = 'HTTP ' + $httpResult.Code
                    $unexpectedHttp++
                    [void]$directFailProxyOkNames.Add($site.SiteName)
                }
            }
        } else {
            if ($directResult.Success) {
                $assessment = 'Direct TCP Reachable'
                $note = 'TCP reachable, HTTP test unavailable'
                $directReachable++
            } else {
                $assessment = 'Unreachable'
                $note = 'No direct TCP and no proxy HTTP response'
                $unreachable++
            }
        }
    } else {
        if ($directResult.Success) {
            $assessment = 'Direct Reachable'
            $note = 'TCP reachable'
            $directReachable++
        } else {
            $assessment = 'Unreachable'
            $note = 'TCP failed'
            $unreachable++
        }
    }

    $level = 'WARN'
    if ($assessment -match '^Direct|^Proxy|^Reachable') { $level = 'OK' }

    Write-Log (
        ($site.SiteName).PadRight(12) +
        ($directText).PadRight(12) +
        ($proxyText).PadRight(14) +
        ($assessment).PadRight(28) +
        '  ' +
        $note
    ) $level

    $script:ExternalResults.Add([PSCustomObject]@{
        SiteName        = $site.SiteName
        Host            = $site.SiteHost
        DirectSuccess   = $directResult.Success
        ProxySuccess    = $proxySucceeded
        ProxyCode       = if ($proxyText -like 'HTTP:*') { $proxyText } else { '' }
        Assessment      = $assessment
        Note            = $note
    }) | Out-Null
}

Add-BlankLine
Write-Log (
    'Summary: ' +
    $directReachable + ' Direct/Direct-TCP Reachable, ' +
    $proxyReachable + ' Proxy Reachable, ' +
    $unauthorizedReachable + ' Reachable but Auth Required, ' +
    $unexpectedHttp + ' Reachable with Unexpected HTTP, ' +
    $unreachable + ' Unreachable'
)

if ($directFailProxyOkNames.Count -gt 0) {
    $hybridList = ($directFailProxyOkNames | Sort-Object -Unique) -join ', '
    Write-Log ('Direct Failed but Proxy Reachable: ' + $hybridList) 'WARN'
} else {
    Write-Log 'Direct Failed but Proxy Reachable: None observed' 'OK'
}

if ($systemProxySummary -and $systemProxySummary -ne 'Direct / No explicit proxy for sample target' -and $systemProxySummary -ne 'Unavailable') {
    Write-Log 'System proxy appears active for at least some targets' 'OK'
}

Add-BlankLine

# ============================================
# Module 8: Log Locations
# ============================================
Write-Log 'MODULE 8: Log Locations' 'HEADER'

$wtLocalState = ''
try {
    $wtCandidates = Get-ChildItem (Join-Path $env:LOCALAPPDATA 'Packages') -Directory -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -like 'Microsoft.WindowsTerminal*' } |
        Select-Object -First 1
    if ($wtCandidates) {
        $wtLocalState = Join-Path $wtCandidates.FullName 'LocalState'
    }
} catch {}

$logPaths = @(
    @{ Name='System Event Logs'; Path='C:\Windows\System32\Winevt\Logs'; CheckSize=$false },
    @{ Name='PowerShell Logs';   Path=(Join-Path $env:USERPROFILE 'AppData\Local\Microsoft\Windows\PowerShell'); CheckSize=$false },
    @{ Name='Windows Terminal';  Path=$wtLocalState; CheckSize=$false },
    @{ Name='User Temp';         Path=$env:TEMP; CheckSize=$true  },
    @{ Name='Windows Temp';      Path='C:\Windows\Temp'; CheckSize=$false },
    @{ Name='Crash Dumps';       Path=(Join-Path $env:USERPROFILE 'AppData\Local\CrashDumps'); CheckSize=$false }
)

foreach ($lp in $logPaths) {
    $displayPath = if ($lp.Path) { $lp.Path } else { 'Not detected' }
    $exists = $false
    if ($lp.Path) {
        try { $exists = Test-Path $lp.Path } catch { $exists = $false }
    }

    $sizeStr = ''
    if ($exists -and $lp.CheckSize) {
        try {
            $size = (Get-ChildItem $lp.Path -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
            if ($size -gt 0) {
                $sizeStr = ' (' + ([math]::Round(($size / 1MB), 1)) + ' MB)'
            }
        } catch {}
    }

    $status = if ($exists) { '[EXISTS]' } else { '[MISSING]' }
    Write-Log ($lp.Name.PadRight(20) + $status.PadRight(10) + $displayPath + $sizeStr)
}

Add-BlankLine
Write-Log 'Recent System Events (Last 3):'

try {
    $events = Get-WinEvent -LogName System -MaxEvents 3 -ErrorAction Stop | Select-Object TimeCreated, LevelDisplayName, Id, Message
    if ($events) {
        foreach ($e in $events) {
            $evtTime = 'Unknown'
            try { $evtTime = $e.TimeCreated.ToString('MM-dd HH:mm') } catch {}

            $evtLevel = Get-FirstNonEmpty @($e.LevelDisplayName, 'Unknown')
            $evtId = Get-FirstNonEmpty @($e.Id, 'Unknown')
            $evtMsg = Get-ShortText -Text (Get-FirstNonEmpty @($e.Message, 'Message unavailable')) -MaxLength 100

            Write-Log ('  [' + $evtTime + '] ' + $evtLevel + ' ID:' + $evtId)
            Write-Log ('    ' + $evtMsg)
        }
    } else {
        Write-Log '  Unable to read events (empty or restricted)' 'WARN'
    }
} catch {
    Write-Log '  Unable to read events (admin required or restricted)' 'WARN'
}

# ============================================
# Final summary
# ============================================
Add-BlankLine
Write-Log 'SUMMARY HIGHLIGHTS' 'HEADER'

if ($ieProxy -and $ieProxy -ne 'Disabled' -and $ieProxy -ne 'Registry Access Failed') {
    Write-Log ('Proxy detected: ' + $ieProxy) 'OK'
}

$allFirewallDisabled = $false
try {
    $fwProfiles2 = Get-NetFirewallProfile
    $allFirewallDisabled = $true
    foreach ($f in $fwProfiles2) {
        if ($f.Enabled) { $allFirewallDisabled = $false; break }
    }
    if ($allFirewallDisabled) {
        Write-Log 'All firewall profiles appear disabled' 'WARN'
    }
} catch {}

if (-not $listenEntries -or $listenEntries.Count -eq 0) {
    Write-Log 'Listening port detection returned no entries' 'WARN'
} else {
    Write-Log ('Listening ports detected: ' + $listenEntries.Count) 'OK'
}

if (-not $isAdmin) {
    Write-Log 'Non-admin execution: some results may be partial' 'WARN'
}

Add-BlankLine
Write-Log 'ENVIRONMENT CONCLUSION' 'HEADER'

$directInternetSummary = 'Unknown'
$proxyModeSummary = 'Unknown'
$highRiskSummary = 'None'
$externalMixedMode = $false

try {
    $anyDirect = $false
    $anyProxyOnly = $false

    foreach ($r in $script:ExternalResults) {
        if ($r.DirectSuccess) { $anyDirect = $true }
        if ((-not $r.DirectSuccess) -and $r.ProxySuccess) { $anyProxyOnly = $true }
    }

    if ($anyDirect -and $anyProxyOnly) {
        $directInternetSummary = 'Partial (Mixed Direct + Proxy)'
        $externalMixedMode = $true
    } elseif ($anyDirect) {
        $directInternetSummary = 'Available'
    } elseif ($anyProxyOnly) {
        $directInternetSummary = 'Not observed; proxy-dependent'
    } else {
        $directInternetSummary = 'Unavailable or not confirmed'
    }

    if ($systemProxySummary -and $systemProxySummary -ne 'Direct / No explicit proxy for sample target' -and $systemProxySummary -ne 'Unavailable') {
        $proxyModeSummary = 'Active'
    } else {
        $proxyModeSummary = 'Not clearly active'
    }

    if ($script:HighRiskPorts.Count -gt 0) {
        $highRiskSummary = ($script:HighRiskPorts | Sort-Object -Unique) -join ', '
    }
} catch {}

Write-Log ('Direct Internet: ' + $directInternetSummary)
Write-Log ('System Proxy: ' + $proxyModeSummary)
Write-Log ('High-Risk Exposed Ports: ' + $highRiskSummary)

if ($externalMixedMode) {
    Write-Log 'Connectivity Pattern: Some targets are directly reachable, while others require proxy fallback' 'WARN'
}

if ($allFirewallDisabled -and $script:HighRiskPorts.Count -gt 0) {
    Write-Log 'Security Note: Firewall appears disabled while non-local high-risk ports are exposed' 'WARN'
}

Add-BlankLine
$elapsed = [math]::Round(((Get-Date) - $script:StartTime).TotalSeconds, 1)

Write-Log ('Report Complete. Duration: ' + $elapsed + ' seconds') 'HEADER'
Write-Log '========================================' 'HEADER'
Write-Log 'END OF REPORT' 'HEADER'
Write-Log '========================================' 'HEADER'

$outFile = 'EnvReport-v2.1.4-' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '.txt'

try {
    $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
    [System.IO.File]::WriteAllLines((Join-Path (Get-Location) $outFile), $script:ReportLines, $utf8NoBom)
    Write-Log ('Full report saved: ' + (Resolve-Path $outFile)) 'OK'
} catch {
    Write-Log ('Failed to save report: ' + $_.Exception.Message) 'ERR'
}
相关推荐
芯智工坊9 小时前
第5章 Mosquitto配置文件完全指南
网络·人工智能·mqtt·开源
@syh.10 小时前
网络基础概念
网络
NaclarbCSDN10 小时前
User ID controlled by request parameter, with unpredictable user IDs -Burp 复现
网络·安全·网络安全
橙露10 小时前
CentOS 服务器安全加固:防火墙、端口、权限配置
服务器·安全·centos
code_pgf10 小时前
yolov8详细讲解,包括网络结构图、关键创新点、部署
网络·人工智能·yolo
zl_dfq11 小时前
计算机网络 之 【TCP协议】(TCP的核心定位与控制本质、TCP报文结构)
网络·计算机网络·tcp
m0_7381207211 小时前
AI安全——Gandalf靶场 Tongue Tied Gandalf 全关卡绕过详解
服务器·网络·人工智能·安全·web安全·prompt
无名的小三轮11 小时前
SYN洪水攻击
网络·安全
源远流长jerry11 小时前
负载均衡详解
linux·运维·服务器·网络·tcp/ip·负载均衡