Trying to get things quickly as possible, I choose to combine three programming languages:
- PowerShell to get the data, because it is really easy with it.
- Python 3.6 to do the main work, because I know it much more than Powershell.
- Batch file :)
When I have time I would like to make all PowerShell solution because I would like to learn it more and this looks like ideal opportunity.
Getting the data
Note that to run unsigned PowerShell scripts you write you might need to run:
Set-ExecutionPolicy RemoteSigned
in the PowerShell opened as an administrator. See this answer on superuser.com.
Getting the data from Windows Event log is really easy with PowerShell:
boots.ps1
$boots = Get-EventLog -Log System -Source Microsoft-Windows-Kernel-Boot -InstanceID 25 -Newest 10
$boots | Select -Property TimeGenerated
shutdowns.ps1
$shutdows = Get-EventLog -Log System -Source Microsoft-Windows-Kernel-Power -InstanceID 42 -Newest 9
$shutdows | Select -Property TimeGenerated
Running it on command line is really easy:
C:\Users\Michal>powershell boots.ps1
TimeGenerated
-------------
11.11.2018 7:24:37
10.11.2018 6:44:16
09.11.2018 19:31:38
09.11.2018 5:59:47
08.11.2018 5:07:07
07.11.2018 19:04:54
07.11.2018 5:12:58
06.11.2018 21:09:23
06.11.2018 5:05:07
05.11.2018 18:57:44
Note that I take last ten boot times but only nine shutdowns. Because I will then match boot times to shutdown times and obviously there is always on more boot time—the last time we started our computer to run the scripts.
To be honest, the InstanceIDs are taken just just by guess by looking at Event Viewer. They works OK for me, however note that I always turn this computer off, never make it sleep or hibernated so I have not tested the scripts in that situation.
Merging the data
runtime.py
#!python3
from datetime import datetime
from subprocess import PIPE, run
def get_datetimes(type):
datetime_strings = run(
f'powershell {type}s.ps1',
check=True,
stdout=PIPE,
encoding="ASCII"
).stdout.split("\n")
datetime_objects = []
for datetime_string in datetime_strings:
# skip non-dates
if ":" not in datetime_string: continue
datetime_object = datetime.strptime(
datetime_string.strip(),
"%d.%m.%Y %H:%M:%S"
)
datetime_objects.append(datetime_object)
return datetime_objects
boot_dts = get_datetimes("boot")
shutdown_dts = get_datetimes("shutdown")
shutdown_dts.insert(0, "Running")
combined = zip(boot_dts, shutdown_dts)
first = True
for boot, shutdown in combined:
print(boot, "==>", shutdown, end="")
if first:
first = False
print(f" ({datetime.now().replace(microsecond=0) - boot})")
else:
print(f" ({shutdown - boot})")
The matching is really naive. If there would be missing event, e.g. no shutdown event because of crashed computer, the data would be matched incorrectly.
Notes on calling subprocess.run():
- f'...{variable}' are the new Python 3.6 formatted string literals.
- check=True will validate that the process exited OK (i.e. with zero exit code).
- In Python 3.7 I you can use capture_output=True instead of stdout=PIPE just for the fact that it is much more readable.
After loading the times we level out the nine shutdown times to ten boot times by adding "Running" string at the begging of the array.
Than we use zip() function to make pairs of (boot time, shutdown time) and print the pairs together with the duration (the "shutdown - boot" expression inside formatted string literal).
We must handle differently first pair when displaying the results because there is no shutdown datetime instance but just string "Running" instead. So we use current time to show how long is computer running now since the last boot time. We could have put the current time in the array shutdown_dts instead of the "Running" string to get rid of this special handling but I like to make it explicit that this is not real shutdown.
The data from our PowerShell script are without milliseconds which is actually nice because they would be useless for this anyway. This leads to the result that the duration would be displayed automatically also without milliseconds. Good—less work for us. However the datetime.now() returns milliseconds. To make it look the same—i.e. truncate the milliseconds we could either do some time formatting or—which I found simpler in this case—just zero them.
Running it from desktop shortcut
I wanted to put shortcut to this script to my desktop. The problem is that Windows would run it and immediately close the window so I would not see the results. I could put call to input("Press Enter to quit") at the end of the script but I don't want to make it wait for the Enter when the script is run in the command prompt window.
So I created small batch file:
runtime.bat
@echo off
py runtime.py
pause
and created shortcut to that. Just make sure that all those four files (boots.ps1, shutdowns.ps1, runtime.py, runtime.bat) are in the same folder so they will find each other. Also make sure that the "Start in" folder of the shortcut properties is set to the directory with these four files.
py is the standard Python Launcher for Windows which is automatically installed with Python. If it is not found, please install standard Python 3.6 or newer from the python.org.
Files
Files are available in GitHub repository.