Powershell Nested functions error handling failing - powershell

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
}

Related

Count commandlet occurence in powershell script at different points

I found some great sample code that autocounts the number of times I call the progress bar cmdlet in a script. Super awesome, then you dont have to manually increment steps and change stuff all the time.
$steps = ([System.Management.Automation.PsParser]::Tokenize((gc "$PSScriptRoot\$($MyInvocation.MyCommand.Name)"), [ref]$null) | where { $_.Type -eq 'Command' -and $_.Content -eq 'Write-ProgressHelper' }).Count
However! I am building a giant script that has multiple sections within it, each time I reboot I start the script at a different point.
I would like to display the overall progress of the ENTIRE installation, so at the start of each step I need to know how many occurrences of Write-Progress occurred before the section that current runs.
Ex: if I call the script to start at point 2, the progress bar should be starting at 4/7.
Is there a way to count the occurrences at a certain point in the script? The tokenize just collects the whole script.
Param(
[Parameter(Mandatory=$True)]
[int]
$startstep
#starting step for script, called with
)
function Write-ProgressHelper {
param(
[int]$StepNumber,
[string]$Message
)
Write-Progress -ID 0 -Activity 'Installation Part 1' -Status $Message -PercentComplete (($StepNumber / $steps) * 100)
#call in code with Write-ProgressHelper -Message 'Doing something' -StepNumber ($step++)
}
$steps = ([System.Management.Automation.PsParser]::Tokenize((gc "$PSScriptRoot\$($MyInvocation.MyCommand.Name)"), [ref]$null) | where { $_.Type -eq 'Command' -and $_.Content -eq 'Write-ProgressHelper' }).Count
$step = 0
if ($startstep -eq 1){
#count the number of occurences here and set STEP to that value (4 in this case)
write-host "Step $startstep"
Write-ProgressHelper -Message 'part 1se' -StepNumber ($step++)
sleep -Seconds 5
write-host "Step $startstep"
Write-ProgressHelper -Message 'part 1se' -StepNumber ($step++)
sleep -Seconds 5
write-host "Step $startstep"
Write-ProgressHelper -Message 'part 1se' -StepNumber ($step++)
sleep -Seconds 5
write-host "Step $startstep"
Write-ProgressHelper -Message 'part 1se' -StepNumber ($step++)
sleep -Seconds 5
}
if ($startstep-eq 2){
write-host "Step $startstep"
Write-ProgressHelper -Message 'Part 2' -StepNumber ($step++)
sleep -Seconds 5
write-host "Step $startstep"
Write-ProgressHelper -Message 'part 1se' -StepNumber ($step++)
sleep -Seconds 5
write-host "Step $startstep"
Write-ProgressHelper -Message 'part 1se' -StepNumber ($step++)
sleep -Seconds 5
}
This can be done using the AST (abstract syntax tree).
Find the if statements that check for the right value of $startstep.
Count the number of Write-ProgressHelper invocations within the bodies of these if statements.
# these variables must exist and will be filled by reference later:
$tokens = $errors = $null
# parse the current script
$ast = [Management.Automation.Language.Parser]::ParseFile( $MyInvocation.MyCommand.Path, [ref] $tokens, [ref] $errors)
# get total number of invocations of Write-ProgressHelper
$steps = $ast.FindAll({ param($item)
$item -is [Management.Automation.Language.CommandAst] -and $item.GetCommandName() -eq 'Write-ProgressHelper'
}, $true).Count
# get if-statements that check for less than current value of $startstep
$ifAsts = $ast.FindAll({ param($item)
if( $item -isnot [Management.Automation.Language.IfStatementAst] ) { return $false }
# Item1 contains the AST of the if statement condition
$item.Clauses.Item1.Extent.Text -match '\$startstep -eq (\d+)' -and ([int] $matches[1]) -lt $startstep
}, $true)
# get number of invocations of Write-ProgressHelper within body of matching if-statements
$step = 1
$ifAsts | ForEach-Object {
# Item2 contains the AST of the if statement body
$step += $_.Clauses.Item2.FindAll({ param($item)
$item -is [Management.Automation.Language.CommandAst] -and $item.GetCommandName() -eq 'Write-ProgressHelper'
}, $true).Count
}
A good starting point for further experimentation is to list all items of the AST like this:
$ast.FindAll({ $true }, $true)

PowerShell Password Timeout [duplicate]

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 ;}
}

Loop get-mailbox cmdlet until no error returned

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
}
}

Foreach loop in a if statement

Wrote a small script to check if certain AD groups exists. For some reason it wont loop through the given array. It only writes the first value of the array to the console. When I put a breakpoint on:
foreach ($item in $SecGroupNames)
I see $secGroupNames being filled with the gives values, can anyone help me out? Can't figure it out for this one.
Code:
Import-Module activedirectory
$SecGroupNames = #(
"DB_DATAREADER",
"DB_DATAWRITER",
"DB_OWNER",
"SQL_Public",
"SQL_SA",
"SQL_SecurityAdmin"
)
foreach ($item in $SecGroupNames)
{
If (Get-ADGroup $item)
{
Write-Host -ForegroundColor Yellow "$item Exists!"
return $true;
}
else
{
Write-Host -ForegroundColor Green "$Item Does not exist, Do something!"
return $false;
}
}
Output:
PS C:\Scripts\CreateOUgroups> C:\Scripts\CreateOUgroups\FunctionCheckSecurityGroup.ps1
DB_DATAREADER Exists!
True
It's because of return statements. It causes the script to return value and end execution in the first loop pass.
If you want to return multiple values from script or function, use Write-Output instead of return.
foreach ($item in $SecGroupNames)
{
if (Get-ADGroup $item)
{
Write-Host -ForegroundColor Yellow "$item Exists!"
Write-Output $true;
}
else
{
Write-Host -ForegroundColor Green "$item Does not exist, Do something!"
Write-Output $false;
}
}

Is there a similar timeout function in Powershell as in CMD prompt? [duplicate]

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 ;}
}