The Deployment Guys

Helping to deploy your world automagically...

Setting the Time Zone using Windows PowerShell

Setting the Time Zone using Windows PowerShell

  • Comments 19
  • Likes
23 Nov 2009 Update - TimeZoneLib.ps1 in the download has been updated to allow proper redirection of the script output to a file.

A few year back a customer of mine was setting the time zone on their computers by directly modifying registry entries.  Since the supported way to do this was to use the SetTimeZoneInformation Win32 API, I created a utility in Visual Basic 6 to allow the customer to set the time zone on their Windows computers from the command line.

Later on, Windows XP included a built-in method to do this using control.exe with a command like:

Control.exe TIMEDATE.CPL,,/Z Pacific Standard Time

where the value after /Z is from the Std or Display values under the time zone subkey of the registry key HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones.

Unfortunately, the control.exe method no longer works on Windows Vista.  When I tried my old VB6 utility on Vista I discovered it no longer worked either.  After asking about it on an internal discussion group, I was informed that my old utility did not work for several reasons.

First, a new privilege (SeTimeZonePrivilege) was created for Windows Vista so that standard users could now change the time zone.  The Date and Time control panel enables this privilege when you use it to change the time zone.  However, custom programs have to enable this privilege programmatically.  Using the AdjustTokenPrivileges Win32 API is one way to do this.  The second reason was that Windows Vista has a new API to set the time zone – SetDynamicTimeZoneInformation.  This API allows control over whether the system uses Dynamic Daylight Savings Time (daylight savings transition dates that change from year to year).  The old API still works but will set the Dynamic Daylight Savings Time setting incorrectly.  It is also recommended that the time zone name parameters for SetDynamicTimeZoneInformation are set from the MUI_Std and MUI_Dlt values from the time zone Registry subkey. To translate these values into the proper language specific string requires using either the RegLoadMUIString or SHLoadIndirectString API.

So I updated my VB6 utility and it now works fine on Windows Vista and higher.  I thought about sharing this version publically but then I decided it would be better if I could demonstrate doing this with a modern language, preferably a scripting language.  Since the Windows Script Host has no mechanism for using Windows API calls, I could not use that.  Luckily there is a newer Windows scripting language that is capable of doing this (albeit indirectly).  That language is Windows PowerShell.

PowerShell itself cannot call Win32 APIs directly.  However, it can use the power of the .NET Framework to do this.  PowerShell can use .NET objects directly using the new-object cmdlet as show here.  However, to call Win32 APIs you have to write some C# code and either create a cmdlet or compile it on the fly from the script.  Since I did not want to have to compiled assemblies as part of this solution I chose the latter.  In PowerShell v1 this can be done with functions like Compile-Csharp found here.  (Note that in the upcoming v2 release there is an Add-Type cmdlet for this.)  Using PINVOKE (Platform Invoke) to call Win32 APIs in C# can be tricky so I’ve provided a few references in case you want to try this for other Windows APIs:

Using P/Invoke to Call Unmanaged APIs from Your Managed Classes

Calling Win32 DLLs in C# with P/Invoke

PInvoke.net

The attached script files implement two commands.  Set-TimeZone is for setting the current time zone.  Get-TimeZone to get the current time zone information and to list the information for all time zones defined in the Registry.  All five files in the attached Zip need to be in the same folder.  Run Set-TimeZone and Get-TimeZone with the –help switch for usage.

If you want to create similar scripts here are a few tips from what I learn doing this.  First, PowerShell v1 does not like it when you reference the C# classes, structures, etc. in the same script that compiles the code.  I originally had a single library script that contained the here-string with the C# code, the step to compile it and the functions that used it.  This worked fine on v2 but v1 gave me errors that it could not find the C# classes, structures, etc referenced in the functions.  So to get this to work on v1 I had to put the here-string in a separate script (TimeZoneCSharp.ps1) from the functions that use it (TimeZoneLib.ps1).  Then in Get-TimeZone and Set-TimeZone I dot sourced TimeZoneCSharp.ps1 and compiled the C# first, then dot sourced TimeZoneLib.ps1.

Second, if you use the Compile-Csharp function found here you need to comment out the $cpar.OutputAssembly = "custom" line.  This line is not needed since we only want to compile the code in memory.  Also, this line causes the compiler to try to write out an assembly in the Windows\System32 folder.  This makes the script fail when run as as standard user.

Note that Windows 7 now includes a command line utility to set the time zone and get time zone information – tzutil.exe.

Disclaimer: The information on this site is provided "AS IS" with no warranties, confers no rights, and is not supported by the authors or Microsoft Corporation. Use of included script samples are subject to the terms specified in the Terms of Use.

This post was contributed by Michael Murgolo, a Senior Consultant with Microsoft Services - U.S. East Region.

Attachment: PSTimeZoneScripts.zip
  • Thanks for the article.  Best info I have found on how to set timezone automatically

  • Thanks for your article. Can I ask you to share your VB6 code anyway?

  • David,

    Out support folks usually do not appreciate us distibuting new code for a discontinued language.  Someone with VB6 experience using the Windows API should be able to port the .NET/PowerShell code I've provided to VB6 with little difficulty.  I figured it out and I'm not a developer.  :-)

    Michael Murgolo

  • Michael, maybe i'm missing something, but in TimeZoneLib.ps1, i see a line that says

    if ($dstoff -eq $false) but i can't seem to find where $dstoff is set. I'm trying to port this to C# and i kinda need that :)

    Thanks,

    Jason Soby

  • Jason,

    $dstoff is false by default and set to true if the -dstoff switch is used with Set-TimeZone.ps1.  

       -dstoff

        (Optional) On Windows Vista and higher this parameter disables dynamic

        daylight savings time, causing the system to use a fixed set of transition

        dates.

    Michael Murgolo

  • oh. of course...sorry, i didnt think to look there

    Thanks so much Michael.

  • Hi mike. Question for you. Did you test this code in Windows 7? I'm having some very strange problems when running this code in C#. I believed i ported your code over correctly.

    It acts like it runs successfully and does not throw any error, but GetLastWin32Error shows a variety of different errors, at different times.

  • Jason,

    Yes I did.  However, I'm not sure I'd be able to help you track down what is going wrong with your code.  I'm an Infrastructure Consultant who writes a lot of scripts.  I don't usually work with compiled languages that often.

    Michael Murgolo

  • Thanks DeploymentGuys, this post was very informative and helpful for me.

    Great post, thanks again.

    -Cleese

  • The initialization or INI file format has been around a long time. In early versions of Windows (especially

  • The initialization or INI file format has been around a long time. In early versions of Windows (especially

  • The initialization or INI file format has been around a long time. In early versions of Windows (especially

  • #Here a a code snippet of how I set the timezone with Powershell

    #Using either 'TZUtil' for Vista/Win7 or 'Control TimeDate.cpl' for XP

    #Modify the next line with your desired timezone

    $timeZone = "Eastern Standard Time"

    $regVer = Get-Item "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"

    $winVer = $regVer.GetValue("CurrentVersion")

    if ($winVer -GE 6){

       tzutil.exe /s $timeZone

    } Else {

    $param = "/c Start `"Change TimeZone`" /MIN %WINDIR%\System32\Control.exe TIMEDATE.CPL,,/z "

    $param += $timeZone

    $proc = [System.Diagnostics.Process]::Start( "CMD.exe", $param )

       }

  • A while back the topic of scripting the installation and removal of fonts came up in an internal discussion

  • A while back the topic of scripting the installation and removal of fonts came up in an internal discussion

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment