Home > Software engineering >  Make Powershell script continue running to next task whilst displaying a countdown timer
Make Powershell script continue running to next task whilst displaying a countdown timer

Time:08-25

I'm trying to execute a script in Powershell that has "time left" to run a script but also want it to continue with the next task (executing policies to Users and exporting results to .csv). The Timer must also stop when the script Ends. Currently, the time counts down to 0, then the script continues. The Script works but I want to be able to execute 2 processes at once. Please help.

Timer Counts 1st

Above the screenshot, Timer Counts down 1st before continuing to process the next part of the script. How do we get it to run both at once?

Script continues

Once the time has counted down to 0, then the above process runs.

$Variable = Import-CSV -Path 
"C:\Users\MicrosoftTeams\locationbasedpolicy1.csv"

$ApproxTime = 10

for ($seconds=$ApproxTime; $seconds -gt -1; $seconds--){
    Write-Host -NoNewLine ("`rTime Left: "   ("{0:d4}" -f $seconds))
    Start-Sleep -Seconds 1
}
foreach ($U in $Variable){
        $user = $U.userPrincipalName
        Grant-CSTeamsEmergencyCallingPolicy -policyname ECP-UK-All-Users 
 -identity  $user
        Write-Host "$($user) has been assigned Policy!" 
}

CodePudding user response:

Note: Your screenshot implies that you're using PowerShell (Core) 7 , which in turn implies that you can use Start-ThreadJob for thread-based background jobs.

With notable limitations - which may or may not be acceptable - you can do the following:

  • Use timer events to update your countdown display message, on a fixed console line that is temporarily restored.

    • Note: The solution below uses the line that would become the next output line in the course of your script's display output. This means that when the window content scrolls as more output that can fit on a single screen is produced. A better approach would be to pick a fixed line - at the top or the bottom of the window - but doing so would require saving and restoring the previous content of that line.
  • Run the commands via Start-ThreadJob, relaying their output; running in a separate thread is necessary in order for the countdown display in the foreground thread to update relatively predictably.

    • The problem with progress messages being emitted from a thread job is that they print on successive lines for every Receive-Job call - and there is no workaround that I'm aware of.

    • You can forgo progress messages by setting $ProgressPreference = 'SilentlyContinue' in the thread job's script block, but you'll obviously lose detailed progress feedback; the best you could do is provide a "still alive" kind of progress message in the foreground loop.

The following self-contained sample code shows this technique:

  • Even if the limitations are acceptable, the effort is nontrivial.
  • As an example command that shows progress messages, Invoke-WebRequest is used to download a file (the file is cleaned up afterwards).
$countDownSecs = 10

# Create a timer that will fire every second once started.
$timer = [System.Timers.Timer]::new(1000)

# Register a handler for the timer's "Elapsed" event, and pass the 
# the current cursor position and the projected end timestamp to the -Action script block.
$eventJob = Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action { 
  # Save the current cursor position
  $currCursorPos = $host.UI.RawUI.CursorPosition
  
  # Restore the cursor position for the countdown message.
  $host.UI.RawUI.CursorPosition = $Event.MessageData.CountDownCursorPos
  [Console]::Write((
    'Time Left: {0:d4}' -f [int] ($Event.MessageData.CountDownTo - [datetime]::UtcNow).TotalSeconds
    ))
  # Restore the cursor position
  $host.UI.RawUI.CursorPosition = $currCursorPos

} -MessageData @{ 
      CountDownCursorPos = $host.UI.RawUI.CursorPosition
      CountDownTo = [datetime]::UtcNow.AddSeconds($countDownSecs) 
    }

# Write the initial countdown message now, both for instand feedback and to
# "reserve" the console line for future updates.
Write-Host ('Time Left: {0:d4}' -f $countDownSecs)

# Start the timer.
$timer.Start()

# Run a command that uses progress display (Write-Progress)
# in a parallel thread with Start-ThreadJob and track its 
# progress.
try {
    $tmpFile = New-TemporaryFile
    # Run the command(s) in a thread job.
    # That way, the timer events are processed relatively predictably in the foreground thread.
    $jb = Start-ThreadJob { 
      # Activate the next line to silence progress messages.
      # $ProgressPreference = 'SilentlyContinue'
      Invoke-WebRequest -OutFile $using:tmpFile https://github.com/PowerShell/PowerShell/releases/download/v7.2.6/powershell-lts_7.2.6-1.deb_amd64.deb 
    }
    # Wait for the thread job to finish, relaying output on an ongoing basis.
    while ($jb.State -in 'NotStarted', 'Running') {
      # Note: While Receive-Job is necessary even for progress messages, such messages:
      # (a) cannot be captured or redirected
      # (b) invariably print on successive lines for every Receive-Job call;
      #     even trying to restore the cursor position after every Receive-Job call doesn't help.
      $jb | Receive-Job
      Start-Sleep -Milliseconds 50
    }
} finally {
   # Stop timer and remove the jobs and the temp. file
   $timer.Stop()
   $jb, $eventJob | Remove-Job -Force
   $tmpFile | Remove-Item
}
  • Related