Tuesday, July 8, 2014

Windows boot time, explored in Powershell

In this post I am going to explore most of the existing ways offered by Windows and Powershell to return the timestamp of the last boot.
 
Let's start with saying that there are multiple ways to retrieve this piece of information and exploring them all will make for what I hope is an interesting trip through some Windows internal mechanisms. Be wary by the way that this subject is very wide, and I have allowed myself some small off-topics that are meant to shed some light on the pieces of code we are now so used to see out there.

Windows is booting, but what time is it?
In old times (more or less one to two decades ago) we could (and still we can) use various legacy tools to fetch a Windows computer boot timestamp.
 
In more recent Windows versions some alternatives to those legacy Tools have appeared and we can today use the information stored:
  • in the Performance counters
  • in the Win32_OperatingSystem WMI instance
  • in the Windows event logs
Each of this approach has pros and cons. Some syntaxes are easier and some are trickier. Going through them all will give you an interesting insight of how you can take advantage of Windows Powershell as your all-in-one administration tool. In any case, please notice that the following examples have been prepared and tested on computers with European settings for the date (dd/MM/yyyy). As a consequence those that are based on text parsing or regular expressions might need some correction if you want to run them on a computer with different regional settings.

Let's start from the beginning: there are three possible legacy executables that you can use the retrieve the last reboot date:
  1. systeminfo.exe in C:\Windows\System32
  2. net.exe in C:\Windows\System32
  3. wmic.exe in C:\Windows\System32\wbem
1. System boot time with SYSTEMINFO
 
Systeminfo.exe is the old good tool every Windows admin used to get a quick look at system information. Used in conjunction with another legacy command, FIND, it allows you to see system boot time.

systeminfo | find "System Boot Time:"
System Boot Time:          07/07/2014, 17:41:11
The returned information is of System.String type.
systeminfo |
     find "System Boot Time:" |
     Get-Member |
     Select-Object Typename -Unique

TypeName                                                                                          
--------
System.String
As usual in Powershell, our aim is to return a re-usable object. In this case, better than a System.String, we would like to have a System.DateTime variable. The transformation can be achieved with an easy one-liner (for an explication of which you can refer to the final part my other blog post:

[datetime]::ParseExact($(systeminfo |
     find "System Boot Time:").substring(27,20),"dd/MM/yyyy, HH:mm:ss",$null)

Monday, July 7, 2014, 17:41:11
The main drawback of using systeminfo is that, since it retrieves the list of all the installed patches, it can take quite a long time to be executed, especially on long living systems, and there is nothing you can do to improve that.
 
2.  System boot time with NET

The second legacy executable is net.exe, which has two possible syntaxes, a long one (net statistics server) and a short one (net stats srv), both returning the same result:

net statistics server
Server Statistics for \\SRV01


Statistics since 07/07/2014 17:41:21


Sessions accepted                  0
Sessions timed-out                 0
Sessions errored-out               0

Kilobytes sent                     16
Kilobytes received                 0

Mean response time (msec)          0

System errors                      0
Permission violations              0
Password violations                0

Files accessed                     21
Communication devices accessed     0
Print jobs spooled                 0

Times buffers exhausted

  Big buffers                      0
  Request buffers                  0

The command completed successfully.
Lot of text in the output, as you can see, and the proceeding to get something meaningful out of it is a bit less comfortable, since some regex pattern matching needs to be done (if you have any kind of question do not hesitate to ask in the comment section below):

[datetime]::ParseExact($($(net stats srv) `
     -match "\d{2}/\d{2}/\d{4}").substring(17,19),"dd/MM/yyyy HH:mm:ss",$null)

Monday, July 7, 2014, 17:41:11

3. System boot time with WMIC
 
Third option, WMIC (which, if don't have it yet, you can get with the WMI Administrative Tools). The only thing you need to know for the moment is that WMIC is an alias based tool which interfaces with WMI (Windows Management Instrumentation). While it has many uses, I find it especially useful for querying certain system parameters, like the type and frequency of the processor, such as in 'wmic cpu get Name'.

To get the system boot up time, the syntax is:
wmic os get lastbootuptime

LastBootUpTime
20140707174111.489051+120
To explain the result of this WMIC example, let me start a brief discussion on the ways WMI stores dates and times, which is always good to know if you are a Windows sysadm. The Common Information Model (CIM) standard, where WMI is sitting on, uses the Universal Time Coordinate (UTC) format. Although UTC dates are non-intuitive at first glance, they are relatively easy to convert to a standard date-time format.

Let's have a look at their format, 20140707174111.489051+120, which is easy for humans to translate to yyyymmddHHMMSS.mmmmmmsUUU (full syntax over here)
In technical terms, this is a variant VT_BSTR returned by WMI as a 27 chars string (where the last two characters are blank in the WMIC output).
wmic os get lastbootuptime | Get-Member | Select-Object typename -Unique

TypeName
--------
System.String
Luckily, to convert this string to a valid Datetime, we can rely on the SWbemDateTime object. This is an object which, starting from Windows XP (way old, isn't it?) is used as a helper to parse CIM datetime values, as in the following example I wrote, where I first use a REGEX expression to skip the undesired text, then I force the remaining data into the Value property:
# Convert from VT_BSTR to System.Datetime
$DateTimeObject = New-Object -ComObject WbemScripting.SWbemDateTime 
$DateTimeObject.Value = $($(wmic os get lastbootuptime) `
     -match "\d{14}\.\d{6}\+\d{3}").TrimEnd() #TrimEnd removes the 2 last blank chars
$DateTimeObject.GetVarDate($DateTimeObject.Value)

Monday, July 7, 2014, 17:41:11
$DateTimeObject.GetVarDate($DateTimeObject.Value) |
     Get-Member |
     Select-Object typename -Unique

TypeName : System.DateTime
This was a general introduction to the three golden ways to retrieve the system boot time on a Windows box using legacy executables and returning the result as a DateTime object.

Of course there are other more Powershell-oriented ways to get to this very same result. Let's take the long windy road to that, by starting to introduce some WMI performance class information.

4. An introduction to WMI performance classes

There are two kinds of WMI performance classes: raw counter classes and cooked counter classes (yes, I didn't made it up: 'cooked' is the technical term for them).
The difference? Well, raw counters are simply raw numbers which have gone through no post-processing. Cooked counters, on the other side, are counters supplied by the Formatted Performance Data Provider, which is in charge of supplying calculated counter data.

As an example, take the two performance classes that store the actual uptime, from which, using a simple subtraction, we can make the system boot date:
Get-WmiObject -List Win32_Perf*System | select Name

Name
----
Win32_PerfFormattedData_PerfOS_System
Win32_PerfRawData_PerfOS_System
Both these classes have the same properties with different values, and this can be confusing. But the raw class has two additional information, which you have to use to get the results you expect (and which are pre-cooked in the formatted class) from the raw data:
  • The first is the Timestamp_Object, which is an object-defined timestamp, defined by the provider, storing the current time measure.
  • The second is the Frequency_Object, which represents the frequency, in ticks per second, of the mentioned Timestamp_Object.
5. System boot time from RAW Performance Counters

Wait, ticks you said? You might be wondering what the heck a tick is, and this allows for a nice short discussion on the internals of Microsoft Windows and, more generally, of how a computer works.
 
Inside any CPU there are processor registers which are in charge of storing data used by arithmetic calculations. One of these, named Time Stamp Counter is a 64-bit register present on all x86 processors since the Pentium (remember?) which counts the number of cycles since reset, known as ticks.

Over time a tick has become a constant which is independent from the processor clock, even do tick count is updated at the System clock interval (every 10msec or 15.6 msec on most systems). The value of this constant is 10 millions per second (as stated over here), which gives a tick a duration of 100 ns.

So, each raw measure of the uptime has this Frequency_Object property constantly set at 10 million ticks per second, which is the frequency at which the Timestamp_Object is recorded.
To calculate the system uptime in seconds from this raw data, the following formula should then logically be used:

UptimeInSec = (Timestamp - SystemUptime) / Frequency

where the SystemUptime is another property containing the timestamp at boot expressed in the same unit as Timestamp_Object.

Let's convert this formula to Powershell:

$TimestampAtBoot = Get-WmiObject Win32_PerfRawData_PerfOS_System |
     Select-Object -ExpandProperty systemuptime
$CurrentTimestamp = Get-WmiObject Win32_PerfRawData_PerfOS_System |
     Select-Object -ExpandProperty Timestamp_Object
$Frequency = Get-WmiObject Win32_PerfRawData_PerfOS_System |
     Select-Object -ExpandProperty Frequency_Object
$UptimeInSec = ($CurrentTimestamp - $TimestampAtBoot)/$Frequency
$UptimeInSec

13180,8610781
Fine. We have been able to calculate our system uptime in seconds starting from truly primary data using a short Powershell script. Now let's try to convert this to something more meaningful and show the system boot time as a DateTime like in the previous examples:

(Get-Date) - (New-TimeSpan -seconds $UptimeInSec)

Monday, July 7, 2014, 17:41:11
Here you have your system boot date and time and the type of the returned object is already of DateTime type, so no parsing required. Great!
 
6. System boot time from [Environment]::TickCount

Another way to get your system boot date is to rely on some information exposed by the .NET Environment class. As explained in MSDN, the system timer we talked above behaves in a way that it increments the value of the property TickCount (which stores in a 32-bit signed integer the number of milliseconds elapsed since the system started) going from zero to Int32.MaxValue (where [int32]::MaxValue is 2147483647) for 24.9 days, then jump to Int32.MinValue (where [int32]::MinValue is -2147483648), which is a negative number, and starts incrementing back to zero for the next 24.9 days.

To see the attributes of the TickCount counter, issue:

[Environment]  |
     Select-Object -ExpandProperty DeclaredProperties |
     Where-Object Name -like TickCount
To get the system boot date using [Environment] we have first to get the time span from the number of milliseconds stored in TickCount, then subtract that time interval from the current date and time:

(Get-Date) - ([timespan]::FromMilliseconds([Math]::Abs([Environment]::TickCount)))

Monday, July 7, 2014, 17:41:11
The function [Math]::Abs() is used to return the absolute value of any number, and it is pretty useful here if your tick count is a negative number (which could be the case on systems up from more than 25 days).

7. System boot time from COOKED Performance Counters

Until now we have explained the differences between raw and cooked performance classes and we have seen how to retrieve the system boot time from the current uptime using the New-TimeSpan cmdlet. Time to see how easy is to get the same information using a cooked performance class in conjunction with a time interval.

The class Win32_PerfFormattedData_PerfOS_System contains a number of performance counters related to the operating system, including SystemUpTime, which tells you how many second the machine has been running, while its raw version Win32_PerfRawData_PerfOS_System stored in Systemuptime just a unit showing the time at boot.

(Get-Date) - (New-TimeSpan -Seconds (Get-WmiObject Win32_PerfFormattedData_PerfOS_System).systemuptime)
Lots of different examples to get the same information, isn't it? Now I am going to show you two more way to get the system uptime. One relies on WMI and the other one on the System eventlogs.

8. System boot time from Win32_OperatingSystem

Let's start from the WMI method. All the lessons we learned above on UTC time formatting and conversion operations from VT_BSTR to DATETIME will be re-used in our next examples and will make you more confident with this kind of Powershell game.

For the WMI part, the only class that we need here is Win32_OperatingSystem. The instance of this class keeps a pretty big bunch of information, such as the Manufacturer of your PC, the name of the registered user, or the OS architecture and language. Furthermore there are a few interesting properties containing precious time-oriented information: CurrentTimeZone, InstallDate, LocalDateTime and, can't believe it, LastBootUpTime. As I explained before, WMI dates are returned as strings and not as datetime objects. These strings are compliant with the CIM standards, so to get a valid re-usable System.DateTime object we have first to make some rework.

Two ways to do that.

The first one is, like I showed in the WMIC example, using the SWbemDateTime helper object:

# Convert from VT_BSTR to System.Datetime
$DateTimeObject = New-Object -ComObject WbemScripting.SWbemDateTime 
$DateTimeObject.Value = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
$DateTimeObject.GetVarDate($DateTimeObject.Value)
The second method relies on the fact that most WMI objects returned by Powershell have a comfortable "ConvertFromDateTime" method implemented out-of-the-box. Cool.
For example:
Get-WmiObject -Class win32_operatingsystem | Get-Member -MemberType ScriptMethod
will return:
ConvertFromDateTime               ScriptMethod  System.Object ConvertFromDateTime();                                                                               
convertToDateTime                 ScriptMethod  System.Object ConvertToDateTime();
Did I already say: "Cool!"?

Let's take advantage of it then:

$OSInfo = Get-WmiObject Win32_OperatingSystem
$OSInfo.ConvertToDateTime($OSInfo.LastBootupTime)
or, if you are a seasoned old school programmer:

$OSInfo  = Get-WmiObject -query "select * from win32_OperatingSystem"
$OSInfo.ConvertToDateTime($OSInfo.LastBootupTime)

For the most curious, there is also a pretty nice ManagementDateTimeConverter Class, which provides methods to convert CIM datetime to System.DateTime:

$OSInfo = Get-WmiObject Win32_OperatingSystem
[Management.ManagementDateTimeConverter]::ToDateTime($OSInfo.lastbootuptime)
That's all for the WMI part.

9. An introduction to Event Tracing for Windows (ETW) 

Time to pass to exploring the information that can be retrieved from your system event logs and see how they compare, but before we see that, and for better understanding, it's interesting to see how the Microsoft Eventing engine has evolved in the past 10 years.

Under Windows NT4 or Windows XP, the Event Log service recorded computer events in three simple logs, like you can see in the following screenshot: System, Security and Application.


Like most Windows developers know (while many administrators have never heard of it), starting with Windows Vista in 2006, the Windows Event Logs has been rewritten on top of the Event Tracing for Windows (ETW) technology, which is a system and software diagnostic, troubleshooting and performance monitoring component.

This new Event Viewer has been completely re-engineered and its appearance has also changed, but it is still familiar enough to not to feel miles from home:


In the new interface there are still a tree pane and a list of events. You can still access the well-known Application, System, and Security logs under the Windows Logs node.
However:
  • some new nodes have been added, such as the Applications and Services logs which is a new category of event logs aimed at storing events from a single application or component rather than systemwide events.
  • the new ForwardedEvents log has been added to the Windows Logs node.
A lot of other Windows components were since then built on top of it, such as Resource Monitor, which allows sysadmins to drill down computer performance much better they could do with Task Manager on older versions.

Version after version, ETW exposes an increasing number of consistent information providers (through the use of a unified event provider model). Those information providers can be retrieved using logman and counted using the Powershell Count() method:

(c:\Windows\System32\logman.exe query providers).count
  • In Windows 8.1 there are 964 providers
  • In Windows 2012 r2 there are 830 providers
  • In Windows 7 there were 668 providers and 513 in Windows 2008 R2
  • In Windows 2003 R2 no more than 10 providers were exposed
Of course these figures increase when additional features or products are installed (such as Active Directory or, say, Hyper-V).

Unlike its predecessors, Windows (starting from Windows NT 4.0/2000/XP) records the system startup and shutdown times in the event log with the following informational Event IDs: 6005,6006,6008,6009.
For what we are trying to achieve, EventID 6005 is the most meaningful, because it occurs when the Event log service is started, just after the system has been booted and Windows has loaded.

In addition to these event IDs, there are some events from the Microsoft-Windows-Kernel-General event provider (exposed by ETW, which we just discussed) which indicate system startup and shutdown time.
You can find deeper information on this event provider as well as on the mentioned events by issuing the following Powershell commands:

c:\Windows\System32\logman.exe query providers Microsoft-Windows-Kernel-General
which can also be used in the form: 

c:\Windows\System32\logman.exe query providers Kernel-General
and:

(Get-WinEvent -listprovider Microsoft-Windows-Kernel-General).events |
     Select-Object Id, Description |
     Format-Table -AutoSize
As you can see in the output, there are thirteen possible events. For calculation our system last boot date, we can optimistically refer to entries tagged with the EventID 12: "The operating system started at system time %7"

Let's focus then on Event ID 12, source Microsoft-Windows-Kernel-General, and Event ID 6005, source EventLog, to see which one gives us the most reliable information on our system boot time.

In Powershell, as you would expect now that you are an accustomed Powershell user, there are six ways to query the Event Logs:
  • Get-WmiObject -Class Win32_NTLogEvent
  • Get-WmiObject -Query "SELECT * FROM Win32_NTLogEvent"
  • Get-EventLog
  • Get-WinEvent -FilterXml
  • Get-WinEvent -FilterHashtable
  • Get-WinEvent -FilterXPath
Let's have a deeper look at each and every one of these possibilities.
 
10. System boot time from Win32_NTLogEvent

First of all, we also have the option of using WMI against the Win32_NTLogEvent class, as shown here:

$Event12 = Get-WmiObject -Class Win32_NTLogEvent `
     -Filter "LogFile = 'System' AND SourceName='Microsoft-Windows-Kernel-General' AND EventCode=12" |
     Select-Object -First 1
$Event12.ConvertToDateTime($Event12.Timegenerated)
and here:

$Event6005 = Get-WmiObject -Class Win32_NTLogEvent `
     -Filter "LogFile = 'System' AND SourceName='EventLog' AND EventCode=6005" |
     Select-Object -First 1
$Event6005.ConvertToDateTime($Event6005.Timegenerated)
or:

$Event12 = Get-WmiObject -Query "SELECT * FROM Win32_NTLogEvent `
     WHERE (logfile='System') AND (SourceName='Microsoft-Windows-Kernel-General') AND (EventCode='12')" |
     Select-Object -First 1
$Event12.ConvertToDateTime($Event12.Timegenerated)
and:

$Event6005 = Get-WmiObject -Query "SELECT * FROM Win32_NTLogEvent `
     WHERE (logfile='System') AND (SourceName='EventLog') AND  (EventCode='6005')" |
     Select-Object -First 1
$Event6005.ConvertToDateTime($Event6005.Timegenerated)

In both cases, the Win32_NTLogEvent WMI class is used to translate instances from the Windows event log, using the same mechanism explained before for Win32_OperatingSystem.
 
11. System boot time from Get-EventLog

Second option (which, let me say, is so Powershell 1.0, and you'll see why in the performance section at the end of the post) is to use Get-EventLog. The main drawback of using Get-EventLog is that it doesn't support much in the way of filtering. That means you have to retrieve all of the entries, and then use Where-Object to filter. Needless to say, it can be pretty CPU and time consuming in case of large logs and can make your System process peak very high. That's why I always suggest to use this cmdlet in conjunction with at least the -After parameters, to shorten the time window.

12, 6005 | % {
     Get-EventLog -LogName System -After 05/05/2012 |
          Where-Object EventId -eq $_ |
          Select-Object EventId, Source, TimeGenerated -First 1
     }
To extract just the TimeGenerated field for Event IDs 12, use:

((Get-EventLog -logname system) | Where-Object {($_.eventid -eq 12) -and ($_.Source -eq 'Microsoft-Windows-Kernel-General') } )[0] | Select-Object -ExpandProperty TimeGenerated
or, for Event IDs 6005:

((Get-EventLog -logname system) | Where-Object {($_.eventid -eq 6005) -and ($_.Source -eq 'EventLog') } )[0] | Select-Object -ExpandProperty TimeGenerated
Notice in both these examples the different syntax I used to index inside the array of the returned results.
 
12. System boot time from Get-WinEvent
 
Third major option is to rely on the powerful Get-WinEvent cmdlet, which was introduced in Powershell 2.0 in 2009.
It does support three types of built-in filtering (making it lighting fast) and it can query the newer log types introduced since Vista/Win2008, so it should be preferred to Get-EventLog.
These three types of filtering are:
    -FilterHashtable 
        
        Required?                    true
        Position?                    0
        Accept pipeline input?       false
        Parameter set name           HashQuerySet
        Aliases                      None
        Dynamic?                     false
        
    -FilterXPath 
        
        Required?                    false
        Position?                    Named
        Accept pipeline input?       false
        Parameter set name           FileSet, GetProviderSet, GetLogSet
        Aliases                      None
        Dynamic?                     false
        
    -FilterXml <xml>
        
        Required?                    true
        Position?                    0
        Accept pipeline input?       false
        Parameter set name           XmlQuerySet
        Aliases                      None
        Dynamic?                     false
FilterXml takes in a XML-style string of text. This is the example for EventID 6005:

$xml6005='<QueryList><Query Id="0" Path="System"><Select Path="System">*[System[Provider[@Name="EventLog"] and (EventID=6005)]]</Select></Query></QueryList>'
Get-WinEvent -FilterXml $xml6005 -MaxEvents 1 | Select-Object -ExpandProperty TimeCreated
and this is the example for EventID 12:

$xml12='<QueryList><Query Id="0" Path="System"><Select Path="System">*[System[Provider[@Name="Microsoft-Windows-Kernel-General"] and (EventID=12)]]</Select></Query></QueryList>'
Get-WinEvent -FilterXml $xml12 -MaxEvents 1 | Select-Object -ExpandProperty TimeCreated
In both cases the returned result is of System.DateTime type. What else?

FilterHashtable takes in a hashtable containing three properties in our case: LogName, Providername and ID:

Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-Kernel-General'; ID=12} -MaxEvents 1 | Select-Object -ExpandProperty TimeCreated
Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='EventLog'; ID=6005} -MaxEvents 1 | Select-Object -ExpandProperty TimeCreated
Last option is to use FilterXPath. To make a long story short, XPath is a language used to query specific nodes from an XML document. If you don't use it often, its syntax can look quit scaring. But I am here to help.

For event 12 you have:

(Get-WinEvent -LogName "System" -MaxEvents 1 -FilterXPath "*[System[Provider[@Name='Microsoft-Windows-Kernel-General']][EventID=12]]").TimeCreated
and for event 6005 you have:

(Get-WinEvent -LogName "System" -MaxEvents 1 -FilterXPath "*[System[Provider[@Name='EventLog']][EventID=6005]]").TimeCreated
This was the last example I wanted to use to show you how to find out the system boot up time and date. To resume, there are 23 possible choices, and it could be difficult to choose the right tool for the job. That's why I have decided that this post wouldn't be complete without a performance analysis.
 
13. Performance
 
Performancewise, any of the above solution has its execution time depending on the information provider. That's why I have tested all of them with Measure-Command on a Windows 2012 R2 and on a Windows 8.1.  The resulting duration is expressed in seconds (from faster to slower):

Duration Title                                   Result              ResultType     
-------- -----                                   ------              ----------     
0,0393308 TickCount + TimeSpan + Get-Date         07/07/2014 17:41:11 System.DateTime
0,0417487 WMI query + SWbemDateTime               07/07/2014 17:41:11 System.DateTime
0,0429207 WMI + ManagementDateTimeConverter       07/07/2014 17:41:11 System.DateTime
0,0459907 Net Statistics Server                   07/07/2014 17:41:21 System.DateTime
0,0484394 WMI + SWbemDateTime                     07/07/2014 17:41:11 System.DateTime
0,0512981 Net Stats Srv                           07/07/2014 17:41:21 System.DateTime
0,0775581 WMI Query 6005 + ConvertToDateTime      07/07/2014 17:41:19 System.DateTime
0,0796125 WMI + ConvertToDateTime                 07/07/2014 17:41:11 System.DateTime
0,0860807 WMI filter 6005 + ConvertToDateTime     07/07/2014 17:41:19 System.DateTime
0,1026782 WMI filter 12 + ConvertToDateTime       07/07/2014 17:41:11 System.DateTime
0,1307235 WMIC + SWbemDateTime                    07/07/2014 17:41:11 System.DateTime
0,2467589 Get-WinEvent + FilterXML 12             07/07/2014 17:41:11 System.DateTime
0,2678846 Get-WinEvent + FilterXPath 6005         07/07/2014 17:41:19 System.DateTime
0,2697007 Get-WinEvent + FilterXPath 12           07/07/2014 17:41:11 System.DateTime
0,2825668 Get-WinEvent + FilterHastTable 6005     07/07/2014 17:41:19 System.DateTime
0,2962874 Get-WinEvent + FilterHastTable 12       07/07/2014 17:41:11 System.DateTime
0,2978704 PerfFormattedData + TimeSpan + Get-Date 07/07/2014 17:41:11 System.DateTime
0,3042627 WMI Query 12 + ConvertToDateTime        07/07/2014 17:41:11 System.DateTime
0,4015614 Get-WinEvent + FilterXML 6005           07/07/2014 17:41:19 System.DateTime
2,7686521 PerfRawData + TimeSpan + Get-Date       07/07/2014 17:41:11 System.DateTime
3,5873439 Systeminfo + ParseExact                 07/07/2014 17:41:11 System.DateTime
12,3051242 Get-EventLog 6005 + Where-Object        07/07/2014 17:41:19 System.DateTime
35,5054786 Get-EventLog 12 + Where-Object          07/07/2014 17:41:11 System.DateTime

The result is self-explanatory:
  • Get-EventLog is to be considered deprecated
  • Systeminfo is pretty time consuming, since it fetches all hotfix information
  • All the others are pretty fast
  • [Environment]::TickCount has jaw-dropping performance
14. Precision

Not all the Tools return the same result. There are three main groups:
  1. those that fetch the system boot date from WMI and those that fetch Event 12 as asynchronously provided from the ETW Kernel-General provider and stored in the System eventlog. This is the most accurate information, and, as I was able to verify, returns the exact time your Windows computer bootstraps after POST.
  2. those that fetch Event 6005, which is less accurate, because it returns the date and time the Event Log service has started and that is 8 seconds late on Kernel-provided information.
  3. those that are based on Network Statistics, which are returning the date and time since when the Server service has started. In this case there is a huge 10 seconds delay from what's being returned by Kernel-General.
Here's the complete result ordered by boot time:

Duration Title                                   Result              ResultType     
  -------- -----                                   ------              ----------     
 3,5873439 Systeminfo + ParseExact                 07/07/2014 17:41:11 System.DateTime
35,5054786 Get-EventLog 12 + Where-Object          07/07/2014 17:41:11 System.DateTime
 0,1307235 WMIC + SWbemDateTime                    07/07/2014 17:41:11 System.DateTime
 0,0484394 WMI + SWbemDateTime                     07/07/2014 17:41:11 System.DateTime
 2,7686521 PerfRawData + TimeSpan + Get-Date       07/07/2014 17:41:11 System.DateTime
 0,0393308 TickCount + TimeSpan + Get-Date         07/07/2014 17:41:11 System.DateTime
 0,2978704 PerfFormattedData + TimeSpan + Get-Date 07/07/2014 17:41:11 System.DateTime
 0,0796125 WMI + ConvertToDateTime                 07/07/2014 17:41:11 System.DateTime
 0,0417487 WMI query + SWbemDateTime               07/07/2014 17:41:11 System.DateTime
 0,0429207 WMI + ManagementDateTimeConverter       07/07/2014 17:41:11 System.DateTime
 0,3042627 WMI Query 12 + ConvertToDateTime        07/07/2014 17:41:11 System.DateTime
 0,1026782 WMI filter 12 + ConvertToDateTime       07/07/2014 17:41:11 System.DateTime
 0,2467589 Get-WinEvent + FilterXML 12             07/07/2014 17:41:11 System.DateTime
 0,2962874 Get-WinEvent + FilterHastTable 12       07/07/2014 17:41:11 System.DateTime
 0,2697007 Get-WinEvent + FilterXPath 12           07/07/2014 17:41:11 System.DateTime
 0,0860807 WMI filter 6005 + ConvertToDateTime     07/07/2014 17:41:19 System.DateTime
 0,0775581 WMI Query 6005 + ConvertToDateTime      07/07/2014 17:41:19 System.DateTime
12,3051242 Get-EventLog 6005 + Where-Object        07/07/2014 17:41:19 System.DateTime
 0,4015614 Get-WinEvent + FilterXML 6005           07/07/2014 17:41:19 System.DateTime
 0,2825668 Get-WinEvent + FilterHastTable 6005     07/07/2014 17:41:19 System.DateTime
 0,2678846 Get-WinEvent + FilterXPath 6005         07/07/2014 17:41:19 System.DateTime
 0,0512981 Net Stats Srv                           07/07/2014 17:41:21 System.DateTime
 0,0459907 Net Statistics Server                   07/07/2014 17:41:21 System.DateTime
15. Conclusions

All in all, taking into consideration speed of execution and precision, the syntax to remember is:

(Get-Date) - ([timespan]::FromMilliseconds([Math]::Abs([Environment]::TickCount)))
I hope you have enjoyed this post. If you do, do not hesitate to leave a comment and to share. Also, if you want to suggest any modification, correction or improvement, or should you need any help with the code, feel free to get in touch.

6 comments:

  1. Learned a lot! Thanks!!!

    ReplyDelete
  2. Cool stuff I did not expect to learn in just one blog post! You rock!

    ReplyDelete

Related Posts Plugin for WordPress, Blogger...