C++获取系统开关机记录并打印

  • 前言
  • 一、直接查询事件日志过程
  • 1.
  • 2.
  • 3.
  • 4.
  • 二、代码查询开关机记录方案
  • 三、源码
  • 四、附录
  • 总结



前言

IDE:VS2019
项目类型:C++控制台应用
系统:WIN7或WIN10
原理:通过调用Windows操作系统API查看windows事件日志中指定的事件ID来获取开关机时间,该记录所能获取的数据量取决于系统事件日志的缓存大小配置。其中,事件ID12表示正常开机,事件ID13表示正常关机,事件ID6008表示意外关闭。


一、直接查询事件日志过程

1.

打印es日志 系统打印日志_c++


点击开始按钮,在搜索框中输入“event”可以看到查看事件日志功能,也直接进入控制面板查找该功能。

2.

打印es日志 系统打印日志_#include_02


进入后点击左侧windows日志“系统”子菜单,即可查看所有系统日志信息。

3.

打印es日志 系统打印日志_Data_03


点击左侧筛选当前日志,在弹出窗体中箭头位置输入要筛选的ID。

4.

打印es日志 系统打印日志_#include_04


选中一条记录即可查看到该事件信息正常开机和关机可以直接使用事件记录的时间。

要注意的是,意外关闭事件是在正常开机之后生成的,因此事件写入的时间并不是实际意外关机的时间,意外关闭的时间在详细信息中,需要先解析出来。

二、代码查询开关机记录方案

使用代码查询开关机记录与上述可视化查询思路相同,需要用到三个windows系统API(Linux系统原理相同但API和事件ID不同)分别是:OpenEventLog、ReadEventLog、CloseEventLog。前两者是C++宏函数,根据项目编码类型自动解析为多字节版本和Unicode版本。非C/C++调用需要直接调用系统动态库中的相应版本而不能使用该宏。
调用OpenEventLog打开windows事件日志(第二个参数为“System”,否则读的不是系统事件),获得日志句柄。然后使用while循环调用ReadEventLog可以一直读到日志末尾而不局限于缓冲区的大小。在每次的调用结果中解析EVENTLOGRECORD类型的事件,将其中事件ID不是12,13,6008的筛选掉,12和13可以直接显示事件的记录时间,6008事件先解析事件描述,在显示事件描述中的时间,要注意的是该事件描述中的时间显示格式为"\09:37:38\0?2020/?12/?3\0",因此需要将日期提前并以“?”字符分割字符串在拼接时间来显示。

三、源码

#include <windows.h>
#include <string>
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
	DWORD dwRead, dwNeeded;
	string Source;
	time_t Time;
	unsigned short ID;
	char Data[5120]; //缓冲区大小,如果太小读到的记录会少于windows日志
	HANDLE Log = OpenEventLog(NULL, _T"System");//第一次参数表示本地计算机,第二个参数表示系统日志
	if (Log == NULL)
	{
		return 0;
	}
	char pLogPath[40]{ "./SystemSwitchRecord.txt" };//存放路径
	ofstream write; //文件操作对象
	write.open(pLogPath, ios::out);//打开该文件,如果没有这个文件,会自动创建这个文件
	while (ReadEventLog(Log, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, Data, sizeof(Data), &dwRead, &dwNeeded))
	{
		for (DWORD i = 0; i < dwRead;)
		{
			EVENTLOGRECORD* ptr = (EVENTLOGRECORD*)(Data + i);
			Source = (TCHAR*)ptr + sizeof(EVENTLOGRECORD);//事件源
			ID = (unsigned short)ptr->EventID;//事件ID
			if (Source != "Microsoft-Windows-Kernel-General" && Source != "EventLog")
			{
				i += ptr->Length;
				continue;
			}
			string EventText = "";
			struct tm SysStartupTime;//时间结构体
			Time = ptr->TimeGenerated;//日期和时间
			char buf[64]{ 0 };//用来存放时间
			switch (ID)
			{
			case 12:
				EventText = "开机";
				localtime_s(&SysStartupTime, &Time);//时间戳转化为格式时间
				strftime(buf, _countof(buf), "%Y-%m-%d %H:%M:%S", &SysStartupTime);//格式化时间到缓冲区
				break;
			case 13:
				EventText = "关机";
				localtime_s(&SysStartupTime, &Time);//时间戳转化为格式时间
				strftime(buf, _countof(buf), "%Y-%m-%d %H:%M:%S", &SysStartupTime);//格式化时间到缓冲区
				break;
			case 6008:
			{
				EventText = "意外关闭";
				//获取事件详细信息
				char* SysShutDownTime = Data + i + ptr->StringOffset;//这里存储的是时分秒
				char* SysShutDownDate = NULL;
				char* SysShutDownDate_Next = NULL;
				//事件描述中存储的格式如下: char* data = "\09:37:38\0?2020/?12/?3\0",所以需要strtok_s()函数以"?"字符来分割字符串
				SysShutDownDate = strtok_s(SysShutDownTime + strlen(SysShutDownTime) + 2, "?", &SysShutDownDate_Next);
				while (SysShutDownDate != NULL)
				{
					memcpy_s(buf + strlen(buf), _countof(buf) - strlen(buf), SysShutDownDate, strlen(SysShutDownDate));
					SysShutDownDate = strtok_s(NULL, "?", &SysShutDownDate_Next);
				}
				strcpy_s(buf + strlen(buf), _countof(buf) - strlen(buf), " ");//增加一个空格
				strcpy_s(buf + strlen(buf), _countof(buf) - strlen(buf), SysShutDownTime);//复制时分秒
				break;
			}
			default:
				i += ptr->Length;
				continue;
			}
			write << "ID:" << ID << "  时间:" << buf << " " << EventText << endl;
			i += ptr->Length;
		}
	}
	CloseEventLog(Log);
	write.close(); // 输出完毕后关闭这个文件
	write.clear();
	system("pause");
	return 0;
}

四、附录

打印es日志 系统打印日志_开关机_05


如果遇到这两行代码报错的问题,是因为项目的编码方式选择了unicode,右键项目->属性->高级 将编码方式切换为多字节字符集即可。

打印es日志 系统打印日志_Data_06


总结

以上就是使用C++读取系统开关机记录的内容了