Use PowerShell to Save Time with Win32_TimeZone and DST Time Changes

Use PowerShell to Save Time with Win32_TimeZone and DST Time Changes

  • Comments 6
  • Likes

Summary: Write a Windows PowerShell function to determine the status of time changes to daylight savings time.

Microsoft Scripting Guy, Ed Wilson, is here. At the Windows PowerShell User Group meeting in Charlotte, North Carolina, Brian Wilhite talked about a few of his scripts during the script club. I said, “Dude, that is cool, and I immediately challenged Brian to share his scripts with us. The result is two excellent guest postings. Here is the first one.

Brian Wilhite works as a Windows System Administrator for a large health-care provider in North Carolina. He has over 15 years of experience in IT. In his current capacity as a Windows SysAdmin, he leads a team of individuals that have responsibilities for Microsoft Exchange Server, Windows Server builds, and management and system performance. Brian also supports and participates in the Charlotte PowerShell Users Group.
Twitter: Brian Wilhite

Here’s Brian…

Just prior to the latest time change in the fall, I was asked to run a time verification script on all of our computers running Windows Server. Typically, this was done after the time change, between 1:00 AM and 2:00 AM. Being an IT Guy, I’m used to long days and/or long, late nights, but I thought to myself, “I wonder if I can be proactive in knowing which servers may have problems making the change this year.” So I researched, and found the Win32_TimeZone WMI Class and all the goodness therein. Here is some of what I found:

Get-WMIObject –Class Win32_TimeZone

DaylightDay         : 2

DaylightDayOfWeek   : 0

DaylightMonth       : 3

DaylightName        : Eastern Daylight Time

Description         : (UTC-05:00) Eastern Time (US & Canada)

StandardDay         : 1

StandardDayOfWeek   : 0

StandardMonth       : 11

StandardName        : Eastern Standard Time

So…

Like I always do when I evaluate WMI classes, I performed a BING search on MSDN Win32_TimeZone.

After I reviewed all of the information there, specifically DaylightDay, DaylightDayOfWeek, etc., I determined that I could, in fact, proactively identify servers that could have time change issues on the day of the time change. 

This excerpt from the MSDN website sums it all up:

If the transition day (DaylightDayOfWeek) occurs on a Sunday, then the value "1" indicates the first Sunday of the DaylightMonth, "2" indicates the second Sunday, and so on. The value "5" indicates the last DaylightDayOfWeek in the month.

I took this information and started thinking, “I can write a function that will gather this information and report what it finds.” So here’s what I did…

First, I created the framework for an advanced function. It contains the usual items, such as: CmdletBinding, defining parameters, setting up the Begin, Process, Try, Catch, and End Script blocks. This is shown in the following image.

Image of function

Next, I started thinking of all the data that I wanted to capture. I know I will need the Win32_TimeZone WMI class. Because in the past, we always checked for current time, I will also query the Win32_LocalTime. Therefore, here is the start of the processing part of the Get-DSTInfo function:

 Image of function

As you see in the previous image, I casted the $LocalTime variable as a DateTime object by passing it a string that is comprised of variables made up from the Win32_LocalTime WMI class. I will use this later to create a custom PSObject, which will allow you to treat this property as a DateTime object, if needed.

I also captured the WMI class that’s going to gather all the data to make the magic happen, the Win32_TimeZone class. You may have noticed the switch statement for the $TimeZone.DaylightDay property. Because the value is numeric and not “display friendly,” I set up several switch statements. They are shortened for brevity, but you get the idea:

Switch ($TimeZone.DaylightDay)

{

1 {$DSTDay = "First"}

2 {$DSTDay = "Second"}

#From 1 to 5 signifying the First through Last week in the month.

}

Switch ($TimeZone.DaylightDayOfWeek)

{

0 {$DSTWeek = "Sunday"}

1 {$DSTWeek = "Monday"}

#From 0 to 6 to signify the days of the week.

}

Switch ($TimeZone.DaylightMonth)

{

1 {$DSTMonth = "January"}

2 {$DSTMonth = "February"}

3 {$DSTMonth = "March"}

#From 1 to 12 to signify months of the year.

}

OK. So consider the following properties of the $TimeZone object, which is an instance of Win32_TimeZone.

$TimeZone.DaylightDay = 2

$TimeZone.DaylightDayOfWeek = 0

$TimeZone.DaylightMonth = 3

These property values will mean the “spring ahead.” The time change will occur on the “Second (2) Sunday (0) of March (3)”.  The same thing holds true with the $TimeZone.StandardDay, $TimeZone.StandardDayOfWeek, and $TimeZone.StandardMonth—but obviously, they will have different values.

When all of my switch statements were setup (six in total), I created several objects depending on what parameters were passed when running the function. The two parameters that I defined were -Standard and -Daylight. Neither parameter is mandatory because running the function without parameters will return both Standard and Daylight time change information in the form of a PSObject for the local host that is stated. Here is the If statement that I used to accomplish neither parameter being passed:

If ((-not $Standard) -and (-not $Daylight))

{

$STND_DL = New-Object PSObject -Property @{

Computer=$Computer

StandardName=$STDTime

StandardDay=$STDDay

StandardDayOfWeek=$STDWeek

StandardMonth=$STDMonth

DaylightName=$DayTime

DaylightDay=$DSTDay

DaylightDayOfWeek=$DSTWeek

DaylightMonth=$DSTMonth

CurrentTime=$LocalTime

}#End $DL New-Object

$STND_DL = $STND_DL | Select-Object -Property Computer, StandardName, StandardDay, StandardDayOfWeek, StandardMonth, DaylightName, DaylightDay, DaylightDayOfWeek, DaylightMonth, CurrentTime

$STND_DL

As you may have noticed, I did something pretty cool. If you have played with custom objects in Windows PowerShell, you know that they don’t always display the properties the way you would like them to. So, I piped my custom PSObject ($STND_DL) to the Select-Object cmdlet to display the object properties in logical order as you read it—something that makes logical sense to a variable. 

This is what you get when you run the function; the output is an object that you can have your way with:

 Image of command output

Yes, you read that right, 3:49AM. Like I said, I’m not a stranger to long late nights—after all, I am an IT Guy.

Getting back to the topic…

You could also pipe computer names to Get-DSTInfo, something like the following:

Get-ADComputer –Filter * | Select –Expandproperty Name | Get-DSTInfo | Export-Csv –Path C:\TimeZoneInfo.csv

This would gather all the computers in your domain and run the Get-DSTInfo function against them, then export the findings to a .csv file. The way you would read the object output is that the time change would occur the First Sunday of November and Second Sunday of March.

This function allowed me to proactively review the time change information of all 1500+ servers in our domain. The great thing is that I did find a few that did not have the correct time change patch installed, and I was able to remediate that before it became an issue when the clocks turned back last fall. 

Thanks for listening to me ramble on about my simple but useful function. Happy PowerShelling!

~Brian

Thank you, Brian. The full script can be found on the Script Center Repository.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • After writing the Get-DSTInfo blog post, I realized I could take it a step further... So I updated the function to also calculate the Time Date Change, in datetime form, instead of just the "First Sunday in November" or the "Second Sunday in March", now it also displays the following:

    Computer          : LAP01

    CurrentTime       : 2/18/2012 8:25:45 AM

    DaylightName      : Eastern Daylight Time

    DaylightDay       : Second

    DaylightDayOfWeek : Sunday

    DaylightMonth     : March

    DaylightChgDate   : 3/11/2012 2:00:00 AM

    StandardName      : Eastern Standard Time

    StandardDay       : First

    StandardDayOfWeek : Sunday

    StandardMonth     : November

    StandardChgDate   : 11/4/2012 2:00:00 AM

    Notice the additional properties DaylightChgDate and StandardChgDate.

    The code that calculates it is posted below:

    #Calculating the actual DST/Standard time change date - Through loops.

    [DateTime]$DDate = "$DSTMonth 01, $CurrentYear $DSTHour`:00:00"

    [DateTime]$SDate = "$STNDMonth 01, $CurrentYear $STNDHour`:00:00"

    #DST Date Loop

    $i = 0

    While ($i -lt $TimeZone.DaylightDay)

     {

      If ($DDate.DayOfWeek -eq $TimeZone.DaylightDayOfWeek)

       {

        $i++

        If ($i -eq $TimeZone.DaylightDay)

         {

          $DFinalDate = $DDate

         }#End If ($i -eq $TimeZone.DaylightDay)

        Else

         {

          $DDate = $DDate.AddDays(1)

         }#End Else

       }#End If ($DDate.DayOfWeek -eq $TimeZone.DaylightDayOfWeek)

      Else

       {

        $DDate = $DDate.AddDays(1)

       }#End Else

     }#End While ($i -lt $TimeZone.DaylightDay)

    #Standard Date Loop

    $i = 0

    While ($i -lt $TimeZone.StandardDay)

     {

      If ($SDate.DayOfWeek -eq $TimeZone.StandardDayOfWeek)

       {

        $i++

        If ($i -eq $TimeZone.StandardDay)

         {

          $SFinalDate = $SDate

         }#End If ($i -eq $TimeZone.StandardDay)

        Else

         {

          $SDate = $SDate.AddDays(1)

         }#End Else

       }#End If ($SDate.DayOfWeek -eq $TimeZone.StandardDayOfWeek)

      Else

       {

        $SDate = $SDate.AddDays(1)

       }#End Else

     }#End While ($i -lt $TimeZone.StandardDay)

  • Awesome post.  Good tip on searching MSDN to learn more about the WMI classes. MSDN can be very helpful when trying to figure how to use the methods in WMI.

  • Hi Brian,

    thank you very much for presenting this solution to us!

    It works for me and I just want to make two proposals to make it a bit easier, if you won't mind.

    1. I would remove the switch statements and use an array to index into the values, you are looking for. We could add this to the "Beginn" section

     $MonthOfYear="January February March April May June July August September October November December".Split()

     $DayOfWeek="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".Split()

     $WeekOfMonth="First Second Third Fourth Last".Split()

    And instead of using long switch statements you can do the following:

        #Using the Switch Statements to convert numeric values into more meaningful information.

        #This information can be found in the links provided in the Function's Help

        $DSTDay    = $WeekOfMonth[$TimeZone.DaylightDay-1]

        $DSTDoW    = $DayOfWeek[$TimeZone.DaylightDayOfWeek]

        $DSTMonth  = $MonthOfYear[$TimeZone.DaylightMonth-1]

        $STNDDay   = $WeekOfMonth[$TimeZone.StandardDay-1]

        $STNDWeek  = $DayOfWeek[$TimeZone.StandardDayOfWeek]

        $STNDMonth = $MonthOfYear[$TimeZone.StandardMonth-1]

    Just remeber to subtract 1 if the enumeration doesn't start with 0!

    The second thing, I would recommend, is exchange the for loops you added with a small function to calculate the nth DayOfWeek of a month. I hopefully did the rigth thing with this little function:

    function Get-nthDoW {

       param (

       [Parameter(Mandatory=$true)]

       [ValidateRange(1,12)][int]$month,

       [Parameter(Mandatory=$true)]

       [ValidateRange(1900,2100)][int]$year,

       [ValidateRange(1,5)][int]$n=1,

       [ValidateRange(0,6)][int]$dow=0

       )

       [DateTime]$firstDayOfMonth="$month/01/$year"

       if ($dow -ge $firstDayOfMonth.DayOfWeek) {

           $firstDayOfMonth.AddDays(7*($n-1)+($dow-$firstDayOfMonth.DayOfWeek))

       } else {

           $firstDayOfMonth.AddDays(7*$n+($dow-$firstDayOfMonth.DayOfWeek))

       }

    }    

    You call it like this: Get-nthDoW 3 2012 2 4

    where "3" is the month (march), "2012" the year, "2" the second and "4" the dayOfWeek (thursday) which will result in "Donnerstag, 8. März 2012 00:00:00" (german date)

    = thursday the 8th of march 2012.

    Klaus (Schulte)

  • @K_Schulte - That sounds interesting, I'll have to give it a shot.  By the way, thanks for the feedback.

  • So much helpfull!

    The time to get all the information from the computers make me change the method used to execute the script, but it's nothing more then a CTRL + C, CTRL + V with a "Invoke-Command"...

    Good job, Scripting Guy!

    Below, changed script:

    $servers = Get-Content 'C:\servers.txt'

    $session = New-PSSession -ComputerName $servers -ErrorAction SilentlyContinue

    $result = Invoke-Command -Session $session -ScriptBlock {

    Function Get-DSTInfo

    {

    .

    .

    .

    }#End function Get-DSTInfo

    Get-DSTInfo

    }

    $result | Export-Csv –Path C:\TimeZoneInfo.csv

  • Hi Brian,

    Thanks for the script, I have run it, but it gave no error nor any result.
    PS D:\james> .\Get-DSTInfo.ps1
    PS D:\james>

    Please advise, many thanks.