build / build (push) Successful in 10m17s
- 同步仓库 CI 配置 .gitea/workflows/build.yml - 纳入 src/public/config/scripts 等工程文件 - .gitignore 增加 .scratch/.backup/.trash,排除本地临时产物
123 lines
4.1 KiB
PowerShell
123 lines
4.1 KiB
PowerShell
<#
|
||
.SYNOPSIS
|
||
根据端口号查找并结束占用它的进程,释放端口。
|
||
|
||
.DESCRIPTION
|
||
默认只处理处于 LISTEN(监听)状态、占用该端口的进程。
|
||
会先列出进程 PID / 名称 / 命令行,确认后再结束(-Force 可跳过确认)。
|
||
结束后自动复查端口是否已释放。优先用 Get-NetTCPConnection,
|
||
不可用时回退解析 netstat -ano。
|
||
|
||
.PARAMETER Port
|
||
要释放的端口号(1-65535)。
|
||
|
||
.PARAMETER Force
|
||
跳过交互确认,直接结束进程。
|
||
|
||
.PARAMETER IncludeClient
|
||
连同“已连接到该端口”的客户端进程一起处理(默认只看服务端监听进程)。
|
||
|
||
.EXAMPLE
|
||
.\kill-port.ps1 8101
|
||
.EXAMPLE
|
||
.\kill-port.ps1 -Port 8101 -Force
|
||
#>
|
||
[CmdletBinding()]
|
||
param(
|
||
[Parameter(Mandatory = $true, Position = 0)]
|
||
[ValidateRange(1, 65535)]
|
||
[int]$Port,
|
||
|
||
[switch]$Force,
|
||
[switch]$IncludeClient
|
||
)
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
|
||
function Get-PortPids {
|
||
param([int]$Port, [bool]$IncludeClient)
|
||
$pids = @()
|
||
try {
|
||
if ($IncludeClient) {
|
||
$conns = Get-NetTCPConnection -LocalPort $Port -ErrorAction Stop
|
||
}
|
||
else {
|
||
$conns = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction Stop
|
||
}
|
||
$pids = @($conns | Select-Object -ExpandProperty OwningProcess -Unique)
|
||
}
|
||
catch {
|
||
# 回退:解析 netstat -ano
|
||
$lines = netstat -ano | Select-String -Pattern (":{0}\s" -f $Port)
|
||
foreach ($l in $lines) {
|
||
if (-not $IncludeClient -and ($l.Line -notmatch 'LISTENING')) { continue }
|
||
$parts = ($l.Line -split '\s+') | Where-Object { $_ }
|
||
$p = $parts[-1]
|
||
if ($p -match '^\d+$') { $pids += [int]$p }
|
||
}
|
||
$pids = @($pids | Select-Object -Unique)
|
||
}
|
||
return $pids
|
||
}
|
||
|
||
Write-Host "查询占用端口 $Port 的进程 ..." -ForegroundColor Cyan
|
||
$pids = @(Get-PortPids -Port $Port -IncludeClient $IncludeClient.IsPresent)
|
||
|
||
if (-not $pids) {
|
||
Write-Host "端口 $Port 当前没有被任何进程占用,无需处理。" -ForegroundColor Green
|
||
return
|
||
}
|
||
|
||
$myPid = $PID
|
||
$rows = foreach ($procId in $pids) {
|
||
$proc = Get-CimInstance Win32_Process -Filter "ProcessId=$procId" -ErrorAction SilentlyContinue
|
||
[pscustomobject]@{
|
||
PID = $procId
|
||
Name = if ($proc) { $proc.Name } else { '(已退出)' }
|
||
CmdLine = if ($proc) { $proc.CommandLine } else { '' }
|
||
}
|
||
}
|
||
|
||
$rows | Format-Table -AutoSize PID, Name
|
||
foreach ($r in $rows) {
|
||
if ($r.CmdLine) { Write-Host " [$($r.PID)] $($r.CmdLine)" -ForegroundColor DarkGray }
|
||
}
|
||
|
||
# 过滤掉系统空闲进程(0)与脚本自身
|
||
$targets = $rows | Where-Object { $_.PID -ne 0 -and $_.PID -ne $myPid }
|
||
if (-not $targets) {
|
||
Write-Host "没有可安全结束的进程(均为系统进程或脚本自身)。" -ForegroundColor Yellow
|
||
return
|
||
}
|
||
|
||
if (-not $Force) {
|
||
# 非交互(stdin 被管道/重定向接管)时绝不自动杀进程,必须显式 -Force
|
||
$interactive = -not ([Console]::IsInputRedirected -or -not [Environment]::UserInteractive)
|
||
if (-not $interactive) {
|
||
Write-Host "非交互环境且未指定 -Force,已取消(不会结束任何进程)。要直接释放请加 -Force。" -ForegroundColor Yellow
|
||
return
|
||
}
|
||
$ans = Read-Host "确认结束以上 $($targets.Count) 个进程并释放端口 $Port ? (y/N)"
|
||
if ($ans -notmatch '^[Yy]') { Write-Host "已取消。" -ForegroundColor Yellow; return }
|
||
}
|
||
|
||
foreach ($t in $targets) {
|
||
try {
|
||
Stop-Process -Id $t.PID -Force -ErrorAction Stop
|
||
Write-Host "已结束 PID $($t.PID) ($($t.Name))" -ForegroundColor Green
|
||
}
|
||
catch {
|
||
Write-Host "结束 PID $($t.PID) 失败:$($_.Exception.Message)" -ForegroundColor Red
|
||
}
|
||
}
|
||
|
||
Start-Sleep -Milliseconds 500
|
||
$remain = @(Get-PortPids -Port $Port -IncludeClient $false)
|
||
if ($remain) {
|
||
Write-Host "端口 $Port 仍被占用:PID $($remain -join ', ')(可能是系统进程或权限不足,试试以管理员身份运行)" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
else {
|
||
Write-Host "端口 $Port 已释放 OK" -ForegroundColor Green
|
||
}
|