If/ElseIf block not working with -or - powershell

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.

Related

Why is a comparison between Date Time objects causing powershell to crash?

I am trying to write a simple script that lets the user select from 5 options to either write some text or run some basic commands. For some reason, the program crashes when I select option 1.
Write-Output " "
Write-Output "Hello. I am the Augur of Dunlain. I know many things. Ask, that which you do not yet know:"
sleep 2
Write-Output "1. How many more days until Christmas?"
Write-Output "2. What processes are running on my computer?"
Write-Output "3. What is System32?"
Write-Output "4. Why are the Blades saying that I have to kill Parthunaax?"
Write-Output "5. What is my age?"
$choice = Read-Host -Prompt "Choose options 1, 2, 3, 4, or 5"
If ($choice -eq "1"){
$currentDate = Get-Date
$christmas = Get-Date "12/25/2021"
$timeSpan = New-Timespan -Start $currentDate -End $christmas
if(timeSpan.days -gt 0){
Write-Output "There are " + $timeSpan.Days + " days left until Christmas."
sleep 3
}
else {
Write-Output "It is Christmas today! Merry Christmas!"
sleep 3
}
}
elseif ($choice -eq "2"){
$processes = Get-Process
Write-Output $processes
sleep 25
}
elseif ($choice -eq "3"){
Write-Output "System 32 is the collection of the following files and directories:"
$system32 = Get-ChildItem -Path C:\Windows\System32
Write-Output $system32
sleep 25
}
elseif ($choice -eq "4"){
Write-Output "They are intollerant, plain and simple. Parthunaax is a total G, so if the Blades are telling you to kill him you can tell them that they are stinky nerds and you don't want to play with them any more."
}
elseif ($choice -eq "5"){
}
else {
Write-Output "Looks like you have failed the simple task of selecting a number 1 through 5. I figured a monkey would have been capable of doing that but I guess you're inferior to the monkey. Try again but this time be better."
}
Based on the current version of the question you are missing a $ in the If(timeSpan.days) should be If($timeSpan.days) and in the write-output don't try to add strings just use PowerShell string expansion as shown.
If ($choice -eq "1"){
$currentDate = Get-Date
$christmas = Get-Date "12/25/2021"
$timeSpan = New-Timespan -Start $currentDate -End $christmas
if($timeSpan.days -gt 0){
Write-Output "There are $($timeSpan.Days) days left until Christmas."
sleep 3
}
Sample output:
Choose options 1, 2, 3, 4, or 5: 1
There are 150 days left until Christmas.
HTH
Not sure what you mean by "the program crashes when I select option 1", but you could use a switch instead of all these if() .. elseif()'s, to make the code a lot clearer.
With the time comparison, you should IMO compare the two dates without the time part, so as of midnight.
On my machine Get-Date "12/25/2021" won't work and will result in an exception:
Get-Date : Cannot bind parameter 'Date'. Cannot convert value "12/25/2021" to type "System.DateTime".
This is because my current locale is set for Dutch, so I would need to reverse the day and month numbers to Get-Date "25/12/2021"
Then of course, if you run this after December 25th, hardcoding the Christmas date is a bad choice and you need to make an adjustment to that if this is the case.
Try below
$choice = Read-Host -Prompt "Choose options 1, 2, 3, 4, or 5"
switch ($choice) {
'1' {
# eliminate the time from both dates, so they are set to midnight
$currentDate = (Get-Date).Date
$christmas = (Get-Date -Year $currentDate.Year -Month 12 -Day 25).Date
# if today is already past December 25th of this year
if ($currentDate -gt $christmas) { $christmas.AddYears(1) }
$timeSpan = New-Timespan -Start $currentDate -End $christmas
if ($timeSpan.TotalDays -eq 0) {
"It is Christmas today! Merry Christmas!"
}
else {
"There are {0} days left until Christmas." -f $timeSpan.TotalDays
}
Start-Sleep 3
}
'2' { Get-Process | Format-Table -AutoSize; Start-Sleep 25 }
# the rest of your options go here
default { "Please enter a number from 1 to 5" }

Powershell Combine Switch Statement

I have the following Powershell code:
$dow = (get-date).DayOfWeek
switch($dow) {{"Sunday"}{$report_Day = (Get-Date).AddDays(-2).Day}{"Monday", "Tuesday", "Wednesday", "Thursday"}{$report_Day = (Get-Date).Day}}
switch($dow) {{"Sunday"}{$report_Month = (Get-Date).AddDays(-2).Month}{"Monday", "Tuesday", "Wednesday", "Thursday"}{$report_Month = (Get-Date).Month}}
Wonder if it is possible to combine the 2 switch statements into 1 - something such as:
$dow = (get-date).DayOfWeek
switch($dow) {{"Sunday"}{$report_Day = (Get-Date).AddDays(-2).Day, $report_Month = (Get-Date).AddDays(-2).Month}{"Monday", "Tuesday", "Wednesday", "Thursday"}{$report_Day = (Get-Date).Day, $report_Month = (Get-Date).Month }}
I tested it it wouldn't work but I wonder if there is a mean to combine them.
Thank you
First: Do yourself, and anyone who will read your code in the future, a favor and format your code so that it's readable. Whitespace doesn't cost you anything and it will save you difficulty later on.
Second: What does your code do on Friday & Saturday?
The issue is the commas between the statements in your switch statement. They should be separate lines (again, formatting will help you) or if you must put them on the same line, terminate each statement with a semicolon (;).
$dow = (get-date).DayOfWeek
switch ($dow) {
"Sunday" {
$report_Day = (Get-Date).AddDays(-2).Day;
$report_Month = (Get-Date).AddDays(-2).Month;
break
}
{($_ -eq "Monday") -or ($_ -eq "Tuesday") -or ($_ -eq "Wednesday") -or ($_ -eq "Thursday")} {
$report_Day = (Get-Date).Day;
$report_Month = (Get-Date).Month
break
}
}

Testing Off-Hours in PowerShell

I need a way to test Off-Hours in PowerShell. My Off-Hours are 7pm-7am. I’m not interested in the date part. The test is only for the time. I tried a few options but no access.
It's easier to use the complete timestamp.
$now = Get-Date
$start = $now.Date.AddHours(7)
$end = $now.Date.AddHours(19)
if ($start -le $now -and $end -ge $now) {
'within work hours'
} else {
'outside work hours'
}
A bit of googling for the syntax yields the following code snippet:
$a = Get-Date
if (($a.Hour -ge 19) -Or ($a.Hour -lt 7)) { Write-Host "Off Hours" } else { Write-Host "On hours" }
Here's a simplified answer:
if ((get-date).Hour -in 7..19) {
'Working Hours'
} else {
'Off hours'
}

Get last work week of month with powershell

I'm trying to get the last work week in a month using powershell. Right now, my code isn't working. I'm getting 5 when it should return 4. The work week is Mon-Fri Here is my code so far:
$d = Get-Date ; [math]::Ceiling(($d.Day+(($d.AddDays(-($d.Day-1))).DayOfWeek.value__)-7)/7+1)
if ($d -eq 5) { $d -=1 }
What can I improve or change to make this work? I'm using powershell 5.
Without understanding your [math] formula and its meaning, next code snippet shows 4 today (2016-10-25):
$d = Get-Date
$e = [math]::Ceiling(($d.Day+(($d.AddDays(-($d.Day-1))).DayOfWeek.value__)-7)/7+1)
if ($e -eq 5) { $e -=1 }
$e
In your code snippet, $d.GetTypeCode() keeps to be DateTime. In other words, $d -eq 5 always results to false.
If your are up to the last monday of a month this should work
$d = Get-Date "2016-1-01"
$LastDayinMonth = (Get-Date "$($d.year)/$($d.month+1)/1").addDays(-1)
$Offset=[int](($LastDayinMonth.dayofweek -1)* -1)
If ($Offset -eq 1) {$Offset=-6}
$LastMonday=$LastDayinMonth.addDays($Offset)
"d : {0}" -f $d.tostring("D")
"LastDayinMonth: {0}" -f $LastDayinMonth.tostring("D")
"LastMonday : {0}" -f $LastMonday.tostring("D")
HTH

Check timespan which may cross day boundary

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
}