// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "precomp.h"

#include <Intsafe.h>
#include "Shlwapi.h"
#include "telemetry.hpp"
#include <time.h>

#include "history.h"

#include "..\interactivity\inc\ServiceLocator.hpp"

TRACELOGGING_DEFINE_PROVIDER(g_hConhostV2EventTraceProvider,
                             "Microsoft.Windows.Console.Host",
                             // {fe1ff234-1f09-50a8-d38d-c44fab43e818}
                             (0xfe1ff234, 0x1f09, 0x50a8, 0xd3, 0x8d, 0xc4, 0x4f, 0xab, 0x43, 0xe8, 0x18),
                             TraceLoggingOptionMicrosoftTelemetry());
#pragma warning(push)
// Disable 4351 so we can initialize the arrays to 0 without a warning.
#pragma warning(disable : 4351)
Telemetry::Telemetry() :
    _fpFindStringLengthAverage(0),
    _fpDirectionDownAverage(0),
    _fpMatchCaseAverage(0),
    _uiFindNextClickedTotal(0),
    _uiColorSelectionUsed(0),
    _tStartedAt(0),
    _wchProcessFileNames(),
    // Start at position 1, since the first 2 bytes contain the number of strings.
    _iProcessFileNamesNext(1),
    _iProcessConnectedCurrently(SIZE_MAX),
    _rgiProccessFileNameIndex(),
    _rguiProcessFileNamesCount(),
    _rgiAlphabeticalIndex(),
    _rguiProcessFileNamesCodesCount(),
    _rguiProcessFileNamesFailedCodesCount(),
    _rguiProcessFileNamesFailedOutsideCodesCount(),
    _rguiTimesApiUsed(),
    _rguiTimesApiUsedAnsi(),
    _uiNumberProcessFileNames(0),
    _fBashUsed(false),
    _fKeyboardTextEditingUsed(false),
    _fKeyboardTextSelectionUsed(false),
    _fUserInteractiveForTelemetry(false),
    _fCtrlPgUpPgDnUsed(false),
    _uiCtrlShiftCProcUsed(0),
    _uiCtrlShiftCRawUsed(0),
    _uiCtrlShiftVProcUsed(0),
    _uiCtrlShiftVRawUsed(0),
    _uiQuickEditCopyProcUsed(0),
    _uiQuickEditCopyRawUsed(0),
    _uiQuickEditPasteProcUsed(0),
    _uiQuickEditPasteRawUsed(0)
{
    time(&_tStartedAt);
    TraceLoggingRegister(g_hConhostV2EventTraceProvider);
    TraceLoggingWriteStart(_activity, "ActivityStart");
    // initialize wil tracelogging
    wil::SetResultLoggingCallback(&Tracing::TraceFailure);
}
#pragma warning(pop)

Telemetry::~Telemetry()
{
    TraceLoggingWriteStop(_activity, "ActivityStop");
    TraceLoggingUnregister(g_hConhostV2EventTraceProvider);
}

void Telemetry::SetUserInteractive()
{
    _fUserInteractiveForTelemetry = true;
}

void Telemetry::SetCtrlPgUpPgDnUsed()
{
    _fCtrlPgUpPgDnUsed = true;
    SetUserInteractive();
}

void Telemetry::LogCtrlShiftCProcUsed()
{
    _uiCtrlShiftCProcUsed++;
    SetUserInteractive();
}

void Telemetry::LogCtrlShiftCRawUsed()
{
    _uiCtrlShiftCRawUsed++;
    SetUserInteractive();
}

void Telemetry::LogCtrlShiftVProcUsed()
{
    _uiCtrlShiftVProcUsed++;
    SetUserInteractive();
}

void Telemetry::LogCtrlShiftVRawUsed()
{
    _uiCtrlShiftVRawUsed++;
    SetUserInteractive();
}

void Telemetry::LogQuickEditCopyProcUsed()
{
    _uiQuickEditCopyProcUsed++;
    SetUserInteractive();
}

void Telemetry::LogQuickEditCopyRawUsed()
{
    _uiQuickEditCopyRawUsed++;
    SetUserInteractive();
}

void Telemetry::LogQuickEditPasteProcUsed()
{
    _uiQuickEditPasteProcUsed++;
    SetUserInteractive();
}

void Telemetry::LogQuickEditPasteRawUsed()
{
    _uiQuickEditPasteRawUsed++;
    SetUserInteractive();
}

// Log usage of the Color Selection option.
void Telemetry::LogColorSelectionUsed()
{
    _uiColorSelectionUsed++;
    SetUserInteractive();
}

void Telemetry::SetWindowSizeChanged()
{
    SetUserInteractive();
}

void Telemetry::SetContextMenuUsed()
{
    SetUserInteractive();
}

void Telemetry::SetKeyboardTextSelectionUsed()
{
    _fKeyboardTextSelectionUsed = true;
    SetUserInteractive();
}

void Telemetry::SetKeyboardTextEditingUsed()
{
    _fKeyboardTextEditingUsed = true;
    SetUserInteractive();
}

// Log an API call was used.
void Telemetry::LogApiCall(const ApiCall api, const BOOLEAN fUnicode)
{
    // Initially we thought about passing over a string (ex. "XYZ") and use a dictionary data type to hold the counts.
    // However we would have to search through the dictionary every time we called this method, so we decided
    // to use an array which has very quick access times.
    // The downside is we have to create an enum type, and then convert them to strings when we finally
    // send out the telemetry, but the upside is we should have very good performance.
    if (fUnicode)
    {
        _rguiTimesApiUsed[api]++;
    }
    else
    {
        _rguiTimesApiUsedAnsi[api]++;
    }
}

// Log an API call was used.
void Telemetry::LogApiCall(const ApiCall api)
{
    _rguiTimesApiUsed[api]++;
}

// Log usage of the Find Dialog.
void Telemetry::LogFindDialogNextClicked(const unsigned int uiStringLength, const bool fDirectionDown, const bool fMatchCase)
{
    // Don't send telemetry for every time it's used, as this will help reduce the load on our servers.
    // Instead just create a running average of the string length, the direction down radio
    // button, and match case checkbox.
    _fpFindStringLengthAverage = ((_fpFindStringLengthAverage * _uiFindNextClickedTotal + uiStringLength) / (_uiFindNextClickedTotal + 1));
    _fpDirectionDownAverage = ((_fpDirectionDownAverage * _uiFindNextClickedTotal + (fDirectionDown ? 1 : 0)) / (_uiFindNextClickedTotal + 1));
    _fpMatchCaseAverage = ((_fpMatchCaseAverage * _uiFindNextClickedTotal + (fMatchCase ? 1 : 0)) / (_uiFindNextClickedTotal + 1));
    _uiFindNextClickedTotal++;
}

// Find dialog was closed, now send out the telemetry.
void Telemetry::FindDialogClosed()
{
    // clang-format off
#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.")
    // clang-format on
    TraceLoggingWriteTagged(_activity,
                            "FindDialogUsed",
                            TraceLoggingValue(_fpFindStringLengthAverage, "StringLengthAverage"),
                            TraceLoggingValue(_fpDirectionDownAverage, "DirectionDownAverage"),
                            TraceLoggingValue(_fpMatchCaseAverage, "MatchCaseAverage"),
                            TraceLoggingValue(_uiFindNextClickedTotal, "FindNextButtonClickedTotal"),
                            TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
                            TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));

    // Get ready for the next time the dialog is used.
    _fpFindStringLengthAverage = 0;
    _fpDirectionDownAverage = 0;
    _fpMatchCaseAverage = 0;
    _uiFindNextClickedTotal = 0;
}

// Total up all the used VT100 codes and assign them to the last process that was attached.
// We originally did this when each process disconnected, but some processes don't
// disconnect when the conhost process exits.  So we have to remember the last process that connected.
void Telemetry::TotalCodesForPreviousProcess()
{
    using namespace Microsoft::Console::VirtualTerminal;
    // Get the values even if we aren't recording the previously connected process, since we want to reset them to 0.
    unsigned int _uiTimesUsedCurrent = TermTelemetry::Instance().GetAndResetTimesUsedCurrent();
    unsigned int _uiTimesFailedCurrent = TermTelemetry::Instance().GetAndResetTimesFailedCurrent();
    unsigned int _uiTimesFailedOutsideRangeCurrent = TermTelemetry::Instance().GetAndResetTimesFailedOutsideRangeCurrent();

    if (_iProcessConnectedCurrently < c_iMaxProcessesConnected)
    {
        _rguiProcessFileNamesCodesCount[_iProcessConnectedCurrently] += _uiTimesUsedCurrent;
        _rguiProcessFileNamesFailedCodesCount[_iProcessConnectedCurrently] += _uiTimesFailedCurrent;
        _rguiProcessFileNamesFailedOutsideCodesCount[_iProcessConnectedCurrently] += _uiTimesFailedOutsideRangeCurrent;

        // Don't total any more process connected telemetry, unless a new processes attaches that we want to gather.
        _iProcessConnectedCurrently = SIZE_MAX;
    }
}

// Tries to find the process name amongst our previous process names by doing a binary search.
// The main difference between this and the standard bsearch library call, is that if this
// can't find the string, it returns the position the new string should be inserted at.  This saves
// us from having an additional search through the array, and improves performance.
bool Telemetry::FindProcessName(const WCHAR* pszProcessName, _Out_ size_t* iPosition) const
{
    int iMin = 0;
    int iMid = 0;
    int iMax = _uiNumberProcessFileNames - 1;
    int result = 0;

    while (iMin <= iMax)
    {
        iMid = (iMax + iMin) / 2;
        // Use a case-insensitive comparison.  We do support running Linux binaries now, but we haven't seen them connect
        // as processes, and even if they did, we don't care about the difference in running emacs vs. Emacs.
        result = _wcsnicmp(pszProcessName, _wchProcessFileNames + _rgiProccessFileNameIndex[_rgiAlphabeticalIndex[iMid]], MAX_PATH);
        if (result < 0)
        {
            iMax = iMid - 1;
        }
        else if (result > 0)
        {
            iMin = iMid + 1;
        }
        else
        {
            // Found the string.
            *iPosition = iMid;
            return true;
        }
    }

    // Let them know which position to insert the string at.
    *iPosition = (result > 0) ? iMid + 1 : iMid;
    return false;
}

// Log a process name and number of times it has connected to the console in preparation to send through telemetry.
// We were considering sending out a log of telemetry when each process connects, but then the telemetry can get
// complicated and spammy, especially since command line utilities like help.exe and where.exe are considered processes.
// Don't send telemetry for every time a process connects, as this will help reduce the load on our servers.
// Just save the name and count, and send the telemetry before the console exits.
void Telemetry::LogProcessConnected(const HANDLE hProcess)
{
    // This is a bit of processing, so don't do it for the 95% of machines that aren't being sampled.
    if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, MICROSOFT_KEYWORD_MEASURES))
    {
        TotalCodesForPreviousProcess();

        // Don't initialize wszFilePathAndName, QueryFullProcessImageName does that for us.  Use QueryFullProcessImageName instead of
        // GetProcessImageFileName because we need the path to begin with a drive letter and not a device name.
        WCHAR wszFilePathAndName[MAX_PATH];
        DWORD dwSize = ARRAYSIZE(wszFilePathAndName);
        if (QueryFullProcessImageName(hProcess, 0, wszFilePathAndName, &dwSize))
        {
            // Stripping out the path also helps with PII issues in case they launched the program
            // from a path containing their username.
            PWSTR pwszFileName = PathFindFileName(wszFilePathAndName);

            size_t iFileName;
            if (FindProcessName(pwszFileName, &iFileName))
            {
                // We already logged this process name, so just increment the count.
                _iProcessConnectedCurrently = _rgiAlphabeticalIndex[iFileName];
                _rguiProcessFileNamesCount[_iProcessConnectedCurrently]++;
            }
            else if ((_uiNumberProcessFileNames < ARRAYSIZE(_rguiProcessFileNamesCount)) &&
                     (_iProcessFileNamesNext < ARRAYSIZE(_wchProcessFileNames) - 10))
            {
                // Check if the MS released bash was used.  MS bash is installed under windows\system32, and it's possible somebody else
                // could be installing their bash into that directory, but not likely.  If the user first runs a non-MS bash,
                // and then runs MS bash, we won't detect the MS bash as running, but it's an acceptable compromise.
                if (!_fBashUsed && !_wcsnicmp(c_pwszBashExeName, pwszFileName, MAX_PATH))
                {
                    // We could have gotten the system directory once when this class starts, but we'd have to hold the memory for it
                    // plus we're not sure we'd ever need it, so just get it when we know we're running bash.exe.
                    WCHAR wszSystemDirectory[MAX_PATH] = L"";
                    if (GetSystemDirectory(wszSystemDirectory, ARRAYSIZE(wszSystemDirectory)))
                    {
                        _fBashUsed = (PathIsSameRoot(wszFilePathAndName, wszSystemDirectory) == TRUE);
                    }
                }

                // In order to send out a dynamic array of strings through telemetry, we have to pack the strings into a single WCHAR array.
                // There currently aren't any helper functions for this, and we have to pack it manually.
                // To understand the format of the single string, consult the documentation in the traceloggingprovider.h file.
                if (SUCCEEDED(StringCchCopyW(_wchProcessFileNames + _iProcessFileNamesNext, ARRAYSIZE(_wchProcessFileNames) - _iProcessFileNamesNext - 1, pwszFileName)))
                {
                    // As each FileName comes in, it's appended to the end.  However to improve searching speed, we have an array of indexes
                    // that is alphabetically sorted.  We could call qsort, but that would be a waste in performance since we're just adding one string
                    // at a time and we always keep the array sorted, so just shift everything over one.
                    for (size_t n = _uiNumberProcessFileNames; n > iFileName; n--)
                    {
                        _rgiAlphabeticalIndex[n] = _rgiAlphabeticalIndex[n - 1];
                    }

                    // Now point to the string, and set the count to 1.
                    _rgiAlphabeticalIndex[iFileName] = _uiNumberProcessFileNames;
                    _rgiProccessFileNameIndex[_uiNumberProcessFileNames] = _iProcessFileNamesNext;
                    _rguiProcessFileNamesCount[_uiNumberProcessFileNames] = 1;
                    _iProcessFileNamesNext += wcslen(pwszFileName) + 1;
                    _iProcessConnectedCurrently = _uiNumberProcessFileNames++;

                    // Packed arrays start with a UINT16 value indicating the number of elements in the array.
                    BYTE* pbFileNames = reinterpret_cast<BYTE*>(_wchProcessFileNames);
                    pbFileNames[0] = (BYTE)_uiNumberProcessFileNames;
                    pbFileNames[1] = (BYTE)(_uiNumberProcessFileNames >> 8);
                }
            }
        }
    }
}

// This Function sends final Trace log before session closes.
// We're primarily sending this telemetry once at the end, and only when the user interacted with the console
// so we don't overwhelm our servers by sending a constant stream of telemetry while the console is being used.
void Telemetry::WriteFinalTraceLog()
{
    const CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
    // This is a bit of processing, so don't do it for the 95% of machines that aren't being sampled.
    if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, MICROSOFT_KEYWORD_MEASURES))
    {
        // Normally we would set the activity Id earlier, but since we know the parser only sends
        // one final log at the end, setting the activity this late should be fine.
        Microsoft::Console::VirtualTerminal::TermTelemetry::Instance().SetActivityId(_activity.Id());
        Microsoft::Console::VirtualTerminal::TermTelemetry::Instance().SetShouldWriteFinalLog(_fUserInteractiveForTelemetry);

        if (_fUserInteractiveForTelemetry)
        {
            TotalCodesForPreviousProcess();

            // Send this back using "measures" since we want a good sampling of our entire userbase.
            time_t tEndedAt;
            time(&tEndedAt);
            // clang-format off
#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.")
            // clang-format on
            TraceLoggingWriteTagged(_activity,
                                    "SessionEnding",
                                    TraceLoggingBool(_fBashUsed, "BashUsed"),
                                    TraceLoggingBool(_fCtrlPgUpPgDnUsed, "CtrlPgUpPgDnUsed"),
                                    TraceLoggingBool(_fKeyboardTextEditingUsed, "KeyboardTextEditingUsed"),
                                    TraceLoggingBool(_fKeyboardTextSelectionUsed, "KeyboardTextSelectionUsed"),
                                    TraceLoggingUInt32(_uiCtrlShiftCProcUsed, "CtrlShiftCProcUsed"),
                                    TraceLoggingUInt32(_uiCtrlShiftCRawUsed, "CtrlShiftCRawUsed"),
                                    TraceLoggingUInt32(_uiCtrlShiftVProcUsed, "CtrlShiftVProcUsed"),
                                    TraceLoggingUInt32(_uiCtrlShiftVRawUsed, "CtrlShiftVRawUsed"),
                                    TraceLoggingUInt32(_uiQuickEditCopyProcUsed, "QuickEditCopyProcUsed"),
                                    TraceLoggingUInt32(_uiQuickEditCopyRawUsed, "QuickEditCopyRawUsed"),
                                    TraceLoggingUInt32(_uiQuickEditPasteProcUsed, "QuickEditPasteProcUsed"),
                                    TraceLoggingUInt32(_uiQuickEditPasteRawUsed, "QuickEditPasteRawUsed"),
                                    TraceLoggingBool(gci.GetLinkTitle().length() == 0, "LaunchedFromShortcut"),
                                    // Normally we would send out a single array containing the name and count,
                                    // but that's difficult to do with our telemetry system, so send out two separate arrays.
                                    // Casting to UINT should be fine, since our array size is only 2K.
                                    TraceLoggingPackedField(_wchProcessFileNames, static_cast<UINT>(sizeof(WCHAR) * _iProcessFileNamesNext), TlgInUNICODESTRING | TlgInVcount, "ProcessesConnected"),
                                    TraceLoggingUInt32Array(_rguiProcessFileNamesCount, _uiNumberProcessFileNames, "ProcessesConnectedCount"),
                                    TraceLoggingUInt32Array(_rguiProcessFileNamesCodesCount, _uiNumberProcessFileNames, "ProcessesConnectedCodesCount"),
                                    TraceLoggingUInt32Array(_rguiProcessFileNamesFailedCodesCount, _uiNumberProcessFileNames, "ProcessesConnectedFailedCodesCount"),
                                    TraceLoggingUInt32Array(_rguiProcessFileNamesFailedOutsideCodesCount, _uiNumberProcessFileNames, "ProcessesConnectedFailedOutsideCount"),
                                    // Send back both starting and ending times separately instead just usage time (ending - starting).
                                    // This can help us determine if they were using multiple consoles at the same time.
                                    TraceLoggingInt32(static_cast<int>(_tStartedAt), "StartedUsingAtSeconds"),
                                    TraceLoggingInt32(static_cast<int>(tEndedAt), "EndedUsingAtSeconds"),
                                    TraceLoggingUInt32(_uiColorSelectionUsed, "ColorSelectionUsed"),
                                    TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
                                    TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));

            // Always send this back.  We could only send this back when they click "OK" in the settings dialog, but sending it
            // back every time should give us a good idea of their current, final settings, and not just only when they change a setting.
            // clang-format off
#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.")
            // clang-format on
            TraceLoggingWriteTagged(_activity,
                                    "Settings",
                                    TraceLoggingBool(gci.GetAutoPosition(), "AutoPosition"),
                                    TraceLoggingBool(gci.GetHistoryNoDup(), "HistoryNoDuplicates"),
                                    TraceLoggingBool(gci.GetInsertMode(), "InsertMode"),
                                    TraceLoggingBool(gci.GetLineSelection(), "LineSelection"),
                                    TraceLoggingBool(gci.GetQuickEdit(), "QuickEdit"),
                                    TraceLoggingValue(gci.GetWindowAlpha(), "WindowAlpha"),
                                    TraceLoggingBool(gci.GetWrapText(), "WrapText"),
                                    TraceLoggingUInt32Array((UINT32 const*)gci.GetColorTable(), (UINT16)gci.GetColorTableSize(), "ColorTable"),
                                    TraceLoggingValue(gci.CP, "CodePageInput"),
                                    TraceLoggingValue(gci.OutputCP, "CodePageOutput"),
                                    TraceLoggingValue(gci.GetFontSize().X, "FontSizeX"),
                                    TraceLoggingValue(gci.GetFontSize().Y, "FontSizeY"),
                                    TraceLoggingValue(gci.GetHotKey(), "HotKey"),
                                    TraceLoggingValue(gci.GetScreenBufferSize().X, "ScreenBufferSizeX"),
                                    TraceLoggingValue(gci.GetScreenBufferSize().Y, "ScreenBufferSizeY"),
                                    TraceLoggingValue(gci.GetStartupFlags(), "StartupFlags"),
                                    TraceLoggingValue(gci.GetVirtTermLevel(), "VirtualTerminalLevel"),
                                    TraceLoggingValue(gci.GetWindowSize().X, "WindowSizeX"),
                                    TraceLoggingValue(gci.GetWindowSize().Y, "WindowSizeY"),
                                    TraceLoggingValue(gci.GetWindowOrigin().X, "WindowOriginX"),
                                    TraceLoggingValue(gci.GetWindowOrigin().Y, "WindowOriginY"),
                                    TraceLoggingValue(gci.GetFaceName(), "FontName"),
                                    TraceLoggingBool(gci.IsAltF4CloseAllowed(), "AllowAltF4Close"),
                                    TraceLoggingBool(gci.GetCtrlKeyShortcutsDisabled(), "ControlKeyShortcutsDisabled"),
                                    TraceLoggingBool(gci.GetEnableColorSelection(), "EnabledColorSelection"),
                                    TraceLoggingBool(gci.GetFilterOnPaste(), "FilterOnPaste"),
                                    TraceLoggingBool(gci.GetTrimLeadingZeros(), "TrimLeadingZeros"),
                                    TraceLoggingValue(gci.GetLaunchFaceName(), "LaunchFontName"),
                                    TraceLoggingValue(CommandHistory::s_CountOfHistories(), "CommandHistoriesNumber"),
                                    TraceLoggingValue(gci.GetCodePage(), "CodePage"),
                                    TraceLoggingValue(gci.GetCursorSize(), "CursorSize"),
                                    TraceLoggingValue(gci.GetFontFamily(), "FontFamily"),
                                    TraceLoggingValue(gci.GetFontWeight(), "FontWeight"),
                                    TraceLoggingValue(gci.GetHistoryBufferSize(), "HistoryBufferSize"),
                                    TraceLoggingValue(gci.GetNumberOfHistoryBuffers(), "HistoryBuffersNumber"),
                                    TraceLoggingValue(gci.GetScrollScale(), "ScrollScale"),
                                    TraceLoggingValue(gci.GetFillAttribute(), "FillAttribute"),
                                    TraceLoggingValue(gci.GetPopupFillAttribute(), "PopupFillAttribute"),
                                    TraceLoggingValue(gci.GetShowWindow(), "ShowWindow"),
                                    TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
                                    TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
            static_assert(sizeof(UINT32) == sizeof(*gci.GetColorTable()), "gci.GetColorTable()");

            // I could use the TraceLoggingUIntArray, but then we would have to know the order of the enums on the backend.
            // So just log each enum count separately with its string representation which makes it more human readable.
            // clang-format off
#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.")
            // clang-format on
            TraceLoggingWriteTagged(_activity,
                                    "ApiUsed",
                                    TraceLoggingUInt32(_rguiTimesApiUsed[AddConsoleAlias], "AddConsoleAlias"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[AllocConsole], "AllocConsole"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[AttachConsole], "AttachConsole"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[CreateConsoleScreenBuffer], "CreateConsoleScreenBuffer"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GenerateConsoleCtrlEvent], "GenerateConsoleCtrlEvent"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[FillConsoleOutputAttribute], "FillConsoleOutputAttribute"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[FillConsoleOutputCharacter], "FillConsoleOutputCharacter"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[FlushConsoleInputBuffer], "FlushConsoleInputBuffer"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[FreeConsole], "FreeConsole"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAlias], "GetConsoleAlias"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliases], "GetConsoleAliases"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasExesLength], "GetConsoleAliasExesLength"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasesLength], "GetConsoleAliasesLength"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasExes], "GetConsoleAliasExes"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleCP], "GetConsoleCP"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleCursorInfo], "GetConsoleCursorInfo"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleDisplayMode], "GetConsoleDisplayMode"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleFontSize], "GetConsoleFontSize"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleHistoryInfo], "GetConsoleHistoryInfo"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleLangId], "GetConsoleLangId"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleMode], "GetConsoleMode"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleOriginalTitle], "GetConsoleOriginalTitle"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleOutputCP], "GetConsoleOutputCP"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleProcessList], "GetConsoleProcessList"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleScreenBufferInfoEx], "GetConsoleScreenBufferInfoEx"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleSelectionInfo], "GetConsoleSelectionInfo"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleTitle], "GetConsoleTitle"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleWindow], "GetConsoleWindow"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetCurrentConsoleFontEx], "GetCurrentConsoleFontEx"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetLargestConsoleWindowSize], "GetLargestConsoleWindowSize"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetNumberOfConsoleInputEvents], "GetNumberOfConsoleInputEvents"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[GetNumberOfConsoleMouseButtons], "GetNumberOfConsoleMouseButtons"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[PeekConsoleInput], "PeekConsoleInput"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsole], "ReadConsole"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleInput], "ReadConsoleInput"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutput], "ReadConsoleOutput"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutputAttribute], "ReadConsoleOutputAttribute"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutputCharacter], "ReadConsoleOutputCharacter"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[ScrollConsoleScreenBuffer], "ScrollConsoleScreenBuffer"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleActiveScreenBuffer], "SetConsoleActiveScreenBuffer"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCP], "SetConsoleCP"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCursorInfo], "SetConsoleCursorInfo"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCursorPosition], "SetConsoleCursorPosition"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleDisplayMode], "SetConsoleDisplayMode"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleHistoryInfo], "SetConsoleHistoryInfo"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleMode], "SetConsoleMode"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleOutputCP], "SetConsoleOutputCP"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleScreenBufferInfoEx], "SetConsoleScreenBufferInfoEx"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleScreenBufferSize], "SetConsoleScreenBufferSize"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleTextAttribute], "SetConsoleTextAttribute"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleTitle], "SetConsoleTitle"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleWindowInfo], "SetConsoleWindowInfo"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[SetCurrentConsoleFontEx], "SetCurrentConsoleFontEx"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsole], "WriteConsole"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleInput], "WriteConsoleInput"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutput], "WriteConsoleOutput"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutputAttribute], "WriteConsoleOutputAttribute"),
                                    TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutputCharacter], "WriteConsoleOutputCharacter"),
                                    TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
                                    TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));

            for (int n = 0; n < ARRAYSIZE(_rguiTimesApiUsedAnsi); n++)
            {
                if (_rguiTimesApiUsedAnsi[n])
                {
                    // Ansi specific API's are used less, so check if we have anything to send back.
                    // Also breaking it up into a separate TraceLoggingWriteTagged fixes a compilation warning that
                    // the heap is too small.
                    // clang-format off
#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.")
                    // clang-format on
                    TraceLoggingWriteTagged(_activity,
                                            "ApiAnsiUsed",
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[AddConsoleAlias], "AddConsoleAlias"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[FillConsoleOutputCharacter], "FillConsoleOutputCharacter"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAlias], "GetConsoleAlias"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliases], "GetConsoleAliases"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasesLength], "GetConsoleAliasesLength"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasExes], "GetConsoleAliasExes"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasExesLength], "GetConsoleAliasExesLength"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleOriginalTitle], "GetConsoleOriginalTitle"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleTitle], "GetConsoleTitle"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[PeekConsoleInput], "PeekConsoleInput"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsole], "ReadConsole"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleInput], "ReadConsoleInput"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleOutput], "ReadConsoleOutput"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleOutputCharacter], "ReadConsoleOutputCharacter"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[SetConsoleTitle], "SetConsoleTitle"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsole], "WriteConsole"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleInput], "WriteConsoleInput"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleOutput], "WriteConsoleOutput"),
                                            TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleOutputCharacter], "WriteConsoleOutputCharacter"),
                                            TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
                                            TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
                    break;
                }
            }
        }
    }
}

// These are legacy error messages with limited value, so don't send them back as telemetry.
void Telemetry::LogRipMessage(_In_z_ const char* pszMessage, ...) const
{
    // Code needed for passing variable parameters to the vsprintf function.
    va_list args;
    va_start(args, pszMessage);
    char szMessageEvaluated[200] = "";
    int cCharsWritten = vsprintf_s(szMessageEvaluated, ARRAYSIZE(szMessageEvaluated), pszMessage, args);
    va_end(args);

#if DBG
    OutputDebugStringA(szMessageEvaluated);
#endif

    if (cCharsWritten > 0)
    {
        // clang-format off
#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.")
        // clang-format on
        TraceLoggingWriteTagged(_activity,
                                "RipMessage",
                                TraceLoggingString(szMessageEvaluated, "Message"));
    }
}
