diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 966e7ad8fc..dbd8b04619 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -541,6 +541,7 @@ EUQ evenodd eventlog everytime +evt EWXFORCE EWXFORCEIFHUNG EWXLOGOFF @@ -954,6 +955,7 @@ IVector IView IVirtual IWeb +IXml ixx IZone IZoom @@ -1785,6 +1787,7 @@ sln SMALLICON SMTO snd +snwprintf softline somil Soref @@ -1944,6 +1947,7 @@ THISCOMPONENT thre tif TILEDWINDOW +timediff TIMERID timeunion timeutil @@ -2127,6 +2131,7 @@ webpack webpage website wekyb +Wevtapi Whichdoes whitespaces WIC @@ -2156,6 +2161,7 @@ windowwalker winerror WINEVENT winexe +winevt winforms winfx winget diff --git a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj index 562580b463..ed31bd4245 100644 --- a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj +++ b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj @@ -30,15 +30,18 @@ Console + Wevtapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) TurnOffAllWarnings + + @@ -52,9 +55,11 @@ + + diff --git a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters index 920c9eff95..d7099bdaac 100644 --- a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters +++ b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters @@ -11,6 +11,8 @@ + + @@ -26,5 +28,7 @@ + + \ No newline at end of file diff --git a/tools/BugReportTool/BugReportTool/EventViewer.cpp b/tools/BugReportTool/BugReportTool/EventViewer.cpp new file mode 100644 index 0000000000..cdf24ffe5e --- /dev/null +++ b/tools/BugReportTool/BugReportTool/EventViewer.cpp @@ -0,0 +1,189 @@ +#include "EventViewer.h" + +#include +#include +#include +#include +#include +#include + +#include "XmlDocumentEx.h" + +namespace +{ + std::vector processes = + { + L"PowerToys.exe", + L"ColorPickerUI.exe", + L"PowerToys.Espresso.exe" + L"FancyZonesEditor.exe", + L"PowerToys.KeyboardManagerEngine.exe", + L"PowerToys.KeyboardManagerEditor.exe", + L"PowerLauncher.exe", + L"PowerToys.ShortcutGuide.exe" + }; + + // Batch size for number of events queried at once + constexpr int BATCH_SIZE = 50; + + class EventViewerReporter + { + private: + // Report last 30 days + const long long PERIOD = 10 * 24 * 3600ll * 1000; + + const std::wstring QUERY = L"" \ + L" " \ + L" " \ + L" " \ + L""; + + std::wstring GetQuery(std::wstring processName) + { + wchar_t buff[1000]; + memset(buff, 0, sizeof(buff)); + _snwprintf_s(buff, sizeof(buff), QUERY.c_str(), PERIOD, processName.c_str()); + return buff; + } + + std::wofstream report; + EVT_HANDLE hResults; + + void PrintEvent(EVT_HANDLE hEvent) + { + DWORD status = ERROR_SUCCESS; + DWORD dwBufferSize = 0; + DWORD dwBufferUsed = 0; + DWORD dwPropertyCount = 0; + LPWSTR pRenderedContent = NULL; + + // The EvtRenderEventXml flag tells EvtRender to render the event as an XML string. + if (!EvtRender(NULL, hEvent, EvtRenderEventXml, dwBufferSize, pRenderedContent, &dwBufferUsed, &dwPropertyCount)) + { + if (ERROR_INSUFFICIENT_BUFFER == (status = GetLastError())) + { + dwBufferSize = dwBufferUsed; + pRenderedContent = (LPWSTR)malloc(dwBufferSize); + if (pRenderedContent) + { + EvtRender(NULL, hEvent, EvtRenderEventXml, dwBufferSize, pRenderedContent, &dwBufferUsed, &dwPropertyCount); + } + } + + if (ERROR_SUCCESS != (status = GetLastError())) + { + report << std::endl << L"EvtRender failed with " << get_last_error_or_default(GetLastError()) << std::endl << std::endl; + if (pRenderedContent) + { + free(pRenderedContent); + } + return; + } + } + + XmlDocumentEx doc; + doc.LoadXml(pRenderedContent); + std::wstring formattedXml = L""; + try + { + formattedXml = doc.GetFormatedXml(); + } + catch (...) + { + formattedXml = pRenderedContent; + } + + report << std::endl << formattedXml << std::endl; + if (pRenderedContent) + { + free(pRenderedContent); + } + } + + // Enumerate all the events in the result set. + void PrintResults(EVT_HANDLE hResults) + { + DWORD status = ERROR_SUCCESS; + EVT_HANDLE hEvents[BATCH_SIZE]; + DWORD dwReturned = 0; + + while (true) + { + // Get a block of events from the result set. + if (!EvtNext(hResults, BATCH_SIZE, hEvents, INFINITE, 0, &dwReturned)) + { + if (ERROR_NO_MORE_ITEMS != (status = GetLastError())) + { + report << L"EvtNext failed with " << status << std::endl; + } + + break; + } + + // For each event, call the PrintEvent function which renders the + // event for display. PrintEvent is shown in RenderingEvents. + for (DWORD i = 0; i < dwReturned; i++) + { + PrintEvent(hEvents[i]); + } + } + + for (DWORD i = 0; i < dwReturned; i++) + { + if (nullptr != hEvents[i]) + EvtClose(hEvents[i]); + } + } + + public: + EventViewerReporter(const std::filesystem::path& tmpDir, std::wstring processName) + { + auto query = GetQuery(processName); + auto reportPath = tmpDir; + reportPath.append(L"EventViewer-" + processName + L".xml"); + report = std::wofstream(reportPath); + + hResults = EvtQuery(NULL, NULL, GetQuery(processName).c_str(), EvtQueryChannelPath); + if (NULL == hResults) + { + report << "Failed to report info for " << processName << ". " << get_last_error_or_default(GetLastError()) << std::endl; + return; + } + } + + ~EventViewerReporter() + { + if (hResults) + { + EvtClose(hResults); + hResults = nullptr; + } + } + + void Report() + { + try + { + if (hResults) + { + PrintResults(hResults); + } + } + catch (...) + { + report << "Failed to report info" << std::endl; + } + } + }; +} + +void EventViewer::ReportEventViewerInfo(const std::filesystem::path& tmpDir) +{ + for (auto& process : processes) + { + EventViewerReporter(tmpDir, process).Report(); + } +} diff --git a/tools/BugReportTool/BugReportTool/EventViewer.h b/tools/BugReportTool/BugReportTool/EventViewer.h new file mode 100644 index 0000000000..0d50f4253e --- /dev/null +++ b/tools/BugReportTool/BugReportTool/EventViewer.h @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace EventViewer +{ + void ReportEventViewerInfo(const std::filesystem::path& tmpDir); +} diff --git a/tools/BugReportTool/BugReportTool/Main.cpp b/tools/BugReportTool/BugReportTool/Main.cpp index 603748630a..dd20e2c5f8 100644 --- a/tools/BugReportTool/BugReportTool/Main.cpp +++ b/tools/BugReportTool/BugReportTool/Main.cpp @@ -16,6 +16,8 @@ #include "ReportMonitorInfo.h" #include "RegistryUtils.h" +#include "EventViewer.h" + using namespace std; using namespace std::filesystem; using namespace winrt::Windows::Data::Json; @@ -331,6 +333,9 @@ int wmain(int argc, wchar_t* argv[], wchar_t*) // Write compatibility tab info to the temporary folder ReportCompatibilityTab(tmpDir); + // Write event viewer logs info to the temporary folder + EventViewer::ReportEventViewerInfo(tmpDir); + ReportBootstrapperLog(tmpDir); // Zip folder diff --git a/tools/BugReportTool/BugReportTool/XmlDocumentEx.cpp b/tools/BugReportTool/BugReportTool/XmlDocumentEx.cpp new file mode 100644 index 0000000000..d2e71705b1 --- /dev/null +++ b/tools/BugReportTool/BugReportTool/XmlDocumentEx.cpp @@ -0,0 +1,56 @@ +#include "XmlDocumentEx.h" + +#include + +std::wstring XmlDocumentEx::GetFormatedXml() +{ + stream.clear(); + Print(FirstChild(), 0); + return stream.str(); +} + +void XmlDocumentEx::Print(winrt::Windows::Data::Xml::Dom::IXmlNode node, int indentation) +{ + for (int i = 0; i < indentation; i++) + { + stream << " "; + } + + PrintTagWithAttributes(node); + if (!node.HasChildNodes()) + { + stream << L"<\\" << node.NodeName().c_str() << ">" << std::endl; + return; + } + + if (node.ChildNodes().Size() == 1 && !node.FirstChild().HasChildNodes()) + { + stream << node.InnerText().c_str() << L"<\\" << node.NodeName().c_str() << ">" << std::endl; + return; + } + + stream << std::endl; + auto child = node.FirstChild(); + do + { + Print(child, indentation + 2); + } while (child = child.NextSibling()); + + for (int i = 0; i < indentation; i++) + { + stream << " "; + } + stream << L"<\\" << node.NodeName().c_str() << ">" << std::endl; +} + +void XmlDocumentEx::PrintTagWithAttributes(winrt::Windows::Data::Xml::Dom::IXmlNode node) +{ + stream << L"<" << node.NodeName().c_str(); + for (int i = 0; i < (int)node.Attributes().Size(); i++) + { + auto attr = node.Attributes().GetAt(i); + stream << L" " << attr.NodeName().c_str() << L"='" << attr.InnerText().c_str() << L"'"; + } + + stream << L">"; +} diff --git a/tools/BugReportTool/BugReportTool/XmlDocumentEx.h b/tools/BugReportTool/BugReportTool/XmlDocumentEx.h new file mode 100644 index 0000000000..65db387ae2 --- /dev/null +++ b/tools/BugReportTool/BugReportTool/XmlDocumentEx.h @@ -0,0 +1,13 @@ +#pragma once +#include + +class XmlDocumentEx : public winrt::Windows::Data::Xml::Dom::XmlDocument +{ +private: + std::wstringstream stream; + void Print(winrt::Windows::Data::Xml::Dom::IXmlNode node, int indentation); + void PrintTagWithAttributes(winrt::Windows::Data::Xml::Dom::IXmlNode node); + +public: + std::wstring GetFormatedXml(); +};