14

So quite simply, the question is how to get the system boot up time in windows with c/c++.

Searching for this hasn't got me any answer, I have only found a really hacky approach which is reading a file timestamp ( needless to say, I abandoned reading that halfway ).

Another approach that I found was actually reading windows diagnostics logged events? Supposedly that has last boot up time.

Does anyone know how to do this (with hopefully not too many ugly hacks)?

Eximius
  • 187
  • 1
  • 1
  • 7

5 Answers5

22

GetTickCount64 "retrieves the number of milliseconds that have elapsed since the system was started."

Once you know how long the system has been running, it is simply a matter of subtracting this duration from the current time to determine when it was booted. For example, using the C++11 chrono library (supported by Visual C++ 2012):

auto uptime = std::chrono::milliseconds(GetTickCount64());
auto boot_time = std::chrono::system_clock::now() - uptime;
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • You'd need to combine that with `GetSystemTime` and do a bit of date maths for a full answer to the question. – Benj Jun 01 '12 at 16:30
  • Sorry if I was unclear, but I meant the actual time, not how much time ago. I.e. : "System booted up at 2012/06/01 10:00" – Eximius Jun 01 '12 at 16:31
  • Benj, I guess that works... Thanks. This still is somewhat of a hack though :/ – Eximius Jun 01 '12 at 16:33
  • 8
    I would not consider _subtraction_ to be a hack. – James McNellis Jun 01 '12 at 16:37
  • I call roundabout ways to achieve something that would appear to be a given - a hack. And since I needed a seconds since 1970 type of date, I'll need to do a converter. – Eximius Jun 01 '12 at 16:40
  • 4
    I'd call it _programming_ as that's what most of it is - synthesizing the data you need from the data you have... – jcoder Jun 01 '12 at 16:45
  • Oh wow, forgot there was a chrono and ctime. Was thinking of using GetSystemTime like Benj suggested, silly me. – Eximius Jun 01 '12 at 16:45
  • 3
    Does this reliably produce the same result every time? What if `system_clock::now`'s tick isn't exactly synchronized with `GetTickCount64`, or the tick happens between the two code lines executing? I need something that I can use as a lookup key. – Sebastian Redl Dec 18 '14 at 12:08
8

You can also use WMI to get the precise time of boot. WMI is not for the faint of heart, but it will get you what you are looking for.

The information in question is on the Win32_OperatingSystem object under the LastBootUpTime property. You can examine other properties using WMI Tools.

WMI Explorer showing property instance

Edit: You can also get this information from the command line if you prefer.

wmic OS Get LastBootUpTime

As an example in C# it would look like the following (Using C++ it is rather verbose):

static void Main(string[] args)
{      
    // Create a query for OS objects
    SelectQuery query = new SelectQuery("Win32_OperatingSystem", "Status=\"OK\"");

    // Initialize an object searcher with this query
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);

    string dtString;
    // Get the resulting collection and loop through it
    foreach (ManagementObject envVar in searcher.Get())
        dtString = envVar["LastBootUpTime"].ToString();
}
linuxuser27
  • 7,183
  • 1
  • 26
  • 22
  • Is it possible to get Last "Boot Type"(Means boot from shutdown or Hibernate)? – RDX Jan 05 '17 at 06:40
  • 1
    The command line `wmic OS Get LastBootUpTime` seems to return the FIRST time the PC was booted.... `wmic LOGON Get StartTime` may be more useful – Mark Stewart Jun 05 '17 at 15:47
4

The "System Up Time" performance counter on the "System" object is another source. It's available programmatically using the PDH Helper methods. It is, however, not robust to sleep/hibernate cycles so is probably not much better than GetTickCount()/GetTickCount64().

Reading the counter returns a 64-bit FILETIME value, the number of 100-NS ticks since the Windows Epoch (1601-01-01 00:00:00 UTC). You can also see the value the counter returns by reading the WMI table exposing the raw values used to compute this. (Read programmatically using COM, or grab the command line from wmic:)

wmic path Win32_PerfRawData_PerfOS_System  get systemuptime

That query produces 132558992761256000 for me, corresponding to Saturday, January 23, 2021 6:14:36 PM UTC.

You can use the PerfFormattedData equivalent to get a floating point number of seconds, or read that from the command line in wmic or query the counter in PowerShell:

Get-Counter -Counter '\system\system up time'

This returns an uptime of 427.0152 seconds.

I also implemented each of the other 3 answers and have some observations that may help those trying to choose a method.

Using GetTickCount64 and subtracting from current time

  • The fastest method, clocking in at 0.112 ms.
  • Does not produce a unique/consistent value at the 100-ns resolution of its arguments, as it is dependent on clock ticks. Returned values are all within 1/64 of a second of each other.
  • Requires Vista or newer. XP's 32-bit counter rolls over at ~49 days and can't be used for this approach, if your application/library must support older Windows versions

Using WMI query of the LastBootUpTime field of Win32_OperatingSystem

  • Took 84 ms using COM, 202ms using wmic command line.
  • Produces a consistent value as a CIM_DATETIME string
  • WMI class requires Vista or newer.

Reading Event Log

  • The slowest method, taking 229 ms
  • Produces a consistent value in units of seconds (Unix time)
  • Works on Windows 2000 or newer.
  • As pointed out by Jonathan Gilbert in the comments, is not guaranteed to produce a result.

The methods also produced different timestamps:

  • UpTime: 1558758098843 = 2019-05-25 04:21:38 UTC (sometimes :37)
  • WMI: 20190524222528.665400-420 = 2019-05-25 05:25:28 UTC
  • Event Log: 1558693023 = 2019-05-24 10:17:03 UTC

Conclusion:

The Event Log method is compatible with older Windows versions, produces a consistent timestamp in unix time that's unaffected by sleep/hibernate cycles, but is also the slowest. Given that this is unlikely to be run in a loop it's this may be an acceptable performance impact. However, using this approach still requires handling the situation where the Event log reaches capacity and deletes older messages, potentially using one of the other options as a backup.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
  • 1
    I think the Event Log method may be unreliable, because if the Event Log gets flooded with messages, the event about the service's start-up may get deleted. – Jonathan Gilbert Jul 22 '20 at 23:34
  • @JonathanGilbert it certainly can search the whole log without a result, but one would assume that "no result" case would then be handled by an exception method (in my app, I fall back to the "now minus uptime" method). – Daniel Widdis Jul 22 '20 at 23:42
3

C++ Boost used to use WMI LastBootUpTime but switched, in version 1.54, to checking the system event log, and apparently for a good reason:

ABI breaking: Changed bootstamp function in Windows to use EventLog service start time as system bootup time. Previously used LastBootupTime from WMI was unstable with time synchronization and hibernation and unusable in practice. If you really need to obtain pre Boost 1.54 behaviour define BOOST_INTERPROCESS_BOOTSTAMP_IS_LASTBOOTUPTIME from command line or detail/workaround.hpp.

Check out boost/interprocess/detail/win32_api.hpp, around line 2201, the implementation of the function inline bool get_last_bootup_time(std::string &stamp) for an example. (I'm looking at version 1.60, if you want to match line numbers.)

Just in case Boost ever dies somehow and my pointing you to Boost doesn't help (yeah right), the function you'll want is mainly ReadEventLogA and the event ID to look for ("Event Log Started" according to Boost comments) is apparently 6005.

Keith M
  • 853
  • 10
  • 28
  • 2
    Thanks for this pointer! Some more info for others trying to implement this -- trying to match the DWORD event id to 6005 will fail because the first 16 bits of the 32-bit number are used for severity and other features; and for the bootup, the severity is "Info" = 01, so the resulting eventId is 2147489653. One needs to compare only the rightmost 16 bits to 6005 and this will work fine. Another thing to note -- there will be multiple entries, one needs to find the latest one, either by iterating backwards (to find the first) or iterating forward and keeping the last. – Daniel Widdis Jun 09 '19 at 21:31
  • 1
    Digging more into this, there's an event id 12 associated with "operating system startup" that triggers about 20 seconds before the event log, and would be a better marker. It doesn't log with "fast startup" however, so the best action would be to check for both 6005 and 12, and if a 12 occurred within a minute of the last 6005, use that 12's data. – Daniel Widdis Jun 10 '19 at 07:40
1

I haven't played with this much, but I personally think the best way is probably going to be to query the start time of the "System" process. On Windows, the kernel allocates a process on startup for its own purposes (surprisingly, a quick Google search doesn't easily uncover what its actual purposes are, though I'm sure the information is out there). This process is called simply "System" in the Task Manager, and always has PID 4 on current Windows versions (apparently NT 4 and Windows 2000 may have used PID 8 for it). This process never exits as long as the system is running, and in my testing behaves like a full-fledged process as far as its metadata is concerned. From my testing, it looks like even non-elevated users can open a handle to PID 4, requesting PROCESS_QUERY_LIMITED_INFORMATION, and the resulting handle can be used with GetProcessTimes, which will fill in the lpCreationTime with the UTC timestamp of the time the process started. As far as I can tell, there isn't any meaningful way in which Windows is running before the System process is running, so this timestamp is pretty much exactly when Windows started up.

#include <iostream>
#include <iomanip>

#include <windows.h>

using namespace std;

int main()
{
    unique_ptr<remove_pointer<HANDLE>::type, decltype(&::CloseHandle)> hProcess(
        ::OpenProcess(
            PROCESS_QUERY_LIMITED_INFORMATION,
            FALSE, // bInheritHandle
            4), // dwProcessId
        ::CloseHandle);

    FILETIME creationTimeStamp, exitTimeStamp, kernelTimeUsed, userTimeUsed;
    FILETIME creationTimeStampLocal;
    SYSTEMTIME creationTimeStampSystem;

    if (::GetProcessTimes(hProcess.get(), &creationTimeStamp, &exitTimeStamp, &kernelTimeUsed, &userTimeUsed)
     && ::FileTimeToLocalFileTime(&creationTimeStamp, &creationTimeStampLocal)
     && ::FileTimeToSystemTime(&creationTimeStampLocal, &creationTimeStampSystem))
    {
        __int64 ticks =
            ((__int64)creationTimeStampLocal.dwHighDateTime) << 32 |
            creationTimeStampLocal.dwLowDateTime;

        wios saved(NULL);

        saved.copyfmt(wcout);

        wcout << setfill(L'0')
            << setw(4)
            << creationTimeStampSystem.wYear << L'-'
            << setw(2)
            << creationTimeStampSystem.wMonth << L'-'
            << creationTimeStampSystem.wDay
            << L' '
            << creationTimeStampSystem.wHour << L':'
            << creationTimeStampSystem.wMinute << L':'
            << creationTimeStampSystem.wSecond << L'.'
            << setw(7)
            << (ticks % 10000000)
            << endl;

        wcout.copyfmt(saved);
    }
}

Comparison for my current boot:

  • system_clock::now() - milliseconds(GetTickCount64()):

2020-07-18 17:36:41.3284297
2020-07-18 17:36:41.3209437
2020-07-18 17:36:41.3134106
2020-07-18 17:36:41.3225148
2020-07-18 17:36:41.3145312

(result varies from call to call because system_clock::now() and ::GetTickCount64() don't run at exactly the same time and don't have the same precision)

  • wmic OS Get LastBootUpTime

2020-07-18 17:36:41.512344

  • Event Log

No result because the event log entry doesn't exist at this time on my system (earliest event is from July 23)

  • GetProcessTimes on PID 4:

2020-07-18 17:36:48.0424863

It's a few seconds different from the other methods, but I can't think of any way that it is wrong per se, because, if the System process wasn't running yet, was the system actually booted?

Jonathan Gilbert
  • 3,526
  • 20
  • 28
  • Did you try GetProcessTimes on PID 0 (Idle Process)? I've found it starts a few hundred nanoseconds before PID 4. – Daniel Widdis Jul 24 '20 at 05:17
  • Hah, to tell you the truth, it didn't even occur to me to try to open PID 0, I guess I just proceeded on the incorrect assumption that it wasn't enough of a "real process". If their start times differ by only a few hundred nanoseconds, then the lines of code that create the two entries in the process table are probably adjacent in the same function, I'd guess :-) – Jonathan Gilbert Jul 24 '20 at 15:58
  • Or possibly multithreaded process somewhere with variable run times/available processors. I just noitced both of those are about 4 seconds later than my PID 72 (Secure System) and PID 128 (Registry) – Daniel Widdis Jul 24 '20 at 16:05
  • Of note, [this link](https://stackoverflow.com/questions/2643902/are-the-system-and-system-idle-process-pids-constant-on-windows) indicates PID 4 may not always be System. I would hazard a guess that 0 is a constant tho. – Daniel Widdis Jul 24 '20 at 16:07
  • 1
    Hmm, interesting, so perhaps a more complete strategy might be to query _all_ the running processes and take the earliest time found :-P – Jonathan Gilbert Jul 24 '20 at 16:20
  • Even more interesting. Parent of pid 4 is pid 0 (expected). While parent of pids 72 and 128 is pid 4, even though they started 4 seconds earlier. – Daniel Widdis Jul 24 '20 at 16:21
  • Probably don't need to query *all* but iterating until you hit one with a parent pid > 4 may be wise. – Daniel Widdis Jul 24 '20 at 16:23
  • Hmm, but on your system the earliest time is one of PID 72 and PID 128 -- do _all_ PIDs less than 72 have a parent PID <= 4? And, if that happens to be the case right now, can it be assumed to always be the case? – Jonathan Gilbert Jul 24 '20 at 16:40
  • You can follow the process tree of pid 0, which spawns pid 4. pid 4 spawns other processes. Which may spawn other processes. If you keep a hashset of seen pids in that tree (or maybe just the max), you can stop when you hit one not in that set. My first 5 processes all derive eventually from pid 0, the rest do not.... numbered 0 4 72 128 and 196 (started slightly after pid 4) – Daniel Widdis Jul 24 '20 at 17:20
  • That's an interesting approach, but doesn't it take just as much effort to find a process' parent as it does to find its start time? And, there is no API for efficiently finding the children of a process, you just have to scan all processes and check their parent PID, right? Without making assumptions about the ordering of PIDs, I think the least amount of work done is going to be to simply query the start time of every process you can get your hands on. Shouldn't take that long anyway, even if there are thousands of processes. :-) – Jonathan Gilbert Jul 24 '20 at 21:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/218548/discussion-between-daniel-widdis-and-jonathan-gilbert). – Daniel Widdis Jul 24 '20 at 22:13