This week's Friday Fun I think epitomizes how much I think about PowerShell. But I also think it will serve as a useful learning device if not something you might actually want to use. For some reason, I thought it would be useful to quickly display a monthly calendar in PowerShell. Sure, I could click over to the clock on the start bar and navigate to a calendar. But then I have to take my hands off the keyboard and click around with a mouse. I'd much rather type a quick command, maybe give it a date and see the results. So this is what I came up with: Get-Calendar.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
#requires -version 2.0 Function Get-Calendar { <# .Synopsis Display a monthly calendar .Description This command will display calendar month based on the given date. .Example PS C:\> get-calendar September 2014 Sun Mon Tue Wed Thu Fri Sat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 .Example PS C:> 1..12 | foreach {get-calendar "$_/1/2015"} | Out-file c:\work\2015.txt Create a calendar text file for 2015 using a North American date format. .Notes Last Updated: September 16, 2014 Version : 1.0 Learn more: PowerShell in Depth: An Administrator's Guide (http://www.manning.com/jones6/) PowerShell Deep Dives (http://manning.com/hicks/) Learn PowerShell 3 in a Month of Lunches (http://manning.com/jones3/) Learn PowerShell Toolmaking in a Month of Lunches (http://manning.com/jones4/) .Link https://jdhitsolutions.com/blog/2014/09/friday-fun-does-anyone-really-know-what-day-it-is .Link Get-Date Get-Culture #> [cmdletbinding()] Param( [Parameter(Position=0)] [ValidateNotNullorEmpty()] [datetime]$Date = (Get-Date) ) #get some information about weekday names that is culture specific $cult = Get-Culture $cal = $cult.Calendar $ui = Get-UICulture $daysOfWeek = $ui.DateTimeFormat.AbbreviatedDayNames $startWeek = $daysOfWeek[0] #create a string with the days of the week $days = $DaysOfWeek -join " " #how many days in the month $intDays = $cal.GetDaysInMonth($Date.year,$Date.month) #get the first day of the month [datetime]$firstday = $Date.addDays(-$Date.Day+1) <# I want to align each row of weekdays with the days of the week header. I need to align the first day of the first week. #> $week1=@() for ($i=0;$i -lt 7;$i++) { if (("{0:ddd}" -f $firstday) -eq $ui.DateTimeFormat.AbbreviatedDayNames[$i] ) { #I am assuming all short day abbreviations will be 3 characters $week1+=$firstday.Day.ToString().PadLeft(3) #got the date so move on break } else { #add an empty day $week1+=" " } } #fill out the rest of the week $j = 1 #loop through until we reach the end of the week while ($i -le 5) { $week1+=$firstday.AddDays($j).day.ToString().PadLeft(3) $i++ $j++ } #construct a centered header $MonthYear = "{0:MMMM yyyy}" -f $Date $pad = ($days.Length/2) + ($Monthyear.length/2) $title = $monthYear.PadLeft($pad) #define a here string for the final calendar output $myCal=@" $title $days $($week1 -join " ")`n "@ #process the rest of the weeks for ($k=2;$k -le 5;$k++ ) { #initialize an array for a week of days $wkArray = @() for ($i = 0; $i -le 6; $i++) { #get the next day $nextday = $firstday.AddDays($j) #test if we are still in the same month if ($nextday.Month -eq $firstday.month) { #add the padded day to the array $wkArray+= $nextday.day.ToString().PadLeft(3) $j++ } else { #new month so break out of the inner For loop Break } } #join each element of the array into a single line #and add to the calendar string $myCal+= "$($wkArray -join " ")`n" } #write out as separate lines which makes it easier to save to a file $mycal.Split("`n") } #end function Set-Alias -Name gcal -Value Get-Calendar
The script will define an alias, gcal, for the function.
Because calendars are culture (i.e. language) specific, I wanted to write something that anyone could use, and not something just for North American users. That meant retrieving culture settings so I could use language specific versions of days of the week. I think. It is very hard to fully test this without a system running completely in a different culture.
I wanted my calendar to display abbreviated week day names in the correct culture.
$cult = Get-Culture $cal = $cult.Calendar $ui = Get-UICulture $daysOfWeek = $ui.DateTimeFormat.AbbreviatedDayNames
After quite a bit of experimenting, I realized the best way to create the calendar was to write each line as a separate string. This meant one line would be the abbreviated week day names.
#create a string with the days of the week $days = $DaysOfWeek -join " "
Then each week would be a string of numbers. But this was the tricky part. It was easy enough to find out how many days were in the month, and what the first day of the month would be.
$intDays = $cal.GetDaysInMonth($Date.year,$Date.month) #get the first day of the month [datetime]$firstday = $Date.addDays(-$Date.Day+1)
Here's the challenge: I can figure out that the first day of the month might be on a Thursday. Which means I need to align '1' under the THU heading, and then add enough other days until I reach the end of the week.
$week1=@() for ($i=0;$i -lt 7;$i++) { if (("{0:ddd}" -f $firstday) -eq $ui.DateTimeFormat.AbbreviatedDayNames[$i] ) { #I am assuming all short day abbreviations will be 3 characters $week1+=$firstday.Day.ToString().PadLeft(3) #got the date so move on break } else { #add an empty day $week1+=" " } }
So I created an array, $Week1, to hold a set of values. I use a FOR loop to check if the date's short day name matches the array of abbreviated day names from the UI culture, I know that is the first day of the month. Otherwise, I simply add a 2 space "filler" to the array. Then I need to add the rest of the days for the first week.
#fill out the rest of the week $j = 1 #loop through until we reach the end of the week while ($i -le 5) { $week1+=$firstday.AddDays($j).day.ToString().PadLeft(3) $i++ $j++ }
With that, I can begin constructing a text representation of the calendar using a here string.
#define a here string for the final calendar output $myCal=@" $title $days $($week1 -join " ")`n "@
The clever part is using the -Join operator to essentially concatenate all of the values in the week 1 array, separating each by a space. From here, I repeat the process for each week until I reach the end of the month.
At the end of the process $mycal is a here string with the calendar. I could simply write $mycal to the pipeline and it would look just fine. However, because you might want to save the calendar to a text file, I found it best to split the here string back into an array.
#write out as separate lines which makes it easier to save to a file $mycal.Split("`n")
Now you can create a text file calendar for next year:
1..12 | foreach {get-calendar "$_/1/2015"} | Out-file c:\work\2015.txt
Or run the command at the prompt.
The function defaults to the current month or you can specify any date. You can even write it to the host and add a little color.
If you run PowerShell under something other than en-US, I'd love to hear how this works for you. Hope you have a little bit of fun with this.
Nice! 🙂
Here’s another technique from fellow MVP Doug Finke: http://www.dougfinke.com/blog/index.php/2013/10/13/calendars-powershell-wolframalpha-rest/
And here’s a variation from Lee Holmes: http://www.leeholmes.com/blog/2008/12/03/showing-calendars-in-your-oof-messages/
I had to substitute
$mycal = ” `n” + “$title`n” + “$days`n” + “$($week1 -join ‘ ‘)`n”
for
$myCal = @”
$title
$days
$($week1 -join ‘ ‘)`n
“@
or when I saved the output to a file, I got irregular line delineation (some
three byte CR CR LF delimiters instead of the two byte CR LF delimiters)
Hmmm. I thought I had taken this into account. Is this when you run the command in the console or ISE? Are you using Out-File or Add-Content?
in the console – running like below (using redirection operator >)
Show-Calendar >calendar.txt
You really should use Out-File instead of the legacy redirection character. Although when I try this and open the file in Notepad it looks fine to me. Odd. I’d like to see if I can duplicate this. What version of PowerShell and what OS?
POSH V3
When I run
get-calendar | out-file xo
I get this file (show below with hex interpreted)
00000000 FF FE 20 00 0D 00 0D 00 0A 00 20 00 20 00 20 00 .. ……. . . .
00000010 20 00 20 00 20 00 53 00 65 00 70 00 74 00 65 00 . . .S.e.p.t.e.
00000020 6D 00 62 00 65 00 72 00 20 00 32 00 30 00 31 00 m.b.e.r. .2.0.1.
00000030 34 00 0D 00 0D 00 0A 00 53 00 75 00 6E 00 20 00 4…….S.u.n. .
00000040 4D 00 6F 00 6E 00 20 00 54 00 75 00 65 00 20 00 M.o.n. .T.u.e. .
00000050 57 00 65 00 64 00 20 00 54 00 68 00 75 00 20 00 W.e.d. .T.h.u. .
00000060 46 00 72 00 69 00 20 00 53 00 61 00 74 00 0D 00 F.r.i. .S.a.t…
00000070 0D 00 0A 00 20 00 20 00 20 00 20 00 20 00 20 00 …. . . . . . .
00000080 31 00 20 00 20 00 20 00 32 00 20 00 20 00 20 00 1. . . .2. . . .
00000090 33 00 20 00 20 00 20 00 34 00 20 00 20 00 20 00 3. . . .4. . . .
000000A0 35 00 20 00 20 00 20 00 36 00 0D 00 0A 00 20 00 5. . . .6….. .
000000B0 20 00 37 00 20 00 20 00 20 00 38 00 20 00 20 00 .7. . . .8. . .
000000C0 20 00 39 00 20 00 20 00 31 00 30 00 20 00 20 00 .9. . .1.0. . .
000000D0 31 00 31 00 20 00 20 00 31 00 32 00 20 00 20 00 1.1. . .1.2. . .
000000E0 31 00 33 00 0D 00 0A 00 20 00 31 00 34 00 20 00 1.3….. .1.4. .
000000F0 20 00 31 00 35 00 20 00 20 00 31 00 36 00 20 00 .1.5. . .1.6. .
00000100 20 00 31 00 37 00 20 00 20 00 31 00 38 00 20 00 .1.7. . .1.8. .
00000110 20 00 31 00 39 00 20 00 20 00 32 00 30 00 0D 00 .1.9. . .2.0…
00000120 0A 00 20 00 32 00 31 00 20 00 20 00 32 00 32 00 .. .2.1. . .2.2.
00000130 20 00 20 00 32 00 33 00 20 00 20 00 32 00 34 00 . .2.3. . .2.4.
00000140 20 00 20 00 32 00 35 00 20 00 20 00 32 00 36 00 . .2.5. . .2.6.
00000150 20 00 20 00 32 00 37 00 0D 00 0A 00 20 00 32 00 . .2.7….. .2.
00000160 38 00 20 00 20 00 32 00 39 00 20 00 20 00 33 00 8. . .2.9. . .3.
00000170 30 00 0D 00 0A 00 0D 00 0A 00 0………
I wonder if when you copied the file from the web if you ended up with some control characters or something.
I checked for that early on, and found none.
I’ll try to isolate it to a minimal program that outputs the three character line delimiters from a string created from a here-string
Here is a simple way to recreate the issue with a script I named HereStringTest.ps1
cat HereStringTest.ps1
$myCal=@”
a
b
c
“@
$mycal.Split(“`n”)
.\HereStringTest.ps1 | out-file HereStringTest.txt
Below is a “hex dump” of the file HereStringTest.txt
00000000 FF FE 20 00 0D 00 0D 00 0A 00 61 00 0D 00 0D 00 .. …….a…..
00000010 0A 00 62 00 0D 00 0D 00 0A 00 63 00 0D 00 0A 00 ..b…….c…..
What the…? Are you saying instead of text you are getting a hex dump in the text file? That makes no sense and I can’t even begin to think what would cause that behavior.
The “hex dump” is just a way to show the actual character codes in the file. This website is not a good place to show a “hex dump” clearly. The left-most part of each line in the dump is the offset into the file, the middle part is the hex codes for each of those 16 bytes, and the right-most part of each line shows the printable characters. You can find a “hex dump” utility in Lee Holmes book “Windows PowerShell Cookbook” as the example script named Format-Hex (page 234 of the 2nd edition of that book)
I understand now. You threw me there. To verify, I posted a text version of the script I run at https://app.box.com/s/vpzcpjw1kttfweh7r2tq if you want to test.
I tested the version at app.box.com and it produces the same file as the version I copied from the article. Both produce extra CR (hex 0x0d) characters for the first three lines of the output.
Do these characters affect what you see when you open the file in Notepad? I just tried on a PS3 system and the calendar in Notepad looks just fine. If there are irregular characters, they aren’t affecting what I see.
notepad just ignores the extra CR (hex 0D) characters. But my favorite editor UltraEdit gives me warnings when I load the file.
Then that’s an artifact of here strings in PowerShell as least as exposed in UltraEdit. I opened the saved file in Notepad++ and nothing seemed odd to me. The only other thing I can think is to try using Out-File and specify encoding as ASCII. But it probably doesn’t really matter. Your concatenated string works just as well.
I created a version that uses the here-string and produces clean lines.
I used ‘r’n instead of ‘n and also did not do the Split when outputting $myCal
I put this version at the URL below for inspection:
http://sp.ntpcug.org/PowerShell/Shared%20Documents/Jeffery_Hicks_Get-Calendar.ps1.txt