# 后台启动/停止网页预览服务器 (无 PowerShell 窗口) # 用法: # powershell -NoProfile -ExecutionPolicy Bypass -File tools\serve-web-bg.ps1 # 后台启动 # powershell -NoProfile -ExecutionPolicy Bypass -File tools\serve-web-bg.ps1 -Stop # 停止 # powershell -NoProfile -ExecutionPolicy Bypass -File tools\serve-web-bg.ps1 -Status # 查看状态 # ... -Root release\web_2026.09 -Port 8080 # 指定目录/端口 # # 原理: 用 Start-Process -WindowStyle Hidden 启动 serve-web.ps1, 并把 PID + 参数写入 # .serve-web.pid 文件, 便于 -Stop / -Status 定位。 param( [string]$Root = '', [int]$Port = 8080, [switch]$Stop, [switch]$Status ) $projRoot = Split-Path $PSScriptRoot -Parent $pidFile = Join-Path $projRoot '.serve-web.pid' # 相对路径按【项目根】转绝对 —— Start-Process 启动的子进程工作目录不确定, # 传相对路径会被解析到别处; 也不能依赖 Get-Location (调用方可能在任意目录)。 # 注意 1: PowerShell 变量名【大小写不敏感】, 不能同时用 $root 和 $Root (会互相覆盖), # 故项目根变量取名 $projRoot。 # 注意 2: Join-Path 只做拼接不做 .. 归约, 必须再用 GetFullPath 规范化。 if ([string]::IsNullOrWhiteSpace($Root)) { $Root = 'release\web_2026.09' } if (-not [System.IO.Path]::IsPathRooted($Root)) { $Root = Join-Path $projRoot $Root } $Root = [System.IO.Path]::GetFullPath($Root) if (-not (Test-Path $Root)) { Write-Output ("目录不存在: " + $Root); exit 1 } $script = Join-Path $PSScriptRoot 'serve-web.ps1' function Get-Running { # 用命令行匹配定位服务进程 (比只靠 PID 文件更可靠: 窗口关过/崩溃后 PID 会失效) Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like '*serve-web.ps1*' } } if ($Stop) { $procs = Get-Running if (-not $procs) { Write-Output '服务未在运行'; if (Test-Path $pidFile) { Remove-Item $pidFile -Force }; exit 0 } foreach ($p in $procs) { Write-Output ("停止 serve-web (PID " + $p.ProcessId + ")") Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } if (Test-Path $pidFile) { Remove-Item $pidFile -Force } exit 0 } if ($Status) { $procs = Get-Running if ($procs) { Write-Output ("运行中: " + ($procs.ProcessId -join ', ')) } else { Write-Output '未运行' } try { $code = (Invoke-WebRequest -Uri ("http://localhost:" + $Port + "/index.html") -UseBasicParsing -TimeoutSec 3).StatusCode Write-Output ("HTTP " + $code + " http://localhost:" + $Port) } catch { Write-Output ("HTTP 不可达 http://localhost:" + $Port) } exit 0 } # 启动前先清理已在跑的, 避免端口占用/多份实例 $old = Get-Running if ($old) { Write-Output ("已有实例, 先停止: " + ($old.ProcessId -join ', ')) foreach ($p in $old) { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 2 } # -WindowStyle Hidden: 不弹 PowerShell 窗口; 进程随本脚本退出继续运行 # 注: 变量名不用 $args (PowerShell 自动变量, 赋值有副作用) $srvArgs = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $script, '-Root', $Root, '-Port', $Port) $p = Start-Process -FilePath 'powershell' -ArgumentList $srvArgs -WindowStyle Hidden -PassThru Set-Content -Path $pidFile -Value $p.Id -Encoding ascii Start-Sleep -Seconds 4 try { $code = (Invoke-WebRequest -Uri ("http://localhost:" + $Port + "/index.html") -UseBasicParsing -TimeoutSec 5).StatusCode Write-Output ("已后台启动 (PID " + $p.Id + ") HTTP " + $code + " http://localhost:" + $Port) Write-Output ("目录: " + $Root) Write-Output "停止: tools\serve-web-bg.ps1 -Stop" } catch { Write-Output ("启动后探测失败, 请检查目录/端口: " + $Root) exit 1 }