Search code examples
windowspowershell

How to stop a fully stuck powershell script when keyboard interrupt and `Stop-Job` not working?


The script to run used to be simply, just pin a given folder to windows quick access, like what this answer doing pintohome.

$o = new-object -com shell.application
$o.Namespace('c:\My Folder').Self.InvokeVerb("pintohome")

It works well for years, but after some window upgrade, i found it will get fully stucked on windows version 10.0.19045, ps version 5.1.19041.5007.

Then i tried using Start-Job and Wait-Job to stop it after a timeout like this:

$job = Start-Job -ScriptBlock {
    $o = new-object -com shell.application
    $o.Namespace('c:\My Folder').Self.InvokeVerb("pintohome")
}

if (Wait-Job -Job $job -Timeout 3) {
    Write-Output "The folder has been pinned successfully."
} else {
    Stop-Job -Job $job
    Write-Error "The operation took too long to run and has been stopped."
}

Remove-Job -Job $job

But it still stucked and even ctrl+c will not be able to stop it, i have to close the powershell window to stop.

I have tried capture the error like this and still get stucked and no output.

cmd.exe /c powershell.exe D:\scripts\test.ps1 1>D:\scripts\log.txt 2>&1

According to this discussion Pin and Unpin from Quick Access using PowerShell, it may takes a long time to run it. But i found it will not stopped even after 10 minutes.

So how can i stop it after a timeout and is it failed because windows have done some restrictions?


Solution

  • put the script into a process can correctly timeout the running script, like this:

    $OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8;
    
    $currentPath = $PSScriptRoot
    
    $scriptBlock = {
        param($scriptPath)
        $shell = New-Object -ComObject Shell.Application
        $shell.Namespace($scriptPath).Self.InvokeVerb("'pintohome'")
    }.ToString()
    
    $arguments = "-Command & {$scriptBlock} -scriptPath '$currentPath'"
    $process = Start-Process powershell -ArgumentList $arguments -NoNewWindow -PassThru
    
    $timeout = 5
    if (-not $process.WaitForExit($timeout * 1000)) {
        try {
            $process.Kill()
            Write-Error "Process execution timed out (${timeout}s), forcefully terminated"
            exit 1
        }
        catch {
            Write-Error "Error occurred while terminating process: $_"
            exit 1
        }
    }