diff --git a/ProcMonX/CSVEventDataSerializer.cpp b/ProcMonX/CSVEventDataSerializer.cpp index 6fb8882..eb33f8a 100644 --- a/ProcMonX/CSVEventDataSerializer.cpp +++ b/ProcMonX/CSVEventDataSerializer.cpp @@ -1,16 +1,76 @@ #include "pch.h" #include "CSVEventDataSerializer.h" +#include "FormatHelper.h" #include +#include + +// Needed for processes like msedge which contain commas in their command line, messing up the csv formatting +void CSVEventDataSerializer::EscapeCsvString(std::wstring& str) +{ + if (str.empty()) { + return; + } + + std::wstring escapedStr = L""; + for (wchar_t c : str) { + if (c == '\"') { + escapedStr += c; + } + escapedStr += c; + } + + str = std::format(L"\"{}\"", escapedStr); +} + +std::wstring CSVEventDataSerializer::SerializeEvent(std::shared_ptr& event) +{ + uint32_t index = event->GetIndex(); + DWORD tPid = event->GetProcessId(); + DWORD tTid = event->GetThreadId(); + + std::wstring ts = (std::wstring)FormatHelper::FormatTime(event->GetTimeStamp()); + std::wstring name = event->GetEventName(); + std::wstring pName = event->GetProcessName(); + std::wstring pid = (tPid == (DWORD) - 1) ? L"" : std::to_wstring(tPid); + std::wstring tid = (tTid == (DWORD) - 1) ? L"" : std::to_wstring(tTid); + std::wstring details = L""; + + std::vector props = event->GetProperties(); + + if (props.size() > 0) { + bool needsEscaping = false; + for (EventProperty& p : props) { + std::wstring pValue = event->FormatProperty(p); + if (!pValue.empty()) { + details += p.Name + L": " + pValue + L"; "; + if (p.Name == L"CommandLine") { + needsEscaping = true; + } + } + } + + if (needsEscaping) { + EscapeCsvString(details); + } + } + + return std::format(L"{},{},{},{},{},{},{}", index, ts, name, pName, pid, tid, details); +} bool CSVEventDataSerializer::Save(const std::vector>& events, const EventDataSerializerOptions& options, PCWSTR path) { - std::ofstream out; + std::wofstream out; out.open(path); if(out.fail()) return false; - for (auto& evt : events) { + out << L"Id,Time Stamp,Event Name,Process Name,PID,TID,Details" << std::endl; + + for (std::shared_ptr evt : events) { + out << SerializeEvent(evt) << std::endl; } - return false; + + out.close(); + return true; } std::vector> CSVEventDataSerializer::Load(PCWSTR path) { diff --git a/ProcMonX/CSVEventDataSerializer.h b/ProcMonX/CSVEventDataSerializer.h index c2750e0..fa79d51 100644 --- a/ProcMonX/CSVEventDataSerializer.h +++ b/ProcMonX/CSVEventDataSerializer.h @@ -2,6 +2,9 @@ #include "IEventDataSerializer.h" class CSVEventDataSerializer : public IEventDataSerializer { +private: + void EscapeCsvString(std::wstring& str); + std::wstring SerializeEvent(std::shared_ptr& event); public: virtual bool Save(const std::vector>& events, const EventDataSerializerOptions& options, PCWSTR path) override; virtual std::vector> Load(PCWSTR path) override; diff --git a/ProcMonX/View.cpp b/ProcMonX/View.cpp index 366706a..6df5d22 100644 --- a/ProcMonX/View.cpp +++ b/ProcMonX/View.cpp @@ -489,6 +489,8 @@ LRESULT CView::OnSave(WORD, WORD, HWND, BOOL&) { auto eventsCopy = m_OrgEvents; if (!serializer->Save(eventsCopy, options, dlg.m_szFileName)) AtlMessageBox(*this, L"Failed to save data", IDS_TITLE, MB_ICONERROR); + else + AtlMessageBox(*this, L"CSV Data saved successfully", IDS_TITLE, MB_ICONINFORMATION); return 0; } }