Author Posts

February 3, 2017 at 4:15 pm

I'm troubleshooting the logic of a script that does some long BG operation. I need user to be able to abort it in GUI. Here's a simplified version, the script sleeps 3 sec in a runspace, then returns "OK" that is picked up by a Winform timer event. If user aborts it by pressing 'Stop' button which calls $Powershell.Stop() method it works fine, but if hes then re-initiates the job – $AsyncResult returns nothing. If you press 'Do' button one more time – it gets back to normal.

That's how it works on PS 5.0. I've checked it on 2.0 – it works as expected there, it returns result every time, no matter if previous job finished normally or was forced quited. What am I missing here?

Add-Type -AssemblyName System.Windows.Forms

$form = New-Object 'System.Windows.Forms.Form'
$buttonDo = New-Object 'System.Windows.Forms.Button'
$buttonStop = New-Object 'System.Windows.Forms.Button'
$timer = New-Object 'System.Windows.Forms.Timer'

$BGScriptBlock = { 
    $synchash.form.Text = "Job is being done..."
    start-sleep 3
    return "OK"
}

$synchash = [Hashtable]::Synchronized(@{  })
$script:Powershell = [PowerShell]::Create().AddScript($BGScriptBlock).AddArgument($PC)
$sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$runspace = [RunspaceFactory]::CreateRunspace($sessionstate)
$runspace.ApartmentState = "STA"
$runspace.ThreadOptions = "ReuseThread"
$runspace.Open()
$runspace.SessionStateProxy.SetVariable("synchash", $synchash)
$Powershell.Runspace = $runspace

$buttonDo_Click = {
    $script:AsyncResult = $Powershell.BeginInvoke()
    $timer.Enabled = $true; $timer.Start(); $script:tick = 0
}

$buttonStop_Click = {
    $Powershell.Stop()
    $form.Text = 'Stopped'
}

$TimerTick = {
    $script:tick++;  write-host  $script:tick; 
    switch ($script:Powershell.InvocationStateInfo.State)
    {
        'Stopped'{write-host "Stopped" }
        'Completed' { $result = $Powershell.EndInvoke($script:AsyncResult); write-host "Completed, result: $result"}
        {$_ -match "Stop|Completed"} {$timer.Stop(); $timer.Enabled = $false; $form.Text = 'Done'}
    }
}

$form.Controls.Add($buttonDo)
$form.Controls.Add($buttonStop)
$form.Size = '400, 150'
$form.Text = 'Form'
$form.Topmost = $true
$buttonDo.Location = '73, 37'
$buttonDo.Size = '100, 30'
$buttonDo.Text = 'Do'
$buttonDo.add_Click($buttonDo_Click)
$buttonStop.Location = '209, 37'
$buttonStop.Size = '100, 30'
$buttonStop.Text = 'Stop'
$buttonStop.add_Click($buttonStop_Click)
$timer.Interval = 1000
$timer.add_Tick($TimerTick)
$synchash.form = $form

$form.ShowDialog()

Well, in real world I'm trying to initiate a PSRemoting session over HHTPS/SSL, the strange thing about is that if the session is initiated against a server that is not set up for HTTPS/SSL it waits a good minute before the time-out occures. In PS 5.0 I can force quit it using $Powershell.Close() or $Powershell.Dispose() or even $runspace.Close() but in 2.0 any of these doesn't kill the thread, it stumbles upon a non-respoding command and freezes until it finishes. These methods are executed but only a minute later. Is there something that can be done about it?

May 15, 2017 at 1:02 pm

I think you want to not Stop so much as "Pause," from what you're saying. Which means you'd need to have a spot in your script which checks to see if it's been asked to pause, and enters a Start-Sleep loop until it is resumed.

PowerShell's not really designed for this type of thing, so whatever you do will end up being a little hack-y.