spencerstewart
spencerstewart

Reputation: 151

Test-Path timeout for PowerShell

I'm trying to routinely check the presence of particular strings in text files on hundreds of computers on our domain.

foreach ($computer in $computers) {
    $hostname = $computer.DNSHostName
    if (Test-Connection $hostname -Count 2 -Quiet) {
        $FilePath = "\\" + $hostname + "c$\SomeDirectory\SomeFile.txt"
        if (Test-Path -Path $FilePath) {
            # Check for string
        }
    }
}

For the most part, the pattern of Test-Connection and then Test-Path is effective and fast. There are certain computers, however, that ping successfully, but Test-Path takes around 60 seconds to resolve to FALSE. I'm not sure why, but it may be a domain trust issue.

For situations like this, I would like to have a timeout for Test-Path that defaults to FALSE if it takes more than two seconds.

Unfortunately, the solution in a related question (How can I wrap this PowerShell cmdlet into a timeout function?) does not apply to my situation. The proposed do-while loop gets hung up in the code block.

I've been experimenting with Jobs, but it appears even this won't force quit the Test-Path command:

Start-Job -ScriptBlock {param($Path) Test-Path $Path} -ArgumentList $Path | Wait-Job -Timeout 2 | Remove-Job -Force

The job continues to hang in the background. Is this the cleanest way I can achieve my requirements above? Is there a better way to timeout Test-Path so the script doesn't hang besides spawning asynchronous activities?

Upvotes: 2

Views: 5545

Answers (1)

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174505

Wrap your code in a [powershell] object and call BeginInvoke() to execute it asynchronously, then use the associated WaitHandle to wait for it to complete only for a set amount of time.

$sleepDuration = Get-Random 2,3
$ps = [powershell]::Create().AddScript("Start-Sleep -Seconds $sleepDuration; 'Done!'")

# execute it asynchronously
$handle = $ps.BeginInvoke()

# Wait 2500 milliseconds for it to finish
if(-not $handle.AsyncWaitHandle.WaitOne(2500)){
    throw "timed out"
    return
}

# WaitOne() returned $true, let's fetch the result
$result = $ps.EndInvoke($handle)

return $result

In the example above, we randomly sleep for either 2 or 3 seconds, but set a 2 and a half second timeout - try running it a couple of times to see the effect :)

Upvotes: 7

Related Questions