MINIFICPP-1147 Implemented.

Signed-off-by: Arpad Boda <aboda@apache.org>

This closes #728
diff --git a/extensions/windows-event-log/ConsumeWindowsEventLog.cpp b/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
index d066f8f..a6832a9 100644
--- a/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
+++ b/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
@@ -164,6 +164,9 @@
 }
 
 ConsumeWindowsEventLog::~ConsumeWindowsEventLog() {
+  if (hMsobjsDll_) {
+    FreeLibrary(hMsobjsDll_);
+  }
 }
 
 void ConsumeWindowsEventLog::initialize() {
@@ -226,6 +229,25 @@
     }
   }
 
+  std::string mode;
+  context->getProperty(OutputFormat.getName(), mode);
+
+  writeXML_ = (mode == Both || mode == XML);
+
+  writePlainText_ = (mode == Both || mode == Plaintext);
+
+  if (writeXML_ && !hMsobjsDll_) {
+    char systemDir[MAX_PATH];
+    if (GetSystemDirectory(systemDir, sizeof(systemDir))) {
+      hMsobjsDll_ = LoadLibrary((systemDir + std::string("\\msobjs.dll")).c_str());
+      if (!hMsobjsDll_) {
+        logger_->log_error("!LoadLibrary error %x", GetLastError());
+      }
+    } else {
+      logger_->log_error("!GetSystemDirectory error %x", GetLastError());
+    }
+  }
+
   if (subscriptionHandle_) {
     logger_->log_error("Processor already subscribed to Event Log, expected cleanup to unsubscribe.");
   } else {
@@ -233,13 +255,6 @@
 
     subscribe(context);
   }
-
-  std::string mode;
-  context->getProperty(OutputFormat.getName(), mode);
-
-  writeXML_ = (mode == Both || mode == XML);
-
-  writePlainText_ = (mode == Both || mode == Plaintext);
 }
 
 void ConsumeWindowsEventLog::onTrigger(const std::shared_ptr<core::ProcessContext> &context, const std::shared_ptr<core::ProcessSession> &session) {
@@ -286,6 +301,96 @@
   return providers_[name];
 } 
 
+
+// !!! Used a non-documented approach to resolve `%%` in XML via C:\Windows\System32\MsObjs.dll.
+// Links which mention this approach: 
+// https://social.technet.microsoft.com/Forums/Windows/en-US/340632d1-60f0-4cc5-ad6f-f8c841107d0d/translate-value-1833quot-on-impersonationlevel-and-similar-values?forum=winservergen
+// https://github.com/libyal/libevtx/blob/master/documentation/Windows%20XML%20Event%20Log%20(EVTX).asciidoc 
+// https://stackoverflow.com/questions/33498244/marshaling-a-message-table-resource
+//
+// Traverse xml and check each node, if it starts with '%%' and contains only digits, use it as key to lookup value in C:\Windows\System32\MsObjs.dll.
+void ConsumeWindowsEventLog::substituteXMLPercentageItems(pugi::xml_document& doc) {
+  if (!hMsobjsDll_) {
+    return;
+  }
+
+  struct TreeWalker: public pugi::xml_tree_walker {
+    TreeWalker(HMODULE hMsobjsDll, std::unordered_map<std::string, std::string>& xmlPercentageItemsResolutions, std::shared_ptr<logging::Logger> logger)
+      : hMsobjsDll_(hMsobjsDll), xmlPercentageItemsResolutions_(xmlPercentageItemsResolutions), logger_(logger) {
+    }
+
+    bool for_each(pugi::xml_node& node) override {
+      static const std::string percentages = "%%";
+
+      bool percentagesReplaced = false;
+
+      std::string nodeText = node.text().get();
+
+      for (size_t numberPos = 0; std::string::npos != (numberPos = nodeText.find(percentages, numberPos));) {
+        numberPos += percentages.size();
+
+        auto number = 0u;
+        try {
+          // Assumption - first character is not '0', otherwise not all digits will be replaced by 'value'.
+          number = std::stoul(&nodeText[numberPos]);
+        } catch (std::invalid_argument& e) {
+          continue;
+        }
+
+        const std::string key = std::to_string(number);
+
+        std::string value;
+        const auto it = xmlPercentageItemsResolutions_.find(key);
+        if (it == xmlPercentageItemsResolutions_.end()) {
+          LPTSTR pBuffer{};
+          if (FormatMessage(
+            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
+            hMsobjsDll_,
+            number,
+            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+            (LPTSTR)&pBuffer,
+            1024,
+            0
+          )) {
+            value = pBuffer;
+            LocalFree(pBuffer);
+
+            value = utils::StringUtils::trimRight(value);
+
+            xmlPercentageItemsResolutions_.insert({key, value});
+          } else {
+            // Add "" to xmlPercentageItemsResolutions_ - don't need to call FormaMessage for this 'key' again.
+            xmlPercentageItemsResolutions_.insert({key, ""});
+
+            logger_->log_error("!FormatMessage error: %d. '%s' is not found in msobjs.dll.", GetLastError(), key.c_str());
+          }
+        } else {
+          value = it->second;
+        }
+
+        if (!value.empty()) {
+          nodeText.replace(numberPos - percentages.size(), key.size() + percentages.size(), value);
+
+          percentagesReplaced = true;
+        }
+      }
+
+      if (percentagesReplaced) {
+        node.text().set(nodeText.c_str());
+      }
+
+      return true;
+    }
+
+   private:
+    HMODULE hMsobjsDll_;
+    std::unordered_map<std::string, std::string>& xmlPercentageItemsResolutions_;
+    std::shared_ptr<logging::Logger> logger_;
+  } treeWalker(hMsobjsDll_, xmlPercentageItemsResolutions_, logger_);
+
+  doc.traverse(treeWalker);
+}
+
 void ConsumeWindowsEventLog::processEvent(EVT_HANDLE hEvent) {
   DWORD size = 0;
   DWORD used = 0;
@@ -343,6 +448,8 @@
       }
 
       if (writeXML_) {
+        substituteXMLPercentageItems(doc);
+
         if (resolve_as_attributes_) {
           renderedData.matched_fields_ = walker.getFieldValues();
         }
diff --git a/extensions/windows-event-log/ConsumeWindowsEventLog.h b/extensions/windows-event-log/ConsumeWindowsEventLog.h
index 31ab521..2314611 100644
--- a/extensions/windows-event-log/ConsumeWindowsEventLog.h
+++ b/extensions/windows-event-log/ConsumeWindowsEventLog.h
@@ -34,6 +34,7 @@
 #include "utils/OsUtils.h"
 #include <Objbase.h>
 #include <mutex>
+#include <unordered_map>
 
 namespace org {
 namespace apache {
@@ -108,13 +109,13 @@
   void LogWindowsError();
   void processEvent(EVT_HANDLE eventHandle);
   bool processEventsAfterBookmark(EVT_HANDLE hEventResults, const std::wstring& channel, const std::wstring& query);
+  void substituteXMLPercentageItems(pugi::xml_document& doc);
 
   static constexpr const char * const XML = "XML";
   static constexpr const char * const Both = "Both";
   static constexpr const char * const Plaintext = "Plaintext";
 
 private:
-
   // Logger
   wel::METADATA_NAMES header_names_;
   std::string header_delimiter_;
@@ -140,6 +141,8 @@
   bool writePlainText_;
   std::unique_ptr<Bookmark> pBookmark_;
   std::mutex onTriggerMutex_;
+  std::unordered_map<std::string, std::string> xmlPercentageItemsResolutions_;
+  HMODULE hMsobjsDll_{};
 };
 
 REGISTER_RESOURCE(ConsumeWindowsEventLog, "Windows Event Log Subscribe Callback to receive FlowFiles from Events on Windows.");