Get TimeZone

I’ve a daily security audit script that checks the timezone of our computers. This script actually queries the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation registry key of computers and checks their StandardName value. Since Windows Vista and Windows 7, this value doesn’t contain a string as it usually did on Windows XP. As you can see it now contains a resource.
regqueryTZ

To help me translate those resource back into strings, I could have used Tobias Weltner’s brillant Get-ResourceString function available on this page but I wanted to dig further and learn more about timezones.
Get-ResourceString

I know that there isn’t any simple way to set a timezone on a computer. Either you have a running computer and you should use the tzutil command or you have an offline image and you can use the dism command.
dism-tzutil
My goal wasn’t to set a timezone, but if you’re interested in performing this task, let me also mention that there’s a script available on the Technet script gallery that is a wrapper of the tzutil command called Set-TimeZone.ps1. Even more interesting, the DeploymentGuys posted a way to set a timezone in powershell on their blog.

To learn what’s behind the scene, I fired up a procmon and checked what the tzutil.exe /l command does. As you can see, it reads the “HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones” and its subkeys. I decided that in addition to the translation of resources, the second feature of my powershell script would read this key and dump the same output as the tzutil.exe /l command. Exploring the registry subkeys also helped me brought all the pieces together, as well as link these two goals and gave me an alternative to Tobias Weltener’s Get-ResourceString function.
procmon_tzutil

Along the learning path, I’ve discovered two useful links:

  • Marc van Orsouw, aka MOW The powershell guy, who shows how to use some methods associated with the .NET Framework 3.5 TimeZoneinfo class: more here on his blog
  • Thomas Lee who also shows on his blog how to use other methods associated with the .NET Framework 3.5 TimeZoneinfo class
    • …and I’ve also learned 3 things:

    • The ‘New-Object System.DateTime 2006, 3, 21, 2, 0, 0’ that Thomas Lee uses, has an ‘unspecified’ kind whereas you’ll get a ‘Kind’ property set to ‘Local’ if you use the Get-Date cmdlet to create a datetime object. This has some importance to use the ConvertTimeToUtc method of the .net TimeZoneInfo object.
      datetimeKind
    • There’s a “General Date Short Time (“g”) Format Specifier” that can be used to format datetimes object.

      # Method 1
      (Get-Date).ToString('dd/MM/yyyy HH:mm')
      # Method 2
      Get-Date -Format g
      # Method 3
      $date = Get-Date
      "{0:dd}/{1:MM}/{2:yyyy} {3:HH}:{4:mm}" -f $date, $date, $date, $date, $date
      # so 'g' is actually this:
      $host.CurrentCulture.DateTimeFormat.ShortDatePattern + " " + $host.CurrentCulture.DateTimeFormat.ShortTimePattern
      

      formatdatetime

    • How to use the ParameterSets in a script
      • Finally here’s my Get-TZ.ps1 script, enjoy 🙂

        #Requires -Version 2.0
        
        <#
         
        .SYNOPSIS    
            Get either all timezones, the local timezone or timezone properties on remote computers
         
        .DESCRIPTION  
            Get either all timezones, the local timezone or timezone properties on remote computers
        
        .PARAMETER Local
            Shows the local timezone
         
        .PARAMETER All
            Show all timezones the same way the tzutil /l command does
        
        .PARAMETER Full
            Must be specified with the All parameter in order to show all timezones with 4 columns: CurrentTime,DisplayName,Id,TimeSpan
        
            CurrentTime: time at specified location formatted in dd/MM/yyyy HH:mm 
            DisplayName: (UTC +/- HH:mm) Location
            Id: the <time zone ID>, i.e., it's name
            TimeSpan: a timespan object of the timezone
        
        .PARAMETER FindResource
            Specifies the array of strings to look for
        
        .PARAMETER ComputerName
            Specifies the array of computername to use to read their timezone using WMI
        
        .PARAMETER Credential
            Specifies the credential to use to query remote computers
        
        .EXAMPLE    
            Get-TZ.ps1 -Local
        
            Id          : Romance Standard Time
            CurrentTime : 11/02/2012 16:27
            DisplayName : (UTC+01:00) Brussels, Copenhagen, Madrid, Paris
            MUI_Std     : @tzres.dll,-302
            MUI_Display : @tzres.dll,-300
            MUI_Dlt     : @tzres.dll,-301
            TimeSpan    : 01:00:00
            Dlt         : Romance Daylight Time
            Std         : Romance Standard Time
        
            Returns the local timezone as a PSCustomObject
        
        .EXAMPLE    
            Get-TZ.ps1 -All
            Retrieve all the timezones from the registry and display them the same way tzutil /l does.
         
        .EXAMPLE    
            Get-TZ.ps1 -All -Full
            Retrieve all the timezones from the registry and display them the same way tzutil /l does, with two addtional columns: CurrentTime and TimeSpan
         
        .EXAMPLE
            Get-TZ.ps1 "."
            Get the timezone of the local computer with WMI (the computername is the default parameter and it can be omitted)
         
        .EXAMPLE    
            .\Get-Tz.ps1 -Computername remotecomputer1,remotecomputer2
            Get the timezone of two remote computers with WMI
          
        .EXAMPLE    
            .\Get-Tz.ps1 -Computername remotecomputer1,remotecomputer2 -Credential (Get-Credential)
            Get the timezone of two remote computers with WMI using the specified prompter credential
        
        .EXAMPLE
            .\Get-Tz.ps1 -FindResource "@tzres.dll,-300"
        
            Id          : Romance Standard Time
            CurrentTime : 11/02/2012 16:33
            DisplayName : (UTC+01:00) Brussels, Copenhagen, Madrid, Paris
            MUI_Std     : @tzres.dll,-302
            MUI_Display : @tzres.dll,-300
            MUI_Dlt     : @tzres.dll,-301
            TimeSpan    : 01:00:00
            Dlt         : Romance Daylight Time
            Std         : Romance Standard Time
            
            Show all the timezones that have this specific resource.
        
        .NOTES    
            Name: Get-TZ
            Author: Emin Atac
            DateCreated: 11/02/2012
         
        .LINK    
            https://p0w3rsh3ll.wordpress.com
         
        #>
        
        [CmdletBinding(DefaultParameterSetName='GetRemoteTZ', SupportsTransactions=$false)]
        param(
           [Parameter(ParameterSetName='LocalTZ', Mandatory=$false, Position=0)]
            [System.Management.Automation.SwitchParameter]${Local},
        
            [Parameter(ParameterSetName='Tzutil', Mandatory=$false, Position=0)]
            [System.Management.Automation.SwitchParameter]${All},
        
            [Parameter(ParameterSetName='Tzutil', Mandatory=$false, Position=1)]
            [switch]${Full},
        
            [Parameter(ParameterSetName='Find', Mandatory=$true, Position=0)]
            [string[]]${FindResource},
        
            [Parameter(ParameterSetName='GetRemoteTZ', Mandatory=$true, Position=0)]
            [string[]]${Computername},
        
            [Parameter(ParameterSetName='GetRemoteTZ', Mandatory=$false, Position=1)]
            [System.Management.Automation.PSCredential]${Credential}
        )
        
        # Build a hashtable for splatting
        $otherparams = @{}
        if ($credential)
        {
            $otherparams += @{Credential = $Credential}
        }
        
        # Read the main key where all time zones are stored
        $root = Get-Childitem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
        
        # Get the current local date time
        $mydatetime = [datetime]::Now
        
        # Initialize an empty array
        $allTZobj = @()
        
        # Loop into each subkey representing a timezone
        foreach ($i in $root)
        {
            # Write-Verbose -Message "Dealing with $($i.Name)" -Verbose:$true
        
            # Build the subkey path    
            $subkey = Join-Path -Path $i.PSParentPath -ChildPath $i.PSChildName
        
            # Get its properties
            $subkeyproperties = Get-ItemProperty -Path $subkey
        
            # There isn't any IsObsolete value, ie, it's null, then it's what we are looking for
            if ($subkeyproperties.IsObsolete -eq $null)
            {
                $Object = $null
                # Build an object to store all the properties we are interested in
                $Object = New-Object -TypeName PSObject -Property @{
                    Id = $i.PSChildName
                    Std = $subkeyproperties.Std
                    TimeSpan = [System.TimeZoneInfo]::FindSystemTimeZoneById($i.PSChildName).BaseUtcOffset
                    CurrentTime = ([System.TimeZoneinfo]::ConvertTime($mydatetime,([System.TimeZoneInfo]::FindSystemTimeZoneById($i.PSChildName)))).tostring('g')
                    DisplayName = $subkeyproperties.Display
                    Dlt = $subkeyproperties.Dlt
                    MUI_Display = $subkeyproperties.MUI_Display
                    MUI_Std = $subkeyproperties.MUI_Std
                    MUI_Dlt = $subkeyproperties.MUI_Dlt
                    }
            # Add this new object to our main array
            $allTZobj += $Object
            }
        }
        
        switch ($PsCmdlet.ParameterSetName)
        { 
            Tzutil {
                # If the additional Full switch was specified, display two additional columns
                if($PSBoundParameters['Full'])
                {
                    $allTZObj | Select-Object -Property CurrentTime,DisplayName,Id,TimeSpan | Sort-Object -Descending:$false -Property TimeSpan
                } else {
                    # Display the same output as tzutil /l
                    # Use timespan to sort like tzutil /l but only display the same 2 properties as tzutil /l
                $allTZObj | Sort-Object -Descending:$false -Property TimeSpan | Select-Object -Property DisplayName,Id
                }
            } # end of Tzutil
        
            Find {
                # Parse our array and find any timezone that matches the string we specified
                foreach ($Resource in $FindResource)
                {
                    $allTZObj | Where-Object { ($_.MUI_Std -eq $Resource) -or ($_.MUI_Display -eq $Resource) -or ($_.MUI_Dlt -eq $Resource)}
                }
            } # end of Find
            
            LocalTZ {
                # Parse our array and find the current time on the local computer
                $allTZObj | Where-Object { $_.Displayname -eq ([System.TimeZoneInfo]::Local).ToString()}
            } # end of LocalTZ
            
            GetRemoteTZ {
                # Read the timezone of remote computers using WMI and parse our array to display their timezone properties
                foreach ($Computer in $ComputerName)
                {
                    # Define the  HKLM Constant and the Key we are looking for
                    $HKLM = 2147483650
                    $Key = "SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
                    try
                    {
                        $result = Invoke-WmiMethod -Path "ROOT\DEFAULT:StdRegProv" -ComputerName $Computer -Name GetStringValue -ArgumentList $HKLM,$Key,"StandardName"  -ErrorAction Stop @otherparams
                    }
                    catch
                    {
                        # WMI was unable to retrieve the information
                        switch ($_)
                        {
                            {$_.Exception.ErrorCode -eq 0x800706ba} { $reason =  "Unavailable (offline, firewall)" }
                            {$_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } { $reason = "Access denied" }
                            default { $reason  = $_.Exception.Message }
                        }
                        Write-Host -ForegroundColor Yellow  -Object "Failed to connect to WMI on $Computer because: $reason"
                    }
                    if ($result.sValue -ne $null)
                    {
                        $allTZObj | Where-Object { ($_.MUI_Std -eq $result.sValue) -or ($_.MUI_Display -eq $result.sValue) -or ($_.MUI_Dlt -eq $result.sValue)}
                    }
                }
            } # end of GetRemoteTZ
        } # end of switch
        
        Advertisements

        Leave a Reply

        Fill in your details below or click an icon to log in:

        WordPress.com Logo

        You are commenting using your WordPress.com account. Log Out / Change )

        Twitter picture

        You are commenting using your Twitter account. Log Out / Change )

        Facebook photo

        You are commenting using your Facebook account. Log Out / Change )

        Google+ photo

        You are commenting using your Google+ account. Log Out / Change )

        Connecting to %s