1

My script opens a WebDAV session to my server, but on some occasions the command is stuck, without any exception. The command is $Session.Open($SessionOptions). To monitor the command with a timeout, I was thinking to run this command as a job.

I was trying start-job -scriptblock {}, but couldn't get my head around how to get the opened session (object?) out of the job.

So I decided to go with Invoke-Command ($Session.Open($SessionOptions)) -computerName "." -AsJob -JobName "Try", I get this exception about the scriptblock:

enter image description here

But the session is opened just fine! So, what am I doing wrong? Also, I'll be happy to know how can I put a timer on this. I tried the following, but it didn't work Invoke-Command ($Session.Open($SessionOptions)) -computerName "." -AsJob -JobName "Try" | Get-Job | where {$_.State -eq "Running" -and (((Get-Date)-$_.PSBeginTime).Seconds -gt 5 ) } | Stop-Job

1
  • As for what you're doing wrong: Like Start-Job, Invoke-Command requires the code to be executed to be passed in the form of a script block ({ ... }). By contrast, ($Session.Open($SessionOptions)) executes the method call up front, and tries to use its result as a script block, which is $null in your case and therefore fails. Commented Mar 22 at 12:16

2 Answers 2

1

A normal job wouldn't solve your problem, there is no way you could pass-in those objects to a new process created by the job and still use them.

The solution would be to either use a ThreadJob, for this approach you would require the module if you're using Windows PowerShell 5.1 otherwise it's built-in in PowerShell 7.

To install it:

Instal-Module ThreadJob -Scope CurrentUser

To pass-in the objects $session and $sessionOptions you can use the $using: scope modifier.

The code using ThreadJob would be:

$job = Start-ThreadJob { ($using:Session).Open($using:SessionOptions) }
if ($job | Wait-Job -Timeout 5) {
    # here the job completed successfully
    $job | Receive-Job -AutoRemoveJob -Wait
}
else {
    # here the job didn't complete or failed
    Write-Error "Task didn't complete in time. Stopping."
    $job | Stop-Job -PassThru | Remove-Job
}

The other approach that doesn't require anything but it's a lot more code is to invoke your code in a new runspace using a PowerShell instance:

try {
    $ps = [powershell]::Create().AddScript({
        param($session, $options)

        $session.Open($options)
    }).AddArgument($Session).AddArgument($SessionOptions)

    $startTime = [datetime]::Now
    $iasync = $ps.BeginInvoke()
    while (-not $iasync.AsyncWaitHandle.WaitOne(200)) {
        if ([datetime]::Now - $startTime -gt '00:00:05') {
            # here the task didn't complete in time, stop it
            Write-Error "Task didn't complete in time. Stopping."
            # do your best to stop
            $null = $ps.BeginStop($null, $null)
            # and break the loop
            break
        }
    }

    # here the task completed successfully
    if ($ps.InvocationStateInfo.State -eq 'Completed') {
        # get the output if any
        $ps.EndInvoke($iasync)
    }
}
finally {
    $ps.Dispose()
}
Sign up to request clarification or add additional context in comments.

4 Comments

It hangs on $ps.Stop()
Try using $null = $ps.BeginStop($null, $null) @POL (see the update)
This what's shown in red: : Task didn't complete in time. Stopping. + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException
@POL unclear what your expectation is. The error is shown if the task failed to call .Open(...) in the desired time (5 seconds). If the error is bothering you can simply remove that line.
0

Ok, thanks to @Santiago Squarzon I discovered the benefits of a runspace.

If anyone is interested, you can improve a bit and add an object of type System.Management.Automation.PSDataCollection[psobject] to inspect whatever is in the pipeline of the runspace, while iasync is still running. Like so:

$Inspector= new-object 'System.Management.Automation.PSDataCollection[psobject]' 
$iasync = $ps.BeginInvoke($Inspector,$Inspector)

More details here:

https://learn-powershell.net/2016/02/14/another-way-to-get-output-from-a-powershell-runspace/

And when typing $Inspector you get the results, but bare in mind that by doing so, you're trapped in this runspace until it ends (in my case it is never).

In my case, involving $ps.BeginStop($null, $null) didn't stop the iasync object from running

enter image description here

The only way to stop it cleanly was to use $Session.Abort() (This is a WinSCP session).

enter image description here

Every .Dispose() I tried to execute, like $PS.Dispose(), $RunSpace.Dispose(), while $iasync was running, resulted in a script-hang.

I wonder what other actions are able to force-complete iasync (or AsyncObject in my case) and terminate the WinSCP session.

Assigning $null to $PS, $RunSpace, $iasync didn't stop the WinSCP session. Let's say, .Abort() wasn't available, is $Session=$null a smart move??

Here's my final code:

$ParameterList=@{
    Session= $Session
    SessionOptions=$SessionOptions
  }             
  $RunSpace= [runspacefactory]::CreateRunspace()             
  $PS= [powershell]::Create()             
  $PS.RunSpace= $RunSpace             
  $RunSpace.Open()             
  $null= $PS.AddScript({
     Param($Session,$SessionOptions)
     $Session.Open($SessionOptions)
       
  }).AddParameters($ParameterList)
  
  # Using the optional inspector object
  $Inspector= new-object 'System.Management.Automation.PSDataCollection[psobject]'
  $AsyncObject= $PS.BeginInvoke($Inspector,$Inspector)
  #$AsyncObject= $PS.BeginInvoke()
  $StartTime= Get-Date
  Do{
   # Timer for 10 seconds
  }While ((([DateTime]::Now)-$StartTime).Seconds -lt 10)
  
  If ($AsyncObject.IsCompleted -ne "True"){
    write-host "$($Session.Output)"
    write-host "`n`nAborting"
    $Session.Abort()
    $Session.Dispose()
    $Inspector.Dispose()           
    $PS.Dispose()   
  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.