Check timespan which may cross day boundary - powershell

I have a monitoring script which is executed every 5 minutes by task scheduler. If the script determines there is a problem, an alert condition is raised and a notification email is sent. The script then terminates. All well and good.
I want to define some 'quiet hours' during which time, monitoring still takes place on schedule, but notifications are suppressed. These hours will cross the midnight boundary.
I could do this with something like this:
$quietHoursStart = "22:00"
$quietHoursEnd = "06:00"
$timeNow = Get-Date
if ( $timeNow -lt (Get-Date $quietHoursStart ) -and
$timeNow -gt (Get-Date $quietHoursEnd ) ) {
Send-NotificationEmail
}
That's fine, but if I ever re-define the quiet hours range so that it does not cross the midnight boundary (e.g. 00:00 -> 06:00) , then the notification will always fail since the if condition can never be true.
If could add additional tests to see if the range crosses midnight, then have two separate checks to see if current time is 'quiet', but I feel there should be a more elegant solution.
The values for the quiet hours start/end are actually pulled in from an XML config file but for the sake of clarity, the example above uses simple definitions.
EDIT: To clarify; the script must run every 5 minutes, regardless of quiet hours, since it records a log of problems found. Changing the schedule will not achieve this.

Add comparison between $quietHoursStart and $quietHoursEnd to check is given interval cross midnight or not.
$quietHoursStart = "22:00"
$quietHoursEnd = "06:00"
$timeNow = Get-Date
$TimeOfDay = $timeNow.TimeOfDay
$IsQuietHours = if ( $quietHoursStart -le $quietHoursEnd ) {
$TimeOfDay -ge $quietHoursStart -and $TimeOfDay -lt $quietHoursEnd
} else {
$TimeOfDay -ge $quietHoursStart -or $TimeOfDay -lt $quietHoursEnd
}
if ( !$IsQuietHours ) {
Send-NotificationEmail
}

Alain O'Dea's link really does address your need quite well, but from a scripting focused stance I think a better way to approach this (script wise, his link to scheduling the task is probably better) would be to define a starting time, and then a duration. So you want it to start at 10PM and go until 6AM? That's an 8 hour duration. Also, I would test to see if it is in your quiet time, and then invert it with -Not (I use the alias !)Try something like this:
$quietHoursStart = "22:00"
$quietHoursDuration = 8
$timeNow = Get-Date
if (! ($timeNow -gt (Get-Date $quietHoursStart ) -and $timeNow -lt (Get-Date $quietHoursStart ).AddHours($quietHoursDuration)) ) {
Send-NotificationEmail
}

Related

Adding Time out section in powershell script

I am trying to test Time out after 30 seconds.
Sample code:
$a = "y"
$b = "n"
$timeout = New-TimeSpan -Seconds 30
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$stopwatch.Start()
$timeout.Seconds
$stopwatch.elapsed.Seconds
do{
if($a -eq "n"){
Write-Host "This block will never run"
break
}
if($stopwatch.elapsed.Seconds -lt $timeout.Seconds){
Write-Host "Testing this block: Time OUT!!"
break
}
}while($a -eq $b)
$stopwatch.Stop()
But the if block if($stopwatch.elapsed.Seconds -lt $timeout.Seconds) is true even $stopwatch.elapsed.Seconds value is 0 and $timeout.Seconds value is 30 in the loop and complete the code in few milliseconds and not taking 30 seconds to print the Time out statement.
Could anyone please give me pointer to resolve this issue.
A couple of things:
You don't need these two lines: $timeout.Seconds and $stopwatch.elapsed.Seconds above the loop
Your while condition should be while($a -ne $b)
The test inside the loop should read if($stopwatch.elapsed.Seconds -ge $timeout.Seconds)
Try
$a = "y"
$b = "n"
$timeout = New-TimeSpan -Seconds 30
$stopwatch = [System.Diagnostics.Stopwatch]::new()
$stopwatch.Start()
do {
if($stopwatch.Elapsed.Seconds -ge $timeout.Seconds){
Write-Host "Testing this block: Time OUT!!"
break
}
# no timeout, so proceed with what needs to be done here
# . . .
} while($a -ne $b) # loop forever unless you set $a equal to $b in the loop somewhere
$stopwatch.Stop()
Theo's helpful answer addresses incidental logic problems with your approach and offers a solution that probably will work, but isn't fully robust: If the activity in your loop exceeds 1 minute before the timeout condition is tested, the test won't work as intended (even with the logic problems fixed).
You have two options:
Use .TotalSeconds instead of .Seconds, for the reasons explained below.
More simply, taking advantage of the fact that [timespan] instances are directly comparable (see below), you can use:
if ($stopwatch.elapsed -gt $timeout) { # ...
As zett42 points out, [timespan] instances are directly comparable, due to implementing the .NET System.IComparable interface (as well as its generic counterpart); e.g.:
# -> $true - a timespan representing a 61-second duration
# is greater than one representing a 60-second (1-minute) duration.
[timespan]::FromSeconds(61) -gt [timespan] '00:01:00'
Therefore, as shown in the top section, you can simply directly compare $stopwatch.elapsed and $timeout - both of which are [timespan] instances.
The .Seconds property of a [timespan] instance is only the seconds component, potentially alongside larger units, such as minutes (.Minutes) and hours (.Hours)
You need the .TotalSeconds property to get the total amount of seconds (analogously, there are also .TotalDays, .TotalHours, and .TotalMinutes properties).
Also note that .Seconds is always a whole number ([int]), whereas .TotalSeconds can be a fractional value ([double]).
To illustrate the difference:
PS> [timespan] '00:01:05' | # 1 minute and 5 seconds
Select-Object Seconds, TotalSeconds
Seconds TotalSeconds
------- ------------
5 65
#sivam The issue is-
You're not applying the proper properties of the timespan command if it goes beyond 59 seconds then at 60 seconds it will consider it 1 minute.
Update the condition inside the loop if($stopwatch.elapsed.Seconds -lt $timeout.Seconds)
Try
$a = "y"
$b = "n"
$timeout = New-TimeSpan -Minutes 1
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$stopwatch.Start()
$timeout.Minutes
$stopwatch.elapsed.Minutes
do{
if($a -eq "n"){
Write-Host "This block will never run"
break
}
if($stopwatch.elapsed.Minutes -ge $timeout.Minutes){
Write-Host "Time OUT!!"
break
}
}while($a -ne $b)
$stopwatch.Stop()

Time-based while loop

I've been trying to get the while & the If loop to read in the time correctly, but it doesn't end the program when the right time has hit. I tried using single quotes '' and double quotes "", as well as different syntax like (-eq, -match, -ne) to see if any of those work....and they don't.
Program Goal: loops until it hits 07:00am
# While the value is 1.
while ($value -ne 2)
{
# Value should be 1 in order to stay in loop.
$value = 1
# Get's the time in 24hr format
$time = get-date -Format HH:mm:ss
# For Debugging; Writes out Time
Write-Output $time
# Creates a Pop-Up Windows that prevents the computer from timing out; runs every 15 minutes.
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Operation Completed",0,"Done",0x1)
# Causes the Program to wait to send the Enter Keystroke.
Sleep 4
# Sends the Enter Keystroke.
$wshell.sendkeys('~')
# Causes the Program to wait to send the Enter Keystroke in seconds (900sec = 15 Minutes).
Sleep 4
# If Condition; If the time is over 2am then the program quits.
If ($time -eq "02:03:00")
{
# While Loop End Condition
$value = 2
# "Debugging Output"
Write-Output $value
Write-Output $time
}
Else
{
# While Loop Condition
$value = 1
# "Debugging Output"
Write-Output $value
Write-Output $time
}
}
# "Debugging Output"
Write-Output "End"
Write-Output $time
Write-Output $value
The chances are really low that your if-statement will become true. Because your while loop takes at least 8 (2x Start-Sleep and other work) seconds until a new beginning. That means the $time variable will probably never be exactly 02:03:00. In this case I would not go for the exact time. Instead I would check if it's 02:03:00 or later. Try that:
$time = Get-Date
if ($time -ge (Get-Date -Hour 02 -Minute 03 -Second 00))
{
}
This condition should do the work:
if ((Get-Date) -gt (Get-Date -Hour 7 -Minute 0 -Second 0)) {
# While Loop End Condition
$value = 2
# more actions
}
It's comparing the current time with the DateTime object with current day, but time set to 07:00:00.
Keep in mind two things:
It will allow the loop to run only between midnight and 7AM. If you want to start the script the day before you need to adjust the conditions.
It might be more readable to not use if, but put the condition directly in while() like this:
while ((Get-Date) -lt (Get-Date -Hour 7 -Minute 0 -Second 0)) {
# do something
}
Currently, you're checking for exact time, so in theory the end condition might be met, However, if it hits that specific line one second before/after, it won't stop the loop.
Finished Code (at least this version of it anyhow). Appreciate the help too :)!
<###
Program Goal: Prevent PC Timeout/Sleep; also loops until it hits 07:00am
Date: 10/14/19
Version: 1.2
###>
# Creates a Pop-Up Windows that prevents the computer from timing out; runs every 15 minutes.
$wshell = New-Object -ComObject Wscript.Shell
# Get's the time in 24hr format
$time = get-date
# While the value is not equal to 2.
while ($value -ne 2)
{
# Value should be 1 in order to stay in loop.
$value = 1
$wshell.Popup("Operation Completed", 5,"Done", 1)
# Causes the Program to wait to send the Enter Keystroke.
Sleep 2
# If Condition; If the time is over 7am then the program quits.
if ((Get-Date) -gt (Get-Date -Hour 7 -Minute 0 -Second 0))
{
# While Loop End Condition
$value = 2
}
else
{
# While Loop Condition
$value = 1
# Causes the Program to wait (900sec = 15 Minutes) to prevent PC Timeout, will re-run again after Sleep.
Sleep 900
}
}
# Cleans up the COM Object
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wshell) | Out-Null

Using Time in a conditional Statement in Powershell

I'm trying to just get the groundwork for running some code with a time conditional. But I can't seem to grasp how to add time into the equation. Any Powershell people out there?
The $EndDate doesnt matter anymore. I just tried to use it as a way to understand how powershell uses conditional
$StartTime=(Get-Date)
$EndDate=[datetime]”00:00”
if ($StartDate -gt "00:00" -and $StartTime -lt "11:59")
{
Write-Host "It is time to work"
}
else
{
Write-Host "it is time to go"
}
$StartTime
My code right now should say its time to go but should say its time to work because as of right now its only 11:56 AM ET.
If you want to compare against the time of day, use the TimeOfDay TimeSpan exposed by [datetime] - PowerShell will automatically convert the right-hand "HH:mm" string into a meaningful TimeSpan that can be compared against:
$StartTime = Get-Date
if($StartTime.TimeOfDay -gt "00:00" -and $StartTime.TimeOfDay -le "12:00"){
# AM
}
else{
# PM
}
These two things are usually true.
(get-date) -gt '00:00'
True
(get-date) -lt '23:59'
True
Check for unset variables:
set-strictmode -v 1

Infinite loop in comparing Time Powershell

I've been trying to create code in PowerShell that's gonna run if the time is below a specific time but upon experimenting it always gets stuck in an infinite loop executing even when the condition is already not satisfied
here's my code:
$a = Get-Date
Write-Host $a
[datetime]$time = "05/12/2016 1:57:00 AM"
DO
{
if($a -gt $time) {break}
Write-Host "not yet time"
if($a -gt $time) {break}
}while( $a -le $time)
CLS
Write-Host "done"
But it still is running even at 1:59 AM already. Any ideas on how to properly compare time? I want it to run till a specific time or I want it to run at least 2 hours.
Since you only assign the output from Get-Date to $a once, it's never updated, and you're performing the exact same comparison (times 3) every time the loop runs.
Call Get-Date in the loop body or conditional to get the updated time:
[datetime]$time = "05/12/2016 1:57:00 AM"
do {
Write-Host "not yet time"
}while($(Get-Date) -le $time)
cls
Write-Host "done"

If/ElseIf block not working with -or

I am having a problem with a script I am writing for my son. My intent is a simple reminder to him to remember his chores. I've just started doing PowerShell recently and I'm really enjoying it. I've bought a couple books and been through numerous other posts.
What I've got so far is below, seems if evaluations aren't working correctly with the -or (or maybe I goofed?)
## REMINDERS TO DO CHORES ##
$sun = "Sunday"
$mon = "Monday"
$tue = "Tuesday"
$wed = "Wednesday"
$thu = "Thursday"
$fri = "Friday"
$sat = "Saturday"
$today = (get-date).DayOfWeek
$choreVac = "Vacuum the rooms and stairs"
$choreBath = "Clean the Bathroom Including emptying the garbage"
$choreOther = "No Chores Today -- But keep dishes done up"
if($today -eq $mon -or $wed -or $fri) {
msg /time:2500 * "Today is a Chore Day: your job is to $choreVac"
}
elseif ($today -eq $tue -or $sat ) {
msg /time:2500 * "Today is a Chore Day: your job is to $choreBath and PLEASE do a good job"
}
else {
msg /time:2500 * $choreOther
}
The problem is I don't think it's being evaluated correctly on the day, so for today being Tuesday, the evaluated result is $mon -or $wed -or $fri
If I re-code this for each day as follows it works like expected. Why is it not working with the -or?
if($today -eq $tue) {
msg /time:2500 * $choreBath
}
Like you discovered yourself PowerShell was not evaluating your if statement how you intended it to be. Your expression could be understood better like this:
if(($today -eq $mon) -or ($wed) -or ($fri))
Just as in your comment the the code you wanted was
$today -eq $mon -or $today -eq $wed -or $today -eq $fri
or another way to look at it.
($today -eq $mon) -or ($today -eq $wed) -or ($today -eq $fri)
PowerShell does not need to brackets but it is good to use them if things are not going your way.
When Non null/zero length strings in PowerShell are true when cast as booleans. Focusing on the second clause it could be rewritten as
"Wednesday" -or "Friday"
Which is always true. That is why your if statement was firing when you didn't expect it to.
What you had coded made some logical sense but it was syntactically incorrect. Another approach I would like to introduce you to, if you are not already familiar with it, is switch. It would help reduce the clutter of all the if statements and would be especially useful if they became more complicated as the chores evolved over time.
$today = (get-date).DayOfWeek
$choreVac = "Vacuum The Apt"
$choreBath = "Clean the Bathroom Including empting the garbage"
$choreOther = "NO CHORES TODAY -- BUT YOU CAN Keep dishes done up, and Keep Garbage from Overflowing AND CLEAN YOUR ROOM and OR Do Laundry!!!. Especially your bedding"
Switch ($today){
{$_ -in 1,3,5}{$message = "Today is a Chore Day: Your job is to`r$choreVac"}
{$_ -in 2,6}{$message = "Today is a Chore Day: Your job is to`r$choreBath and PLEASE do a good job"}
default{$message = $choreOther}
}
msg /time:2500 * $message
We removed all the calls to msg into one statement since only the $message changes. If a chore day is not covered with a clause in the switch then the default is simply $choreOther.
Also the days of the week can be represented as integers as well like you see above. This could arguably reduce the readability of the code but I think that is a stretch.
Here is the full code corrected and working as I wanted it to.
## REMINDERS TO DO CHORES ##
$sun = "Sunday"
$mon = "Monday"
$tue = "Tuesday"
$wed = "Wednesday"
$thu = "Thursday"
$fri = "Friday"
$sat = "Saturday"
$today = (get-date).DayOfWeek
$choreVac = "Vacuum The Apt"
$choreBath = "Clean the Bathroom Including empting the garbage"
$choreOther = "NO CHORES TODAY -- BUT YOU CAN Keep dishes done up, and Keep Garbage from Overflowing AND CLEAN YOUR ROOM and OR Do Laundry!!!. Especially your bedding"
if($today -in ($mon, $wed ,$fri) ) {
msg /time:2500 * "Today is a Chore Day: your job is to $choreVac"
}
elseif ($today -in ($tue,$sat)) {
msg /time:2500 * "Today is a Chore Day: your job is to $choreBath and PLEASE do a good job"
}
else {
msg /time:2500 * $choreOther
}
You can also use Hashtables to handle the lists of chores and the associated days. The following will allow you to easily add chores to any given day (or even put multiple chores on a single day)
$chores = #{Vac = "Vacuum the Appt";
Bath = 'Clean the bathroom including emptying the garbage';
Other = 'Nothing today -- But keep dishes done up'
}
$day = #{Sunday = $chores.Other;
Monday = $chores.Vac;
Tuesday = $chores.Bath;
Wednesday = #($chores.Vac,$chores.Other);
Thursday = $chores.Other;
Friday = $chores.Vac;
Saturday = $chores.Bath;
}
$base = "Chores for today: "
$today = (Get-Date).DayOfWeek
if ($day."$today".count -gt 1) {
$first = "`n " + $day."$today"[0]
$rest = for ($i = 1; $i -lt $day."$today".count; $i++) {
"`n " + $day."$today"[$i]
}
$msg = $base + $first + $rest
} else {
$msg = $base + "`n " + $day."$today"
}
msg /time:2500 * $msg
If you don't want to support having multiple chores on a day (which in the above applies for Wednesday), just replace the entire If/Else block with the else clause and it will simply list the chores of the day.