I’ve been doing a lot of work lately for some global organizations that have servers in multiple different global time zones. In particular configuration manager distribution points. Now if you are familiar with the environment you might not have any kind of problem knowing exactly what time it is for each one of your distribution points, or servers at any given time. However, for myself I find it much easier to simply ask the servers what time it is for them instead.

Round 1 – Picking our tools

In order to do this we need to figure out what tools we have available to us and of course a relatively obvious choice is our good friend the PowerShell command “Get-Date”.  Simple and easy right?

Dang a quick check with Get-Help will reveal though that Get-Date doesn’t support an option to execute it against a remote computer. So that won’t quite work, but what about, PowerShell remoting maybe if we invoke it?

 

Unfortunately that doesn’t quite work either as “SRVMDT03” is in Sweden and I’m in Eastern time I’m pretty sure that those times shouldn’t match, and for a bonus round – check it if you don’t believe me “-AsJob” doesn’t work either. While it’s nice that it’s observing my culture settings this isn’t very helpful when I need to know what time it is for that server to determine if its a safe time to remotely take actions on the server. Now, I know what you’re thinking OK if invoking it didn’t work maybe we can do a proper PSSession to get the time! Lets try it.

 

 

Hey look that worked! Unfortunately though, Enter-PSSession is intended for interactive usage and so that doesn’t really solve our issue in the long term. Now, there are several other ways to use this including some interesting methods that would use WMI to ask for the time information from WMI on the remote endpoint you can read some more about that over here:

https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-tasks–dates-and-times

I find that method to be particularly annoying, as you end up with a result that looks something like this

 

There clearly has to be a better way to deal with this as computers deal with time all the time right? Enter the .NetFramework and the TimeZoneInfo class. You can read more about it here: https://docs.microsoft.com/en-us/dotnet/api/system.timezoneinfo?view=netframework-4.8

Round 2 – Dealing with the .NETFramework

At this point we’ve picked a tool .NETFramework and the class timezoneInfo and if you’ve done some light reading you can probably guess that we are going to use one of the time conversion methods. In fact we are going to use the ConvertTiemZoneBySystemTimeZoneID method. Something that looks like this

ConvertTimeBySystemTimeZoneId(DateTime, String)

OK but how do we take that into our world of PowerShell? First we need to deal with getting the timezone of the remote server, and if we take a trip down memory lane we can find the answer to that we just need to use the invoke command we tried earlier and this properly returns the right information instead of being automatically translated into our culture.

$TimeZone = Invoke-Command -ComputerName $Server -ScriptBlock {Get-TimeZone}

This then gets us some timezone information for the remote server.

 

Throw one more line at it and before you know it we’ve got what time it is over in Sweden!

[timezoneinfo]::ConvertTimeBySystemTimeZoneId((Get-Date) , $TimeZone.Id)

 

Round 3 – Making it useful

Now while this is a pretty cool application of using the .NETFramework and how we can’t always assume that obvious things should work the way they should how do we actually make this useful? Well this entire code came about as previously mentioned by needing to gather the current time and timezone for every distribution point in an environment. So from a server with the Configuration Manager Console installed we could run the following code to get that information.

Import-Module (Join-Path $(Split-Path $ENV:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1) -Verbose:$false
$Drive = (Get-PSDrive -PSProvider CMSite).Name
Set-Location $($Drive + ":")
$List = [System.Collections.Generic.List[object]]::new()
$Servers = Get-CMDistributionPointInfo | Select-Object -ExpandProperty ServerName
ForEach($Server in $Servers){
    $TimeZone = Invoke-Command -ComputerName $Server -ScriptBlock {Get-TimeZone}
    $CurrentTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "$($TimeZone.ID)")
    $Hash = [ordered]@{
        ServerName = $Server
        CurrentTime = $Currenttime
        TimeZone = $TimeZone.DisplayName
    }
    $Item = New-Object -TypeName PSobject -Property $Hash
    $List.Add($Item)
    return $List
}

We could also turn this into a function and instead using the name of the ConfigMgr server leverage WMI to find all of the distribution points therein removing the requirement of the ConfigMgr console.

function Get-DPCurrentTime{
    [CmdletBinding()]
    param(
    [Parameter(HelpMessage = "Enter the name of the ConfigMgr Server",Mandatory = $true)]
    [string]$ConfigMgrServer,
    [Parameter(HelpMessage = "Enter the ConfigMgr Site Server", Mandatory = $true)]
    [string]$SiteCode
    )
    begin{}
    process{
        $DPList = Get-WmiObject -ComputerName $ConfigMgrServer -Namespace root\sms\site_$SiteCode -Query "select distinct ServerName from sms_distributionpointInfo"
        $List = [System.Collections.Generic.List[object]]::new()
        ForEach($Server in $DPList){
            $TimeZone = Invoke-Command -ComputerName $Server.ServerName -ScriptBlock {Get-TimeZone}
            $CurrentTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "$($TimeZone.ID)")
            $Hash = [ordered]@{
                ServerName = $Server.SERVERNAME
                CurrentTime = $Currenttime
                TimeZone = $TimeZone.DisplayName
            }
            $Item = New-Object -TypeName PSobject -Property $Hash
            $List.Add($Item)
        }
    return $List
    }
}

And finally we could always just turn it into a simple function that works against any remote machine, or steal these two lines and run them with the ConfigMgr Run Scripts feature.

function Get-RemoteTime {
    [CmdletBinding()]
    param(
        [Parameter(HelpMessage = "The name of the remote computer")]
        [string]$ComputerName
    )
    $TimeZone = Invoke-Command -ComputerName $ComputerName -ScriptBlock {Get-TimeZone}
    $CurrentTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "$($TimeZone.ID)")
    return $CurrentTime
}

 

Hopefully this helps you the next time you need to ask, is it 5’oclock over there yet?

 

(563)

There are no comments.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.