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.
Here are most common way to find Windows Install Date;
1. Most popular way, using following command line
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.
3. The following Powershell is a common solution, checking the WMI Win32_OperatingSystem[2] class for InstallDate outputs the wrong date.
4. The following Powershell is common solution checking the Windows Registry key
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallDate
or using the C# equivalent snippet
will always output the wrong date, even in the C# snippet.
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.
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.
as per Microsoft TechNet article titled "Working with Dates and Times using WMI[6]".
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.[7]
Powershell Version
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.
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