Setting the Time Zone using Windows PowerShell
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
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.
Comments
Anonymous
January 01, 2003
Introduction I strongly believe that in most application we are building, we would need to store dateAnonymous
January 01, 2003
A while back the topic of scripting the installation and removal of fonts came up in an internal discussionAnonymous
January 01, 2003
The initialization or INI file format has been around a long time. In early versions of Windows (especiallyAnonymous
January 01, 2003
The initialization or INI file format has been around a long time. In early versions of Windows (especiallyAnonymous
January 01, 2003
The initialization or INI file format has been around a long time. In early versions of Windows (especiallyAnonymous
January 01, 2003
A while back the topic of scripting the installation and removal of fonts came up in an internal discussionAnonymous
January 01, 2003
Yes, Tim the Enchanter, I am aware of tzutil.exe for Windows 7 and I even mentioned it at the end of post. However, when this post was written (June 2009) Windows 7 hadn't shipped yet. This code was required for Windows Vista as there was no built-in utility to change the time zone on that OS.Anonymous
January 01, 2003
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 MurgoloAnonymous
January 01, 2003
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 MurgoloAnonymous
January 01, 2003
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 MurgoloAnonymous
June 08, 2009
Thanks for the article. Best info I have found on how to set timezone automaticallyAnonymous
December 08, 2009
Thanks for your article. Can I ask you to share your VB6 code anyway?Anonymous
January 07, 2010
The comment has been removedAnonymous
January 07, 2010
oh. of course...sorry, i didnt think to look there Thanks so much Michael.Anonymous
February 01, 2010
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.Anonymous
February 18, 2010
Thanks DeploymentGuys, this post was very informative and helpful for me. Great post, thanks again. -CleeseAnonymous
August 31, 2010
The comment has been removedAnonymous
January 14, 2011
@Chris.. nice code, works great on both Server 2003 and 2008.Anonymous
April 26, 2013
Nice blog to show how to do simple things in a complex way! :-)Anonymous
June 18, 2013
The comment has been removedAnonymous
October 26, 2014
This is overly complicated - especially since windows has a built-in utility to handle this. tzutil.exe. I could replace all this nonsense with just 'tzutil /s "Pacific Standard Time"'. Not as fancy as using powershell to compile a C# script to modify the registry, but I feel like it still has it's redeeming qualities.