Command Line Countdown Timer Skipping a Second - powershell

I've been looking to add a simple countdown timer to my script. I found this one that seemed to do the trick and modified it slightly to only display seconds since I don't need anything more than that.
When I run it, it will skip the 2nd second in the countdown. For example, if I ran Start-CountdownTimer -Seconds 10, the output will be (this is split into separate lines for demo purposes since it'll be on the same line):
| Starting in 10s ...
/ Starting in 8s ...
- Starting in 7s ...
\ Starting in 6s ...
| Starting in 5s ...
/ Starting in 4s ...
- Starting in 3s ...
\ Starting in 2s ...
| Starting in 1s ...
/ Starting in 0s ...
Any ideas how I can fix this? This is the (slightly modified) code from the link above:
Function Start-CountdownTimer{
param (
<#[int]$Days = 0,
[int]$Hours = 0,
[int]$Minutes = 0,#>
[int]$Seconds = 0,
[int]$TickLength = 1
)
$t = New-TimeSpan <#-Days $Days -Hours $Hours -Minutes $Minutes#> -Seconds $Seconds
$origpos = $host.UI.RawUI.CursorPosition
$spinner =#('|', '/', '-', '\')
$spinnerPos = 0
$remain = $t
$d =( get-date) + $t
$remain = ($d - (get-date))
while ($remain.TotalSeconds -gt 0){
Write-Host (" {0} " -f $spinner[$spinnerPos%4]) -ForegroundColor White -NoNewline
write-Host (" Starting in {0:d1}s ..." -f $remain.Seconds)
$host.UI.RawUI.CursorPosition = $origpos
$spinnerPos += 1
Start-Sleep -seconds $TickLength
$remain = ($d - (get-date))
}
$host.UI.RawUI.CursorPosition = $origpos
}

If you want a solution that never skips a countdown step - at the expense of running a bit longer than the specified countdown duration (increasingly so, the longer the duration) - see Santiago Squarzon's helpful answer.
If you want a solution that may skip a countdown step, under heavy system load, but remains close to the specified countdown duration, irrespective of its length, see below.
As Theo points out, your implementation is somewhat inefficient, which increases the risk of skipping a step.
The following is a streamlined implementation that lessens the risk of skipping, though under heavy system load it may still occur.
Note:
The function does not work properly in the Windows PowerShell ISE, but the ISE is obsolescent and there have always been reasons not to use it (bottom section); by contrast, the function does work in the ISE's successor, Visual Studio Code with its PowerShell extension
Instead of using $host.UI.RawUI, a simple CR ("`r") is issued at the start of each countdown step's Write-Host -NoNewLine call, which causes the cursor to return to the start of the line and overwrite the existing line.
To avoid flickering when the line is overwritten, the cursor is temporarily turned off, using [Console]::CursorVisible]
Strictly speaking, based on PowerShell's description of its approved verbs, your function should be named Invoke-CoutdownTimer, given that it executes synchronously, though the standard Start-Sleep is similarly misnamed - see GitHub docs issue #4474 for a discussion.
Function Start-CountdownTimer {
param (
[Parameter(Mandatory)]
[ValidateRange(0,999)]
[int] $Seconds,
[int] $SleepIntervalMilliSeconds = 1000
)
$spinner = '|', '/', '-', '\'
$spinnerPos = 0
[Console]::CursorVisible = $false
$dtEnd = [datetime]::UtcNow.AddSeconds($Seconds)
try {
while (($remainingSecs = ($dtEnd - [datetime]::UtcNow).TotalSeconds) -gt 0) {
Write-Host -NoNewline ("`r {0} " -f $spinner[$spinnerPos++ % 4]) -ForegroundColor White
Write-Host -NoNewLine (" Starting in {0,3}s ... " -f [Math]::Ceiling($remainingSecs))
Start-Sleep -Milliseconds ([Math]::Min($SleepIntervalMilliSeconds, $remainingSecs * 1000))
}
Write-Host
}
finally {
[Console]::CursorVisible = $true
}
}

Well this should be an improvement to your function and also should solve the problem of jumping an extra second back. I personally do not agree with using $host and would use Clear-Host instead so the function is compatible with PowerShell ISE too but you can change that as you please.
Function Start-CountdownTimer{
param(
[CmdletBinding(DefaultParameterSetName = 'Time')]
[parameter(ParameterSetName = 'Hours')]
[ValidateRange(1, [int]::MaxValue)]
[int]$Hours = 0,
[parameter(ParameterSetName = 'Minutes')]
[ValidateRange(1, [int]::MaxValue)]
[int]$Minutes = 0,
[parameter(ParameterSetName = 'Seconds', Position = 0)]
[ValidateRange(1, [int]::MaxValue)]
[int]$Seconds = 0
)
$now = [datetime]::Now
$spinner = #('|', '/', '-', '\')
$spinnerPos = 0
$origpos = $host.UI.RawUI.CursorPosition
$runTime = switch($PSBoundParameters.Keys)
{
Hours { $now.AddHours($PSBoundParameters[$PSBoundParameters.Keys]) }
Minutes { $now.AddMinutes($PSBoundParameters[$PSBoundParameters.Keys]) }
Seconds { $now.AddSeconds($PSBoundParameters[$PSBoundParameters.Keys]) }
}
$runTime = $runTime.TimeOfDay.TotalSeconds - $now.TimeOfDay.TotalSeconds
while($runTime)
{
Write-Host (" {0} " -f $spinner[$spinnerPos%4]) -NoNewline
Write-Host " Starting in ${runTime}s ..."
$host.UI.RawUI.CursorPosition = $origpos
$spinnerPos += 1
$runTime--
Start-Sleep -Seconds 1
}
}

Related

Math unexpectedly produces infinity '∞'

The goal is to have a progress bar monitoring an external process that writes the step it is on into a watch file.
If I do not have a Start-Sleep in the loop, it will produce messages about not being able to convert infinity. Why is this? I am willing to have some sleep time, but why is it needed and what is the minimum time needed to sleep?
PS C:\src\t\pb> .\Monitor-Progress2.ps1 -TotalCount 5 -WatchFile wf.txt -Verbose
New-TimeSpan : Cannot bind parameter 'Seconds'. Cannot convert value "∞" to type "System.Int32". Error: "Value was either too large or too small for an Int32."
At C:\src\t\pb\Monitor-Progress2.ps1:46 char:37
+ ... an -Seconds (($ts.TotalSeconds / $currentCount) * ($TotalCount - $cur ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-TimeSpan], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.NewTimeSpanCommand
Here is the code. The same problem occurs on PowerShell 5.1 and 6.0. All I do to run it is to ECHO>wf.txt 1, then ECHO>wf.txt 2, etc. Sometimes the error occurs on step two, but sometimes on step 3.
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[int]$TotalCount
,[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_ -PathType 'Leaf'})]
[string]$WatchFile
)
$currentcount = 0
$previouscount = $currentcount
$starttimestamp = Get-Date
while ($currentcount -lt $TotalCount) {
$currentcount = [int32](Get-Content $WatchFile)
### Write-Verbose $currentcount
if (($currentcount -lt $TotalCount) -and ($currentcount -ne $previouscount)) {
$ts = $(Get-Date) - $starttimestamp
### Write-Verbose $ts.TotalSeconds
### Write-Verbose $currentcount
### Write-Verbose ($ts.TotalSeconds / $currentcount)
### Write-Verbose ($TotalCount - $currentcount)
### Write-Verbose (($ts.TotalSeconds / $currentcount) * ($TotalCount - $currentcount))
$et = New-TimeSpan -Seconds (($ts.TotalSeconds / $currentCount) * ($TotalCount - $currentcount))
$runningstatus = "Long process running for {0:%d} days {0:%h} hours {0:%m} minutes {0:%s} seconds" -f $ts
$completionstatus = "Estimated completion in {0:%d} days {0:%h} hours {0:%m} minutes {0:%s} seconds" -f $et
Write-Progress -Activity $runningstatus `
-Status $completionstatus `
-percentComplete ($currentcount / $TotalCount*100)
$previouscount = $currentcount
}
#Start-Sleep -Seconds 1
}
While your code didn't process a single entity, your $currentcount is zero, therefore your naive ETA calculation returns infinity. You should check if you can calculate ETA first by comparing current count with zero.
EDIT: You have done an implicit conversion of Get-Content which returns an array of strings, so if your file has a newline in it, you get a conversion error and your $currentcount remains zero, thus you divide by zero to get that infinity. You should either use [IO.File]::ReadAllText() method, or use only the first line (with index 0) for parsing. Or you should write to the file with -NoNewLine flag to Out-File cmdlet.

Powershell script exits ForEach-Object loop prematurely [duplicate]

This question already has answers here:
Why does 'continue' behave like 'break' in a Foreach-Object?
(4 answers)
Closed 5 years ago.
So I've been writing a script that will take all of the data that is stored in 238 spreadsheets and copy it into a master sheet, as well as 9 high level report sheets. I'm really not sure why, but after a specific document, the script ends prematurely without any errors being posted. It's very strange. I'll post some anonymized code below so maybe someone can help me find the error of my ways here.
As far as I can tell, the document that it exits after is fine. I don't see any data errors in it, and the info is copied successfully to the master document before powershell just calls it quits on the script completely.
I've tried changing the size of the data set by limiting only to the folder that contains the problem file. It still ends after the same file with no error output. I cannot upload the file due to company policy, but I really don't see anything different about the data on that one file when compared to any other file of the same nature.
Also, apologies in advance for the crappy code. I'm not a developer and have been relearning powershell since it's the only tool available to me right now.
$StartTime = Get-Date -Format g
Write-Host $StartTime
pushd "Z:\Shared Documents\IO"
$TrackTemplate = "C:\Users\USERNAME\Desktop\IODATA\MasterTemplate.xlsx"
# Initialize the Master Spreadsheet
$xlMaster = New-Object -ComObject Excel.Application
$xlMaster.Visible = $False
$xlMaster.DisplayAlerts = $False
$MasterFilePath = "C:\Users\USERNAME\Desktop\IODATA\Master.xlsx"
Copy-Item $TrackTemplate $MasterFilePath
$wbMaster = $xlMaster.Workbooks.Open($MasterFilePath)
$wsMaster = $wbMaster.Worksheets.Item(2)
$wsMaster.Unprotect("PASSWORD")
$wsMasterRow = 3
# Initialize L4 Document Object
$xlL4 = New-Object -ComObject Excel.Application
$xlL4.Visible = $False
$xlL4.DisplayAlerts = $False
# Initialize object for input documents
$xlInput = New-Object -ComObject Excel.Application
$xlInput.Visible = $False
$xlInput.DisplayAlerts = $False
# Arrays used to create folder path names
$ArrayRoot = #("FOLDER1","FOLDER2","FOLDER3","FOLDER4","FOLDER5","FOLDER6","FOLDER7","FOLDER8","FOLDER9")
$ArrayShort = #("SUB1","SUB2","SUB3","SUB4","SUB5","SUB6","SUB7","SUB8","SUB9")
# $counter is used to iterate inside the loop over the short name array.
$counter = 0
$FileNumber = 0
$TotalFiles = 238
$ArrayRoot | ForEach-Object {
$FilePathL4 = "C:\Users\USERNAME\Desktop\IODATA\ROLLUP\" + $ArrayShort[$counter] + "_DOC_ROLLUP.xlsx"
Copy-Item $TrackTemplate $FilePathL4
$wbL4 = $xlL4.Workbooks.Open($FilePathL4)
$wsL4 = $wbL4.Worksheets.Item(2)
$wsL4.Unprotect("PASSWORD")
$wsL4Row = 3
If ($ArrayShort[$counter] -eq "SUB7") {$FilePath = "Z:\Shared Documents\IO\" + $_ + "\" + $ArrayShort[$counter] + " - DOC v2\"}
Else {$FilePath = "Z:\Shared Documents\IO\" + $_ + "\!" + $ArrayShort[$counter] + " - DOC v2\"}
Get-ChildItem -Path $FilePath | ForEach-Object {
If ($_.Name -eq "SPECIFIC_DOC.xlsx") {Continue}
$FileNumber += 1
Write-Host "$FileNumber / $TotalFiles $_"
$wbInput = $xlInput.Workbooks.Open($_.FullName)
$wsInput = $wbInput.Worksheets.Item(2)
$wsInputLastRow = 0
#Find the last row in the Input document
For ($i = 3; $i -le 10000; $i++) {
If ([string]::IsNullOrEmpty($wsInput.Cells.Item($i,1).Value2)) {
$wsInputLastRow = $i - 1
Break
}
Else { Continue }
}
[void]$wsInput.Range("A3:AC$wsInputLastRow").Copy()
Start-Sleep -Seconds 1
[void]$wsMaster.Range("A$wsMasterRow").PasteSpecial(-4163)
Start-Sleep -Seconds 1
$wsMasterRow += $wsInputLastRow - 2
[void]$wsL4.Range("A$wsL4Row").PasteSpecial(-4163)
Start-Sleep -Seconds 1
$wsL4Row += $wsInputLastRow - 2
$wbInput.Close()
$wbMaster.Save()
}
$counter += 1
$wsL4.Protect("PASSWORD")
$wbL4.Save()
$wbL4.Close()
}
$wsMaster.Protect("PASSWORD")
$wbMaster.Save()
$wbMaster.Close()
$xlMaster.Quit()
$EndTime = Get-Date -Format g
$TimeTotal = New-Timespan -Start $StartTime -End $EndTime
Write-Host $TimeTotal
To continue pipeline processing with the next input object, use return - not continue - in the script block passed to the ForEach-Object cmdlet.
The following simple example skips the 1st object output by Get-ChildItem and passes the remaining ones through:
$i = 0; Get-ChildItem | ForEach-Object{ if ($i++ -eq 0) { return }; $_ }
There is currently (PSv5.1) no direct way to stop the processing of further input objects - for workarounds, see this answer of mine.
By contrast, as you've discovered, break and continue only work as expected in the script block of a for / foreach statement, not directly in the script block passed to the ForeEach-Object cmdlet:
For instance, the following produces no output (using break would have the same effect):
$i = 0; Get-ChildItem | ForEach-Object{ if ($i++ -eq 0) { continue }; $_ }
The reason is that continue and break look for an enclosing for / foreach statement to continue / break out of, and since there is none, the entire command is exited; in a script, the entire script is exited if there's no enclosing for / foreach / switch statement on the call stack.

PowerShell - Overwriting line written with Write-Host

I'm trying to overwrite a line in PowerShell written with Write-Host (I have a process that's running in a loop and I want to show percentage updated on the screen). What I've tried to do is this:
Write-Host -NoNewline "`rWriting $outputFileName ($i/$fileCount)... $perc%"
but instead of overwriting the line it stays on the same line and appends to it.
what am I missing here?
Thanks
You cannot overwrite a line in a Powershell window. What you can do is blank the window with cls(Clear-Host):
# loop code
cls
Write-Host "`rWriting $outputFileName ($i/$fileCount)... $perc%"
# end loop
But what you should really be using is Write-Progress, a cmdlet built specifically for this purpose:
# loop code
Write-Progress -Activity "Writing $outputFileName" -PercentComplete $perc
# end loop
More on Write-Progress here: http://technet.microsoft.com/en-us/library/hh849902.aspx
As a tweak to Raf's answer above, You don't have to wipe the screen every time to update your last line.
Calling Write-Host with -NoNewLine and carriage return `r is enough.
for ($a=0; $a -le 100; $a++) {
Write-Host -NoNewLine "`r$a% complete"
Start-Sleep -Milliseconds 10
}
Write-Host #ends the line after loop
It not perfect but here is a script which has a spinning character in place. The part that lets you do this is:
$origpos = $host.UI.RawUI.CursorPosition
$origpos.Y += 1
Get the current position and save it so that we can keep referring to it. As you progress you change the $host.UI.RawUI.CursorPosition. Since it was previously saved you can reset it back $host.UI.RawUI.CursorPosition = $origpos. You should be able to experiment with that.
$scroll = "/-\|/-\|"
$idx = 0
$job = Invoke-Command -ComputerName $env:ComputerName -ScriptBlock { Start-Sleep -Seconds 10 } -AsJob
$origpos = $host.UI.RawUI.CursorPosition
$origpos.Y += 1
while (($job.State -eq "Running") -and ($job.State -ne "NotStarted"))
{
$host.UI.RawUI.CursorPosition = $origpos
Write-Host $scroll[$idx] -NoNewline
$idx++
if ($idx -ge $scroll.Length)
{
$idx = 0
}
Start-Sleep -Milliseconds 100
}
# It's over - clear the activity indicator.
$host.UI.RawUI.CursorPosition = $origpos
Write-Host 'Complete'
Remove-Variable('job')
$job = Start-Job -ScriptBlock { Start-Sleep -Seconds 10 }
while (($job.State -eq "Running") -and ($job.State -ne "NotStarted"))
{
Write-Host '.' -NoNewline
Start-Sleep -Seconds 1
}
Write-Host ""
So as log as you remember where you want to go back to then you can use this logic. This will not work properly in ISE. You can also use `b as a back space character as well.
I know, thats quite old, but i was in the same Situation und modified the Solution from Boluwade Kujero, just because writing blank lines before writing the new output may result in a "flickering" output.
So in the following function, I just do overwrite the existing line, write blanks until reaching the old cursorposition, and go back to the last character of the new line.
In addition i added an optical progressbar. Progress is calculated by the function through given Parameters:
function Write-Status
{
param([int]$Current,
[int]$Total,
[string]$Statustext,
[string]$CurStatusText,
[int]$ProgressbarLength = 35)
# Save current Cursorposition for later
[int]$XOrg = $host.UI.RawUI.CursorPosition.X
# Create Progressbar
[string]$progressbar = ""
for ($i = 0 ; $i -lt $([System.Math]::Round($(([System.Math]::Round(($($Current) / $Total) * 100, 2) * $ProgressbarLength) / 100), 0)); $i++) {
$progressbar = $progressbar + $([char]9608)
}
for ($i = 0 ; $i -lt ($ProgressbarLength - $([System.Math]::Round($(([System.Math]::Round(($($Current) / $Total) * 100, 2) * $ProgressbarLength) / 100), 0))); $i++) {
$progressbar = $progressbar + $([char]9617)
}
# Overwrite Current Line with the current Status
Write-Host -NoNewline "`r$Statustext $progressbar [$($Current.ToString("#,###").PadLeft($Total.ToString("#,###").Length)) / $($Total.ToString("#,###"))] ($($( ($Current / $Total) * 100).ToString("##0.00").PadLeft(6)) %) $CurStatusText"
# There might be old Text behing the current Currsor, so let's write some blanks to the Position of $XOrg
[int]$XNow = $host.UI.RawUI.CursorPosition.X
for ([int]$i = $XNow; $i -lt $XOrg; $i++) {
Write-Host -NoNewline " "
}
# Just for optical reasons: Go back to the last Position of current Line
for ([int]$i = $XNow; $i -lt $XOrg; $i++) {
Write-Host -NoNewline "`b"
}
}
Use the function like this:
For ([int]$i=0; $i -le 8192; $i++) {
Write-Status -Current $i -Total 8192 -Statustext "Running a long Task" -CurStatusText "Working on Position $i"
}
The result will be a running progressbar that will look like this (in a single line):
Running a long Task ██████████████████░░░░░░░░░░░░░░░░░ [4.242 /
8.192] ( 51,78 %) Working on Position 4242
Hope this will help someone else
You can use the .NET console class to do exactly what you want where you want it.
Works in console windows only and not the ISE.
cls
[Console]::SetCursorPosition(40,5)
[Console]::Write('Value of $i = ')
[Console]::SetCursorPosition(40,7)
[Console]::Write('Value of $j = ')
For ($i = 1; $i -lt 11; $i++)
{
[Console]::SetCursorPosition(57,5)
[Console]::Write($i)
for ($j = 1; $j -lt 11; $j++)
{
[Console]::SetCursorPosition(57,7)
[Console]::Write("$j ")
Start-Sleep -Milliseconds 200
}
Start-Sleep -Milliseconds 200
}
[Console]::SetCursorPosition(40,5)
[Console]::Write(" `n")
[Console]::SetCursorPosition(40,7)
[Console]::Write(" `n")
[Console]::SetCursorPosition(0,0)
If the goal is strictly to overwrite powershell console prompt line (the current line with the cursor) then all the answers here work only to an extent, and in some ways doing more than is desired.
Raf's and Craig's answers that use the Clear-Host cmdlet (cls) in their first line, like Dullson noted, are doing too much. Blanking the entire screen assumes the things cleared are no longer important for viewing which may not be true. Sometimes these are necessary to make sense of the current line.
Raf's Write-Progress solution is a powerful cmdlet but seems like an overkill for just overwriting the current line.
Raf's Write-Host proposal, Matt's submission and Dullson's tweak are all good where only one character position at a definite screen position needs updating or where the succeeding line text is longer in length than the current. If not, the succeeding line text would only overwrite the current line to the extent of its length leaving those parts of the succeeded line whose length position is longer than the new to remain in view together with the new line.
For example, if the previous value was 10 and the new value is 9 what would be shown is 90. The 9 just overwrites the portion of the preceding value that is equal to its length - 1. So the solutions work well for increments but not so well for decrements where length of value reduces compared to previous.
The following block shows how to guarantee total (visual) overwrite of the current line text with a new one.
$LongString = "This string is long"
$ShortString = "This is short"
#Simulate typing a string on the console line
$L = 1
While ($L -le $LongString.Length)
{
$Sub = $LongString.Substring(0,$L)
Write-Host "`r$Sub" -NoNewline
$L++
# This sleep is just to simulate manual typing delay
Start-Sleep -Milliseconds 20
}
# Now blank out the entire line with the space character " "
# The quantity of spaces should be equal to the length of the current text
# Which in this case is contained in $Sub.Length
$Blank = " "
For($L = 1; $L -le $Sub.Length; $L++)
{
$Blank = $Blank + " "
}
Write-Host "`r$Blank" -NoNewline
# Overwrite the blank console line with the new string
$L = 1
While ($L -le $ShortString.Length)
{
$Sub = $ShortString.Substring(0,$L)
Write-Host "`r$Sub" -NoNewline
$L++
# This sleep is just to simulate delay in manual typing
Start-Sleep -Milliseconds 20
}
# The following is not required if you want the Powershell prompt
# to resume to the next line and not overwrite current console line.
# It is only required if you want the Powershell prompt to return
# to the current console line.
# You therefore blank out the entire line with spaces again.
# Otherwise prompt text might be written into just the left part of the last
# console line text instead of over its entirety.
For($L = 1; $L -le $Sub.Length; $L++)
{
$Blank = $Blank + " "
}
Write-Host "`r$Blank" -NoNewline
Write-Host "`r" -NoNewline
This one I got from a blog post by Thomas Rayner. He uses ANSI Escape Sequences to save the cursor position [s and update the cursor position [u
$E=[char]27
Then save the current cursor position using the save escape sequence:
"${E}[s"
Usage: Use the update sequence ${E}[u to tell PS where to start the string:
1..10 | %{"${E}[uThere are $_ s remaining"; Start-Sleep -Seconds 1}
Does not work in the ISE however.
I know links get stale but it is here today.
Try
for ($i=1;$i -le 100;$i++){Write-Host -NoNewline "`r" $i;sleep 1}
https://241931348f64b1d1.wordpress.com/2017/08/23/how-to-write-on-the-same-line-with-write-output/
This method worked for me to write output value in a loop until its status changed to "Succeeded". Ensure you set the cursor up by required number of lines and it overwrites the same line
while($val -ne 1)
{
if($taskstates.Tasks.state[0] -eq "Succeeded" -and $taskstates.Tasks.state[1] -eq "Succeeded" -and $taskstates.Tasks.state[2] -eq "Succeeded" -and $taskstates.Tasks.state[3] -eq "Succeeded")
{
$val = 1
}
#Clear-Host
$taskstates.Tasks.StartTime[0].ToString() +" "+ $taskstates.Tasks.name[0] +" is "+ $taskstates.Tasks.state[0]
$taskstates.Tasks.StartTime[1].ToString() +" "+ $taskstates.Tasks.name[1] +" is "+ $taskstates.Tasks.state[1]
$taskstates.Tasks.StartTime[2].ToString() +" "+ $taskstates.Tasks.name[2] +" is "+ $taskstates.Tasks.state[2]
$taskstates.Tasks.StartTime[3].ToString() +" "+ $taskstates.Tasks.name[3] +" is "+ $taskstates.Tasks.state[3]
$taskstates = Get-ASRJob -Name $failoverjob.Name
"ASR VMs build is in Progress"
Start-Sleep 5
[console]::setcursorposition($([console]::Cursorleft ),$([console]::CursorTop - 4))
}
I'm late to the party. Here's a proof of concept I recently discovered and adapted for my purposes. This example overwrites the line.
$count = 1
# Used for calculating the max number length for padding trailing spaces
$totalCount = 100
#Get current cursor position
$curCursorPos = New-Object System.Management.Automation.Host.Coordinates
$curCursorPos.X = $host.ui.rawui.CursorPosition.X
$curCursorPos.Y = $host.ui.rawui.CursorPosition.Y
# Counter code
While ($count -le 100) {
# Keep cursor in the same position on the same line
$host.ui.rawui.CursorPosition = $curCursorPos
# Display with padded trailing spaces to overwrite any extra digits
$pad = ($totalCount -as [string]).Length
# Display the counter
Write-Host "$(([string]$count).Padright($pad))" -NoNewline -ForegroundColor Green
# Run through the example quickly
Start-Sleep -Milliseconds 100
#increment $count
$count++
}
You can experiment with Write-Host -NoNewline property, by keeping it or removing it, to see which looks better for you.
I like below code...
$dots = ""
while (!$isTrue) {
if ($dots -eq "...") {
$dots = ""
}
else {
$dots += "."
}
Write-Host -NoNewLine "`rLoading$dots"
Start-Sleep 1
}
You can use $Host.UI.RawUI.WindowSize.Width to find the display width and then use .PadRight to fill up the line with spaces. This avoids having to clear the screen with each loop, the issue of characters persisted from the last loop, having to manipulate cursor position, or having to write a custom function or lots of cumbersome code, e.g.:
# only works in a console window
If ($Host.Name -eq "ConsoleHost")
{
Write-Host 'Starting...'
# find the max line length of the console host
$maxLineLength = $Host.UI.RawUI.WindowSize.Width
# loop a few times
For ($i = 1; $i -le 10; $i++)
{
# for the sake of demonstration, generate a random-length string of letters
$randStringLength = Get-Random -Minimum 1 -Maximum $maxLineLength
$randCharIndex = Get-Random -Minimum 65 -Maximum (65+26) # A = ASCII 65
$randChar = ([char]$randCharIndex)
$myString = [string]$randChar*$randStringLength
# overwrite at the current console line
Write-Host ("`r"+$myString.PadRight($maxLineLength," ")) -NoNewline
# pause briefly before going again
Start-Sleep -Milliseconds 200
}
Write-Host 'Done.'
}
Another option in PowerShell 7.2+ is to use the minimal Write-Progress view $PSStyle.Progress.View = Minimal:
# only works in a console window
If ($Host.Name -eq "ConsoleHost")
{
# loop a few times
For ($i = 1; $i -le 10; $i++)
{
# for the sake of demonstration, generate a random-length string of letters
$randStringLength = Get-Random -Minimum 1 -Maximum 500
$randCharIndex = Get-Random -Minimum 65 -Maximum (65+26) # A = ASCII 65
$randChar = ([char]$randCharIndex)
$myString = [string]$randChar*$randStringLength
# overwrite at the current console line
Write-Progress -Activity $i -Status $myString
# pause briefly before going again
Start-Sleep -Milliseconds 200
}
}
Alot of good suggestions here...
I use the WindowTitle bar for monitoring the status of my scripts, indicate where I am within my code, & the current progress.
For($t = 0; $t -le 100; $t++) {
$Host.UI.RawUI.WindowTitle = "Progress - $t% complete"
Start-Sleep -Milliseconds 10
}
I'll even insert updated "position" info within my code, to indicate where I'm at within my code:
$Host.UI.RawUI.WindowTitle = "Querying index..."
$Host.UI.RawUI.WindowTitle = "Updating search field..."
$Host.UI.RawUI.WindowTitle = "Conducting Robocopy..."
and of course when it's completed:
$Host.UI.RawUI.WindowTitle = "Script completed."

Synchronizing pc-clock occasionally by Powershell and W32tm if time difference -gt 10 sec?

once and a while my prg running at a quite high cpu-time consumption.
After that the pc-clock could differ up to 10 sec despite I sync. the pc-clock with the internet-time twice a day.
Now I want to sync. the pc-clock if the time differs more than (e.g.) 2 sec. and I'd like to use:
PS C:\Windows\system32> w32tm /stripchart /computer:us.pool.ntp.org /dataonly /samples:5
Tracking us.pool.ntp.org [50.7.72.4:123].
Collecting 5 samples.
The current time is 08.08.2014 08:35:44.
08:35:44, -00.1019929s
08:35:47, -00.1082726s
08:35:49, -00.1077418s
08:35:51, -00.1082350s
08:35:53, -00.1007278s
My problem how can my powershell script can receive the time differences from the w32tm?
Then of course I can check the difference and do:
start-process w32tm /resync
For a millisecond the console appears but for sure the resync must fail as I do not start w32tm as admin yet - which causes an access not allowed-error.
Thanks in advance!
Fooling around I fond the solution - surprisingly easy:
$x = w32tm /stripchart /computer:us.pool.ntp.org /dataonly /samples:5 | Out-String
$x
As I cannot answer my own question here now my solution.
Once and a while high cpu-usages causes the pc-clock to run too fast and I have time sensitive programs that need to know the exact time.
On the other hand I can't define the exact time-stamp(s) of Win 7's time-sync (only every x seconds) which might cause a time change right when I do not want it.
So I programmed this solution which is fine for me but can be improved as
1) the time of the check and sync. are hard coded: always at Min 12 or 42 and Sec. 27 and
2) it must run as Administrator as other wise the time-sync fails with Error 0x80070005 => access denied.
Anyway for those who might be interested:
# http://technet.microsoft.com/en-us/library/cc773263%28v=ws.10%29.aspx
function chkSync { param([bool]$sync=$true)
$x = w32tm /stripchart /computer:ptbtime2.ptb.de /dataonly /samples:5 | Out-String
$y = $x.Split("`n")
$c = 0
$n = 0
foreach( $a in $y ) {
$z = $a.Split(",")
if ( $z[1] -match '([\d\.\-\+]+)' ){
$b = [double]$matches[1]
$c += $b; $n++
}
}
if ( $n -gt 0 ) {
$d = $c/$n
} else { $d = 0 }
if ( $sync -and $d*$d -gt 1.0 ) { # instead of [Math]::Abs
Write-Host (get-date).ToString('HH:mm:ss') "bef. sync.:" $d.ToString('f3') "start sync. now"
$s = w32tm /resync | Out-String
Write-Host $s # Error 0x80070005 => access denied: run as adimnistartor!!
chkSync $false
} else {
# every 30 min at 12:47 -or 42:47
$slp = [int]$(1800 + 747 - ((((get-date).Minute)%30)*60 + $((get-date).Second)))
Write-Host (get-date).ToString('HH:mm:ss') "avg. delay:" $d.ToString('f3')`
"next check:" $($(get-date).AddSeconds($slp)).ToString('HH:mm:ss')
}
Start-Sleep -s $slp
}
do { chkSync } while ( $true )

Windows Powershell - how to re-display the statuses of some windows services only when they change?

I have just started with Powershell. I have a bat file which simply kicks off the following PowerShell script, which then re-displays the status of services that I am interested every 5 seconds. It works fine (although I could use some pointers on how to make this cleaner) except that there is a brief annoying flicker every time when the screen is re-painted. So, I would like to change this so that the sleep interval is 1 second or 500ms, but the re-painting is done only when the content changes. Alternatively, if it is easier to repaint a dos screen unconditionally, without causing it to flicker, then I would be happy with that solution as well. Also please help me clean up the code. I am so far afraid of functions, variables etc. in PowerShell because PS frequently yells at me when I try to use C-family/Python syntax and constructs. PS is somehow different from Python, Java, etc. and I have not figured out the philosophy of it yet.
# When you run this script, it will show a simple window with the status of the services;
# Do we want to XYZ as well?
# To assign $true value, use:
#PowerShell.exe .\ShowServices.ps1 -showXYZ:$true
#param([switch]$showXYZ=$false)
param([switch]$showXYZ=$true)
# Build a regex for services
$servicesRegex = "Microsoft.*|Network.*"
if ($showXYZ -eq $true) { $servicesRegex = $servicesRegex + "|XYZ.*" }
# Controlling the appearance of the window
$pshost = get-host
$pswindow = $pshost.ui.rawui
$newsize = $pswindow.buffersize
$newsize.height = 3000
$newsize.width = 50
$pswindow.buffersize = $newsize
$newsize = $pswindow.windowsize
$newsize.height = 10
$newsize.width = 50
$pswindow.windowsize = $newsize
$global:CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
#$global:ComputerName = gc env:computername
#$pswindow.WindowTitle = "Service statuses for {0} on {1}." -f $CurrentUser.Name, $ComputerName
$pswindow.WindowTitle = "Service statuses for {0}." -f $CurrentUser.Name
# Clear the screen once
clear
# Formatting details.
[int]$global:len1 = 35
[int]$global:len2 = 8
[int]$global:sleepInterval = 5 #seconds - I want this to be more frequent, but not annoying.
function printHeader
{
Write-Host("") # Blank line
[string]$line = "{0,-$global:len1} {1,-$global:len2}" -f "Service Name", "Status"
Write-Host $line
Write-Host("_" * $global:len1 + " " + "_" * $global:len2)
}
function printService($serviceObject)
{
[string]$foreColor = "yellow" # Default color, if neither Stopped nor Running
if ($serviceObject.status -eq "Stopped") {$foreColor = "red" }
if ($serviceObject.status -eq "Running") {$foreColor = "green" }
[string]$outStr = "{0,-$global:len1} {1,-$global:len2}" -f $serviceObject.displayname, $serviceObject.status
Write-Host $outStr -foregroundcolor $foreColor #-backgroundcolor white
}
# The meat of it.
while($true)
{
printHeader
Get-Service | Where-Object {$_.name -match $servicesRegex} | ForEach-Object { printService($_) }
Start-Sleep -s $global:sleepInterval # Sleep x seconds
clear
}
Try changing the last part of you script to this:
# The meat of it.
$data = #()
while($true)
{
$new = Get-Service | Where-Object {$_.name -match $servicesRegex}
if (Compare-Object $data $new -Property Status) {
$data = $new
clear
printHeader
$data | ForEach-Object { printService($_) }
}
Start-Sleep -s $global:sleepInterval # Sleep x seconds
}