$startTime = $(get-date)
write-host "`rElapsed:00:00:00"
$NoEvent = $true
While ($NoEvent)
{
Start-Sleep 1
$elapsedTime = new-timespan $startTime $(get-date)
write-host "`rElapsed:$($elapsedTime.ToString('hh\:mm\:ss'))"
#Handle event
if(event){$NoEvent = $false}
}
I've tried running this in the ISE as well as through the regular console. The returns are never output.
I eventually got it working using -NoNewLine switch
write-host -NoNewLine "`rElapsed:$($elapsedTime.ToString('hh\:mm\:ss'))"
`r only issues a CR, not a CR+LF (which is probably what you want). Use `n (‘newline’) instead.
I don't think there is any PS cmdlet that can help with overwriting text from the same line unless you are clearing the entire window with clear-host or cls, but PowerShell has a built in write-progress cmdlet if that is something else you would want to consider.
You can try:
$startTime = $(get-date)
$NoEvent = $true
While ($NoEvent)
{
for ($a=1; $a -lt 100; $a++) {
Start-Sleep 1
$elapsedTime = new-timespan $startTime $(get-date)
Write-Progress -Activity "`rElapsed:$($elapsedTime.ToString('hh\:mm\:ss'))" -PercentComplete $a -CurrentOperation "$a% Processed" -Status "Please wait."
#Handle event
if(event){$NoEvent = $false}
}
}
See: https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Utility/Write-Progress?view=powershell-5.1 for info on write-progress
and here is a related question: PowerShell - Overwriting line written with Write-Host
Related
I have hybrid setup where shared mailboxes are getting created on-prem and synced through to Exchange Online in a span of couple of minutes.
My routine is to create a shared mailbox on-prem and then convert it, populate, enable messagecopy, etc. through Connect-ExchangeOnline.
I want to have a tiny script to check if it synced to EO or not.
I've tried several different ways and seemingly this one should work, but unfortunately it breaks after both success or error without attempting to run get-mailbox in 10 seconds as I expect it to.
Please review and advise.
$ErrorActionPreference = 'SilentlyContinue'
$ID = "xxx#yyy"
$i=0
while ($i -le 10) {
try {
Get-Mailbox $ID
break
}
catch {
$i++
$i
Start-Sleep 10
}
}
As commented, to catch also non-terminating exceptions, you must use -ErrorAction Stop.
But why not simply do something like
$ID = "xxx#yyy"
for ($i = 0; $i -le 10; $i++) { # # loop 11 attempts maximum
$mbx = Get-Mailbox -Identity $ID -ErrorAction SilentlyContinue
if ($mbx) {
Write-Host "Mailbox for '$ID' is found" -ForegroundColor Green
break
}
Write-Host "Mailbox for '$ID' not found.. Waiting 10 more seconds"
Start-Sleep -Seconds 10
}
Or, if you want to use try{..} catch{..}
$ID = "xxx#yyy"
for ($i = 0; $i -le 10; $i++) { # loop 11 attempts maximum
try {
$mbx = Get-Mailbox -Identity $ID -ErrorAction Stop
Write-Host "Mailbox for '$ID' is found" -ForegroundColor Green
$i = 999 # exit the loop by setting the counter to a high value
}
catch {
Write-Host "Mailbox for '$ID' not found.. Waiting 10 more seconds"
Start-Sleep -Seconds 10
}
}
I'm working on a process to start up some services across remote servers, however server 2 can't start up until a message is found in server1 logs, server 3 can't start until same message in server 2, etc.
My question is, is it possible to read the file in a "loop" and not proceed with my initial loop until that message is found (and then move forward)? I was thinking I could do below, however while it does recognize that the string in the log file is found, it just repeats that it found it until the timer built in finishes and then moves forward.
So, the process would look like "read this file, if string is found, move forward. If string is not found, wait 30 seconds and rescan the file. If found, move forward, if not found, wait an additional 30 seconds and rescan (I'm fine with a continuous repeat) < do this until string is found.
Example: enter image description here
Any advice would be much appreciated as I think I might be approaching this from the wrong angle...
-- I left out the majority of the script prior to this script and only included the If/Else statement as this is where it checks the files.
$SEL = Select-String -Path \\$Server\$RootDir\folder\anotherfolder\A-Log-File.log -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
}
else
{
Write-Host **** Waiting 60 seconds for cache to build ****
[int]$Time = 60
$Lenght = $Time / 100
For ($Time; $Time -gt 0; $Time--) {
$min = [int](([string]($Time/60)).split('.')[0])
$text = " " + $min + " minutes " + ($Time % 60) + " seconds left"
Write-Progress -Activity "Waiting for Started Message" -Status $Text -PercentComplete ($Time / $Lenght)
Start-Sleep 1
$SEL = Select-String -Path \\$Server\$RootDir\folder\anotherfolder\A-Log-File.log -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
}
else
{
Write-Host **** A-Log-File.log Log does NOT contain a started message **** -ForegroundColor Red -BackgroundColor Yellow
Write-Host **** Investigate further or increase the int-time time on Line 54 to 180 seconds **** -ForegroundColor Red -BackgroundColor Yellow ##This part goes away once action can be taken based on reading contents of the file
}
}
}
You don't need a loop, just use Get-Content -Wait:
$null = Get-Content "\\$Server\$RootDir\folder\anotherfolder\A-Log-File.log" -Wait |Where-Object { $_ -match 'Switching to status: STARTED' } |Select -First 1
Get-Content -Wait will continue outputting new lines written to the file until it's interrupted - luckily we can use Select -First 1 to stop the pipeline once we observe the string
You said "loop" so why aren't you using a loop?
while ($true) {
# (re)try
$SEL = Select-String -Path "\\$Server\$RootDir\folder\anotherfolder\A-Log-File.log" -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
# exit the loop
break;
}
# wait
Write-Host "**** Waiting 60 seconds for cache to build ****"
Start-Sleep 1
}
#marsze, I meant to come back to this and update. I ended up using your suggestion and also wanted to add in that I was able to build in a timer. I wanted to provide the example for anyone that stumbles upon this.
while ($true) {
# (re)try
$SEL = Select-String -Path "\\$Server\$SomeRoot\A.Folder\b.folder2\ThisFileToParse.log" -Pattern "Message Here"
if ($SEL -ne $null)
{
Write-Host "FOUND: Message Here" -ForegroundColor Yellow -BackgroundColor DarkGreen
# exit the loop
break;
}
# wait
Write-Host "**** File Does NOT contain a Message Here message ****" -ForegroundColor Red -BackgroundColor Yellow
Write-Host "**** Waiting 30 seconds, will loop until message is found ****" -ForegroundColor Red -BackgroundColor Yellow
#Start-Sleep 1
[int]$Time = 30
$Lenght = $Time / 100
For ($Time; $Time -gt 0; $Time--) {
$min = [int](([string]($Time/60)).split('.')[0])
$text = " " + $min + " minutes " + ($Time % 60) + " seconds left"
Write-Progress -Activity "Waiting for ThisFileToParse.log to update showing Message Here... " -Status $Text -PercentComplete ($Time / $Lenght)
Start-Sleep 1
}
I have a script which starts a process only after specific service is running.
It's a loop that's trying to Get-Service its status.
I can't find how to limit loop by time.
The part where I'm stuck:
#add Start button
$button_start = New-Object System.Windows.Forms.Button
$button_start.Location = New-Object System.Drawing.Size(25,70)
$button_start.Size = New-Object System.Drawing.Size(240,32)
$button_start.TextAlign = "MiddleCenter"
$button_start.font = New-Object System.Drawing.Font("Segoe UI",14,[System.Drawing.FontStyle]::Regular)
$button_start.BackColor = "seashell"
$button_start.Text = "Start"
$button_start.Add_Click({
#add statement
while ((Get-Service -ComputerName $textBox_IP.text -ServiceName wscsvc).Status -ne "Running") {
# Pause before next check
Start-Sleep -Seconds 1
}
#only then..
Start-Process -FilePath "C:\Users\username\Desktop\software.exe" -verb RunAs -ArgumentList $textBox_IP.text
})
$Form_remoteControl.Controls.Add($button_start)
I've tried internet searching information on network without any success.
Define a time limit and check if the current time exceeds that limit.
$limit = (Get-Date).AddMinutes(5)
while (... -or (Get-Date) -le $limit) {
Start-Sleep -Seconds 1
}
If you want to skip starting the external program when the service still isn't running after that add another check after the loop upon which you return:
if ((Get-Service ...).Status -ne "Running") {
return
}
This is an example how to stop a service and wait until it is stopped or timeout applies.
You can modify to start a service.
Function StopService ($serv)
{
Write-Host "Config service " $serv " ..."
$service = Get-Service $serv -ErrorAction SilentlyContinue
if ($service)
{
if($service.status -eq "running")
{
write-host "Stop service" $serv
Stop-Service $serv -Force
# Wait until service is stopped (max. 1 minute)
$acttime = 0
$waittime = 100
$maxtime = 60000
$TestService = Get-Service $serv
While($TestService | Where-Object {$_.Status -eq 'Running'})
{
Start-Sleep -m $waittime
$acttime += $waittime
if ($acttime -gt $maxtime)
{
write-host "ERROR: Service" $serv " could not be stopped!" -ForegroundColor Red
return $False
}
}
}
else
{
write-host "Service already stopped!" -ForegroundColor Green
return $True
}
}
else
{
write-host "Service not installed" -ForegroundColor Green
return $True
}
}
I recommend you not using any polling While loops (with Start-Sleep cmdlets) in a Windows forms interface. It will stall your interface for important form events as button clicks etc.
Instead, I would anticipate on the Windows.Forms Timer class by creating a timer event and take appropriate checks and actions after a certain time period (e.g. a new Start-Process depending on a service state).
I am attempting to create a simple console timer display.
...
$rpt = $null
write-status "Opening report", $rpt
# long-running
$rpt = rpt-open -path "C:\Documents and Settings\foobar\Desktop\Calendar.rpt"
...
function write-status ($msg, $obj) {
write-host "$msg" -nonewline
do {
sleep -seconds 1
write-host "." -nonewline
[System.Windows.Forms.Application]::DoEvents()
} while ($obj -eq $null)
write-host
}
The example generates 'Opening report ....', but the loop never exits.
I should probably use a call-back or delegate, but I'm not sure of the pattern in this situation.
What am I missing?
You should be using Write-Progress to inform the user of the run status of your process and update it as events warrant.
If you want to do some "parallel" computing with powershell use jobs.
$job = Start-Job -ScriptBlock {Open-File $file} -ArgumentList $file
while($job.status -eq 'Running'){
Write-Host '.' -NoNewLine
}
Here is what I think about Write-Host.
The script runs in sequence. When you input a $null object, the while condition will always be true. The function will continue forever until something breaks it (which never happends in your script.
First then will it be done with the function and continue with your next lines:
# long-running
$rpt = rpt-open -path "C:\Documents and Settings\foobar\Desktop\Calendar.rpt"
Summary: Your while loop works like while($true) atm = bad design
I have a script that kicks off an event and waits for the user to press any key to stop the script execution. I was trying to find a way to show a timer (how long the script has been running) while waiting for user input at Read-Host. Is there a way to accomplish this?
This works
$Time = [System.Diagnostics.Stopwatch]::StartNew()
while ($true) {
$CurrentTime = $Time.Elapsed
write-host $([string]::Format("`rTime: {0:d2}:{1:d2}:{2:d2}",
$CurrentTime.hours,
$CurrentTime.minutes,
$CurrentTime.seconds)) -nonewline
sleep 1
if ($Host.UI.RawUI.KeyAvailable -and ("q" -eq $Host.UI.RawUI.ReadKey("IncludeKeyUp,NoEcho").Character)) {
Write-Host "Exiting now"
break;
}
}
This gave me the output I was after :)
$StartTime = $(get-date)
$elapsedTime = $(get-date) - $StartTime
$totalTime = "{0:HH:mm:ss}" -f ([datetime]$elapsedTime.Ticks)
From the article Measuring Elapsed Time in Powershell (archived copy):
Assuming that the variable $script:StartTime was set at the
beginning of your script, elapsed time can be determined using either
of the following methods:
$elapsedTime = new-timespan $script:StartTime $(get-date)
or
$elapsedTime = $(get-date) - $script:StartTime
Both methods work exactly the same and produce a System.TimeSpan object.
So, using the above example, you could set $script:StartTime before Read-Host and then call
$elapsedTime = $(get-date) - $script:StartTime after.
Using a timer class (RIP poshtips) gave me something like this:
$Time = [System.Diagnostics.Stopwatch]::StartNew()
while ($NoEvent) {
$CurrentTime = $Time.Elapsed
write-host $([string]::Format("`rTime: {0:d2}:{1:d2}:{2:d2}",
$CurrentTime.hours,
$CurrentTime.minutes,
$CurrentTime.seconds)) -nonewline
sleep 1
#Handle event
if(event){$NoEvent = false}
}
Where $NoEvent is your event/boolean (key press func, etc.).
I'm happy with this little function expanded on from Xelco52's answer
$startTime = $(get-date)
write-host "Elapsed:00:00:00"
$NoEvent = $true
While ($NoEvent)
{
Start-Sleep 1
$elapsedTime = new-timespan $startTime $(get-date)
write-host "Elapsed:$($elapsedTime.ToString("hh\:mm\:ss"))"
#Handle event
if(event){$NoEvent = $false}
}
TLDR answer
$StartTime = $(get-date)
#...do something...
$elapsedTime = $(get-date) - $StartTime
Most of the times $elapsedTime.TotalSeconds or $elapsedTime.TotalMilliseconds is what you want.