private/functions/Start-PowerStubUpdateCheck.ps1

<#
.SYNOPSIS
  Starts a background check of whether a stub's repository is behind its remote.

.DESCRIPTION
  Runs private/scripts/Update-PowerStubRemoteStatus.ps1 in a hidden, detached pwsh process
  and returns immediately. A separate process (not a job) is used because pstb often runs in
  short-lived processes, which would take a job down with them.

  Only one check per stub runs at a time: a claim file is created with CreateNew, which
  succeeds in exactly one process. A claim older than 30 minutes belongs to a check that
  died, and is taken over.

  If the process cannot be started, a CheckFailed result is recorded so the next command
  waits for the normal interval instead of trying again straight away.

.PARAMETER StubPath
  The stub folder to check.

.PARAMETER StateFile
  Where the background check records its result.
#>


function Start-PowerStubUpdateCheck {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$StubPath,

        [Parameter(Mandatory = $true)]
        [string]$StateFile
    )

    $lockFile = "$StateFile.lock"
    [IO.Directory]::CreateDirectory((Split-Path -Parent $StateFile)) | Out-Null
    try {
        [IO.File]::Open($lockFile, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None).Dispose()
    }
    catch {
        if (-not [IO.File]::Exists($lockFile)) { throw }
        $claimAge = [datetime]::UtcNow - [IO.File]::GetLastWriteTimeUtc($lockFile)
        if ($claimAge.TotalMinutes -lt 30) { return }
        [IO.File]::SetLastWriteTimeUtc($lockFile, [datetime]::UtcNow)
    }

    try {
        $pwsh = Join-Path $PSHOME $(if ($IsWindows) { 'pwsh.exe' } else { 'pwsh' })
        $script = Join-Path $Script:ModulePath 'private/scripts/Update-PowerStubRemoteStatus.ps1'
        $arguments = @('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $script,
            '-Path', $StubPath, '-StateFile', $StateFile)

        # On Linux and macOS, setsid detaches the check from the terminal, so closing the
        # terminal does not kill it and ssh cannot prompt on it for a passphrase.
        $setsid = if (-not $IsWindows) { Get-Command setsid -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 }
        $startInfo = if ($setsid) {
            $arguments = @($pwsh) + $arguments
            [Diagnostics.ProcessStartInfo]::new($setsid.Source)
        }
        else {
            [Diagnostics.ProcessStartInfo]::new($pwsh)
        }
        foreach ($argument in $arguments) { $startInfo.ArgumentList.Add($argument) }
        $startInfo.UseShellExecute = $false
        $startInfo.CreateNoWindow = $true
        # Keep the check off the user's console entirely.
        $startInfo.RedirectStandardInput = $true
        $startInfo.RedirectStandardOutput = $true
        $startInfo.RedirectStandardError = $true
        # Git and Git Credential Manager prompts are disabled by Get-PowerStubGitInfo; this stops ssh's GUI askpass.
        $startInfo.Environment['SSH_ASKPASS_REQUIRE'] = 'never'

        $process = [Diagnostics.Process]::Start($startInfo)
        $process.StandardInput.Close()
        $process.Dispose()
    }
    catch {
        Write-Debug "Could not start the update check for '$StubPath': $_"
        Write-PowerStubUpdateCheckState -File $StateFile -Path $StubPath -Status 'CheckFailed'
        Remove-Item -LiteralPath $lockFile -Force -ErrorAction SilentlyContinue
    }
}