Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Get-XtrhJava { param([string]$JavaHome) if (-not $JavaHome) { $JavaHome = $env:JAVA_HOME } if ($JavaHome) { $candidate = Join-Path $JavaHome 'bin/java.exe' if (Test-Path -LiteralPath $candidate) { return (Resolve-Path -LiteralPath $candidate).Path } throw 'JAVA_HOME 中没有找到 bin/java.exe。' } $command = Get-Command java.exe -ErrorAction SilentlyContinue if ($command) { return $command.Source } throw '请先安装 Java 17 或更新版本,并设置 JAVA_HOME。' } function Protect-XtrhPrivateFile { param([Parameter(Mandatory)][string]$Path) # 私有凭证只允许当前部署用户、SYSTEM 和本机管理员读取,避免共享目录泄露。 # 读取现有对象后仅修改访问规则;不改所有者和审计规则,普通部署用户也可执行。 $access = New-Object System.Security.AccessControl.FileSecurity($Path, [System.Security.AccessControl.AccessControlSections]::Access) $access.SetAccessRuleProtection($true, $false) foreach ($rule in $access.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier])) { [void]$access.RemoveAccessRuleSpecific($rule) } $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().User foreach ($sid in @($identity, (New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')), (New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')))) { $access.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($sid, 'FullControl', 'Allow'))) } $file = Get-Item -LiteralPath $Path if ($file.PSObject.Methods['SetAccessControl']) { $file.SetAccessControl($access) } else { [System.IO.FileSystemAclExtensions]::SetAccessControl($file, $access) } } function Write-XtrhText { param([Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][string]$Content) [System.IO.File]::WriteAllText($Path, $Content, (New-Object System.Text.UTF8Encoding($false))) } function ConvertTo-XtrhPropertyValue { param([Parameter(Mandatory)][string]$Value) # Java properties 的字符编码可能因加载器不同而变化,中文节点名用 Unicode 转义保存。 $builder = New-Object System.Text.StringBuilder foreach ($character in $Value.ToCharArray()) { if ([int]$character -gt 127) { [void]$builder.Append(('\u{0:x4}' -f [int]$character)) } elseif ($character -eq '\') { [void]$builder.Append('\\') } else { [void]$builder.Append($character) } } return $builder.ToString() } function Get-XtrhOwnedProcess { param([Parameter(Mandatory)][string]$InstallRoot, [Parameter(Mandatory)]$Record) $process = Get-Process -Id ([int]$Record.pid) -ErrorAction SilentlyContinue if (-not $process) { return $null } $live = Get-CimInstance Win32_Process -Filter ('ProcessId=' + [int]$Record.pid) $expectedJar = [System.IO.Path]::GetFullPath((Join-Path $InstallRoot 'xtrh-services.jar')) $javaPath = [System.IO.Path]::GetFullPath([string]$Record.javaPath) # PID 会被操作系统复用;同时校验启动时间、Java 路径和 -jar 参数才能结束进程。 if (-not $live -or $process.StartTime.ToUniversalTime().Ticks -ne [long]$Record.startTicks -or [System.IO.Path]::GetFullPath([string]$live.ExecutablePath) -ne $javaPath) { throw '进程身份不匹配;已拒绝结束该 PID。' } $match = [regex]::Match([string]$live.CommandLine, '(?i)(?:^|\s)-jar\s+(?:"(?[^"]+)"|(?\S+))') if (-not $match.Success -or [System.IO.Path]::GetFullPath($match.Groups['jar'].Value) -ne $expectedJar) { throw '进程启动文件与本部署目录不一致;已拒绝结束该 PID。' } return $process }