PowerShell: Runspace problem with DownloadFileAsync
Thanks stej for the nod.
Andrey, powershell has its own threadpool and each service thread keeps a threadstatic pointer to a runspace (the System.Management.Automation.Runspaces.Runspace.DefaultRunspace static member exposes this - and would be a null ref in your callbacks.) Ultimately this means it's difficult - especially in script - to use your own threadpool (as is provided by .NET for async methods) to execute scriptblocks.
PowerShell 2.0
Regardless, there is no need to play with this as powershell v2 has full support for eventing:
$client = New-Object System.Net.WebClient$url = [uri]"http://download.microsoft.com/download/6/2/F/" + "62F70029-A592-4158-BB51-E102812CBD4F/IE9-Windows7-x64-enu.exe"try { Register-ObjectEvent $client DownloadProgressChanged -action { Write-Progress -Activity "Downloading" -Status ` ("{0} of {1}" -f $eventargs.BytesReceived, $eventargs.TotalBytesToReceive) ` -PercentComplete $eventargs.ProgressPercentage } Register-ObjectEvent $client DownloadFileCompleted -SourceIdentifier Finished $file = "c:\temp\ie9-beta.exe" $client.DownloadFileAsync($url, $file) # optionally wait, but you can break out and it will still write progress Wait-Event -SourceIdentifier Finished} finally { $client.dispose()}
PowerShell v1.0
If you're stuck on v1 (this is not specifically for you as you mention v2 in the question) you can use my powershell 1.0 eventing snap-in at http://pseventing.codeplex.com/
Async Callbacks
Another tricky area in .NET is async callbacks. There is nothing directly in v1 or v2 of powershell that can help you here, but you can convert an async callback to an event with some simple plumbing and then deal with that event using regular eventing. I posted a script for this (New-ScriptBlockCallback) at http://poshcode.org/1382
Hope this helps,
-Oisin
I see that you use async call so that you can show the progress. Then you can use BitsTransfer module for that. It shows the progress by default:
Import-Module BitsTransferStart-BitsTransfer -Source $url -dest d:\temp\yourfile.zip
If you would like to transfer the file in the background, you could use something like this:
Import-Module BitsTransfer$timer = New-Object Timers.Timer$timer.Interval = 300Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action { if ($transfer.JobState -ne 'Transferring') { $timer.Enabled = 0; Write-Progress -Completed -Activity Downloading -Status done return } $progress = [int](100* $transfer.BytesTransferred/$transfer.BytesTotal) Write-Progress -Activity Downloading -Status "$progress% done" -PercentComplete $progress} -sourceId mytransfer$transfer = Start-BitsTransfer -Source $url -dest d:\temp\yourfile.zip -async$timer.Enabled = 1# after thatUnregister-Event -SourceIdentifier mytransfer$timer.Dispose()
The key parameter is -async
. It starts the transfer in background. I haven't found any event triggered by the transfer, so I query the job each second to report the state via Timers.Timer
object.
However with this solution, it is needed to unregister the event and dispose the timer. Some time ago I had problems with unregistering in the scriptblock passed as -Action
(it could be in the if
branch), so I unregister the event in separate command.
I think @oising (x0n) has some solution on his blog. He'll tell you that hopefully and that would be answer for your question.