// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== #include #include "dbglog.h" #include "wininet.h" #include "util.h" //#include "lock.h" #include "version.h" extern HINSTANCE g_hInst; extern WCHAR g_FusionDllPath[MAX_PATH+1]; #define XSP_APP_CACHE_DIR L"Temporary ASP.NET Files" #define XSP_FUSION_LOG_DIR L"Bind Logs" #define REG_KEY_FUSION_SETTINGS TEXT("Software\\Microsoft\\Fusion\\Installer\\1.0.0.0\\") #define DLTYPE_DEFAULT L"DEFAULT" #define DLTYPE_FOREGROUND L"Foreground" #define DLTYPE_BACKGROUND L"Background" WCHAR g_wzEXEPath[MAX_PATH+1]; DWORD g_dwDisableLog; DWORD g_dwForceLog; DWORD g_dwLogFailures; DWORD g_dwLoggingLevel=1; void GetExePath() { static BOOL bFirstTime=TRUE; if(bFirstTime) { if (!GetModuleFileNameW(NULL, g_wzEXEPath, MAX_PATH)) { lstrcpyW(g_wzEXEPath, L"Unknown"); } bFirstTime=FALSE; } } BOOL IsHosted() { return FALSE; } HRESULT GetRegValues() { DWORD dwSize; DWORD dwType; DWORD lResult; HKEY hkey=0; DWORD dwValue=0; lResult = RegOpenKeyEx(HKEY_CURRENT_USER, REG_KEY_FUSION_SETTINGS, 0, KEY_READ, &hkey); if(lResult == ERROR_SUCCESS) { // Get Logging Level dwSize = sizeof(DWORD); lResult = RegQueryValueEx(hkey, REG_VAL_FUSION_LOG_LEVEL, NULL, &dwType, (LPBYTE)&dwValue, &dwSize); if (lResult != ERROR_SUCCESS) { g_dwLoggingLevel = 0; } else { g_dwLoggingLevel = dwValue; } // Get Log Failures dwSize = sizeof(DWORD); lResult = RegQueryValueEx(hkey, REG_VAL_FUSION_LOG_FAILURES, NULL, &dwType, (LPBYTE)&dwValue, &dwSize); if (lResult != ERROR_SUCCESS) { g_dwLogFailures = 0; } else { g_dwLogFailures = dwValue; } } return S_OK; } HRESULT CreateLogObject(CDebugLog **ppdbglog, LPCWSTR szCodebase) { HRESULT hr = S_OK; MAKE_ERROR_MACROS_STATIC(hr); IF_FAILED_EXIT(CDebugLog::Create(NULL, NULL, ppdbglog)); // hr = GetRegValues(); exit: return hr; } // // CDebugLogElement Class // HRESULT CDebugLogElement::Create(DWORD dwDetailLvl, LPCWSTR pwzMsg, CDebugLogElement **ppLogElem) { HRESULT hr = S_OK; CDebugLogElement *pLogElem = NULL; if (!ppLogElem || !pwzMsg) { hr = E_INVALIDARG; goto Exit; } *ppLogElem = NULL; pLogElem = FUSION_NEW_SINGLETON(CDebugLogElement(dwDetailLvl)); if (!pLogElem) { hr = E_OUTOFMEMORY; goto Exit; } hr = pLogElem->Init(pwzMsg); if (FAILED(hr)) { SAFEDELETE(pLogElem); goto Exit; } *ppLogElem = pLogElem; Exit: return hr; } CDebugLogElement::CDebugLogElement(DWORD dwDetailLvl) : _pszMsg(NULL) , _dwDetailLvl(dwDetailLvl) { } CDebugLogElement::~CDebugLogElement() { SAFEDELETEARRAY(_pszMsg); } HRESULT CDebugLogElement::Init(LPCWSTR pwzMsg) { HRESULT hr = S_OK; ASSERT(pwzMsg); _pszMsg = WSTRDupDynamic(pwzMsg); if (!_pszMsg) { hr = E_OUTOFMEMORY; goto Exit; } Exit: return hr; } /******************************************************************* NAME: Unicode2Ansi SYNOPSIS: Converts a unicode widechar string to ansi (MBCS) NOTES: Caller must free out parameter using delete ********************************************************************/ HRESULT Unicode2Ansi(const wchar_t *src, char ** dest) { if ((src == NULL) || (dest == NULL)) return E_INVALIDARG; // find out required buffer size and allocate it. int len = WideCharToMultiByte(CP_ACP, 0, src, -1, NULL, 0, NULL, NULL); *dest = NEW(char [len*sizeof(char)]); if (!*dest) return E_OUTOFMEMORY; // Now do the actual conversion if ((WideCharToMultiByte(CP_ACP, 0, src, -1, *dest, len*sizeof(char), NULL, NULL)) != 0) return S_OK; else return HRESULT_FROM_WIN32(GetLastError()); } HRESULT CDebugLogElement::Dump(HANDLE hFile) { HRESULT hr = S_OK; DWORD dwLen = 0; DWORD dwWritten = 0; DWORD dwSize = 0; DWORD dwBufSize = 0; LPSTR szBuf = NULL; BOOL bRet; if (!hFile) { hr = E_INVALIDARG; goto Exit; } dwSize = lstrlenW(_pszMsg) + 1; hr = Unicode2Ansi(_pszMsg, &szBuf); if(FAILED(hr)) goto Exit; bRet = WriteFile(hFile, szBuf, strlen(szBuf), &dwWritten, NULL); if (!bRet) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } Exit: SAFEDELETEARRAY(szBuf); return hr; } // // CDebugLog Class // HRESULT CDebugLog::Create(IApplicationContext *pAppCtx, LPCWSTR pwzAsmName, CDebugLog **ppdl) { HRESULT hr = S_OK; CDebugLog *pdl = NULL; *ppdl = NULL; pdl = NEW(CDebugLog); if (!pdl) { hr = E_OUTOFMEMORY; goto Exit; } hr = pdl->Init(pAppCtx, pwzAsmName); if (FAILED(hr)) { delete pdl; pdl = NULL; goto Exit; } *ppdl = pdl; Exit: return hr; } CDebugLog::CDebugLog() : _pwzAsmName(NULL) , _cRef(1) , _bLogToWininet(TRUE) , _dwNumEntries(0) , _hr(S_OK) , _wzEXEName(NULL) , _bWroteDetails(FALSE) { _szLogPath[0] = L'\0'; InitializeCriticalSection(&_cs); GetExePath(); } CDebugLog::~CDebugLog() { LISTNODE pos = NULL; CDebugLogElement *pLogElem = NULL; pos = _listDbgMsg.GetHeadPosition(); while (pos) { pLogElem = _listDbgMsg.GetNext(pos); SAFEDELETE(pLogElem); } _listDbgMsg.RemoveAll(); SAFEDELETEARRAY(_pwzAsmName); SAFEDELETEARRAY(_wzEXEName); DeleteCriticalSection(&_cs); } HRESULT CDebugLog::Init(IApplicationContext *pAppCtx, LPCWSTR pwzAsmName) { HRESULT hr = S_OK; BOOL bIsHosted = FALSE; DWORD dwSize; DWORD dwType; DWORD lResult; DWORD dwAttr; HKEY hkey; LPWSTR wzAppName = NULL; LPWSTR wzEXEName = NULL; hr = _sDLType.Assign(DLTYPE_DEFAULT); if (wzAppName && lstrlenW(wzAppName)) { _wzEXEName = WSTRDupDynamic(wzAppName); if (!_wzEXEName) { hr = E_OUTOFMEMORY; goto Exit; } } else { LPWSTR wzFileName; // Didn't find EXE name in appctx. Use the .EXE name. wzFileName = PathFindFileName(g_wzEXEPath); ASSERT(wzFileName); hr = _sAppName.Assign(wzFileName); _wzEXEName = WSTRDupDynamic(wzFileName); if (!_wzEXEName) { hr = E_OUTOFMEMORY; goto Exit; } } // Log path lResult = RegOpenKeyEx(HKEY_CURRENT_USER, REG_KEY_FUSION_SETTINGS, 0, KEY_READ, &hkey); if (lResult == ERROR_SUCCESS) { dwSize = MAX_PATH; lResult = RegQueryValueEx(hkey, REG_VAL_FUSION_LOG_PATH, NULL, &dwType, (LPBYTE)_szLogPath, &dwSize); if (lResult == ERROR_SUCCESS) { PathRemoveBackslashW(_szLogPath); } else { _szLogPath[0] = L'\0'; } RegCloseKey(hkey); dwAttr = GetFileAttributesW(_szLogPath); if (dwAttr != -1 && (dwAttr & FILE_ATTRIBUTE_DIRECTORY)) { _bLogToWininet = FALSE; } } /* bIsHosted = IsHosted(); if (bIsHosted && !lstrlenW(_szLogPath)) { BOOL bRet; WCHAR wzCorSystemDir[MAX_PATH]; WCHAR wzXSPAppCacheDir[MAX_PATH]; if (!GetCorSystemDirectory(wzCorSystemDir)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } PathRemoveBackslash(wzCorSystemDir); wnsprintfW(wzXSPAppCacheDir, MAX_PATH, L"%ws\\%ws", wzCorSystemDir, XSP_APP_CACHE_DIR); dwAttr = GetFileAttributes(wzXSPAppCacheDir); if (dwAttr == -1) { bRet = CreateDirectory(wzXSPAppCacheDir, NULL); if (!bRet) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } } wnsprintfW(_szLogPath, MAX_PATH, L"%ws\\%ws", wzXSPAppCacheDir, XSP_FUSION_LOG_DIR); dwAttr = GetFileAttributes(_szLogPath); if (dwAttr == -1) { bRet = CreateDirectory(_szLogPath, NULL); if (!bRet) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } } _bLogToWininet = FALSE; } */ Exit: SAFEDELETEARRAY(wzAppName); return hr; } HRESULT CDebugLog::SetAsmName(LPCWSTR pwzAsmName) { HRESULT hr = S_OK; int iLen; if (_pwzAsmName) { // You can only set the name once. hr = S_FALSE; goto Exit; } if (!pwzAsmName) { hr = E_INVALIDARG; goto Exit; } iLen = lstrlenW(pwzAsmName) + 1; _pwzAsmName = NEW(WCHAR[iLen]); if (!_pwzAsmName) { hr = E_OUTOFMEMORY; goto Exit; } lstrcpyW(_pwzAsmName, pwzAsmName); Exit: return hr; } HRESULT CDebugLog::SetDownloadType(DWORD dwFlags) { HRESULT hr = S_OK; int iLen; if(dwFlags & DOWNLOAD_FLAGS_PROGRESS_UI) IF_FAILED_EXIT(_sDLType.Assign(DLTYPE_FOREGROUND)); else IF_FAILED_EXIT(_sDLType.Assign(DLTYPE_BACKGROUND)); exit: return hr; } HRESULT CDebugLog::SetAppName(LPCWSTR pwzAppName) { HRESULT hr = S_OK; int iLen; ASSERT(pwzAppName); IF_FAILED_EXIT(_sAppName.Assign(pwzAppName)); exit: return hr; } // // IUnknown // STDMETHODIMP CDebugLog::QueryInterface(REFIID riid, void **ppv) { HRESULT hr = E_FAIL; /* if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IFusionBindLog)) { *ppv = static_cast(this); } else { *ppv = NULL; hr = E_NOINTERFACE; } if (*ppv) { AddRef(); } */ return hr; } STDMETHODIMP_(ULONG) CDebugLog::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CDebugLog::Release() { ULONG ulRef; ulRef = InterlockedDecrement(&_cRef); if (ulRef == 0) { delete this; } return ulRef; } // // IFusionBindLog // STDMETHODIMP CDebugLog::GetResultCode() { return _hr; } HRESULT CDebugLog::GetLoggedMsgs(DWORD dwDetailLevel, CString& sLogMsgs ) { HRESULT hr = S_OK; MAKE_ERROR_MACROS_STATIC(hr); BOOL bHaveMsgs = FALSE; LISTNODE pos = NULL; CDebugLogElement *pLogElem = NULL; pos = _listDbgMsg.GetHeadPosition(); while (pos) { pLogElem = _listDbgMsg.GetNext(pos); ASSERT(pLogElem); if (pLogElem->_dwDetailLvl <= dwDetailLevel) { IF_FAILED_EXIT(sLogMsgs.Append(pLogElem->_pszMsg)); IF_FAILED_EXIT(sLogMsgs.Append( L"\r\n")); bHaveMsgs = TRUE; } } if(bHaveMsgs == FALSE) hr = S_FALSE; exit: return hr; } STDMETHODIMP CDebugLog::GetBindLog(DWORD dwDetailLevel, LPWSTR pwzDebugLog, DWORD *pcbDebugLog) { HRESULT hr = S_OK; LISTNODE pos = NULL; DWORD dwCharsReqd; CDebugLogElement *pLogElem = NULL; if (!pcbDebugLog) { hr = E_INVALIDARG; goto Exit; } pos = _listDbgMsg.GetHeadPosition(); if (!pos) { // No entries in debug log! hr = S_FALSE; goto Exit; } // Calculate total size (entries + new line chars + NULL) dwCharsReqd = 0; while (pos) { pLogElem = _listDbgMsg.GetNext(pos); ASSERT(pLogElem); if (pLogElem->_dwDetailLvl <= dwDetailLevel) { dwCharsReqd += lstrlenW(pLogElem->_pszMsg) * sizeof(WCHAR); dwCharsReqd += sizeof(L"\r\n"); } } dwCharsReqd += 1; // NULL char if (!pwzDebugLog || *pcbDebugLog < dwCharsReqd) { *pcbDebugLog = dwCharsReqd; hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); goto Exit; } *pwzDebugLog = L'\0'; pos = _listDbgMsg.GetHeadPosition(); while (pos) { pLogElem = _listDbgMsg.GetNext(pos); ASSERT(pLogElem); if (pLogElem->_dwDetailLvl <= dwDetailLevel) { StrCatW(pwzDebugLog, pLogElem->_pszMsg); StrCatW(pwzDebugLog, L"\r\n"); } } ASSERT((DWORD)lstrlenW(pwzDebugLog) * sizeof(WCHAR) < dwCharsReqd); Exit: return hr; } // // CDebugLog helpers // HRESULT CDebugLog::SetResultCode(HRESULT hr) { _hr = hr; return S_OK; } HRESULT CDebugLog::DebugOut(DWORD dwDetailLvl, LPWSTR pwzFormatString, ...) { HRESULT hr = S_OK; va_list args; LPWSTR wzFormatString = NULL; LPWSTR wzDebugStr = NULL; /* wzFormatString = NEW(WCHAR[MAX_DBG_STR_LEN]); if (!wzFormatString) { hr = E_OUTOFMEMORY; goto Exit; } */ wzDebugStr = NEW(WCHAR[MAX_DBG_STR_LEN]); if (!wzDebugStr) { hr = E_OUTOFMEMORY; goto Exit; } /* wzFormatString[0] = L'\0'; if (!WszLoadString(g_hInst, dwResId, wzFormatString, MAX_DBG_STR_LEN)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } */ va_start(args, pwzFormatString); wvnsprintfW(wzDebugStr, MAX_DBG_STR_LEN, pwzFormatString, args); va_end(args); hr = LogMessage(dwDetailLvl, wzDebugStr, FALSE); if (FAILED(hr)) { goto Exit; } Exit: SAFEDELETEARRAY(wzDebugStr); SAFEDELETEARRAY(wzFormatString); return hr; } HRESULT CDebugLog::LogMessage(DWORD dwDetailLvl, LPCWSTR wzDebugStr, BOOL bPrepend) { HRESULT hr = S_OK; CDebugLogElement *pLogElem = NULL; hr = CDebugLogElement::Create(dwDetailLvl, wzDebugStr, &pLogElem); if (FAILED(hr)) { goto Exit; } _dwNumEntries += 1; if (bPrepend) { _listDbgMsg.AddHead(pLogElem); } else { _listDbgMsg.AddTail(pLogElem); } Exit: return hr; } HRESULT CDebugLog::DumpDebugLog(DWORD dwDetailLvl, HRESULT hrLog) { HRESULT hr = S_OK; HANDLE hFile = INVALID_HANDLE_VALUE; LISTNODE pos = NULL; LPWSTR wzUrlName=NULL; CDebugLogElement *pLogElem = NULL; WCHAR *wzExtension = L"HTM"; WCHAR wzFileName[MAX_PATH]; WCHAR wzSiteName[MAX_PATH]; WCHAR wzAppLogDir[MAX_PATH]; LPWSTR wzEXEName = NULL; LPWSTR wzResourceName = NULL; FILETIME ftTime; FILETIME ftExpireTime; DWORD dwBytes; DWORD dwSize; BOOL bRet; // CCriticalSection cs(&_cs); /* if (!g_dwLogFailures && !g_dwForceLog) { return S_FALSE; } */ /* hr = cs.Lock(); if (FAILED(hr)) { return hr; } */ pos = _listDbgMsg.GetHeadPosition(); if (!pos) { hr = S_FALSE; goto Exit; } wzUrlName = NEW(WCHAR[MAX_URL_LENGTH+1]); if (!wzUrlName) { hr = E_OUTOFMEMORY; goto Exit; } // Build the log entry URL and Wininet cache file wnsprintfW(wzUrlName, MAX_URL_LENGTH, L"?ClickOnceErrorLog!exe=%ws!name=%ws", _sAppName._pwz, _sDLType._pwz); if( dwDetailLvl == -1) { dwDetailLvl = g_dwLoggingLevel; } if (_bLogToWininet) { // Replace all characters > 0x80 with '?' dwSize = lstrlenW(wzUrlName); for (unsigned i = 0; i < dwSize; i++) { if (wzUrlName[i] > 0x80) { wzUrlName[i] = L'?'; } } bRet = CreateUrlCacheEntryW(wzUrlName, 0, wzExtension, wzFileName, 0); if (!bRet) { goto Exit; } } else { wnsprintfW(wzAppLogDir, MAX_PATH, L"%ws\\%ws", _szLogPath, _wzEXEName); if (GetFileAttributes(wzAppLogDir) == -1) { BOOL bRet; bRet = CreateDirectory(wzAppLogDir, NULL); if (!bRet) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } } if (PathIsURLW(_pwzAsmName)) { // This was a where-ref bind. We can't spit out a filename w/ // the URL of the bind because the URL has invalid filename chars. // The best we can do is show that it was a where-ref bind, and // give the filename, and maybe the site. dwSize = MAX_PATH; hr = UrlGetPartW(_pwzAsmName, wzSiteName, &dwSize, URL_PART_HOSTNAME, 0); if (FAILED(hr)) { goto Exit; } wzResourceName = PathFindFileName(_pwzAsmName); ASSERT(wzResourceName); if (!lstrlenW(wzSiteName)) { lstrcpyW(wzSiteName, L"LocalMachine"); } wnsprintfW(wzFileName, MAX_PATH, L"%ws\\FusionBindError!exe=%ws!name=WhereRefBind!Host=(%ws)!FileName=(%ws).HTM", wzAppLogDir, _wzEXEName, wzSiteName, wzResourceName); } else { wnsprintfW(wzFileName, MAX_PATH, L"%ws\\FusionBindError!exe=%ws!name=%ws.HTM", wzAppLogDir, _wzEXEName, _pwzAsmName); } } // Create the and write the log file hr = CreateLogFile(&hFile, wzFileName, _wzEXEName, hrLog); if (FAILED(hr)) { goto Exit; } pos = _listDbgMsg.GetHeadPosition(); while (pos) { pLogElem = _listDbgMsg.GetNext(pos); ASSERT(pLogElem); if (pLogElem->_dwDetailLvl <= dwDetailLvl) { pLogElem->Dump(hFile); WriteFile(hFile, DEBUG_LOG_NEW_LINE, lstrlenW(DEBUG_LOG_NEW_LINE) * sizeof(WCHAR), &dwBytes, NULL); } } // Close the log file and commit the wininet cache entry hr = CloseLogFile(&hFile); if (FAILED(hr)) { goto Exit; } if (_bLogToWininet) { GetSystemTimeAsFileTime(&ftTime); ftExpireTime.dwLowDateTime = (DWORD)0; ftExpireTime.dwHighDateTime = (DWORD)0; bRet = CommitUrlCacheEntryW(wzUrlName, wzFileName, ftExpireTime, ftTime, NORMAL_CACHE_ENTRY, NULL, 0, NULL, 0); if (!bRet) { hr = FusionpHresultFromLastError(); goto Exit; } } Exit: // cs.Unlock(); SAFEDELETEARRAY(wzUrlName); return hr; } HRESULT CDebugLog::CloseLogFile(HANDLE *phFile) { HRESULT hr = S_OK; DWORD dwBytes; if (!phFile) { hr = E_INVALIDARG; goto Exit; } WriteFile(*phFile, DEBUG_LOG_HTML_END, lstrlenW(DEBUG_LOG_HTML_END) * sizeof(WCHAR), &dwBytes, NULL); CloseHandle(*phFile); *phFile = INVALID_HANDLE_VALUE; Exit: return hr; } HRESULT CDebugLog::CreateLogFile(HANDLE *phFile, LPCWSTR wzFileName, LPCWSTR wzEXEName, HRESULT hrLog) { HRESULT hr = S_OK; SYSTEMTIME systime; LPWSTR pwzFormatMessage = NULL; DWORD dwFMResult = 0; LPWSTR wzBuffer = NULL; LPWSTR wzBuf = NULL; LPWSTR wzResultText = NULL; WCHAR wzDateBuffer[MAX_DATE_LEN]; WCHAR wzTimeBuffer[MAX_DATE_LEN]; if (!phFile || !wzFileName || !wzEXEName) { hr = E_INVALIDARG; goto Exit; } wzBuffer = NEW(WCHAR[MAX_DBG_STR_LEN]); if (!wzBuffer) { hr = E_OUTOFMEMORY; goto Exit; } wzBuf = NEW(WCHAR[MAX_DBG_STR_LEN]); if (!wzBuffer) { hr = E_OUTOFMEMORY; goto Exit; } wzResultText = NEW(WCHAR[MAX_DBG_STR_LEN]); if (!wzResultText) { hr = E_OUTOFMEMORY; goto Exit; } *phFile = CreateFile(wzFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (*phFile == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } if (!_bWroteDetails) { // Details LogMessage(0, ID_COL_DETAILED_LOG, TRUE); // Executable path wnsprintfW(wzBuffer, MAX_DBG_STR_LEN, L"%ws %ws", ID_COL_EXECUTABLE, g_wzEXEPath); LogMessage(0, wzBuffer, TRUE); dwFMResult = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, hrLog, 0, (LPWSTR)&pwzFormatMessage, 0, NULL); if (dwFMResult) { wnsprintfW(wzBuffer, MAX_DBG_STR_LEN, ID_COL_FINAL_HR, hrLog, pwzFormatMessage); } else { WCHAR wzNoDescription[MAX_DBG_STR_LEN] = L" "; wnsprintfW(wzBuffer, MAX_DBG_STR_LEN, ID_COL_FINAL_HR, hrLog, wzNoDescription); } LogMessage(0, wzBuffer, TRUE); // Header text GetLocalTime(&systime); if (!GetDateFormat(LOCALE_SYSTEM_DEFAULT, 0, &systime, NULL, wzDateBuffer, MAX_DATE_LEN)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } if (!GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, &systime, NULL, wzTimeBuffer, MAX_DATE_LEN)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Exit; } wnsprintfW(wzBuffer, MAX_DBG_STR_LEN, L"%ws (%ws @ %ws) *** (Version=%ws)\n", ID_COL_HEADER_TEXT, wzDateBuffer, wzTimeBuffer, VER_PRODUCTVERSION_STR_L); LogMessage(0, wzBuffer, TRUE); // HTML start/end LogMessage(0, DEBUG_LOG_HTML_START, TRUE); LogMessage(0, DEBUG_LOG_HTML_META_LANGUAGE, TRUE); _bWroteDetails = TRUE; } Exit: if (pwzFormatMessage) { LocalFree(pwzFormatMessage); } SAFEDELETEARRAY(wzBuffer); SAFEDELETEARRAY(wzBuf); SAFEDELETEARRAY(wzResultText); return hr; }