Skip to content Skip to sidebar Skip to footer

How to get the most accurate Windows Install Date (time zone adjusted)

There have been many attempts to get the correct Windows Install Date but 99% of the solutions I have seen are technically the wrong date. It was driving me bonkers.


Most Common Solutions for getting Windows Install Date are All Wrong


Here are most common way to find Windows Install Date;

1. Most popular way, using following command line

  1  
  systeminfo.exe | find /i "Original Install Date"   

    filtering for Original Install Date outputs the wrong date.


2. The following Powershell is a common solution; checking WMI Win32_Registry class[1] for InstallDate but outputs the wrong date.

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

3. The following Powershell is a common solution, checking the WMI Win32_OperatingSystem[2] class for InstallDate outputs the wrong date.


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


4. The following Powershell is common solution checking the Windows Registry key
    HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallDate



  1  2  
   [TimeZone]::CurrentTimeZone.ToLocalTime([DateTime]'1.1.1970').AddSeconds(   (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').InstallDate )  

or using the C# equivalent snippet


   1   2   3   4   5   6   7   8   9  10  11  12  
  var key = Microsoft.Win32.RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);    key = key.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false);  if (key != null)  {      DateTime startDate = new DateTime(1970, 1, 1, 0, 0, 0);      object objValue = key.GetValue("InstallDate");      string stringValue = objValue.ToString();      Int64 regVal = Convert.ToInt64(stringValue);        DateTime installDate = startDate.AddSeconds(regVal);  }  

will always output the wrong date, even in the C# snippet. 



Reason why these will not work;


1  systeminfo.exe uses UNIX timestamp will always be wrong.

2. ConvertToDateTime is not converting the datetime datatype correctly, timezone info is lost.
3. Ditto.
4. HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallDate contains a UNIX timestamp and will always be wrong.



Explanation


Now let me explain; 

The definition of a UNIX timestamp[3] is time zone independent. The UNIX timestamp is defined as the number of seconds that have elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970 and not counting leap seconds.

In other words, if you have installed you computer in Seattle, WA and moved to New York,NY the HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallDate will give you the date in NY time zone, not in Seattle time zone where Windows was original installed. It's the wrong date, it doesn't store time zone info where the computer was initially installed. 

So what you really want is a data type that can accurately capture the time and date + time zone. This is where UTC Format[4] (UTC±HH:MM) comes in, it carries time zone information. The Microsoft(MS) engineers then optimized this for storage and came up with their own UTC format (yyyymmddHHMMSS.xxxxxx±UUU), striping hours, expanding minutes to be 4 digits with sign. This fits ±720 minutes (± 12 hrs or equivalent of 24hrs/time zones). 

Fun Fact: Earth rotates at 15 degrees of longitude per hour. Each time zone[5] is 15 degrees wide or 1669.79km (1,040 miles) if perfectly divided. In 1 minute, earth rotates 28km (17.4 miles). At 89.8 degrees latitude it would be an easy walking speed of 6.4 km (4 mph) to match earths rotation, but you have to be 22.53km (14 miles) away from the North/South pole.

In the MS UTC format, dates are displayed as yyyymmddHHMMSS.xxxxxx+/-UUU, where:

  • yyyy represents t he year.
  • mm represents the month.
  • dd represents the day.
  • HH represents the hour (in 24-hour format).
  • MM represents the minutes.
  • SS represents the seconds.
  • xxxxxx represents the milliseconds.
  • ± represents positive or negative time zone (TMZ) offset
  • UUU represents the time zone (TMZ) offset, the difference, in minutes, between the local time zone and Greenwich Mean Time (GMT).

as per Microsoft TechNet article titled "Working with Dates and Times using WMI[6]". 

Example Microsoft UTC Format values look like  

  1  2  3  4  5  6  
  20100216010920.000000-300   20110106073639.000000+480   
  xxxxxxxxxxxxxx.000000+xxx --- general search patterns below for search engines ----  xxxxxxxxxxxxxx.000000-xxx   ##############.000000+###  ##############.000000-###  

So where is UTC Format stored for Windows Install Date?


Luckily, we can get timezone information from Installdate located in the WMI Win32_Registry class.

Sidenote: When you try to list all the software on you computer using the Win32_Product class[8]  there a InstallDate which contains yyyymmdd and InstallDate2 which should contain a MS UTC Format, but warning this was not a required field for vendors and usually empty.

[7]



SOLUTION

Finally the solution, how to get an accurate timezone adjusted Windows Install Date in C#. Powershell is below this.


   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  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  
          /// <summary>          /// Get Windows Registry InstallDate from Win32_Registry WMI key with UTC Offset (timezone added)          /// </summary>          /// <returns>A string formated to RFC1123 date format except add day of week and replace GMT with AM/PM</returns>          /// <remarks>          /// <author>Mark Pahulje, MetadataConsulting.ca - 2017</author>          /// <description>          ///           /// Converting WMI Dates to a Standard Date-Time Format - <see href="https://technet.microsoft.com/en-us/library/ee198928.aspx"/>          /// Working with Dates and Times using WMI              - <see href="https://technet.microsoft.com/en-us/library/ee198928.aspx"/>           ///          /// InstallDate is in the UTC format (yyyymmddHHMMSS.xxxxxx±UUU) where critically          ///           /// xxxxxx is milliseconds and                 /// ±UUU   is number of minutes different from Greenwich Mean Time.           ///           /// Examples            /// 20100216010920.000000-300           /// 20110106073639.000000+480           ///          /// TL;DR: The upshot is the accurate install date of Windows, adjusted to the time zone on the date of the install!          /// </description>          /// </remarks>                    private static string WindowsInstallDateTMZAdjusted()          {                DateTime InstallDate = new DateTime(1970, 1, 1, 0, 0, 0); //DUMMY LINE - NOT A UNIX timestamp as in most solutions                         ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Registry");                            foreach (ManagementObject wmi_Windows in searcher.Get())              {                  try                  {                                            string installdate = wmi_Windows["InstallDate"].ToString();                      //NOT a UNIX timestamp 99% of online solutions incorrect identify this as!!!!                                             if (installdate.Length==25)                      {                          string yyyymmddHHMMSS = installdate.Split('.')[0];                          string xxxxxxsUUU = installdate.Split('.')[1];      //±=s for sign                            int year  = int.Parse(yyyymmddHHMMSS.Substring(0, 4));                          int month = int.Parse(yyyymmddHHMMSS.Substring(4, 2));                          int date  = int.Parse(yyyymmddHHMMSS.Substring(4 + 2, 2));                          int hour  = int.Parse(yyyymmddHHMMSS.Substring(4 + 2 + 2, 2));                          int mins  = int.Parse(yyyymmddHHMMSS.Substring(4 + 2 + 2 + 2,  2));                          int secs  = int.Parse(yyyymmddHHMMSS.Substring(4 + 2 + 2 + 2 + 2, 2));                          int msecs = int.Parse(xxxxxxsUUU.Substring(0, 6));                            double UTCoffsetinMins = double.Parse(xxxxxxsUUU.Substring(6, 4));                          TimeSpan UTCoffset = TimeSpan.FromMinutes(UTCoffsetinMins);                            InstallDate = new DateTime(year, month, date, hour, mins, secs, msecs) + UTCoffset;                         }                      break;                  }                  catch (Exception)                  {                      InstallDate = DateTime.Now; //fails now                  }              }              //choose your format              return String.Format("{0:ddd d-MMM-yyyy h:mm:ss tt}", InstallDate);                }  

Powershell Version

    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   29   30   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45   46   47   48   49   50   51   52   53   54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69   70   71   72   73   74   75   76   77   78   79   80   81   82   83   84   85   86   87   88   89   90   91   92   93   94   95   96   97   98   99  100  101  102  103  104  105  106  107  108  109  110  111  112  113  114  115  116  117  118  119  120  121  122  123  124  125  126  127  128  129  130  131  
  #requires -version 2.0     # -----------------------------------------------------------------------------  # Script: Get-WindowsInstallDateTMZAdjusted.ps1  # Version: 1.2017.07.08  # Author: Mark Pahulje  #    http://metadataconsulting.blogspot.com/  # Date: 08-Apr-2017  # Keywords: Registry, WMI, Windows Install Date  # Comments:  #  # "Those who forget to script are doomed to repeat their work."  #  #  ****************************************************************  #  * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *  #  * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK.  IF   *  #  * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *  #  * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING.             *  #  ****************************************************************  # -----------------------------------------------------------------------------     Function Get-WindowsInstallDateTMZAdjusted {     <#  .SYNOPSIS  Get accurate Windows Install Date Time Zone (TMZ) Adjusted  .DESCRIPTION  This command uses WMI to retrieve install date of the Windows registry, which is equivallent to Windows Install Date that is time zone adjusted.  Win32_Registry InstallDate is stored as WMI Datetime a datatype which really stores a   Microsoft UTC format (yyyymmddHHMMSS.xxxxxx±UUU)   where crucially   ±UUU is number of minutes different local time zone (at the time Windows was installed) from Greenwich Mean Time and   xxxxxx is milliseconds.  This scripts add the time zone offset (±UUU) to get the real correct Windows install date.   This version has no provision for alternate credentials.  .MUNCHIES  99% of all scripts out there do not account for this!   .OPTIONAL PARAMETER Computername  The name of a computer to query. The default is the local host.  .EXAMPLE  PS C:\> Get-WindowsInstallDateTMZAdjusted      Computername                       : THUNDERBALL-W7U  Windows Install Date (typical way) : 16-Feb-10 1:09:20 AM  Windows Install Date TMZ adjusted  : 15-Feb-10 8:09:20 PM  Windows Install Date Value         : 20100216010920.000000-300  Age                                : 7 years, 1 months, 21 days & 07 hours       Return registry usage information for the local host.  .EXAMPLE  PS C:\> Get-Content Computers.txt | Get-WindowsInstallDateTMZAdjusted | Export-CSV c:\work\ListofComputerswithInstallDates.csv  Retrieve registry install date (Windows install date) for all the computers in the text file, computers.txt. The results  are exported to a CSV file.  .NOTES  NAME        :  Get-WindowsInstallDateTMZAdjusted  VERSION     :  1.2017.04.08    LAST UPDATED:  08-Apr-2017  AUTHOR      :  Mark Pahulje  .LINK  http://metadataconsulting.blogspot.ca/2017/04/How-to-get-the-most-accurate-Windows-Install-Date-time-zone-adjusted.html  .LINK  Get-WindowsInstallDateTMZAdjusted  .INPUTS  String  .OUTPUTS  A formatted table  #>     [cmdletbinding()]     Param (  [Parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]  [ValidateNotNullorEmpty()]  [String[]]$Computername=$env:Computername  )     Begin {      Write-Verbose "Starting $($myinvocation.mycommand)"  } #Begin     Process {      Foreach ($computer in $computername) {          Write-Verbose "Processing $computer"          Try {           #retrieve registry information via WMI           $data=Get-WmiObject -Class Win32_Registry -ComputerName $computer -ErrorAction Stop                             $installdatestring = ($data).InstallDate             # converts yyyymmddHHMMSS to datetime by default           $installdatetime = ([WMI]'').ConvertToDateTime($installdatestring);              $xxxxxxsUUU = $installdatestring.Split('.')[1];                  [long]$msecs = [convert]::ToInt64($xxxxxxsUUU.Substring(0, 6),10);           [int]$UTCoffsetinMins = [convert]::ToInt32($xxxxxxsUUU.Substring(6, 4),10);                      $installdatetimeTMZ = $installdatetime.AddMinutes($UTCoffsetinMins).AddMilliseconds($msecs);                    #add a member to iterate over in our table  - re http://windowsitpro.com/powershell/powershell-basics-custom-objects - great tip           Add-Member -InputObject $data -MemberType NoteProperty `          -Name InstallDateTMZ `          -Value $installdatetimeTMZ                     #Format the results and write an object to the pipeline                    $data | Select-Object -Property @{Name="Computername";Expression={$_.__SERVER}},           @{Name="Windows Install Date (typical way)";Expression={ $_.ConvertToDateTime($_.InstallDate) }},           @{Name="Windows Install Date TMZ adjusted";Expression={ $_.InstallDateTMZ }},           @{Name="Windows Install Date Value";Expression={$_.InstallDate}},                                @{Name="Age";Expression={"{1:N0} years, {2:N0} months, {3:N0} days & {0:hh} hours" -f (  ((Get-Date) - ($_.InstallDateTMZ)), [Math]::Truncate( (((Get-Date) - ($_.InstallDateTMZ)).Days/365.2425) ), [Math]::Truncate( ((((Get-Date) - ($_.InstallDateTMZ)).Days%365.2425)/30.436875)),  ((((Get-Date) - ($_.InstallDateTMZ)).Days%30.436875))   ) }}                     } #try                   Catch {              Write-Warning "Failed to retrieve registry information from $($Computer.ToUpper())"              Write-Warning $_.Exception.Message          }#Catch           }#foreach $computer  } #Process     End {      Write-Verbose "Ending $($myinvocation.mycommand)"  } #End     }