/*-- Copyright (c) 2001 Microsoft Corporation Module Name: ahui.cpp Abstract: Shows an apphelp message, and returns 0 if the program shouldn't run, and non- zero if the program should run Accepts a command line with a GUID and a TAGID, in the following format: {243B08D7-8CF7-4072-AF64-FD5DF4085E26} 0x0000009E Author: dmunsil 04/03/2001 Revision History: Notes: --*/ #define _UNICODE #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include "shimdb.h" } #include "ids.h" #include "shlobj.h" #include "shlobjp.h" #include "shellapi.h" #include "shlwapi.h" #ifndef _WIN64 #include #endif #include "ahmsg.h" #include "strsafe.h" extern "C" VOID AllowForegroundActivation(VOID); #define APPHELP_DIALOG_FAILED ((DWORD)-1) // // TODO: add parameters to apphelp.exe's command line to work with these // variables. // BOOL g_bShowOnlyOfflineContent = FALSE; BOOL g_bUseHtmlHelp = FALSE; BOOL g_bMSI = FALSE; USHORT g_uExeImageType= DEFAULT_IMAGE; WCHAR g_wszApphelpPath[MAX_PATH]; HFONT g_hFontBold = NULL; HINSTANCE g_hInstance; // // Global variables used while parsing args // DWORD g_dwHtmlHelpID; DWORD g_dwTagID; DWORD g_dwSeverity; LPCWSTR g_pAppName; LPCWSTR g_pszGuid; BOOL g_bPreserveChoice; WCHAR wszHtmlHelpID[] = L"HtmlHelpID"; WCHAR wszAppName[] = L"AppName"; WCHAR wszSeverity[] = L"Severity"; WCHAR wszGUID[] = L"GUID"; WCHAR wszTagID[] = L"TagID"; WCHAR wszOfflineContent[] = L"OfflineContent"; WCHAR wszPreserveChoice[] = L"PreserveChoice"; WCHAR wszMSI[] = L"MSI"; WCHAR wszPlatform[] = L"Platform"; WCHAR wszX86[] = L"X86"; WCHAR wszIA64[] = L"IA64"; // // FORWARD DECLARATIONS OF FUNCTIONS DWORD ShowApphelpDialog( IN PAPPHELP_DATA pApphelpData ); PSID GetCurrentUserSID(void) { HANDLE hProcessToken = INVALID_HANDLE_VALUE; BOOL bRet = FALSE; DWORD dwTokenLength = 0; DWORD dwReturnLength = 0; DWORD dwSIDLength = 0; PTOKEN_USER pTokenUser = NULL; BOOL bSuccess = FALSE; PSID pSID = NULL; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hProcessToken)) { goto out; } GetTokenInformation(hProcessToken, TokenUser, NULL, dwTokenLength, &dwReturnLength); if (dwReturnLength == 0) { goto out; } dwTokenLength = dwReturnLength; dwReturnLength = 0; pTokenUser = (PTOKEN_USER)malloc(dwTokenLength); if (!pTokenUser) { goto out; } if (!GetTokenInformation(hProcessToken, TokenUser, (LPVOID)pTokenUser, dwTokenLength, &dwReturnLength)) { goto out; } if (!IsValidSid(pTokenUser->User.Sid)) { goto out; } dwSIDLength = GetLengthSid(pTokenUser->User.Sid); pSID = malloc(dwSIDLength); if (!pSID) { goto out; } if (!CopySid(dwSIDLength, pSID, pTokenUser->User.Sid)) { goto out; } bSuccess = TRUE; out: if (pTokenUser) { free(pTokenUser); } if (hProcessToken != INVALID_HANDLE_VALUE) { CloseHandle(hProcessToken); } if (!bSuccess && pSID) { free(pSID); pSID = NULL; } return pSID; } void DeleteCurrentUserSID(PSID pSID) { if (pSID) { free(pSID); } } BOOL AppHelpLogEnabled( void ) { HKEY hKey; LONG lResult; DWORD dwValue, dwSize = sizeof(dwValue); DWORD dwType; // First, check for a policy. lResult = RegOpenKeyExW (HKEY_LOCAL_MACHINE, POLICY_KEY_APPCOMPAT_W, 0, KEY_READ, &hKey); if (lResult == ERROR_SUCCESS) { dwValue = 0; lResult = RegQueryValueExW (hKey, POLICY_VALUE_APPHELP_LOG_W, 0, &dwType, (LPBYTE) &dwValue, &dwSize); RegCloseKey (hKey); } // Exit if a policy value was found. if (lResult == ERROR_SUCCESS && dwValue != 0) { return TRUE; } return FALSE; } VOID LogAppHelpEvent( APPHELP_DATA *pApphelpData ) { if (!AppHelpLogEnabled()) { return; } LPCWSTR apszMessage[1]; HANDLE hEventLog = RegisterEventSourceW(NULL, L"apphelp"); if (!hEventLog) { DWORD dwErr = GetLastError(); } else { PSID pSID = GetCurrentUserSID(); apszMessage[0] = pApphelpData->szAppName; if (pApphelpData->dwSeverity == APPHELP_HARDBLOCK) { ReportEventW(hEventLog, EVENTLOG_INFORMATION_TYPE, 0, ID_APPHELP_BLOCK_TRIGGERED, pSID, 2, 0, apszMessage, NULL); } else { ReportEventW(hEventLog, EVENTLOG_INFORMATION_TYPE, 0, ID_APPHELP_TRIGGERED, pSID, 2, 0, apszMessage, NULL); } DeleteCurrentUserSID(pSID); DeregisterEventSource(hEventLog); } } DWORD ShowApphelp( IN PAPPHELP_DATA pApphelpData, IN LPCWSTR pwszDetailsDatabasePath, IN PDB pdbDetails ) /*++ Return: The return value can be one of the following based on what the user has selected: -1 - failed to show the info IDOK | 0x8000 - "no ui" checked, run the app IDCANCEL - do not run the app IDOK - run the app Desc: Open the details database, collect the details info and then show it. --*/ { DWORD dwRet = APPHELP_DIALOG_FAILED; BOOL bCloseDetails = FALSE; if (pdbDetails == NULL) { // // Open the database containing the details info, if one wasn't passed in. // pdbDetails = SdbOpenApphelpDetailsDatabase(pwszDetailsDatabasePath); bCloseDetails = TRUE; if (pdbDetails == NULL) { DBGPRINT((sdlError, "ShowApphelp", "Failed to open the details database.\n")); goto Done; } } // // Read apphelp details data. // if (!SdbReadApphelpDetailsData(pdbDetails, pApphelpData)) { DBGPRINT((sdlError, "ShowApphelp", "Failed to read apphelp details.\n")); goto Done; } // // log an event, if necessary // LogAppHelpEvent(pApphelpData); // // Show the dialog box. The return values can be: // -1 - error // IDOK | 0x8000 - "no ui" checked, run the app // IDCANCEL - do not run the app // IDOK - run the app // dwRet = ShowApphelpDialog(pApphelpData); if (dwRet == APPHELP_DIALOG_FAILED) { DBGPRINT((sdlError, "ShowApphelp", "Failed to show the apphelp info.\n")); } Done: if (pdbDetails != NULL && bCloseDetails) { SdbCloseDatabase(pdbDetails); } return dwRet; } void FixEditControlScrollBar( IN HWND hDlg, IN int nCtrlId ) /*++ Return: void. Desc: This function tricks the edit control to not show the vertical scrollbar unless absolutely necessary. --*/ { HFONT hFont = NULL; HFONT hFontOld = NULL; HDC hDC = NULL; TEXTMETRICW tm; RECT rc; int nVisibleLines = 0; int nLines; DWORD dwStyle; HWND hCtl; // // Get the edit control's rectangle. // SendDlgItemMessageW(hDlg, nCtrlId, EM_GETRECT, 0, (LPARAM)&rc); // // Retrieve the number of lines. // nLines = (int)SendDlgItemMessageW(hDlg, nCtrlId, EM_GETLINECOUNT, 0, 0); // // Calculate how many lines will fit. // hFont = (HFONT)SendDlgItemMessageW(hDlg, nCtrlId, WM_GETFONT, 0, 0); if (hFont != NULL) { hDC = CreateCompatibleDC(NULL); if (hDC != NULL) { hFontOld = (HFONT)SelectObject(hDC, hFont); // // Now get the metrics // if (GetTextMetricsW(hDC, &tm)) { nVisibleLines = (rc.bottom - rc.top) / tm.tmHeight; } SelectObject(hDC, hFontOld); DeleteDC(hDC); } } if (nVisibleLines && nVisibleLines >= nLines) { hCtl = GetDlgItem(hDlg, nCtrlId); dwStyle = (DWORD)GetWindowLongPtrW(hCtl, GWL_STYLE); SetWindowLongPtrW(hCtl, GWL_STYLE, (LONG)(dwStyle & ~WS_VSCROLL)); SetWindowPos(hCtl, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } } BOOL ShowApphelpHtmlHelp( HWND hDlg, PAPPHELP_DATA pApphelpData ) /*++ Return: TRUE on success, FALSE otherwise. Desc: Shows html help using hhctrl.ocx --*/ { WCHAR szAppHelpURL[2048]; WCHAR szWindowsDir[MAX_PATH]; WCHAR szChmFile[MAX_PATH]; WCHAR szChmURL[1024]; HINSTANCE hInst = NULL; UINT nChars; int nChURL, nch; HRESULT hr; DWORD cch; LPWSTR lpwszUnescaped = NULL; BOOL bSuccess = FALSE; BOOL bCustom = FALSE; LCID lcid; size_t nLen; BOOL bFound = FALSE; bCustom = !(pApphelpData->dwData & SDB_DATABASE_MAIN); // apphelp is not in the main database, then it's custom apphelp nChars = GetSystemWindowsDirectoryW(szWindowsDir, CHARCOUNT(szWindowsDir)); if (!nChars || nChars > CHARCOUNT(szWindowsDir)) { DBGPRINT((sdlError, "ShowApphelpHtmlHelp", "Error trying to retrieve Windows Directory %d.\n", GetLastError())); goto errHandle; } lcid = GetUserDefaultUILanguage(); if (lcid != MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)) { StringCchPrintfExW(szChmFile, CHARCOUNT(szChmFile), NULL, &nLen, 0, L"%s\\Help\\MUI\\%04x\\apps.chm", szWindowsDir, lcid); if (nLen > 0) { bFound = RtlDoesFileExists_U(szChmFile); } } if (!bFound) { StringCchPrintfW(szChmFile, CHARCOUNT(szChmFile), L"%s\\Help\\apps.chm", szWindowsDir); } if (bCustom) { // // this is a custom DB, and therefore the URL in it should be taken // as-is, without using the MS redirector // StringCchCopyW(szAppHelpURL, CHARCOUNT(szAppHelpURL), pApphelpData->szURL); } else { StringCchPrintfW(szAppHelpURL, CHARCOUNT(szAppHelpURL), L"hcp://services/redirect?online="); nChURL = wcslen(szAppHelpURL); // // When we are compiling retail we check for the offline content as well. // if (!g_bShowOnlyOfflineContent) { // // First thing, unescape url // if (pApphelpData->szURL != NULL) { // // Unescape the url first, using shell. // cch = wcslen(pApphelpData->szURL) + 1; lpwszUnescaped = (LPWSTR)malloc(cch * sizeof(WCHAR)); if (lpwszUnescaped == NULL) { DBGPRINT((sdlError, "ShowApphelpHtmlHelp", "Error trying to allocate memory for \"%S\"\n", pApphelpData->szURL)); goto errHandle; } // // Unescape round 1 - use the shell function (same as used to encode // it for xml/database). // hr = UrlUnescapeW(pApphelpData->szURL, lpwszUnescaped, &cch, 0); if (!SUCCEEDED(hr)) { DBGPRINT((sdlError, "ShowApphelpHtmlHelp", "UrlUnescapeW failed on \"%S\"\n", pApphelpData->szURL)); goto errHandle; } // // Unescape round 2 - use our function borrowed from help center // cch = (DWORD)(CHARCOUNT(szAppHelpURL) - nChURL); if (!SdbEscapeApphelpURL(szAppHelpURL + nChURL, &cch, lpwszUnescaped)) { DBGPRINT((sdlError, "ShowApphelpHtmlHelp", "Error escaping URL \"%S\"\n", lpwszUnescaped)); goto errHandle; } nChURL += (int)cch; } } // // At this point szAppHelpURL contains redirected URL for online usage // for custom db szAppHelpURL contains full URL // // If Apphelp file is provided -- use it // if (*g_wszApphelpPath) { StringCchPrintfW(szChmURL, CHARCOUNT(szChmURL), L"mk:@msitstore:%ls::/idh_w2_%d.htm", g_wszApphelpPath, pApphelpData->dwHTMLHelpID); } else { StringCchPrintfW(szChmURL, CHARCOUNT(szChmURL), L"mk:@msitstore:%ls::/idh_w2_%d.htm", szChmFile, pApphelpData->dwHTMLHelpID); } // // at this point szChmURL contains a URL pointing to the offline help file // we put it into the szAppHelpURL for both online and offline case // if (g_bShowOnlyOfflineContent) { cch = (DWORD)(CHARCOUNT(szAppHelpURL) - nChURL); if (g_bUseHtmlHelp) { hr = UrlEscapeW(szChmURL, szAppHelpURL + nChURL, &cch, 0); if (SUCCEEDED(hr)) { nChURL += (INT)cch; } } else { if (!SdbEscapeApphelpURL(szAppHelpURL+nChURL, &cch, szChmURL)) { DBGPRINT((sdlError, "ShowApphelpHtmlHelp", "Error escaping URL \"%S\"\n", szChmURL)); goto errHandle; } nChURL += (int)cch; } } // // now offline sequence // cch = (DWORD)(CHARCOUNT(szAppHelpURL) - nChURL); StringCchPrintfW(szAppHelpURL + nChURL, cch, L"&offline="); nch = wcslen(szAppHelpURL + nChURL); nChURL += nch; cch = (DWORD)(CHARCOUNT(szAppHelpURL) - nChURL); if (!SdbEscapeApphelpURL(szAppHelpURL+nChURL, &cch, szChmURL)) { DBGPRINT((sdlError, "ShowApphelpHtmlHelp", "Error escaping URL \"%S\"\n", szChmURL)); goto errHandle; } nChURL += (int)cch; *(szAppHelpURL + nChURL) = L'\0'; } // // WARNING: On Whistler execution of the following line will cause // an AV (it works properly on Win2k) when it's executed twice // from the same process. We should be able to just call // shell with szAppHelpURL but we can't. // So for now, use hh.exe as the stub. // // right before we do ShellExecute -- set current directory to windows dir // if (g_bUseHtmlHelp && !bCustom) { WCHAR szHHPath[MAX_PATH]; DBGPRINT((sdlInfo, "ShowApphelpHtmlHelp", "Opening Apphelp URL \"%S\"\n", szChmURL)); StringCchCopyW(szHHPath, ARRAYSIZE(szHHPath), szWindowsDir); StringCchCatW(szHHPath, ARRAYSIZE(szHHPath), L"\\hh.exe"); hInst = ShellExecuteW(hDlg, L"open", szHHPath, szChmURL, NULL, SW_SHOWNORMAL); } else if (!bCustom) { WCHAR szHSCPath[MAX_PATH]; WCHAR* pszParameters; size_t cchUrl = ARRAYSIZE(szAppHelpURL); static WCHAR szUrlPrefix[] = L"-url "; StringCchCopyW(szHSCPath, ARRAYSIZE(szHSCPath), szWindowsDir); StringCchCatW (szHSCPath, ARRAYSIZE(szHSCPath), L"\\pchealth\\helpctr\\binaries\\helpctr.exe"); // format for parameters is " -url " StringCchLengthW(szAppHelpURL, ARRAYSIZE(szAppHelpURL), &cchUrl); cchUrl += CHARCOUNT(szUrlPrefix) + 1; pszParameters = new WCHAR[cchUrl]; if (pszParameters == NULL) { bSuccess = FALSE; goto errHandle; } StringCchCopyW(pszParameters, cchUrl, szUrlPrefix); StringCchCatW (pszParameters, cchUrl, szAppHelpURL); DBGPRINT((sdlInfo, "ShowApphelpHtmlHelp", "Opening APPHELP URL \"%S\"\n", szAppHelpURL)); hInst = ShellExecuteW(hDlg, L"open", szHSCPath, pszParameters, NULL, SW_SHOWNORMAL); delete[] pszParameters; } else { DBGPRINT((sdlInfo, "ShowApphelpHtmlHelp", "Opening Custom APPHELP URL \"%S\"\n", szAppHelpURL)); hInst = ShellExecuteW(hDlg, L"open", szAppHelpURL, NULL, NULL, SW_SHOWNORMAL); } if (HandleToUlong(hInst) <= 32) { DBGPRINT((sdlError, "ShowApphelpHtmlHelp", "Error 0x%p trying to show help topic \"%ls\"\n", hInst, szAppHelpURL)); } // // If we unload html help now we'll get weird and unpredictable behavior! // So don't do it :-( // bSuccess = (HandleToUlong(hInst) > 32); errHandle: if (lpwszUnescaped != NULL) { free(lpwszUnescaped); } return bSuccess; } INT_PTR CALLBACK AppCompatDlgProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) /*++ Return: void. Desc: This is the dialog proc for the apphelp dialog. --*/ { BOOL bReturn = TRUE; PAPPHELP_DATA pApphelpData; pApphelpData = (PAPPHELP_DATA)GetWindowLongPtrW(hDlg, DWLP_USER); switch (uMsg) { case WM_INITDIALOG: { WCHAR wszMessage[2048]; DWORD dwResActionString; HFONT hFont; LOGFONTW LogFont; WCHAR* pwszAppTitle; INT nChars; DWORD dwDefID = IDD_DETAILS; DWORD dwDefBtn; // old default button id HICON hIcon; LPWSTR IconID = MAKEINTRESOURCEW(IDI_WARNING); pApphelpData = (PAPPHELP_DATA)lParam; SetWindowLongPtrW(hDlg, DWLP_USER, (LONG_PTR)pApphelpData); // // Show the app icon. // hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDD_ICON_TRASH)); SetClassLongPtr(hDlg, GCLP_HICON, (LONG_PTR)hIcon); mouse_event(MOUSEEVENTF_WHEEL, 0, 0, 0, 0); SetForegroundWindow(hDlg); pwszAppTitle = pApphelpData->szAppTitle; if (pwszAppTitle == NULL) { pwszAppTitle = pApphelpData->szAppName; } if (pwszAppTitle != NULL) { SetDlgItemTextW(hDlg, IDD_APPNAME, pwszAppTitle); // // Make sure that we only utilize the first line of that text // for the window title. // SetWindowTextW(hDlg, pwszAppTitle); } hFont = (HFONT)SendDlgItemMessageW(hDlg, IDD_APPNAME, WM_GETFONT, 0, 0); if (hFont && GetObjectW(hFont, sizeof(LogFont), (LPVOID)&LogFont)) { LogFont.lfWeight = FW_BOLD; hFont = CreateFontIndirectW(&LogFont); if (hFont != NULL) { g_hFontBold = hFont; SendDlgItemMessageW(hDlg, IDD_APPNAME, WM_SETFONT, (WPARAM)hFont, TRUE); } } // // By default, we have both RUN AND CANCEL // dwResActionString = IDS_APPCOMPAT_RUNCANCEL; switch (pApphelpData->dwSeverity) { case APPHELP_HARDBLOCK: // // Disable run button and "don't show this again" box // Reset the "defpushbutton" style from this one... // EnableWindow(GetDlgItem(hDlg, IDD_STATE), FALSE); EnableWindow(GetDlgItem(hDlg, IDOK), FALSE); dwResActionString = IDS_APPCOMPAT_CANCEL; dwDefID = IDD_DETAILS; // set for hardblock case since RUN is not avail IconID = MAKEINTRESOURCEW(IDI_ERROR); break; case APPHELP_MINORPROBLEM: break; case APPHELP_NOBLOCK: break; case APPHELP_REINSTALL: break; } // // if we have no URL, or the URL begins with "null" gray out the "details" button // if (!pApphelpData->szURL || !pApphelpData->szURL[0] || _wcsnicmp(pApphelpData->szURL, L"null", 4) == 0) { EnableWindow(GetDlgItem(hDlg, IDD_DETAILS), FALSE); } hIcon = LoadIconW(NULL, IconID); if (hIcon != NULL) { SendDlgItemMessageW(hDlg, IDD_ICON, STM_SETICON, (WPARAM)hIcon, 0); } // // Set the default push button // Reset the current default push button to a regular button. // // Update the default push button's control ID. // dwDefBtn = (DWORD)SendMessageW(hDlg, DM_GETDEFID, 0, 0); if (HIWORD(dwDefBtn) == DC_HASDEFID) { dwDefBtn = LOWORD(dwDefBtn); SendDlgItemMessageW(hDlg, dwDefBtn, BM_SETSTYLE, BS_PUSHBUTTON, TRUE); } SendDlgItemMessageW(hDlg, dwDefID, BM_SETSTYLE, BS_DEFPUSHBUTTON, TRUE); SendMessageW(hDlg, DM_SETDEFID, (WPARAM)dwDefID, 0); // // now set the focus // be careful and do not mess with other focus-related messages, else use PostMessage here // SendMessageW(hDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hDlg, dwDefID), TRUE); // // If dwHTMLHelpID is not present disable "Details" button // if (!pApphelpData->dwHTMLHelpID) { EnableWindow(GetDlgItem(hDlg, IDD_DETAILS), FALSE); } wszMessage[0] = L'\0'; LoadStringW(g_hInstance, dwResActionString, wszMessage, sizeof(wszMessage) / sizeof(WCHAR)); SetDlgItemTextW(hDlg, IDD_LINE_2, wszMessage); SetDlgItemTextW(hDlg, IDD_APPHELP_DETAILS, pApphelpData->szDetails ? pApphelpData->szDetails : L""); FixEditControlScrollBar(hDlg, IDD_APPHELP_DETAILS); // // Return false so that the default focus-setting would not apply. // bReturn = FALSE; break; } case WM_DESTROY: // // perform cleanup - remove the font we've had created // if (g_hFontBold != NULL) { DeleteObject(g_hFontBold); g_hFontBold = NULL; } AllowForegroundActivation(); PostQuitMessage(0); // we just bailed out break; case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDOK: // // Check the NO UI checkbox // EndDialog(hDlg, (INT_PTR)(IsDlgButtonChecked(hDlg, IDD_STATE) ? (IDOK | 0x8000) : IDOK)); break; case IDCANCEL: EndDialog(hDlg, (INT_PTR)(IsDlgButtonChecked(hDlg, IDD_STATE) && g_bPreserveChoice ? (IDCANCEL | 0x8000) : IDCANCEL)); break; case IDD_DETAILS: // // Launch details. // ShowApphelpHtmlHelp(hDlg, pApphelpData); break; default: bReturn = FALSE; break; } break; default: bReturn = FALSE; break; } return bReturn; } typedef NTSTATUS (NTAPI *PFNUSERTESTTOKENFORINTERACTIVE)(HANDLE Token, PLUID pluidCaller); PFNUSERTESTTOKENFORINTERACTIVE UserTestTokenForInteractive = NULL; BOOL CheckUserToken( ) /*++ returns TRUE if the apphelp should be shown FALSE if we should not present apphelp UI --*/ { NTSTATUS Status; HANDLE hToken = NULL; LUID LuidUser; HMODULE hWinsrv = NULL; BOOL bShowUI = FALSE; UINT nChars; WCHAR szSystemDir[MAX_PATH]; WCHAR szWinsrvDll[MAX_PATH]; static WCHAR szWinsrvDllName[] = L"winsrv.dll"; nChars = GetSystemDirectoryW(szSystemDir, CHARCOUNT(szSystemDir)); if (nChars == 0 || nChars > CHARCOUNT(szSystemDir) - 2 - CHARCOUNT(szWinsrvDllName)) { DBGPRINT((sdlError, "CheckUserToken", "Error trying to get systemdirectory %d.\n", GetLastError())); *szSystemDir = L'\0'; } else { szSystemDir[nChars] = L'\\'; szSystemDir[nChars + 1] = L'\0'; } StringCchCopyW(szWinsrvDll, ARRAYSIZE(szWinsrvDll), szSystemDir); StringCchCatW (szWinsrvDll, ARRAYSIZE(szWinsrvDll), szWinsrvDllName); hWinsrv = LoadLibraryW(szWinsrvDll); if (hWinsrv == NULL) { goto ErrHandle; } UserTestTokenForInteractive = (PFNUSERTESTTOKENFORINTERACTIVE)GetProcAddress(hWinsrv, "_UserTestTokenForInteractive"); if (UserTestTokenForInteractive == NULL) { goto ErrHandle; } Status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken); if (NT_SUCCESS(Status)) { Status = UserTestTokenForInteractive(hToken, &LuidUser); NtClose(hToken); if (NT_SUCCESS(Status)) { bShowUI = TRUE; goto ErrHandle; } } Status = NtOpenThreadToken(NtCurrentThread(), TOKEN_QUERY, TRUE, &hToken); if (NT_SUCCESS(Status)) { Status = UserTestTokenForInteractive(hToken, &LuidUser); NtClose(hToken); if (NT_SUCCESS(Status)) { bShowUI = TRUE; goto ErrHandle; } } ErrHandle: if (hWinsrv) { FreeLibrary(hWinsrv); } return bShowUI; } BOOL CheckWindowStation( ) /*++ returns TRUE if the apphelp should be shown FALSE if we should not bother with apphelp UI --*/ { HWINSTA hWindowStation; BOOL bShowUI = FALSE; DWORD dwLength = 0; DWORD dwBufferSize = 0; DWORD dwError; BOOL bSuccess; LPWSTR pwszWindowStation = NULL; hWindowStation = GetProcessWindowStation(); if (hWindowStation == NULL) { DBGPRINT((sdlError, "ApphelpCheckWindowStation", "GetProcessWindowStation failed error 0x%lx\n", GetLastError())); goto ErrHandle; // the app is not a Windows NT/Windows 2000 app??? try to show UI } // get the information please bSuccess = GetUserObjectInformationW(hWindowStation, UOI_NAME, NULL, 0, &dwBufferSize); if (!bSuccess) { dwError = GetLastError(); if (dwError != ERROR_INSUFFICIENT_BUFFER) { DBGPRINT((sdlError, "ApphelpCheckWindowStation", "GetUserObjectInformation failed error 0x%lx\n", dwError)); goto ErrHandle; } pwszWindowStation = (LPWSTR)malloc(dwBufferSize); if (pwszWindowStation == NULL) { DBGPRINT((sdlError, "ApphelpCheckWindowStation", "Failed to allocate 0x%lx bytes for Window Station name\n", dwBufferSize)); goto ErrHandle; } // ok, call again bSuccess = GetUserObjectInformationW(hWindowStation, UOI_NAME, pwszWindowStation, dwBufferSize, &dwLength); if (!bSuccess) { DBGPRINT((sdlError, "ApphelpCheckWindowStation", "GetUserObjectInformation failed error 0x%lx, buffer size 0x%lx returned 0x%lx\n", GetLastError(), dwBufferSize, dwLength)); goto ErrHandle; } // now we have window station name, compare it to winsta0 // bShowUI = (_wcsicmp(pwszWindowStation, L"Winsta0") == 0); if (!bShowUI) { DBGPRINT((sdlInfo, "ApphelpCheckWindowStation", "Apphelp UI will not be shown, running this process on window station \"%s\"\n", pwszWindowStation)); } } // // presumably we will try and check the token for interactive access // ErrHandle: // should we do a close handle ??? // if (hWindowStation != NULL) { CloseWindowStation(hWindowStation); } if (pwszWindowStation != NULL) { free(pwszWindowStation); } return bShowUI; } DWORD ShowApphelpDialog( IN PAPPHELP_DATA pApphelpData ) /*++ Return: (IDOK | IDCANCEL) | [0x8000] IDOK | 0x8000 - the user has chosen to run the app and checked "don't show me this anymore" IDOK - the user has chosen to run the app, dialog will be shown again IDCANCEL - the user has chosen not to run the app -1 - we have failed to import APIs necessary to show dlg box Desc: Shows the dialog box with apphelp info. --*/ { BOOL bSuccess; INT_PTR retVal = 0; retVal = DialogBoxParamW(g_hInstance, MAKEINTRESOURCEW(DLG_APPCOMPAT), NULL, AppCompatDlgProc, (LPARAM)pApphelpData); // parameter happens to be a pointer of type PAPPHELP_DATA return (DWORD)retVal; } VOID ParseCommandLineArgs( int argc, WCHAR* argv[] ) { WCHAR ch; WCHAR* pArg; WCHAR* pEnd; while (--argc) { pArg = argv[argc]; if (*pArg == L'/' || *pArg == '-') { ch = *++pArg; switch(towupper(ch)) { case L'H': if (!_wcsnicmp(pArg, wszHtmlHelpID, CHARCOUNT(wszHtmlHelpID)-1)) { pArg = wcschr(pArg, L':'); if (pArg) { ++pArg; // skip over : g_dwHtmlHelpID = (DWORD)wcstoul(pArg, &pEnd, 0); } } break; case L'A': if (!_wcsnicmp(pArg, wszAppName, CHARCOUNT(wszAppName)-1)) { pArg = wcschr(pArg, L':'); if (pArg) { ++pArg; g_pAppName = pArg; // this is app name, remove the quotes } } break; case L'S': if (!_wcsnicmp(pArg, wszSeverity, CHARCOUNT(wszSeverity)-1)) { pArg = wcschr(pArg, L':'); if (pArg) { ++pArg; g_dwSeverity = (DWORD)wcstoul(pArg, &pEnd, 0); } } break; case L'T': if (!_wcsnicmp(pArg, wszTagID, CHARCOUNT(wszTagID)-1)) { pArg = wcschr(pArg, L':'); if (pArg) { ++pArg; g_dwTagID = (DWORD)wcstoul(pArg, &pEnd, 0); } } break; case L'G': if (!_wcsnicmp(pArg, wszGUID, CHARCOUNT(wszGUID)-1)) { if ((pArg = wcschr(pArg, L':')) != NULL) { ++pArg; g_pszGuid = pArg; } } break; case L'O': if (!_wcsnicmp(pArg, wszOfflineContent, CHARCOUNT(wszOfflineContent)-1)) { g_bShowOnlyOfflineContent = TRUE; } break; case L'P': if (!_wcsnicmp(pArg, wszPreserveChoice, CHARCOUNT(wszPreserveChoice)-1)) { g_bPreserveChoice = TRUE; } else if (!_wcsnicmp(pArg, wszPlatform, CHARCOUNT(wszPlatform) - 1)) { if ((pArg = wcschr(pArg, L':')) != NULL) { ++pArg; // // identify each of the supported platforms // if (!_wcsnicmp(pArg, wszX86, CHARCOUNT(wszX86) - 1)) { g_uExeImageType = IMAGE_FILE_MACHINE_I386; } else if (!_wcsnicmp(pArg, wszIA64, CHARCOUNT(wszIA64) - 1)) { g_uExeImageType = IMAGE_FILE_MACHINE_IA64; } } } break; case L'M': if (!_wcsnicmp(pArg, wszMSI, CHARCOUNT(wszMSI))) { g_bMSI = TRUE; } break; default: // unrecognized switch DBGPRINT((sdlError, "ParseCommandLineArgs", "Unrecognized parameter %s\n", pArg)); break; } } else { // not a switch if (*pArg == L'{') { g_pszGuid = pArg; } else { g_dwTagID = (DWORD)wcstoul(pArg, &pEnd, 0); } } } } INT_PTR CALLBACK FeedbackDlgProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) /*++ Return: void. Desc: This is the dialog proc for the apphelp dialog. --*/ { BOOL bReturn = TRUE; LPWSTR lpszExeName; lpszExeName = (LPWSTR)GetWindowLongPtrW(hDlg, DWLP_USER); switch (uMsg) { case WM_INITDIALOG: { HICON hIcon; lpszExeName = (LPWSTR)lParam; SetWindowLongPtrW(hDlg, DWLP_USER, (LONG_PTR)lpszExeName); // // Show the app icon. // hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDD_ICON_TRASH)); SetClassLongPtr(hDlg, GCLP_HICON, (LONG_PTR)hIcon); SendDlgItemMessage(hDlg, IDC_WORKED, BM_SETCHECK, BST_CHECKED, 0); } case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDOK: EndDialog(hDlg, IDOK); break; case IDCANCEL: EndDialog(hDlg, IDCANCEL); break; default: bReturn = FALSE; break; } break; default: bReturn = FALSE; break; } return bReturn; } void ShowFeedbackDialog( LPWSTR lpszAppName ) { DialogBoxParamW(g_hInstance, MAKEINTRESOURCEW(DLG_FEEDBACK), NULL, FeedbackDlgProc, (LPARAM)lpszAppName); } BOOL CheckWOW64Redirection( int* pReturnCode ) /*++ Return: TRUE if redirected FALSE if no redirection is needed Desc: Checks whether we're running on wow64, if so - redirects to 64-bit ahui.exe --*/ { LPWSTR pszCommandLine; UINT nChars; WCHAR szWindowsDir[MAX_PATH]; WCHAR szAhuiExePath[MAX_PATH]; STARTUPINFOW StartupInfo = { 0 }; PROCESS_INFORMATION ProcessInfo = { 0 }; static WCHAR szPlatformX86[] = L"/Platform:X86"; WCHAR* pszAhuiCmdLine = NULL; DWORD dwExit = 0; BOOL bWow64 = FALSE; BOOL bReturn = FALSE; BOOL bRedirect = FALSE; WCHAR** argv = NULL; int nArg, argc; size_t cchCmd; size_t cchLeft; WCHAR* pchCur; #ifndef _WIN64 if (IsWow64Process(GetCurrentProcess(), &bWow64)) { if (!bWow64) { return FALSE; } } // if a check for wow64 failes, we do not redirect and will try to check // for interactive token. That test will fail and we won't show any UI, // which is better than the alternative (getting a service stuck on a // dialog which is invisible #endif // _WIN64 // // we are running in the context of a wow64 process, redirect to native 64-bit ahui.exe // nChars = GetSystemWindowsDirectoryW(szWindowsDir, CHARCOUNT(szWindowsDir)); if (!nChars || nChars > CHARCOUNT(szWindowsDir) - 1) { DBGPRINT((sdlError, "CheckWow64Redirection", "Error trying to retrieve Windows Directory %d.\n", GetLastError())); goto out; } StringCchPrintfW(szAhuiExePath, CHARCOUNT(szAhuiExePath), L"%s\\system32\\ahui.exe", szWindowsDir); #ifndef _WIN64 Wow64DisableFilesystemRedirector(szAhuiExePath); bRedirect = TRUE; #endif pszCommandLine = GetCommandLineW(); if (pszCommandLine == NULL) { goto out; } argv = CommandLineToArgvW(pszCommandLine, &argc); if (argv != NULL) { for (cchCmd = 0, nArg = 1; nArg < argc; ++nArg) { cchCmd += wcslen(argv[nArg]) + 2 + 1; // 2 for " " and 1 for space } } // now allocate space for command line, ahui exe path and additional parameter (/Platform:x86) nChars = wcslen(szAhuiExePath) + cchCmd + CHARCOUNT(szPlatformX86) + 3; pszAhuiCmdLine = new WCHAR[nChars]; if (pszAhuiCmdLine == NULL) { goto out; } pchCur = pszAhuiCmdLine; cchLeft = nChars; StringCchPrintfExW(pszAhuiCmdLine, nChars, &pchCur, &cchLeft, 0, L"%s ", szAhuiExePath); if (argv != NULL) { for (nArg = 1; nArg < argc; ++nArg) { // // check for platform parameter, if it exists -- skip it // if ((*argv[nArg] == L'/' || *argv[nArg] == L'-') && _wcsnicmp(argv[nArg] + 1, wszPlatform, CHARCOUNT(wszPlatform) - 1) == 0) { continue; } StringCchPrintfExW(pchCur, cchLeft, &pchCur, &cchLeft, 0, L" \"%s\"", argv[nArg]); } } StringCchPrintfExW(pchCur, cchLeft, &pchCur, &cchLeft, 0, L" %s", szPlatformX86); StartupInfo.cb = sizeof(StartupInfo); if (!CreateProcessW(szAhuiExePath, pszAhuiCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &StartupInfo, &ProcessInfo)) { DBGPRINT((sdlError, "CheckWow64Redirection", "Failed to launch apphelp process %d.\n", GetLastError())); goto out; } WaitForSingleObject(ProcessInfo.hProcess, INFINITE); bReturn = GetExitCodeProcess(ProcessInfo.hProcess, &dwExit); if (bReturn) { *pReturnCode = (int)dwExit; bReturn = TRUE; } out: if (pszAhuiCmdLine) { delete[] pszAhuiCmdLine; } if (argv) { HGLOBAL hMem = GlobalHandle(argv); if (hMem != NULL) { GlobalFree(hMem); } } #ifndef _WIN64 if (bRedirect) { Wow64EnableFilesystemRedirector(); } #endif return bReturn; } extern "C" int APIENTRY wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow ) /*++ Return: 1 if the app for which the apphelp is shown should run, 0 otherwise. Desc: The command line looks like this: apphelp.exe GUID tagID [USELOCALCHM USEHTMLHELP APPHELPPATH] Ex: apphelp.exe {243B08D7-8CF7-4072-AF64-FD5DF4085E26} 0x0000009E apphelp.exe {243B08D7-8CF7-4072-AF64-FD5DF4085E26} 0x0000009E 1 1 c:\temp --*/ { int nReturn = 1; // we always default to running, if something goes wrong LPWSTR szCommandLine; LPWSTR* argv; int argc; UNICODE_STRING ustrGuid; GUID guidDB = { 0 }; GUID guidExeID = { 0 }; TAGID tiExe = TAGID_NULL; TAGID tiExeID = TAGID_NULL; TAGREF trExe = TAGREF_NULL; WCHAR wszDBPath[MAX_PATH]; DWORD dwType = 0; APPHELP_DATA ApphelpData; WCHAR szDBPath[MAX_PATH]; HSDB hSDB = NULL; PDB pdb = NULL; DWORD dwFlags = 0; BOOL bAppHelp = FALSE; BOOL bRunApp = FALSE; g_hInstance = hInstance; #ifndef _WIN64 if (CheckWOW64Redirection(&nReturn)) { goto out; } #endif // _WIN64 InitCommonControls(); ZeroMemory(&ApphelpData, sizeof(ApphelpData)); // // Note that this memory isn't freed because it will automatically // be freed on exit anyway, and there are a lot of exit cases from // this application. // szCommandLine = GetCommandLineW(); argv = CommandLineToArgvW(szCommandLine, &argc); ParseCommandLineArgs(argc, argv); if (argc > 1) { if (_wcsicmp(L"feedback", argv[1]) == 0) { ShowFeedbackDialog(argc > 2 ? argv[2] : NULL); } } if (g_pszGuid == NULL) { DBGPRINT((sdlError, "AHUI!wWinMain", "GUID not provided\n")); goto out; } if (!(g_dwTagID ^ g_dwHtmlHelpID)) { DBGPRINT((sdlError, "AHUI!wWinMain", "Only TagID or HtmlHelpID should be provided\n")); goto out; } RtlInitUnicodeString(&ustrGuid, g_pszGuid); if (g_dwHtmlHelpID) { // // provided here: guid, severity and html help id along with app name // if (!NT_SUCCESS(RtlGUIDFromString(&ustrGuid, &guidExeID))) { DBGPRINT((sdlError, "Ahui!wWinMain", "Error getting GUID from string %s\n", g_pszGuid)); goto out; } ApphelpData.dwSeverity = g_dwSeverity; ApphelpData.dwHTMLHelpID = g_dwHtmlHelpID; ApphelpData.szAppName = (LPWSTR)g_pAppName; bAppHelp = TRUE; dwType = SDB_DATABASE_MAIN_SHIM; goto ProceedWithApphelp; } // non-htmlid case, guid is a database guid // also dwTagID is specified if (RtlGUIDFromString(&ustrGuid, &guidDB) != STATUS_SUCCESS) { DBGPRINT((sdlError, "AppHelp.exe!wWinMain", "Error trying to convert GUID string %S.\n", g_pszGuid)); goto out; } tiExe = (TAGID)g_dwTagID; if (tiExe == TAGID_NULL) { DBGPRINT((sdlError, "AppHelp.exe!wWinMain", "Error getting TAGID from param %S\n", argv[2])); goto out; } hSDB = SdbInitDatabaseEx(0, NULL, g_uExeImageType); if (!hSDB) { DBGPRINT((sdlError, "AppHelp.exe!wWinMain", "Error initing database context.\n")); goto out; } if (g_bMSI) { SdbSetImageType(hSDB, IMAGE_FILE_MSI); } pdb = SdbGetPDBFromGUID(hSDB, &guidDB); if (!pdb) { DWORD dwLen; // // It's not one of the main DBs, try it as a local. // dwLen = SdbResolveDatabase(hSDB, &guidDB, &dwType, szDBPath, MAX_PATH); if (!dwLen || dwLen > MAX_PATH) { DBGPRINT((sdlError, "AppHelp.exe!wWinMain", "Error resolving database from GUID\n")); goto out; } // // We have many "main" databases -- we should limit the check // if (dwType != SDB_DATABASE_MAIN_SHIM && dwType != SDB_DATABASE_MAIN_TEST) { SdbOpenLocalDatabase(hSDB, szDBPath); } pdb = SdbGetPDBFromGUID(hSDB, &guidDB); if (!pdb) { DBGPRINT((sdlError, "AppHelp.exe!wWinMain", "Error getting pdb from GUID.\n")); goto out; } } else { dwType |= SDB_DATABASE_MAIN; // we will use details from the main db } if (!SdbTagIDToTagRef(hSDB, pdb, tiExe, &trExe)) { DBGPRINT((sdlError, "AppHelp.exe!wWinMain", "Error converting TAGID to TAGREF.\n")); goto out; } tiExeID = SdbFindFirstTag(pdb, tiExe, TAG_EXE_ID); if (tiExeID == TAGID_NULL) { DBGPRINT((sdlError, "AppHelp.exe!wWinMain", "Error trying to find TAG_EXE_ID.\n")); goto out; } if (!SdbReadBinaryTag(pdb, tiExeID, (PBYTE)&guidExeID, sizeof(GUID))) { DBGPRINT((sdlError, "AppHelp.exe!wWinMain", "Error trying to read TAG_EXE_ID.\n")); goto out; } bAppHelp = SdbReadApphelpData(hSDB, trExe, &ApphelpData); ProceedWithApphelp: if (SdbIsNullGUID(&guidExeID) || !SdbGetEntryFlags(&guidExeID, &dwFlags)) { dwFlags = 0; } if (bAppHelp) { // // Check whether the disable bit is set. // if (!(dwFlags & SHIMREG_DISABLE_APPHELP)) { BOOL bNoUI; // // See whether the user has checked "Don't show this anymore" box before. // bNoUI = ((dwFlags & SHIMREG_APPHELP_NOUI) != 0); if (!bNoUI) { bNoUI = !CheckWindowStation(); } if (!bNoUI) { bNoUI = !CheckUserToken(); } if (bNoUI) { DBGPRINT((sdlInfo, "bCheckRunBadapp", "NoUI flag is set, apphelp UI disabled for this app.\n")); } // // depending on severity of the problem... // switch (ApphelpData.dwSeverity) { case APPHELP_MINORPROBLEM: case APPHELP_HARDBLOCK: case APPHELP_NOBLOCK: case APPHELP_REINSTALL: if (bNoUI) { bRunApp = (ApphelpData.dwSeverity != APPHELP_HARDBLOCK) && !(dwFlags & SHIMREG_APPHELP_CANCEL); } else { DWORD dwRet; // // Show the UI. This function returns -1 in case of error or one // of the following values on success: // IDOK | 0x8000 - "no ui" checked, run app // IDCANCEL - do not run app // IDOK - run app ApphelpData.dwData = dwType; // we use custom data for database type dwRet = ShowApphelp(&ApphelpData, NULL, (dwType & SDB_DATABASE_MAIN) ? NULL : SdbGetLocalPDB(hSDB)); if (dwRet != APPHELP_DIALOG_FAILED) { // // The UI was shown. See whether the user has // checked the "no ui" box. // if (dwRet & 0x8000) { // // "no ui" box was checked. Save the appropriate bits // in the registry. // dwFlags |= SHIMREG_APPHELP_NOUI; if ((dwRet & 0x0FFF) != IDOK) { dwFlags |= SHIMREG_APPHELP_CANCEL; // we will not be hitting this path unless g_bPreserveChoice is enabled } if (!SdbIsNullGUID(&guidExeID)) { SdbSetEntryFlags(&guidExeID, dwFlags); } } // // Check whether the user has chosen to run the app. // bRunApp = ((dwRet & 0x0FFF) == IDOK) && (ApphelpData.dwSeverity != APPHELP_HARDBLOCK); } else { // // The UI was not shown (some error prevented that). // If the app is not "Hardblock" run it anyway. // bRunApp = (APPHELP_HARDBLOCK != ApphelpData.dwSeverity); } } break; } } } if (!bRunApp) { nReturn = 0; } out: return nReturn; }