[CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$PidFile, [int]$WaitSeconds = 10 ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest . (Join-Path $PSScriptRoot 'ShadowScriptCommon.ps1') $pidPath = Resolve-ShadowFile $PidFile $record = Read-ShadowJson $pidPath $deployment = Resolve-ShadowDirectory $record.deploymentDirectory if (-not (Test-ShadowPathWithin $pidPath $deployment)) { throw 'PID_FILE_OUTSIDE_RECORDED_DEPLOYMENT' } $artifact = Resolve-ShadowFile $record.shadowArtifact if ((Get-ShadowSha256 $artifact) -ne $record.shadowArtifactSha256) { throw 'RECORDED_SHADOW_ARTIFACT_HASH_MISMATCH' } $process = Get-Process -Id ([int]$record.pid) -ErrorAction Stop $actualExecutable = [IO.Path]::GetFullPath($process.Path) $recordedExecutable = [IO.Path]::GetFullPath($record.javaExecutable) if (-not $actualExecutable.Equals($recordedExecutable, [StringComparison]::OrdinalIgnoreCase)) { throw 'RECORDED_PROCESS_EXECUTABLE_MISMATCH' } $recordedStart = [DateTime]::Parse($record.processStartTimeUtc).ToUniversalTime() $actualStart = $process.StartTime.ToUniversalTime() if ([Math]::Abs(($actualStart - $recordedStart).TotalSeconds) -gt 1) { throw 'RECORDED_PROCESS_START_TIME_MISMATCH' } $commandLine = Get-ShadowCommandLine $process.Id if ([string]::IsNullOrWhiteSpace($commandLine) -or ` $commandLine -notlike '*-jar*' -or ` ($commandLine -notlike "*$artifact*" -and ` $commandLine -notlike "*$([IO.Path]::GetFileName($artifact))*")) { throw 'RECORDED_PROCESS_COMMAND_MISMATCH' } Stop-Process -Id $process.Id -Force -ErrorAction Stop $deadline = [DateTime]::UtcNow.AddSeconds($WaitSeconds) while ([DateTime]::UtcNow -lt $deadline -and ` $null -ne (Get-Process -Id $process.Id -ErrorAction SilentlyContinue)) { Start-Sleep -Milliseconds 100 } if ($null -ne (Get-Process -Id $process.Id -ErrorAction SilentlyContinue)) { throw 'SHADOW_PROCESS_DID_NOT_STOP' } $archive = Join-Path $deployment ` ("shadow.stopped.{0}.json" -f [DateTime]::UtcNow.ToString('yyyyMMddHHmmssfff')) Move-Item -LiteralPath $pidPath -Destination $archive return [pscustomobject]@{ status = 'STOPPED' pid = [int]$record.pid stoppedAtUtc = [DateTime]::UtcNow.ToString('o') archivedPidRecord = $archive }