Monday, April 17, 2017

Powershell ConvertToDateTime Method Definition and Implementation Error, ConvertToDateTime Bug

The post deals with the hunt for the definitive Powershell ConvertToDateTime method definition and implementation error. I will prove that ConvertToDateTime in Powershell yields the incorrect result every time.

Let's start with a typical Powershell classes that you will invariable touch upon and that is the Win32_OperatingSystem class that has properties about the version and name of Windows you are running. It also has some interesting methods, which are Reboot, SetDateTime, Shutdown, Win32Shutdown, Win32ShutdownTracker which are well documented on MSDN.

But when we list these members in Powersehell we get two additional methods 8,9 in results below with a Member Type of ScriptMethod.

There is no ScriptMethod type that is defined in any .NET class or CMI base classes, nor is there any mention of the ConvertFromDateTime and ConvertToDateTime methods in the CMI classes.

1
Get-WmiObject Win32_OperatingSystem | Get-Member -MemberType *method

results in

1
2
3
4
5
6
7
8
9
Name                 MemberType   Definition                                                                                             
----                 ----------   ----------                                                                                             
Reboot               Method       System.Management.ManagementBaseObject Reboot()                                                        
SetDateTime          Method       System.Management.ManagementBaseObject SetDateTime(System.String LocalDateTime)                        
Shutdown             Method       System.Management.ManagementBaseObject Shutdown()                                                      
Win32Shutdown        Method       System.Management.ManagementBaseObject Win32Shutdown(System.Int32 Flags, System.Int32 Reserved)        
Win32ShutdownTracker Method       System.Management.ManagementBaseObject Win32ShutdownTracker(System.UInt32 Timeout, System.String Com...
ConvertFromDateTime  ScriptMethod System.Object ConvertFromDateTime();                                                                   
ConvertToDateTime    ScriptMethod System.Object ConvertToDateTime(); 


So just what is a ScriptMethod?

Turns out that you can extended type data defines additional properties and methods ("members") of object types of the  Microsoft .NET Framework in Windows PowerShell. These extensions load by default is your session when powershell is started by loading built-in Types.ps1xml file that adds several elements  to the .NET Framework types.

This file is located at
$PSHOME\Types.ps1xml (that is not a typo, there is no ps1.xml)

In Types.ps1xml we can see how ConvertToDateTime ScriptMethod is defined at line 16 which calls

[System.Management.ManagementDateTimeConverter]::ToDateTime($args[0]) 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<ScriptMethod>: Defines a method whose value is the output of a script.

         The <ScriptMethod> tag must have a pair of <Name> tags that specify
         the name of the new method and a pair of <Script> tags that enclose
         the script block that returns the method result.       
            
         For example, the ConvertToDateTime and ConvertFromDateTime methods of 
         management objects (System.System.Management.ManagementObject) are
         script methods that use the ToDateTime and ToDmtfDateTime static 
         methods of the System.Management.ManagementDateTimeConverter class. 

             <Type>
                 <Name>System.Management.ManagementObject</Name>
                 <Members>
                     <ScriptMethod>
                         <Name>ConvertToDateTime</Name>
                         <Script>
                             [System.Management.ManagementDateTimeConverter]::ToDateTime($args[0])
                         </Script>
                     </ScriptMethod>
                     <ScriptMethod>
                         <Name>ConvertFromDateTime</Name>
                         <Script>
                             [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($args[0])
                         </Script>
                     </ScriptMethod>
                 </Members>
             </Type>


This is a direct call to the .NET ManagementDateTimeConverter Class invoking the ToDateTime method, with following description.
ToDateTime(String)
Converts a given DMTF datetime (CIM_DATETIME) to DateTime. The returned DateTime will be in the current time zone of the system.
WMI uses the DMTF datetime formats defined by the Distributed Management Task Force (DMTF.org) Common Information Model (CIM) specification. The CIM_DATETIME format is implemented in the Microsoft Managed Object Format (MOF) by the DATETIME MOF datatype. The date and time formats can also express an interval of time. CIM_Datetime format is defined as yyyymmddHHMMSS.mmmmmmsUUU where UUU is number of minutes different from UTC/Greenwich Mean Time as per Microsoft TechNet "Working with Dates and Times using WMI"
We can use the following Powershell script to demonstrate to output CIM_DateTime format and its converted date and time using ConvertToDateTime ScriptMethod.

1
2
(Get-WmiObject Win32_OperatingSystem).InstallDate
([WMI]'').ConvertToDateTime((Get-WmiObject Win32_OperatingSystem).InstallDate)

which yields CIM_Datetime string and equivalent datetime value.


1
2
20100216010920.000000-300
Tuesday, February 16, 2010 1:09:20 AM

Now lets test the C# equivalent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static string WindowsOSInstallDate()
{
    try
    {
        ManagementObjectSearcher _mbs = new ManagementObjectSearcher("Select InstallDate From Win32_OperatingSystem");
        ManagementObjectCollection _mbsList = _mbs.Get();
        string installCIMdatestring = string.Empty;
        foreach (ManagementObject _mo in _mbsList)
        {
            installCIMdatestring = _mo["InstallDate"].ToString();
            break;
        }
        // Converting DMTF datetime to System.DateTime
        DateTime installdatetime = ManagementDateTimeConverter.ToDateTime(installCIMdatestring);
        // returns both
        return "CIM_DATE : " + installCIMdatestring + "\n" + String.Format("{0:ddd d-MMM-yyyy h:mm:ss tt}", installdatetime);
    }
    catch
    {
        return string.Empty;
    }
}

which yields identical result! But.................

1
2
CIM_DATE : 20100216010920.000000-300
Tue 16-Feb-2010 1:09:20 AM

But there's a gotcha!


Testing in with different time zones


When I changed my time zone to (UTC) Coordinated Universal Time (UTC+0:00), I get the following result using C# code. 


1
2
CIM_DATE : 20100216060920.000000+000
Tue 16-Feb-2010 6:09:20 AM

The CIM_Datetime string (+000) indicates the correct time zone which is UTC.
The datetime value (6:09:20 AM) has adjusted to the current UTC time zone, as expected.


However, when I run the Powershell script in the 
(UTC) Coordinated Universal Time (UTC+0:00) time zone, get the following result.


1
2
20100216060920.000000+000
Tuesday, February 16, 2010 1:09:20 AM

The CIM_datetime string (+000) indicating the correct time zone which is UTC.

But the datetime value (1:09:20 AM) has NOT CHANGED. It's the same as in my my default (UTC-05:00) Eastern Time (US & Canada). The returned DateTime is not in the current time zone of the system. In fact it's the same in all time zones I tested.


DANGER WILL ROBINSON DANGER, ERROR, ERROR



Powershell ConvertToDateTime Bug


Powershell ConvertToDateTime does NOT adjusted to the current time zone! 

Running ConvertToDateTime on any WMI class will result in the datetime not be in the current time zone. Line 2 of Powershell script will produce the same result in any time zone, for any CIM_datetime property in any WMI class.

1
2
(Get-WmiObject Win32_OperatingSystem).InstallDate
([WMI]'').ConvertToDateTime((Get-WmiObject Win32_OperatingSystem).InstallDate)


1
2
20100216060920.000000+000
Tuesday, February 16, 2010 1:09:20 AM

It's a subtle error that I am still try to get an answer to from Microsoft.

Is this the returning the original timezone the computer was installed in? Or is just ignoring the timezone completely.

This applies both powershell.exe and powershell_ise.exe.

Please upvote this issue "Powershell ConvertToDateTime bug" on the Microsoft's PowerShell forum site.



Update May 10, 2017 PowerShell forum site



Hi Ilya, thank you and you are correct, in your assumption of not restarting Powershell (PS). (koodoos).
          However, there is an error still occuring.
When you run the PS code line 1, (Get-WmiObject Win32_OperatingSystem).InstallDate it emits CIM_DATE 20100216060920.000000-300 for me in my default Eastern Time Zone. Then while PS is still running, I change the time zone to UTC, rerun line 1 and get CIM_DATE 20100216060920.000000+000 which indicates it correctly grabbed new change time zone to UTC (indicated by +000). Time zone offset being indicate by +/-xxx in minutes in CIM_DATE format.

The InstallDate in UTC time zone remains the same, if you restart PS or not. This tells us PS is grabbing the correct changed time zone, while PS is still running. So far, so good, behaviour as expected.

But the next PS code line 2, ([WMI]'').ConvertToDateTime((Get-WmiObject Win32_OperatingSystem).InstallDate) does not apply the change time zone to UTC (while still running PS) as you keenly indicated. (great catch btw!). Only when you reboot PS code line 2 gives you the correct result and applies the change time zone. This is the error.

So I vote this still a PS issue, since the line 1 is saying it pick-up the time zone change, but does not apply it to line 2 calculation. Conversely, if PS did not pick-up time zone change in line 1, then you would think to restart PS so it might get the new time zone.

Restart of Powershell does produce the correct calculation, taking into account UTC time zone. See above for the remaining issue.  


1
2
20100216060920.000000+000
Tuesday, February 16, 2010 6:09:20 AM



No comments:

Post a Comment