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, but applies your current local time zone to the result when you run it.
2. ConvertToDateTime is not converting the CMI datetime datatype correctly period. See below
3. Ditto.
4. HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallDate contains a UNIX timestamp is correct, it contains no times zone information.
Explanation
Now let me explain;
The definition of a UNIX timestamp 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) the Western European Time (WET) time zone, Thursday, 1 January 1970 and not counting leap seconds.
It does not store the original time zone, in which it was created. Or you could say it always stores the
[3]Western European Time (WET) timezone.In other words, if you have installed you computer in Seattle, WA and moved to New York,NY the H KLM\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.
SOLUTION
1) Change you computer time zone (right-click on you clock->Adjust date/time->Adjust time zone) to the time zone where windows was installed, or first turned on.Then run systeminfo.exe find /i "Original Install Date"
2) Get the UNIX Timestamp found in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallDate for me is 16-Feb-10 6:09:20 AM
This is the date with UTC+/-0:00, or "(UTC) Coordinated Universal Time" time zone. Add your target time zone to it.
3) Poweshell is complicated, since (Get-WmiObject Win32_Registry).InstallDate does store the original time zone as the format CIM_DATE TIME[4] would suggest.
But when you interrogate (Get-WmiObject Win32_Registry).InstallDate in different time zones the value changes? by adding the current time zone to the value. Not what I would expect, this is a datatype and values should not change.
Runs show how ConvertToDateTime is broken.
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 | Computername : THUNDERBALL-W7U Windows Install Current Time Zone : (UTC) Coordinated Universal Time Windows Install Date (ConvertToDateTime) : 16-Feb-10 1:09:20 AM Windows Install Date : 16-Feb-10 6:09:20 AM Windows Install Date (ParseExact with TMZ offset 0) : 16-Feb-10 6:09:20 AM Windows Install Date TMZ adjusted : 16-Feb-10 6:09:20 AM Windows Install Date Value : 20100216060920.000000+000 Age : 7 years, 1 months, 22.865625 days & 20 hours Computername : THUNDERBALL-W7U Windows Install Current Time Zone : (UTC-08:00) Pacific Time (US & Canada) Windows Install Date (ConvertToDateTime) : 16-Feb-10 1:09:20 AM Windows Install Date : 15-Feb-10 10:09:20 PM Windows Install Date (ParseExact with TMZ offset -480) : 15-Feb-10 2:09:20 PM Windows Install Date TMZ adjusted : 15-Feb-10 10:09:20 PM Windows Install Date Value : 20100215220920.000000-480 Age : 7 years, 1 months, 23.865625 days & 04 hours Computername : THUNDERBALL-W7U Windows Install Current Time Zone : (UTC-05:00) Eastern Time (US & Canada) Windows Install Date (ConvertToDateTime) : 16-Feb-10 1:09:20 AM Windows Install Date : 16-Feb-10 1:09:20 AM Windows Install Date (ParseExact with TMZ offset -300) : 15-Feb-10 8:09:20 PM Windows Install Date TMZ adjusted : 16-Feb-10 1:09:20 AM Windows Install Date Value : 20100216010920.000000-300 Age : 7 years, 1 months, 23.865625 days & 01 hours |
Poweshell Script for above.
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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | #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 $timeZone=Get-WmiObject -Class Win32_Timezone -ComputerName $computer -ErrorAction Stop #UTC = local time - bias #https://msdn.microsoft.com/en-us/library/aa394498%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 #$timeZone=[TimeZoneInfo]::Local.DisplayName - this does not work when you change timezones # converts yyyymmddHHMMSS to datetime by default # $installdatetime = ([WMI]'').ConvertToDateTime($installdatestring); not trusting this function add +5:00 for some reason $yyyymmddHHMMSS = $installdatestring.Split('.')[0]; #[int]$year = [convert]::ToInt32($yyyymmddHHMMSS.Substring(0, 4),10); #[int]$month = [convert]::ToInt32($yyyymmddHHMMSS.Substring(4, 2),10); #[int]$day = [convert]::ToInt32($yyyymmddHHMMSS.Substring(4 + 2, 2),10); #[int]$hours = [convert]::ToInt32($yyyymmddHHMMSS.Substring(4 + 2 + 2, 2),10); #[int]$mins = [convert]::ToInt32($yyyymmddHHMMSS.Substring(4 + 2 + 2 + 2),10); #[int]$secounds = [convert]::ToInt32($yyyymmddHHMMSS.Substring(4 + 2 + 2 + 2 + 2, 4),10); #http://dusan.kuzmanovic.net/2012/05/07/powershell-parsing-date-and-time/ $template = 'yyyyMMddHHmmss' $installdatetime = [DateTime]::ParseExact($yyyymmddHHMMSS, $template, $null) $xxxxxxsUUU = $installdatestring.Split('.')[1]; [long]$msecs = [convert]::ToInt64($xxxxxxsUUU.Substring(0, 6),10); [int]$UTCoffsetinMins = [convert]::ToInt32($xxxxxxsUUU.Substring(6, 4),10); $installdatetime = $installdatetime.AddMilliseconds($msecs) #block output $installdatetimePE = $installdatetime #timezone added from InstallDate $installdatetimePE = $installdatetimePE.AddMinutes($UTCoffsetinMins) #SET YOUR TARGET TIMEZONE OF INITIAL #(UTC-08:00) Pacific Time (US & Canada) #[TimeZoneInfo]::Local.BaseUtcOffset # does not work when you swith timezones! #[TimeZoneInfo]::Local.SupportsDaylightSavingTime $installdatetimeTMZ = $installdatetime.AddMinutes(($timeZone.Bias*-1)); $installdatetimeTMZ = $installdatetimeTMZ.AddMinutes($UTCoffsetinMins); #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 InstallDateRAW ` -Value $installdatetime Add-Member -InputObject $data -MemberType NoteProperty ` -Name InstallDatePE ` -Value $installdatetimePE Add-Member -InputObject $data -MemberType NoteProperty ` -Name InstallDateTMZ ` -Value $installdatetimeTMZ Add-Member -InputObject $data -MemberType NoteProperty ` -Name TimeZone ` -Value $timeZone.Caption #Format the results and write an object to the pipeline $data | Select-Object -Property @{Name="Computername";Expression={$_.__SERVER}}, @{Name="Windows Install Current Time Zone ";Expression={ $_.TimeZone }}, @{Name="Windows Install Date (ConvertToDateTime)";Expression={ $_.ConvertToDateTime($_.InstallDate) }}, @{Name="Windows Install Date ";Expression={ $_.InstallDateRAW }}, @{Name="Windows Install Date (ParseExact with TMZ offset $UTCoffsetinMins)";Expression={ $_.InstallDatePE }}, @{Name="Windows Install Date TMZ adjusted";Expression={ $_.InstallDateTMZ }}, @{Name="Windows Install Date Value";Expression={$_.InstallDate}}, #THIS DOES ROUND UP DAYS need more work to calculate remainder and add it to hours @{Name="Age";Expression={"{1:N0} years, {2:N0} months, {3} 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 } |