this show 0%:
this show 4%:
the realted script:
$iWrapper = [hashtable]::Synchronized(@{ i = 0 })
$srcfile = "C:\Users\wnune\OneDrive\Escritorio\imagenes\cardlist.txt"
$urls = Get-Content $srcfile
$lines = 0
switch -File $srcfile { default { ++$lines } }
Write-Host "Total Urls to process: $lines "
Write-Progress -Activity "Downloading files" -Status "In progress" -PercentComplete $i;
$urls | ForEach-Object -Parallel {
try {
$url = $_
$filename = Split-Path $url -Leaf
$destination = "C:\Users\wnune\OneDrive\Escritorio\imagenes\$filename"
$ProgressPreference = 'SilentlyContinue'
$response = Invoke-WebRequest -Uri $url -ErrorAction SilentlyContinue
if ($response.StatusCode -ne 200) {
Write-Warning "============================================="
Write-Warning "Url $url return Error. "
continue
}
if (Test-Path $destination) {
Write-Warning "============================================="
Write-Warning "File Exist in Destination: $filename "
continue
}
$job = Start-BitsTransfer -Source $url -Destination $destination -Asynchronous
while (($job | Get-BitsTransfer).JobState -eq "Transferring" -or ($job | Get-BitsTransfer).JobState -eq "Connecting")
{
Start-Sleep -m 250
}
Switch(($job | Get-BitsTransfer).JobState)
{
"Transferred" {
Complete-BitsTransfer -BitsJob $job
}
"Error" {
$job | Format-List
}
}
}
catch
{
Write-Warning "============================================="
Write-Warning "There was an error Downloading"
Write-Warning "url: $url"
Write-Warning "file: $filename"
Write-Warning "Exception Message:"
Write-Warning "$($_.Exception.Message)"
}
$j = ++($using:iWrapper).i
$k = $using:lines
$percent = [int](100 * $j / $k)
Write-Host "PercentCalculated: $percent"
Write-Host "Progress bar not Show the %"
Write-Progress -Activity "Downloading files " -Status " In progress $percent" -PercentComplete $percent
}
Write-Progress -Activity "Downloading files" -Status "Completed" -Completed
If I am passing in -PercentComplete $percent
which is an integer
why does the progress bar not receive it correctly?
I have verified that the script and the environment are correctly configured but I cannot validate because the progress bar is not seen correctly.
Note:
A potential future enhancement has been proposed in GitHub issue #13433, suggesting adding parameter(s) such as
-ShowProgressBar
toForEach-Object -Parallel
so that it would automatically show a progress bar based on how many parallel threads have completed so far.A simpler solution than the ones below can be found in miljbee's helpful answer, which simply delegates updating the progress play to an additional, non-parallel
ForEach-Object
call, which receives the outputs from the parallel threads as they are being emitted.Leaving the discussion about whether
Start-BitsTransfer
alone is sufficient aside:At least as of PowerShell v7.3.1, it seemingly is possible to call
Write-Progress
from inside threads created byForEach-Object
-Parallel
, based on a running counter of how many threads have exited so far.However, there are two challenges:
You cannot directly update a counter variable in the caller's runspace (thread), you can only refer to an object that is an instance of a .NET reference type in the caller's runspace...
...and modifying such an object, e.g. a hashtable must be done in a thread-safe manner, such as via
System.Threading.Monitor
.Note that I don't know whether calling
Write-Progress
from different threads is officially supported, but it seems to work in practice, at least when the call is made in a thread-safe manner, as below.ForEach-Object -Parallel
or not, If you callWrite-Progress
too quickly in succession, only the first in such a sequence of calls takes effect:Start-Sleep -Milliseconds 200
call after eachWrite-Progress
call.5
threads are allowed to run concurrently by default; use-ThrottleLimit
to change that.A simple proof of concept:
An alternative approach in which the calling thread centrally tracks the progress of all parallel threads:
Doing so requires adding the
-AsJob
switch toForEach-Object -Parallel
, which, instead of the synchronous execution that happens by default, starts a (thread-based) background job, and returns a[System.Management.Automation.PSTasks.PSTaskJob]
instance that represents all parallel threads as PowerShell (thread) jobs in the.ChildJobs
property.A simple proof of concept:
While this is more work and less efficient due to the polling loop, it has two advantages:
The script blocks running in the parallel threads need not be burdened with progress-reporting code.
The polling loop affords the opportunity to perform other foreground activity while the parallel threads are running in the background.
The bug discussed above needn't be worked around, assuming your
Start-Sleep
interval in the polling loop is at least 200 msecs.