Early this week, I came across an old snippet of code in my scripts folder, originally published by Lee Holmes. It was an old script, from 2008, on using PowerShell to display a calendar with out of office information. I seem to recall that I had been trying to do something similar -- display a monthly calendar in the console, when I came across Lee's much better solution. I decided to revive his script and update it. The new version includes a function that displays a calendar in your console.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
The original script let you specify a begin and end date of months to display. You could also specify days which would be highlighted with asterisks or square brackets. I decided to simplify and only kept the code to highlight certain days with asterisks. I also made the decision to turn the original code, which was a script, into an advanced function. Going to a function, made it easier to use and let me take advantage of advanced function features like parameter sets. Yes, I know you can use many of these features in a script file, but the function makes it all much easier. In fact, now that I think about the code I've written, give a me a few minutes. I'll be right back.
I realized I needed to take the code and turn it into a proper module. It is now published to the PowerShell Gallery.
Install-Module PSCalendar
The module should work on PowerShell Core with both Windows and Linux platforms. I don't have a Mac to test. The source code can be found at https://github.com/jdhitsolutions/PSCalendar, although I'm going to explain a few things here.
The primary command is Get-Calendar. The default behavior is to display the current month and highlight the current date.
You can also specify a range of months.
I wanted to have the ability to specify a month, but I also needed the value to be culture-aware. I didn't want to hard-code a ValidateSet attribute because that would have force me to use US values. I started down the path of using a dynamic param but those are not very user-friendly. So I ended up adding an auto-completer to the module.
PowerShell now includes a command called Register-AutoCompleter. This command allows you to define code that will help you autocomplete a parameter value. You've probably seen that when you type Get-Service followed by a space and then a tab, that PowerShell autocompletes possible values. You can add the same functionality to your code.
Register-ArgumentCompleter -CommandName Get-Calendar, Show-Calendar -ParameterName Month -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) #get month names, filtering out blanks (Get-Culture).DateTimeFormat.MonthNames | Where-object {$_ -match "\w+" -and $_ -match "$WordToComplete"} | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.Trim(), $_.Trim(), 'ParameterValue', $_) } }
The code is very similar to what you see when looking at help for Register-AutoCompleter. In my case, I am defining an autocompleter for the commands Get-Calendar and Show-Calendar (more on this one later) that both have a parameter name of Month. The scriptblock is essentially a function that will be executed. As far as I can tell use the parameters with the scriptblock as I have here. There is very little documentation on this command so I'm working from examples . The first part of the scriptblock is getting the culture specific month names. On my computer I was also getting some blanks so I'm filtering to Where-Object to only keep values with word characters. I'm also using the $WordToComplete variable/parameter. When I run Get-Calendar -Month and begin pressing Tab, PowerShell will start with the first item in the list, January. But if I type "ju" and hit tab, PowerShell will jump to "June". The "ju" becomes the value of $WordToComplete. Each month name is then added as a completion result. The parameters are CompletionText, ListItemText,ResultType (which is a paramter value), and ToolTip.
I do something similar with the -Year parameter to display the current year plus the next 5.
Register-ArgumentCompleter -CommandName Get-Calendar, Show-Calendar -ParameterName Year -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) $first = (Get-Date).Year $last = (Get-Date).AddYears(5).Year $first..$last | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } }
Here's what it looks like:
Currently, there's nothing preventing a user from entering an invalid month. But I'm assuming the autocompleter provides guidance. Still, I will go back at some point and add some additional error handling for an incorrect month.
Oh, and the command also lets you highlight one or more dates.
But I love color. So I also wrote a "wrapper" function called Show-Calendar that uses the same parameters, but this uses Write-Host to write a colorized version to the console.
Get-Calendar writes a string to the pipeline. So the challenge I had was turning the string into an array of strings and write each line back using Write-Host. The trickiest part was identifying days marked with asterisks so I could display those days in a highlight color. This required a little regular expression magic.
$cal = Get-Calendar @PSBoundParameters #turn the calendar into an array of strings $calarray = $cal.split("`n") # a regular expression pattern to match on highlighted days [regex]$m = "(\*)?[\s|\*]\d{1,2}(\*)?" foreach ($line in $calarray) { if ($line -match "\d{4}") { write-Host $line -ForegroundColor Yellow } elseif ($line -match "\w{3}|-{3}") { Write-Host $line -ForegroundColor cyan } elseif ($line -match "\*") { #write-host $line -foregroundcolor Magenta $week = $line $m.Matches($week).Value| foreach-object { $day = "$_" if ($day -match "\*") { write-host "$($day.replace('*','').padleft(3," ")) " -NoNewline -ForegroundColor $HighlightColor } else { write-host "$($day.PadLeft(3," ")) " -nonewline } } write-host "" } else { Write-host $line } }
But now I can use it like this:
I am using Show-Calendar in my PowerShell profile script along with some other code to highlight important dates coming up.
I hope this shows you that old code can still serve a purpose. Give the module a try and let me know what you think. Use the Github repo to report any issues.
Enjoy!
UPDATE: I was concerned that there might be problems with this code in other cultures and that appears to be the case. It is challenging testing and developing with other cultures. But I've filed an issue with myself and will look into it further.
UPDATE #2: I've pushed v1.2.0 to the PowerShell Gallery. I'm basing a number of cultural decisions based on culture values from [system.threading.thread]::CurrentThread. For non-US friends, please let me know how this works for you.