I've written a custom Start-SleepNoUIHang function to sleep a windows form UI whilst not hanging it to allow for user interaction using a ForEach loop and inside that loop it calls [System.Windows.Forms.Application]::DoEvents() to prevent it from doing so.
It works as I intended but the only trouble is that the function slightly drifts past the argument $Milliseconds.
If I set that to say 5000 the timer takes around 6300 milliseconds.
I've tried to add a counter inside the ForEach loop and then break out of it once it reaches the $Milliseconds argument but that doesn't seem to work.
I didn't want to use the .net timer so I created this as a one-liner to use anywhere in the program where it was needed.
Any help here would be greatly appreciated.
Here's the code (with comments):
<#
This function attempts to pause the UI without hanging it without the need for a
timer event that does work.
The only trouble is that the timer slight drifts more than the provided
$Milliseconds argument.
#>
function Start-SleepNoUIHang {
  Param
  (
    [Parameter(Mandatory = $false, HelpMessage = 'The time to wait in milliseconds.')]
    [int]$Milliseconds
    )
  $timeBetween = 50 # This value seems to be a good value in order for the UI not to hang itself.
  $timeElapsed = 0 # Increment this to check and break out of the ForEach loop.
  # ($Milliseconds/$timeBetween)*$timeBetween # Time is the total wait time in milliseconds.
  1..($Milliseconds/$timeBetween) | ForEach {
    Start-Sleep -Milliseconds $timeBetween
    Try { [System.Windows.Forms.Application]::DoEvents() } catch{} # A try catch here in case there's no windows form.
    $timeElapsed = $timeElapsed + $timeBetween # Increment the $timeElapsed counter.
    Write-Host $timeElapsed
    # This doesn't seem to have any effect on the timer. It ends on its own accord.
    if ($timeElapsed -gt $Milliseconds) {
      Write-Host 'Break'
      break
    }
  }
}
$elapsed = [System.Diagnostics.Stopwatch]::StartNew()
Write-Host "Started at $(get-date)"
Start-SleepNoUIHang -Milliseconds 5000
Write-Host "Ended at $(get-date)"
Write-Host "Total Elapsed Time: $($elapsed.Elapsed.ToString())"
I've also tried to do a While loop replacing the ForEach loop with this but that behaved the same.
  While ( $Milliseconds -gt $timeElapsed ) {
    $timeElapsed = $timeElapsed + $timeBetween # Increment the $timeElapsed counter.
    Start-Sleep -Milliseconds $timeBetween
    Write-Host $timeElapsed
  }
