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'
}