Skip to content

Instantly share code, notes, and snippets.

@santisq
Last active November 9, 2023 02:10
Show Gist options
  • Save santisq/f2dc09390b8e617c70206d04639ee8dd to your computer and use it in GitHub Desktop.
Save santisq/f2dc09390b8e617c70206d04639ee8dd to your computer and use it in GitHub Desktop.
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()
@santisq
Copy link
Author

santisq commented Nov 9, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment