Last active
November 9, 2023 02:10
-
-
Save santisq/f2dc09390b8e617c70206d04639ee8dd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add-Type -Assembly System.Windows.Forms | |
[System.Windows.Forms.Application]::EnableVisualStyles() | |
$MainForm = New-Object System.Windows.Forms.Form | |
$MainForm.Width = 420 | |
$MainForm.Height = 200 | |
$MainForm.FormBorderStyle = "Fixed3d" | |
$MainForm.MaximizeBox = $false | |
$MainForm.StartPosition = "CenterScreen" | |
$Button1 = New-Object System.Windows.Forms.Button | |
$Button1.Location = New-Object System.Drawing.Size(120,50) | |
$Button1.Size = New-Object System.Drawing.Size(160,50) | |
$Button1.Text = "Download selected" | |
$Button1.Name = "Button1" | |
$Button1.Add_Click({ | |
# from this event handler we just enqueue downloads, there is no need to disable | |
# this button because the current implementation will allow as many downloads as you like | |
# and the download is limited by a `SemaphoreSlim` to handle throttling. | |
if ($CheckBox1.Checked) { | |
$queue.Enqueue(@{ | |
Link = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" | |
Path = Join-Path $pwd -ChildPath ('chrome' + [guid]::NewGuid() + '.exe') | |
}) | |
} | |
if ($CheckBox2.Checked) { | |
$queue.Enqueue(@{ | |
Link = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" | |
Path = Join-Path $pwd -ChildPath ('firefox' + [guid]::NewGuid() + '.exe') | |
}) | |
} | |
}) | |
$MainForm.Controls.Add($Button1) | |
$CheckBox1 = New-Object System.Windows.Forms.CheckBox | |
$CheckBox1.Location = New-Object System.Drawing.Size(10,10) | |
$CheckBox1.Size = New-Object System.Drawing.Size(100,20) | |
$CheckBox1.Text = "Chrome" | |
$MainForm.Controls.Add($CheckBox1) | |
$CheckBox2 = New-Object System.Windows.Forms.CheckBox | |
$CheckBox2.Location = New-Object System.Drawing.Size(10,30) | |
$CheckBox2.Size = New-Object System.Drawing.Size(100,20) | |
$CheckBox2.Text = "Firefox" | |
$MainForm.Controls.Add($CheckBox2) | |
$ProgressBar1 = New-Object System.Windows.Forms.ProgressBar | |
$ProgressBar1.Location = New-Object System.Drawing.Size(5,120) | |
$ProgressBar1.Size = New-Object System.Drawing.Size(390,30) | |
$ProgressBar1.Name = "ProgressBar1" | |
$MainForm.Controls.Add($ProgressBar1) | |
$queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() | |
$rs = [runspacefactory]::CreateRunspace($Host) | |
$rs.Open() | |
# the 2 objects that we need available in the worker thread are the queue and the progressbar, | |
# if you want more objects available from the form itslef it might be better to pass in `$MainForm` | |
# then use `.Controls.Find(...)` as shown in the previous answer | |
$rs.SessionStateProxy.PSVariable.Set('queue', $queue) | |
$rs.SessionStateProxy.PSVariable.Set('progress', $ProgressBar1) | |
$ps = [powershell]::Create().AddScript({ | |
$client = [System.Net.WebClient]::new() | |
$task = $null | |
# allow only 1 download at a time | |
$throttleLimit = [System.Threading.SemaphoreSlim]::new(1, 1) | |
# NOTE: A single instance of `WebClient` can handle more than one download at the same | |
# but you need additional logic, i.e. check for `$client.IsBusy` before downloading async. | |
$registerObjectEventSplat = @{ | |
InputObject = $client | |
EventName = 'DownloadProgressChanged' | |
Action = { | |
$progress.Value = $eventArgs.ProgressPercentage | |
Write-Progress -Activity 'Downloading...' -PercentComplete $eventArgs.ProgressPercentage | |
} | |
} | |
Register-ObjectEvent @registerObjectEventSplat | |
$registerObjectEventSplat = @{ | |
InputObject = $client | |
EventName = 'DownloadFileCompleted' | |
Action = { | |
# Release the Semaphore handle here once the download is completed | |
$throttleLimit.Release() | |
"A download was completed...", "Items in Queue: $($queue.Count)" | Out-Host | |
Write-Progress -Activity 'Downloading...' -Completed | |
} | |
} | |
Register-ObjectEvent @registerObjectEventSplat | |
while ($true) { | |
# if there is nothing in queue | |
if (-not $queue.TryDequeue([ref] $task)) { | |
# sleep for a bit | |
Start-Sleep -Milliseconds 200 | |
# and go to the next iteration | |
continue | |
} | |
# else, we know there is a download here but we want to allow only 1 at a time | |
# so, this inner `while` will unblock only when the SemaphoreSlim allows it | |
while (-not $throttleLimit.Wait(200)) { } | |
# once we have the handle of the Semaphore we can start the download here | |
$client.DownloadFileAsync($task['Link'], $task['Path']) | |
"A download has started...", "Items in Queue: $($queue.Count)" | Out-Host | |
$task | Out-Host | |
} | |
}, $false) | |
$ps.Runspace = $rs | |
$task = $ps.BeginInvoke() | |
$MainForm.ShowDialog() | |
# this should be a must in your code, | |
# always dispose the resources when done | |
$ps.Stop() | |
$ps.Dispose() | |
$rs.Dispose() |
Author
santisq
commented
Nov 9, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment