I would like to develop a looping which if the error was caught, and attempts 3 times, then stop and exit.
The question is how to make the loop count 3 times and stop it?
Thanks.
Powershell Code
function loop() {
$attempts = 0
$flags = $false
do {
try {
Write-Host 'Updating...Please Wait!'
***Do something action***
Write-Host 'INFO: Update Completed!' -BackgroundColor BLACK -ForegroundColor GREEN
Start-Sleep 1
$flags = $false
} catch {
Write-Host 'Error: Update Failure!' -BackgroundColor BLACK -ForegroundColor RED
$attempts++
$flags = $true
Start-Sleep 2
}
} while ($flags)
}
Insert the following at the start of your try block:
if($attempts -gt 3){
Write-Host "Giving up";
break;
}
break will cause powershell to exit the loop
I would rewrite this as a for-loop for clearer code:
foreach( $attempts in 1..3 ) {
try {
Write-Host 'Updating...Please Wait!'
***Do something action***
Write-Host 'INFO: Update Completed!' -BackgroundColor BLACK -ForegroundColor GREEN
Start-Sleep 1
break # SUCCESS-> exit loop early
} catch {
Write-Host 'Error: Update Failure!' -BackgroundColor BLACK -ForegroundColor RED
Start-Sleep 2
}
}
From just looking at the first line, we can clearly see that this is a counting loop. It also lets us get rid of one variable so we have less state, making the code easier to comprehend.
$i = 0
if ( $(do { $i++;$i;sleep 1} while ($i -le 2)) ) {
"i is $i"
}
i is 3
Related
I have searched but apparently my google foo is weak. What I need is a way to prompt for user input in the console and have the request time out after a period of time and continue executing the script if no input comes in. As near as I can tell, Read-Host does not provide this functionality. Neither does $host.UI.PromptForChoice() nor does $host.UI.RawUI.ReadKey(). Thanks in advance for any pointers.
EDIT: Much thanks to Lars Truijens for finding the answer. I have taken the code that he pointed out and encapsulated it into a function. Note that the way that I have implemented it means there could be up to one second of delay between when the user hits a key and when script execution continues.
function Pause-Host
{
param(
$Delay = 1
)
$counter = 0;
While(!$host.UI.RawUI.KeyAvailable -and ($counter++ -lt $Delay))
{
[Threading.Thread]::Sleep(1000)
}
}
Found something here:
$counter = 0
while(!$Host.UI.RawUI.KeyAvailable -and ($counter++ -lt 600))
{
[Threading.Thread]::Sleep( 1000 )
}
It's quite old now but how I solved it based on the same KeyAvailable method is here:
https://gist.github.com/nathanchere/704920a4a43f06f4f0d2
It waits for x seconds, displaying a . for each second that elapses up to the maximum wait time. If a key is pressed it returns $true, otherwise $false.
Function TimedPrompt($prompt,$secondsToWait){
Write-Host -NoNewline $prompt
$secondsCounter = 0
$subCounter = 0
While ( (!$host.ui.rawui.KeyAvailable) -and ($count -lt $secondsToWait) ){
start-sleep -m 10
$subCounter = $subCounter + 10
if($subCounter -eq 1000)
{
$secondsCounter++
$subCounter = 0
Write-Host -NoNewline "."
}
If ($secondsCounter -eq $secondsToWait) {
Write-Host "`r`n"
return $false;
}
}
Write-Host "`r`n"
return $true;
}
And to use:
$val = TimedPrompt "Press key to cancel restore; will begin in 3 seconds" 3
Write-Host $val
For people who are looking for a modern age solution with an additional constraint for exiting a PowerShell script on a pre-defined key press, the following solution might help you:
Write-Host ("PowerShell Script to run a loop and exit on pressing 'q'!")
$count=0
$sleepTimer=500 #in milliseconds
$QuitKey=81 #Character code for 'q' key.
while($count -le 100)
{
if($host.UI.RawUI.KeyAvailable) {
$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp")
if($key.VirtualKeyCode -eq $QuitKey) {
#For Key Combination: eg., press 'LeftCtrl + q' to quit.
#Use condition: (($key.VirtualKeyCode -eq $Qkey) -and ($key.ControlKeyState -match "LeftCtrlPressed"))
Write-Host -ForegroundColor Yellow ("'q' is pressed! Stopping the script now.")
break
}
}
#Do your operations
$count++
Write-Host ("Count Incremented to - {0}" -f $count)
Write-Host ("Press 'q' to stop the script!")
Start-Sleep -m $sleepTimer
}
Write-Host -ForegroundColor Green ("The script has stopped.")
Sample script output:
Refer Microsoft document on key states for handling more combinations.
Credits: Technet Link
Here is a keystroke utility function that accepts:
Validation character set (as a 1-character regex).
Optional message
Optional timeout in seconds
Only matching keystrokes are reflected to the screen.
Usage:
$key = GetKeyPress '[ynq]' "Run step X ([y]/n/q)?" 5
if ($key -eq $null)
{
Write-Host "No key was pressed.";
}
else
{
Write-Host "The key was '$($key)'."
}
Implementation:
Function GetKeyPress([string]$regexPattern='[ynq]', [string]$message=$null, [int]$timeOutSeconds=0)
{
$key = $null
$Host.UI.RawUI.FlushInputBuffer()
if (![string]::IsNullOrEmpty($message))
{
Write-Host -NoNewLine $message
}
$counter = $timeOutSeconds * 1000 / 250
while($key -eq $null -and ($timeOutSeconds -eq 0 -or $counter-- -gt 0))
{
if (($timeOutSeconds -eq 0) -or $Host.UI.RawUI.KeyAvailable)
{
$key_ = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,IncludeKeyUp")
if ($key_.KeyDown -and $key_.Character -match $regexPattern)
{
$key = $key_
}
}
else
{
Start-Sleep -m 250 # Milliseconds
}
}
if (-not ($key -eq $null))
{
Write-Host -NoNewLine "$($key.Character)"
}
if (![string]::IsNullOrEmpty($message))
{
Write-Host "" # newline
}
return $(if ($key -eq $null) {$null} else {$key.Character})
}
timeout.exe 5
still works from powershell. Waits 5 seconds or until key press.
Inelegant perhaps. But easy.
However,
From the Powershell_ISE it pops up a new command prompt window, and returns immediately, so it doesnt wait (From the powershell console it uses that console and does wait). You can make it wait from the ISE with a little more work (still pops up its own window tho):
if ($psISE) {
start -Wait timeout.exe 5
} else {
timeout.exe 5
}
Another option: you can use choice shell command which comes with every Windows version since Windows 2000
Choice /C yn /D n /t 5 /m "Are you sure? You have 5 seconds to decide"
if ($LASTEXITCODE -eq "1") # 1 for "yes" 2 for "no"
{
# do stuff
}
else
{
# don't do stuff
}
Stackoverflow syntax higlighting doesn't work for powershell, # means "comment" here
function ReadKeyWithDefault($prompt, $defaultKey, [int]$timeoutInSecond = 5 ) {
$counter = $timeoutInSecond * 10
do{
$remainingSeconds = [math]::floor($counter / 10)
Write-Host "`r$prompt (default $defaultKey in $remainingSeconds seconds): " -NoNewline
if($Host.UI.RawUI.KeyAvailable){
$key = $host.UI.RawUI.ReadKey("IncludeKeyUp")
Write-Host
return $key
}
Start-Sleep -Milliseconds 100
}while($counter-- -gt 0)
Write-Host $defaultKey
return $defaultKey
}
$readKey = ReadKeyWithDefault "If error auto exit( y/n )" 'y' 5
To optionally pause the script before it exits (useful for running headless scripts and pausing on error output), I appended nathanchere's answer with:
if ([Console]::KeyAvailable) { $pressedKey = [Console]::ReadKey($true); read-host; break; }
elseif ($secondsCounter -gt $secondsToWait) {
Write-Host "`r`n"
return $false;
}
The $Host.UI.RawUI.KeyAvailable seems to be buggy so I had more luck using [Console]::KeyAvailable:
function keypress_wait {
param (
[int]$seconds = 10
)
$loops = $seconds*10
Write-Host "Press any key within $seconds seconds to continue"
for ($i = 0; $i -le $loops; $i++){
if ([Console]::KeyAvailable) { break; }
Start-Sleep -Milliseconds 100
}
if ([Console]::KeyAvailable) { return [Console]::ReadKey($true); }
else { return $null ;}
}
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 need help on the issue which I'm facing using the below code.
The below function has 2 nested functions, the problem here is when I get an error in the first nested function I put a break to come out entirely from the Main function (without executing anything further).
In my case, it is continuing to execute the 2nd nested function, which I
don't want.
<
Write-host "All the retry are finished..!"
break >
Hereafter break, I would want it to come out from the main function, need your help to correct me if I am
phrasing them in the wrong way..!
Function MAIN
{
[CmdletBinding()]
param (
[Parameter(Position=0,mandatory=$true)] [string]$server
)
Function Failover-Readiness
{
Try
{
[int]$Log_size_th=100
[int]$retry=0
$TF=
"sample query 1"
DO
{
$RS=invoke-sqlcmd -ServerInstance $server -Database Master -Query $TF | Where-Object{$_.role_desc -like "*secondary*"} |
`Where-Object {($_.log_send_queue_size -gt $Log_size_th) -or ($_.redo_queue_size -gt $Log_size_th)}
##############################################
$Q= "sample query 2"
$R=(Invoke-Sqlcmd -ServerInstance $server -Database "MASTER" -Query $Q -QueryTimeout 20 -ea stop ).cnt
$Count =$R
$RSCOUNT=$RS.count
write-host `n"open tran count is $count and log/redo queue size DB count is $RSCOUNT " -ForegroundColor Yellow
if (($count -gt 0) -or ($RS.Count -ge 1))
{
$retry=$retry+1
write-host "retry count is $retry"
Write-Warning "there are atleast 1 open tran or Log/redo queue size are gt 100"
start-sleep -Seconds 5
if ($retry -gt 5)
{
Write-host "All the retry are finihsed..!" -ForegroundColor Red
break
}
}
else
{
write-host `n"There are no open transactions and Log/redo queue size is lt 100 !" -ForegroundColor green
}
}
while(($count -gt 0) -or ($RS.Count -gt 1) )
} ##Try
catch
{
write-host $_.exception.message -ForegroundColor Red
}
}##Function
Failover-Readiness
Function Test
{
write-host "Test Function"
}
Test
}
I have write a short powershell script for a guessing number game. This looks like following:
clear-host
$Guess = "11"
$response=read-host "Enter your guess"
if ($response -eq $guess)
{
write-host "Congratulations"
}
else {
write-host "Try again"
}
Now, if I want to do in such way that after a certain number of times wrong guessing for example, 3 times wrong guessing the program will exit. How I can do this? Can you please shade some light on that issue?
Here is a solution with a while loop
$count = 0
$answered = $false
while ($count -lt 3 -and $answered -eq $false) {
clear-host
$Guess = "11"
$response=read-host "Enter your guess"
if ($response -eq $guess)
{
$answered = $true
write-host "Congratulations"
}
else {
write-host "Try again"
}
$count++
}
I have searched but apparently my google foo is weak. What I need is a way to prompt for user input in the console and have the request time out after a period of time and continue executing the script if no input comes in. As near as I can tell, Read-Host does not provide this functionality. Neither does $host.UI.PromptForChoice() nor does $host.UI.RawUI.ReadKey(). Thanks in advance for any pointers.
EDIT: Much thanks to Lars Truijens for finding the answer. I have taken the code that he pointed out and encapsulated it into a function. Note that the way that I have implemented it means there could be up to one second of delay between when the user hits a key and when script execution continues.
function Pause-Host
{
param(
$Delay = 1
)
$counter = 0;
While(!$host.UI.RawUI.KeyAvailable -and ($counter++ -lt $Delay))
{
[Threading.Thread]::Sleep(1000)
}
}
Found something here:
$counter = 0
while(!$Host.UI.RawUI.KeyAvailable -and ($counter++ -lt 600))
{
[Threading.Thread]::Sleep( 1000 )
}
It's quite old now but how I solved it based on the same KeyAvailable method is here:
https://gist.github.com/nathanchere/704920a4a43f06f4f0d2
It waits for x seconds, displaying a . for each second that elapses up to the maximum wait time. If a key is pressed it returns $true, otherwise $false.
Function TimedPrompt($prompt,$secondsToWait){
Write-Host -NoNewline $prompt
$secondsCounter = 0
$subCounter = 0
While ( (!$host.ui.rawui.KeyAvailable) -and ($count -lt $secondsToWait) ){
start-sleep -m 10
$subCounter = $subCounter + 10
if($subCounter -eq 1000)
{
$secondsCounter++
$subCounter = 0
Write-Host -NoNewline "."
}
If ($secondsCounter -eq $secondsToWait) {
Write-Host "`r`n"
return $false;
}
}
Write-Host "`r`n"
return $true;
}
And to use:
$val = TimedPrompt "Press key to cancel restore; will begin in 3 seconds" 3
Write-Host $val
For people who are looking for a modern age solution with an additional constraint for exiting a PowerShell script on a pre-defined key press, the following solution might help you:
Write-Host ("PowerShell Script to run a loop and exit on pressing 'q'!")
$count=0
$sleepTimer=500 #in milliseconds
$QuitKey=81 #Character code for 'q' key.
while($count -le 100)
{
if($host.UI.RawUI.KeyAvailable) {
$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp")
if($key.VirtualKeyCode -eq $QuitKey) {
#For Key Combination: eg., press 'LeftCtrl + q' to quit.
#Use condition: (($key.VirtualKeyCode -eq $Qkey) -and ($key.ControlKeyState -match "LeftCtrlPressed"))
Write-Host -ForegroundColor Yellow ("'q' is pressed! Stopping the script now.")
break
}
}
#Do your operations
$count++
Write-Host ("Count Incremented to - {0}" -f $count)
Write-Host ("Press 'q' to stop the script!")
Start-Sleep -m $sleepTimer
}
Write-Host -ForegroundColor Green ("The script has stopped.")
Sample script output:
Refer Microsoft document on key states for handling more combinations.
Credits: Technet Link
Here is a keystroke utility function that accepts:
Validation character set (as a 1-character regex).
Optional message
Optional timeout in seconds
Only matching keystrokes are reflected to the screen.
Usage:
$key = GetKeyPress '[ynq]' "Run step X ([y]/n/q)?" 5
if ($key -eq $null)
{
Write-Host "No key was pressed.";
}
else
{
Write-Host "The key was '$($key)'."
}
Implementation:
Function GetKeyPress([string]$regexPattern='[ynq]', [string]$message=$null, [int]$timeOutSeconds=0)
{
$key = $null
$Host.UI.RawUI.FlushInputBuffer()
if (![string]::IsNullOrEmpty($message))
{
Write-Host -NoNewLine $message
}
$counter = $timeOutSeconds * 1000 / 250
while($key -eq $null -and ($timeOutSeconds -eq 0 -or $counter-- -gt 0))
{
if (($timeOutSeconds -eq 0) -or $Host.UI.RawUI.KeyAvailable)
{
$key_ = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,IncludeKeyUp")
if ($key_.KeyDown -and $key_.Character -match $regexPattern)
{
$key = $key_
}
}
else
{
Start-Sleep -m 250 # Milliseconds
}
}
if (-not ($key -eq $null))
{
Write-Host -NoNewLine "$($key.Character)"
}
if (![string]::IsNullOrEmpty($message))
{
Write-Host "" # newline
}
return $(if ($key -eq $null) {$null} else {$key.Character})
}
timeout.exe 5
still works from powershell. Waits 5 seconds or until key press.
Inelegant perhaps. But easy.
However,
From the Powershell_ISE it pops up a new command prompt window, and returns immediately, so it doesnt wait (From the powershell console it uses that console and does wait). You can make it wait from the ISE with a little more work (still pops up its own window tho):
if ($psISE) {
start -Wait timeout.exe 5
} else {
timeout.exe 5
}
Another option: you can use choice shell command which comes with every Windows version since Windows 2000
Choice /C yn /D n /t 5 /m "Are you sure? You have 5 seconds to decide"
if ($LASTEXITCODE -eq "1") # 1 for "yes" 2 for "no"
{
# do stuff
}
else
{
# don't do stuff
}
Stackoverflow syntax higlighting doesn't work for powershell, # means "comment" here
function ReadKeyWithDefault($prompt, $defaultKey, [int]$timeoutInSecond = 5 ) {
$counter = $timeoutInSecond * 10
do{
$remainingSeconds = [math]::floor($counter / 10)
Write-Host "`r$prompt (default $defaultKey in $remainingSeconds seconds): " -NoNewline
if($Host.UI.RawUI.KeyAvailable){
$key = $host.UI.RawUI.ReadKey("IncludeKeyUp")
Write-Host
return $key
}
Start-Sleep -Milliseconds 100
}while($counter-- -gt 0)
Write-Host $defaultKey
return $defaultKey
}
$readKey = ReadKeyWithDefault "If error auto exit( y/n )" 'y' 5
To optionally pause the script before it exits (useful for running headless scripts and pausing on error output), I appended nathanchere's answer with:
if ([Console]::KeyAvailable) { $pressedKey = [Console]::ReadKey($true); read-host; break; }
elseif ($secondsCounter -gt $secondsToWait) {
Write-Host "`r`n"
return $false;
}
The $Host.UI.RawUI.KeyAvailable seems to be buggy so I had more luck using [Console]::KeyAvailable:
function keypress_wait {
param (
[int]$seconds = 10
)
$loops = $seconds*10
Write-Host "Press any key within $seconds seconds to continue"
for ($i = 0; $i -le $loops; $i++){
if ([Console]::KeyAvailable) { break; }
Start-Sleep -Milliseconds 100
}
if ([Console]::KeyAvailable) { return [Console]::ReadKey($true); }
else { return $null ;}
}