1074 lines
33 KiB
C++
1074 lines
33 KiB
C++
|
/////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// SvcUtils.cpp
|
||
|
//
|
||
|
// Utilities routines specific for system services.
|
||
|
// Mostly used to display services properties.
|
||
|
//
|
||
|
// HISTORY
|
||
|
// t-danmo 96.10.10 Creation.
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
#include <iads.h>
|
||
|
#include <iadsp.h> // IADsPathname
|
||
|
#include <atlcom.h> // CComPtr and CComBSTR
|
||
|
extern "C"
|
||
|
{
|
||
|
#include <objsel.h> // IDsObjectPicker
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Service current state
|
||
|
//
|
||
|
CString g_strSvcStateStarted; // Service is started
|
||
|
CString g_strSvcStateStarting; // Service is starting
|
||
|
CString g_strSvcStateStopped; // Service is stopped
|
||
|
CString g_strSvcStateStopping; // Service is stopping
|
||
|
CString g_strSvcStatePaused; // Service is paused
|
||
|
CString g_strSvcStatePausing; // Service is pausing
|
||
|
CString g_strSvcStateResuming; // Service is resuming
|
||
|
|
||
|
//
|
||
|
// Service startup type
|
||
|
//
|
||
|
CString g_strSvcStartupBoot;
|
||
|
CString g_strSvcStartupSystem;
|
||
|
CString g_strSvcStartupAutomatic;
|
||
|
CString g_strSvcStartupManual;
|
||
|
CString g_strSvcStartupDisabled;
|
||
|
|
||
|
//
|
||
|
// Service startup account
|
||
|
// JonN 188203 11/13/00
|
||
|
//
|
||
|
CString g_strLocalSystem;
|
||
|
CString g_strLocalService;
|
||
|
CString g_strNetworkService;
|
||
|
|
||
|
CString g_strUnknown;
|
||
|
CString g_strLocalMachine; // "Local Machine"
|
||
|
|
||
|
BOOL g_fStringsLoaded = FALSE;
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
Service_LoadResourceStrings()
|
||
|
{
|
||
|
|
||
|
if (g_fStringsLoaded)
|
||
|
return;
|
||
|
g_fStringsLoaded = TRUE;
|
||
|
|
||
|
VERIFY(g_strSvcStateStarted.LoadString(IDS_SVC_STATUS_STARTED));
|
||
|
VERIFY(g_strSvcStateStarting.LoadString(IDS_SVC_STATUS_STARTING));
|
||
|
VERIFY(g_strSvcStateStopped.LoadString(IDS_SVC_STATUS_STOPPED));
|
||
|
VERIFY(g_strSvcStateStopping.LoadString(IDS_SVC_STATUS_STOPPING));
|
||
|
VERIFY(g_strSvcStatePaused.LoadString(IDS_SVC_STATUS_PAUSED));
|
||
|
VERIFY(g_strSvcStatePausing.LoadString(IDS_SVC_STATUS_PAUSING));
|
||
|
VERIFY(g_strSvcStateResuming.LoadString(IDS_SVC_STATUS_RESUMING));
|
||
|
|
||
|
VERIFY(g_strSvcStartupBoot.LoadString(IDS_SVC_STARTUP_BOOT));
|
||
|
VERIFY(g_strSvcStartupSystem.LoadString(IDS_SVC_STARTUP_SYSTEM));
|
||
|
VERIFY(g_strSvcStartupAutomatic.LoadString(IDS_SVC_STARTUP_AUTOMATIC));
|
||
|
VERIFY(g_strSvcStartupManual.LoadString(IDS_SVC_STARTUP_MANUAL));
|
||
|
VERIFY(g_strSvcStartupDisabled.LoadString(IDS_SVC_STARTUP_DISABLED));
|
||
|
|
||
|
// JonN 11/13/00 188203 support LocalService/NetworkService
|
||
|
VERIFY(g_strLocalSystem.LoadString(IDS_SVC_STARTUP_LOCALSYSTEM));
|
||
|
VERIFY(g_strLocalService.LoadString(IDS_SVC_STARTUP_LOCALSERVICE));
|
||
|
VERIFY(g_strNetworkService.LoadString(IDS_SVC_STARTUP_NETWORKSERVICE));
|
||
|
|
||
|
VERIFY(g_strUnknown.LoadString(IDS_SVC_UNKNOWN));
|
||
|
VERIFY(g_strLocalMachine.LoadString(IDS_LOCAL_MACHINE));
|
||
|
} // Service_LoadResourceStrings()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// Service_PszMapStateToName()
|
||
|
//
|
||
|
// Map the service state to a null-terminated string.
|
||
|
//
|
||
|
LPCTSTR
|
||
|
Service_PszMapStateToName(
|
||
|
DWORD dwServiceState, // From SERVICE_STATUS.dwCurrentState
|
||
|
BOOL fLongString) // TRUE => Display the name in a long string format
|
||
|
{
|
||
|
switch(dwServiceState)
|
||
|
{
|
||
|
case SERVICE_STOPPED:
|
||
|
if (fLongString)
|
||
|
{
|
||
|
return g_strSvcStateStopped;
|
||
|
}
|
||
|
// Note that, by design, we never display the service
|
||
|
// status as "Stopped". Instead, we just don't display
|
||
|
// the status. Hence, the empty string.
|
||
|
return _T("");
|
||
|
|
||
|
case SERVICE_STOP_PENDING:
|
||
|
return g_strSvcStateStopping;
|
||
|
|
||
|
case SERVICE_RUNNING:
|
||
|
return g_strSvcStateStarted;
|
||
|
|
||
|
case SERVICE_START_PENDING:
|
||
|
return g_strSvcStateStarting;
|
||
|
|
||
|
case SERVICE_PAUSED:
|
||
|
return g_strSvcStatePaused;
|
||
|
|
||
|
case SERVICE_PAUSE_PENDING:
|
||
|
return g_strSvcStatePausing;
|
||
|
|
||
|
case SERVICE_CONTINUE_PENDING:
|
||
|
return g_strSvcStateResuming;
|
||
|
|
||
|
default:
|
||
|
TRACE0("INFO Unknown service state.\n");
|
||
|
} // switch
|
||
|
return g_strUnknown;
|
||
|
} // Service_PszMapStateToName()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// Service_PszMapStartupTypeToName()
|
||
|
//
|
||
|
// Map the service startup type to a null-terminated string.
|
||
|
// -1L is blank string
|
||
|
//
|
||
|
LPCTSTR
|
||
|
Service_PszMapStartupTypeToName(DWORD dwStartupType)
|
||
|
{
|
||
|
switch(dwStartupType)
|
||
|
{
|
||
|
case SERVICE_BOOT_START:
|
||
|
return g_strSvcStartupBoot;
|
||
|
|
||
|
case SERVICE_SYSTEM_START:
|
||
|
return g_strSvcStartupSystem;
|
||
|
|
||
|
case SERVICE_AUTO_START:
|
||
|
return g_strSvcStartupAutomatic;
|
||
|
|
||
|
case SERVICE_DEMAND_START:
|
||
|
return g_strSvcStartupManual;
|
||
|
|
||
|
case SERVICE_DISABLED :
|
||
|
return g_strSvcStartupDisabled;
|
||
|
|
||
|
case -1L:
|
||
|
return L"";
|
||
|
|
||
|
default:
|
||
|
ASSERT(FALSE);
|
||
|
}
|
||
|
return g_strUnknown;
|
||
|
} // Service_PszMapStartupTypeToName()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// Service_PszMapStartupAccountToName()
|
||
|
//
|
||
|
// Map the service startup account to a null-terminated string.
|
||
|
//
|
||
|
// Note that if they use the localized version of the two special
|
||
|
// accounts, I just won't pick that up. JSchwart and I agree that
|
||
|
// this should be acceptable.
|
||
|
//
|
||
|
// JonN 188203 11/13/00
|
||
|
// Services Snapin: Should support NetworkService and LocalService account
|
||
|
//
|
||
|
LPCTSTR
|
||
|
Service_PszMapStartupAccountToName(LPCTSTR pcszStartupAccount)
|
||
|
{
|
||
|
if ( !pcszStartupAccount || !*pcszStartupAccount )
|
||
|
return g_strLocalSystem;
|
||
|
else if ( !_wcsicmp(pcszStartupAccount,TEXT("NT AUTHORITY\\LocalService")) )
|
||
|
return g_strLocalService;
|
||
|
else if ( !_wcsicmp(pcszStartupAccount,TEXT("NT AUTHORITY\\NetworkService")) )
|
||
|
return g_strNetworkService;
|
||
|
return pcszStartupAccount;
|
||
|
} // Service_PszMapStartupAccountToName()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// Service_FGetServiceButtonStatus()
|
||
|
//
|
||
|
// Query the service control manager database and fill in
|
||
|
// array of flags indicating if the action is enabled.
|
||
|
// rgfEnableButton[0] = TRUE; => Button 'start' is enabled
|
||
|
// rgfEnableButton[0] = FALSE; => Button 'start' is disabled
|
||
|
//
|
||
|
// INTERFACE NOTES
|
||
|
// The length of the array must be length iServiceActionMax (or larger).
|
||
|
// Each representing start, stop, pause, resume and restart respectively.
|
||
|
//
|
||
|
// Return TRUE if the service status was queried successfully, otherwise FALSE.
|
||
|
//
|
||
|
BOOL
|
||
|
Service_FGetServiceButtonStatus(
|
||
|
SC_HANDLE hScManager, // IN: Handle of service control manager database
|
||
|
CONST TCHAR * pszServiceName, // IN: Name of service
|
||
|
BOOL rgfEnableButton[iServiceActionMax], // OUT: Array of flags to enable the buttons
|
||
|
DWORD * pdwCurrentState, // OUT: Optional: Current state of service
|
||
|
BOOL fSilentError) // IN: TRUE => Do not display any error message to user
|
||
|
{
|
||
|
Endorse(hScManager == NULL);
|
||
|
Assert(pszServiceName != NULL);
|
||
|
Assert(rgfEnableButton != NULL);
|
||
|
Endorse(pdwCurrentState == NULL);
|
||
|
|
||
|
// Open service to get its status
|
||
|
BOOL fSuccess = TRUE;
|
||
|
SC_HANDLE hService;
|
||
|
SERVICE_STATUS ss;
|
||
|
QUERY_SERVICE_CONFIG qsc;
|
||
|
DWORD cbBytesNeeded;
|
||
|
DWORD dwErr;
|
||
|
|
||
|
::ZeroMemory(OUT rgfEnableButton, iServiceActionMax * sizeof(BOOL));
|
||
|
if (pdwCurrentState != NULL)
|
||
|
*pdwCurrentState = 0;
|
||
|
if (hScManager == NULL || pszServiceName[0] == '\0')
|
||
|
return FALSE;
|
||
|
|
||
|
hService = ::OpenService(
|
||
|
hScManager,
|
||
|
pszServiceName,
|
||
|
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
|
||
|
if (hService == NULL)
|
||
|
{
|
||
|
dwErr = ::GetLastError();
|
||
|
Assert(dwErr != ERROR_SUCCESS);
|
||
|
TRACE2("Failed to open service %s. err=%u.\n",
|
||
|
pszServiceName, dwErr);
|
||
|
if (!fSilentError)
|
||
|
DoServicesErrMsgBox(::GetActiveWindow(), MB_OK | MB_ICONEXCLAMATION, dwErr);
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (!::QueryServiceStatus(hService, OUT &ss))
|
||
|
{
|
||
|
dwErr = ::GetLastError();
|
||
|
Assert(dwErr != ERROR_SUCCESS);
|
||
|
TRACE2("::QueryServiceStatus(Service=%s) failed. err=%u.\n",
|
||
|
pszServiceName, dwErr);
|
||
|
if (!fSilentError)
|
||
|
DoServicesErrMsgBox(::GetActiveWindow(), MB_OK | MB_ICONEXCLAMATION, dwErr);
|
||
|
fSuccess = FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Determine which menu items should be grayed
|
||
|
if (pdwCurrentState != NULL)
|
||
|
*pdwCurrentState = ss.dwCurrentState;
|
||
|
|
||
|
switch (ss.dwCurrentState)
|
||
|
{
|
||
|
default:
|
||
|
Assert(FALSE && "Illegal service status state.");
|
||
|
case SERVICE_START_PENDING:
|
||
|
case SERVICE_STOP_PENDING:
|
||
|
case SERVICE_PAUSE_PENDING:
|
||
|
case SERVICE_CONTINUE_PENDING:
|
||
|
break;
|
||
|
|
||
|
case SERVICE_STOPPED:
|
||
|
qsc.dwStartType = (DWORD)-1;
|
||
|
(void)::QueryServiceConfig(
|
||
|
hService,
|
||
|
OUT &qsc,
|
||
|
sizeof(qsc),
|
||
|
OUT IGNORED &cbBytesNeeded);
|
||
|
Report(qsc.dwStartType != (DWORD)-1);
|
||
|
|
||
|
if (qsc.dwStartType != SERVICE_DISABLED)
|
||
|
{
|
||
|
rgfEnableButton[iServiceActionStart] = TRUE; // Enable 'Start' menu item
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SERVICE_RUNNING:
|
||
|
// Some services are not allowed to be stoped and/or paused
|
||
|
if (ss.dwControlsAccepted & SERVICE_ACCEPT_STOP)
|
||
|
{
|
||
|
rgfEnableButton[iServiceActionStop] = TRUE; // Enable 'Stop' menu item
|
||
|
}
|
||
|
if (ss.dwControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE)
|
||
|
{
|
||
|
rgfEnableButton[iServiceActionPause] = TRUE; // Enable 'Pause' menu item
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SERVICE_PAUSED:
|
||
|
if (ss.dwControlsAccepted & SERVICE_ACCEPT_STOP)
|
||
|
{
|
||
|
rgfEnableButton[iServiceActionStop] = TRUE; // Enable 'Stop' menu item
|
||
|
}
|
||
|
rgfEnableButton[iServiceActionResume] = TRUE; // Enable 'Resume' menu item
|
||
|
break;
|
||
|
} // switch
|
||
|
} // if...else
|
||
|
|
||
|
// A 'Restart' has the same characteristics as a 'Stop'
|
||
|
rgfEnableButton[iServiceActionRestart] = rgfEnableButton[iServiceActionStop];
|
||
|
|
||
|
(void)::CloseServiceHandle(hService);
|
||
|
return fSuccess;
|
||
|
} // Service_FGetServiceButtonStatus()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// Service_SplitCommandLine()
|
||
|
//
|
||
|
// Split a string into two strings.
|
||
|
// Very similar to PchParseCommandLine() but uses CString objects.
|
||
|
//
|
||
|
void
|
||
|
Service_SplitCommandLine(
|
||
|
LPCTSTR pszFullCommand, // IN: Full command line
|
||
|
CString * pstrBinaryPath, // OUT: Path of the executable binary
|
||
|
CString * pstrParameters, // OUT: Parameters for the executable
|
||
|
BOOL * pfAbend) // OUT: Optional: Search for string "/fail=%1%"
|
||
|
{
|
||
|
Assert(pszFullCommand != NULL);
|
||
|
Assert(pstrBinaryPath != NULL);
|
||
|
Assert(pstrParameters != NULL);
|
||
|
Endorse(pfAbend == NULL);
|
||
|
|
||
|
// Since there is no upper bound on the command
|
||
|
// arguments, we need to allocate memory for
|
||
|
// its processing.
|
||
|
TCHAR * paszCommandT; // Temporary buffer
|
||
|
TCHAR * pszCommandArguments;
|
||
|
INT cchMemAlloc; // Number of bytes to allocate
|
||
|
|
||
|
cchMemAlloc = lstrlen(pszFullCommand) + 1;
|
||
|
paszCommandT = new TCHAR[cchMemAlloc];
|
||
|
paszCommandT[0] = '\0'; // Just in case
|
||
|
pszCommandArguments = PchParseCommandLine(
|
||
|
IN pszFullCommand,
|
||
|
OUT paszCommandT,
|
||
|
cchMemAlloc);
|
||
|
*pstrBinaryPath = paszCommandT;
|
||
|
|
||
|
if (pfAbend != NULL)
|
||
|
{
|
||
|
INT cStringSubstitutions; // Number of string substitutions
|
||
|
|
||
|
// Find out if the string contains "/fail=%1%"
|
||
|
cStringSubstitutions = Str_SubstituteStrStr(
|
||
|
OUT pszCommandArguments,
|
||
|
IN pszCommandArguments,
|
||
|
IN szAbend,
|
||
|
L"");
|
||
|
Report((cStringSubstitutions == 0 || cStringSubstitutions == 1) &&
|
||
|
"INFO: Multiple substitutions will be consolidated.");
|
||
|
*pfAbend = cStringSubstitutions != 0;
|
||
|
}
|
||
|
*pstrParameters = pszCommandArguments;
|
||
|
TrimString(*pstrParameters);
|
||
|
|
||
|
delete paszCommandT;
|
||
|
} // Service_SplitCommandLine()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// Service_UnSplitCommandLine()
|
||
|
//
|
||
|
// Just do the opposite of Service_SplitCommandLine().
|
||
|
// Combine the executable path and its arguments into a single string.
|
||
|
//
|
||
|
void
|
||
|
Service_UnSplitCommandLine(
|
||
|
CString * pstrFullCommand, // OUT: Full command line
|
||
|
LPCTSTR pszBinaryPath, // IN: Path of the executable binary
|
||
|
LPCTSTR pszParameters) // IN: Parameters for the executable
|
||
|
{
|
||
|
Assert(pstrFullCommand != NULL);
|
||
|
Assert(pszBinaryPath != NULL);
|
||
|
Assert(pszParameters != NULL);
|
||
|
|
||
|
TCHAR * psz;
|
||
|
psz = pstrFullCommand->GetBuffer(lstrlen(pszBinaryPath) + lstrlen(pszParameters) + 32);
|
||
|
// Build a string with the binary path surrounded by quotes
|
||
|
wsprintf(OUT psz, L"\"%s\" %s", pszBinaryPath, pszParameters);
|
||
|
pstrFullCommand->ReleaseBuffer();
|
||
|
} // Service_UnSplitCommandLine()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// LoadSystemString()
|
||
|
//
|
||
|
// Load a string from system's resources. This function will check if
|
||
|
// the string Id can be located in netmsg.dll before attempting to
|
||
|
// load the string from the 'system resource'.
|
||
|
// If string cannot be loaded, *ppaszBuffer is set to NULL.
|
||
|
//
|
||
|
// RETURN
|
||
|
// Pointer to allocated string and number of characters put
|
||
|
// into *ppaszBuffer.
|
||
|
//
|
||
|
// INTERFACE NOTES
|
||
|
// Caller must call LocalFree(*ppaszBuffer) when done with the string.
|
||
|
//
|
||
|
// HISTORY
|
||
|
// 96.10.21 t-danmo Copied from net\ui\common\src\string\string\strload.cxx.
|
||
|
//
|
||
|
DWORD
|
||
|
LoadSystemString(
|
||
|
UINT wIdString, // IN: String Id. Typically error code from GetLastError().
|
||
|
LPTSTR * ppaszBuffer) // OUT: Address of pointer to allocated string.
|
||
|
{
|
||
|
Assert(ppaszBuffer != NULL);
|
||
|
|
||
|
UINT cch;
|
||
|
HMODULE hModule = NULL;
|
||
|
DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||
|
FORMAT_MESSAGE_IGNORE_INSERTS |
|
||
|
FORMAT_MESSAGE_MAX_WIDTH_MASK;
|
||
|
|
||
|
if ((wIdString >= MIN_LANMAN_MESSAGE_ID) && (wIdString <= MAX_LANMAN_MESSAGE_ID))
|
||
|
{
|
||
|
// Network Errors
|
||
|
dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
|
||
|
hModule = ::LoadLibrary(_T("netmsg.dll"));
|
||
|
if (hModule == NULL)
|
||
|
{
|
||
|
TRACE1("LoadLibrary(\"netmsg.dll\") failed. err=%u.\n", GetLastError());
|
||
|
Report("Unable to get module handle for netmsg.dll");
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Other system errors
|
||
|
dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
|
||
|
}
|
||
|
|
||
|
*ppaszBuffer = NULL; // Just in case
|
||
|
cch = ::FormatMessage(
|
||
|
dwFlags,
|
||
|
hModule,
|
||
|
wIdString,
|
||
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
||
|
OUT (LPTSTR)ppaszBuffer, // Buffer will be allocated by FormatMessage()
|
||
|
0,
|
||
|
NULL);
|
||
|
Report((cch > 0) && "FormatMessage() returned an empty string");
|
||
|
if (hModule != NULL)
|
||
|
{
|
||
|
VERIFY(FreeLibrary(hModule));
|
||
|
}
|
||
|
return cch;
|
||
|
} // LoadSystemString()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// GetMsgHelper()
|
||
|
//
|
||
|
// This function will retrieve the error msg if dwErr is specified,
|
||
|
// load resource string if specified, and format the string with
|
||
|
// the error msg and other optional arguments.
|
||
|
//
|
||
|
//
|
||
|
HRESULT
|
||
|
GetMsgHelper(
|
||
|
OUT CString& strMsg,// OUT: the message
|
||
|
DWORD dwErr, // IN: Error code from GetLastError()
|
||
|
UINT wIdString, // IN: String ID
|
||
|
va_list* parglist // IN: OPTIONAL arguments
|
||
|
)
|
||
|
{
|
||
|
if (!dwErr && !wIdString)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
TCHAR *pszMsgResourceString = NULL;
|
||
|
TCHAR *pszT = L"";
|
||
|
|
||
|
//
|
||
|
// retrieve error msg
|
||
|
//
|
||
|
CString strErrorMessage;
|
||
|
if (dwErr != 0)
|
||
|
{
|
||
|
GetErrorMessage(dwErr, strErrorMessage);
|
||
|
pszT = (LPTSTR)(LPCTSTR)strErrorMessage;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// load string resource, and format it with the error msg and
|
||
|
// other optional arguments
|
||
|
//
|
||
|
if (wIdString == 0)
|
||
|
{
|
||
|
strMsg = pszT;
|
||
|
} else
|
||
|
{
|
||
|
pszMsgResourceString = PaszLoadStringPrintf(wIdString, *parglist);
|
||
|
if (dwErr == 0)
|
||
|
strMsg = pszMsgResourceString;
|
||
|
else if ((HRESULT)dwErr < 0)
|
||
|
LoadStringPrintf(IDS_sus_ERROR_HR, OUT &strMsg, pszMsgResourceString, dwErr, pszT);
|
||
|
else
|
||
|
LoadStringPrintf(IDS_sus_ERROR, OUT &strMsg, pszMsgResourceString, dwErr, pszT);
|
||
|
}
|
||
|
|
||
|
if (pszMsgResourceString)
|
||
|
LocalFree(pszMsgResourceString);
|
||
|
|
||
|
return S_OK;
|
||
|
} // GetMsgHelper()
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// GetMsg()
|
||
|
//
|
||
|
// This function will call GetMsgHelp to retrieve the error msg
|
||
|
// if dwErr is specified, load resource string if specified, and
|
||
|
// format the string with the error msg and other optional arguments.
|
||
|
//
|
||
|
//
|
||
|
void
|
||
|
GetMsg(
|
||
|
OUT CString& strMsg,// OUT: the message
|
||
|
DWORD dwErr, // IN: Error code from GetLastError()
|
||
|
UINT wIdString, // IN: String resource Id
|
||
|
...) // IN: Optional arguments
|
||
|
{
|
||
|
va_list arglist;
|
||
|
va_start(arglist, wIdString);
|
||
|
|
||
|
HRESULT hr = GetMsgHelper(strMsg, dwErr, wIdString, &arglist);
|
||
|
if (FAILED(hr))
|
||
|
strMsg.Format(_T("0x%x"), hr);
|
||
|
|
||
|
va_end(arglist);
|
||
|
|
||
|
} // GetMsg()
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// DoErrMsgBox()
|
||
|
//
|
||
|
// Display a message box for the error code. This function will
|
||
|
// load the error message from the system resource and append
|
||
|
// the optional string (if any)
|
||
|
//
|
||
|
// EXAMPLE
|
||
|
// DoErrMsgBox(GetActiveWindow(), MB_OK, GetLastError(), IDS_s_FILE_READ_ERROR, L"foo.txt");
|
||
|
//
|
||
|
INT
|
||
|
DoErrMsgBoxHelper(
|
||
|
HWND hwndParent, // IN: Parent of the dialog box
|
||
|
UINT uType, // IN: style of message box
|
||
|
DWORD dwErr, // IN: Error code from GetLastError()
|
||
|
UINT wIdString, // IN: String resource Id
|
||
|
bool fServicesSnapin, // IN: Is this filemgmt or svcmgmt?
|
||
|
va_list& arglist) // IN: Optional arguments
|
||
|
{
|
||
|
//
|
||
|
// get string and the error msg
|
||
|
//
|
||
|
CString strMsg;
|
||
|
HRESULT hr = GetMsgHelper(strMsg, dwErr, wIdString, &arglist);
|
||
|
if (FAILED(hr))
|
||
|
strMsg.Format(_T("0x%x"), hr);
|
||
|
|
||
|
//
|
||
|
// Load the caption
|
||
|
//
|
||
|
CString strCaption;
|
||
|
strCaption.LoadString(
|
||
|
(fServicesSnapin) ? IDS_CAPTION_SERVICES : IDS_CAPTION_FILEMGMT);
|
||
|
|
||
|
//
|
||
|
// Display the message.
|
||
|
//
|
||
|
CThemeContextActivator activator;;
|
||
|
return MessageBox(hwndParent, strMsg, strCaption, uType);
|
||
|
|
||
|
} // DoErrMsgBox()
|
||
|
|
||
|
INT
|
||
|
DoErrMsgBox(
|
||
|
HWND hwndParent, // IN: Parent of the dialog box
|
||
|
UINT uType, // IN: style of message box
|
||
|
DWORD dwErr, // IN: Error code from GetLastError()
|
||
|
UINT wIdString, // IN: String resource Id
|
||
|
...) // IN: Optional arguments
|
||
|
{
|
||
|
//
|
||
|
// get string and the error msg
|
||
|
//
|
||
|
va_list arglist;
|
||
|
va_start(arglist, wIdString);
|
||
|
|
||
|
INT retval = DoErrMsgBoxHelper(
|
||
|
hwndParent, uType, dwErr, wIdString, false, arglist );
|
||
|
|
||
|
va_end(arglist);
|
||
|
|
||
|
return retval;
|
||
|
|
||
|
} // DoErrMsgBox()
|
||
|
|
||
|
//
|
||
|
// JonN 3/5/01 4635
|
||
|
// Services Snapin - String length error dialog title shouldn't be "File Service Management"
|
||
|
//
|
||
|
INT
|
||
|
DoServicesErrMsgBox(
|
||
|
HWND hwndParent, // IN: Parent of the dialog box
|
||
|
UINT uType, // IN: style of message box
|
||
|
DWORD dwErr, // IN: Error code from GetLastError()
|
||
|
UINT wIdString, // IN: String resource Id
|
||
|
...) // IN: Optional arguments
|
||
|
{
|
||
|
//
|
||
|
// get string and the error msg
|
||
|
//
|
||
|
va_list arglist;
|
||
|
va_start(arglist, wIdString);
|
||
|
|
||
|
INT retval = DoErrMsgBoxHelper(
|
||
|
hwndParent, uType, dwErr, wIdString, true, arglist );
|
||
|
|
||
|
va_end(arglist);
|
||
|
|
||
|
return retval;
|
||
|
|
||
|
}
|
||
|
|
||
|
//+--------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: InitObjectPickerForUsers
|
||
|
//
|
||
|
// Synopsis: Call IDsObjectPicker::Initialize with arguments that will
|
||
|
// set it to allow the user to pick one user.
|
||
|
//
|
||
|
// Arguments: [pDsObjectPicker] - object picker interface instance
|
||
|
//
|
||
|
// Returns: Result of calling IDsObjectPicker::Initialize.
|
||
|
//
|
||
|
// History: 10-14-1998 DavidMun Sample code InitObjectPickerForGroups
|
||
|
// 10-14-1998 JonN Changed to InitObjectPickerForUsers
|
||
|
// 11-11-2000 JonN 188203 support LocalService/NetworkService
|
||
|
//
|
||
|
//---------------------------------------------------------------------------
|
||
|
|
||
|
// CODEWORK do I want to allow USER_ENTERED?
|
||
|
HRESULT
|
||
|
InitObjectPickerForUsers(
|
||
|
IDsObjectPicker *pDsObjectPicker,
|
||
|
LPCTSTR pszServerName)
|
||
|
{
|
||
|
//
|
||
|
// Prepare to initialize the object picker.
|
||
|
// Set up the array of scope initializer structures.
|
||
|
//
|
||
|
|
||
|
static const int SCOPE_INIT_COUNT = 5;
|
||
|
DSOP_SCOPE_INIT_INFO aScopeInit[SCOPE_INIT_COUNT];
|
||
|
|
||
|
ZeroMemory(aScopeInit, sizeof(DSOP_SCOPE_INIT_INFO) * SCOPE_INIT_COUNT);
|
||
|
|
||
|
//
|
||
|
// Target computer scope. This adds a "Look In" entry for the
|
||
|
// target computer. Computer scopes are always treated as
|
||
|
// downlevel (i.e., they use the WinNT provider).
|
||
|
//
|
||
|
|
||
|
aScopeInit[0].cbSize = sizeof(DSOP_SCOPE_INIT_INFO);
|
||
|
aScopeInit[0].flType = DSOP_SCOPE_TYPE_TARGET_COMPUTER;
|
||
|
aScopeInit[0].flScope = DSOP_SCOPE_FLAG_STARTING_SCOPE
|
||
|
| DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT;
|
||
|
// JonN 11/14/00 188203 support LocalService/NetworkService
|
||
|
aScopeInit[0].FilterFlags.flDownlevel = DSOP_DOWNLEVEL_FILTER_USERS
|
||
|
| DSOP_DOWNLEVEL_FILTER_LOCAL_SERVICE
|
||
|
| DSOP_DOWNLEVEL_FILTER_NETWORK_SERVICE;
|
||
|
|
||
|
//
|
||
|
// The domain to which the target computer is joined. Note we're
|
||
|
// combining two scope types into flType here for convenience.
|
||
|
//
|
||
|
|
||
|
aScopeInit[1].cbSize = sizeof(DSOP_SCOPE_INIT_INFO);
|
||
|
aScopeInit[1].flType = DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN
|
||
|
| DSOP_SCOPE_TYPE_DOWNLEVEL_JOINED_DOMAIN;
|
||
|
aScopeInit[1].flScope = DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT;
|
||
|
aScopeInit[1].FilterFlags.Uplevel.flNativeModeOnly =
|
||
|
DSOP_FILTER_USERS;
|
||
|
aScopeInit[1].FilterFlags.Uplevel.flMixedModeOnly =
|
||
|
DSOP_FILTER_USERS;
|
||
|
aScopeInit[1].FilterFlags.flDownlevel =
|
||
|
DSOP_DOWNLEVEL_FILTER_USERS;
|
||
|
|
||
|
//
|
||
|
// The domains in the same forest (enterprise) as the domain to which
|
||
|
// the target machine is joined. Note these can only be DS-aware
|
||
|
//
|
||
|
|
||
|
aScopeInit[2].cbSize = sizeof(DSOP_SCOPE_INIT_INFO);
|
||
|
aScopeInit[2].flType = DSOP_SCOPE_TYPE_ENTERPRISE_DOMAIN;
|
||
|
aScopeInit[2].flScope = DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT;
|
||
|
aScopeInit[2].FilterFlags.Uplevel.flNativeModeOnly =
|
||
|
DSOP_FILTER_USERS;
|
||
|
aScopeInit[2].FilterFlags.Uplevel.flMixedModeOnly =
|
||
|
DSOP_FILTER_USERS;
|
||
|
|
||
|
//
|
||
|
// Domains external to the enterprise but trusted directly by the
|
||
|
// domain to which the target machine is joined.
|
||
|
//
|
||
|
// If the target machine is joined to an NT4 domain, only the
|
||
|
// external downlevel domain scope applies, and it will cause
|
||
|
// all domains trusted by the joined domain to appear.
|
||
|
//
|
||
|
|
||
|
aScopeInit[3].cbSize = sizeof(DSOP_SCOPE_INIT_INFO);
|
||
|
aScopeInit[3].flType = DSOP_SCOPE_TYPE_EXTERNAL_UPLEVEL_DOMAIN
|
||
|
| DSOP_SCOPE_TYPE_EXTERNAL_DOWNLEVEL_DOMAIN;
|
||
|
aScopeInit[3].flScope = DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT;
|
||
|
|
||
|
aScopeInit[3].FilterFlags.Uplevel.flNativeModeOnly =
|
||
|
DSOP_FILTER_USERS;
|
||
|
|
||
|
aScopeInit[3].FilterFlags.Uplevel.flMixedModeOnly =
|
||
|
DSOP_FILTER_USERS;
|
||
|
|
||
|
aScopeInit[3].FilterFlags.flDownlevel =
|
||
|
DSOP_DOWNLEVEL_FILTER_USERS;
|
||
|
|
||
|
//
|
||
|
// The Global Catalog
|
||
|
//
|
||
|
|
||
|
aScopeInit[4].cbSize = sizeof(DSOP_SCOPE_INIT_INFO);
|
||
|
aScopeInit[4].flScope = DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT;
|
||
|
aScopeInit[4].flType = DSOP_SCOPE_TYPE_GLOBAL_CATALOG;
|
||
|
|
||
|
// Only native mode applies to gc scope.
|
||
|
|
||
|
aScopeInit[4].FilterFlags.Uplevel.flNativeModeOnly =
|
||
|
DSOP_FILTER_USERS;
|
||
|
|
||
|
//
|
||
|
// Put the scope init array into the object picker init array
|
||
|
//
|
||
|
|
||
|
DSOP_INIT_INFO InitInfo;
|
||
|
ZeroMemory(&InitInfo, sizeof(InitInfo));
|
||
|
|
||
|
InitInfo.cbSize = sizeof(InitInfo);
|
||
|
|
||
|
//
|
||
|
// The pwzTargetComputer member allows the object picker to be
|
||
|
// retargetted to a different computer. It will behave as if it
|
||
|
// were being run ON THAT COMPUTER.
|
||
|
//
|
||
|
|
||
|
InitInfo.pwzTargetComputer = pszServerName; // NULL == local machine
|
||
|
// InitInfo.pwzTargetComputer = NULL; // NULL == local machine
|
||
|
InitInfo.cDsScopeInfos = SCOPE_INIT_COUNT;
|
||
|
InitInfo.aDsScopeInfos = aScopeInit;
|
||
|
|
||
|
// JonN 11/14/00 188203 support LocalService/NetworkService
|
||
|
static PCWSTR g_pszObjectSid = L"objectSid";
|
||
|
InitInfo.cAttributesToFetch = 1;
|
||
|
InitInfo.apwzAttributeNames = &g_pszObjectSid;
|
||
|
|
||
|
//
|
||
|
// Note object picker makes its own copy of InitInfo. Also note
|
||
|
// that Initialize may be called multiple times, last call wins.
|
||
|
//
|
||
|
|
||
|
HRESULT hr = pDsObjectPicker->Initialize(&InitInfo);
|
||
|
ASSERT( SUCCEEDED(hr) );
|
||
|
|
||
|
return hr;
|
||
|
} // InitObjectPickerForUsers
|
||
|
|
||
|
//+--------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: ExtractADsPathAndUPN
|
||
|
//
|
||
|
// Synopsis: Retrieve the selected username from the data object
|
||
|
// created by the object picker.
|
||
|
//
|
||
|
// Arguments: [pdo] - data object returned by object picker
|
||
|
//
|
||
|
// History: 10-14-1998 DavidMun Sample code ProcessSelectedObjects
|
||
|
// 10-14-1998 JonN Changed to ExtractADsPath
|
||
|
// 01-25-1999 JonN Added pflScopeType parameter
|
||
|
// 03-16-1999 JonN Changed to ExtractADsPathAndUPN
|
||
|
// 11-14-2000 JonN Added svarrefObjectSid for 188203
|
||
|
//
|
||
|
//---------------------------------------------------------------------------
|
||
|
|
||
|
UINT g_cfDsObjectPicker = RegisterClipboardFormat(CFSTR_DSOP_DS_SELECTION_LIST);
|
||
|
|
||
|
HRESULT
|
||
|
ExtractADsPathAndUPN(
|
||
|
IN IDataObject *pdo,
|
||
|
OUT CString& strrefADsPath,
|
||
|
OUT CString& strrefUPN,
|
||
|
OUT CComVariant& svarrefObjectSid,
|
||
|
OUT ULONG *pflScopeType)
|
||
|
{
|
||
|
if (NULL == pdo)
|
||
|
return E_POINTER;
|
||
|
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
STGMEDIUM stgmedium =
|
||
|
{
|
||
|
TYMED_HGLOBAL,
|
||
|
NULL,
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
FORMATETC formatetc =
|
||
|
{
|
||
|
(CLIPFORMAT)g_cfDsObjectPicker,
|
||
|
NULL,
|
||
|
DVASPECT_CONTENT,
|
||
|
-1,
|
||
|
TYMED_HGLOBAL
|
||
|
};
|
||
|
|
||
|
bool fGotStgMedium = false;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
hr = pdo->GetData(&formatetc, &stgmedium);
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
ASSERT(FALSE);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
fGotStgMedium = true;
|
||
|
|
||
|
PDS_SELECTION_LIST pDsSelList =
|
||
|
(PDS_SELECTION_LIST) GlobalLock(stgmedium.hGlobal);
|
||
|
|
||
|
if ( NULL == pDsSelList
|
||
|
|| 1 != pDsSelList->cItems
|
||
|
)
|
||
|
{
|
||
|
ASSERT(FALSE);
|
||
|
hr = E_FAIL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
DS_SELECTION& sel = pDsSelList->aDsSelection[0];
|
||
|
strrefADsPath = sel.pwzADsPath;
|
||
|
strrefUPN = sel.pwzUPN;
|
||
|
if ( sel.pvarFetchedAttributes )
|
||
|
svarrefObjectSid = sel.pvarFetchedAttributes[0];
|
||
|
|
||
|
if (NULL != pflScopeType)
|
||
|
*pflScopeType = pDsSelList->aDsSelection[0].flScopeType;
|
||
|
|
||
|
GlobalUnlock(stgmedium.hGlobal);
|
||
|
} while (0);
|
||
|
|
||
|
if (fGotStgMedium)
|
||
|
{
|
||
|
ReleaseStgMedium(&stgmedium);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// UiGetUser()
|
||
|
//
|
||
|
// Invoke a user picker dialog.
|
||
|
//
|
||
|
// Return TRUE iff an account was selected.
|
||
|
//
|
||
|
// HISTORY
|
||
|
// 96.10.12 t-danmo Creation. Inspired from function GetUser() located
|
||
|
// at \nt\private\windows\shell\security\aclui\misc.cpp.
|
||
|
// 96.10.30 t-danmo Added/modified comments.
|
||
|
// 98.03.17 jonn Modified to use User/Group Picker
|
||
|
// 98.10.20 jonn Modified to use updated Object Picker interfaces
|
||
|
//
|
||
|
|
||
|
//+--------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: ExtractDomainUserString
|
||
|
//
|
||
|
// Synopsis: Converts an ADspath to the format needed by Service Controller
|
||
|
//
|
||
|
// History: 10-14-1998 JonN Created
|
||
|
// 01-25-1999 JonN added flScopeType parameter
|
||
|
//
|
||
|
//---------------------------------------------------------------------------
|
||
|
|
||
|
HRESULT
|
||
|
ExtractDomainUserString(
|
||
|
IN LPCTSTR pwzADsPath,
|
||
|
IN ULONG flScopeType,
|
||
|
IN OUT CString& strrefDomainUser)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
CComPtr<IADsPathname> spIADsPathname;
|
||
|
hr = CoCreateInstance(CLSID_Pathname, NULL, CLSCTX_INPROC_SERVER,
|
||
|
IID_IADsPathname, (PVOID *)&spIADsPathname);
|
||
|
RETURN_HR_IF_FAIL;
|
||
|
|
||
|
hr = spIADsPathname->Set( const_cast<LPTSTR>(pwzADsPath), ADS_SETTYPE_FULL );
|
||
|
RETURN_HR_IF_FAIL;
|
||
|
|
||
|
CComBSTR sbstrUser;
|
||
|
hr = spIADsPathname->GetElement( 0, &sbstrUser );
|
||
|
RETURN_HR_IF_FAIL;
|
||
|
|
||
|
CComBSTR sbstrDomain = OLESTR(".");
|
||
|
if (DSOP_SCOPE_TYPE_TARGET_COMPUTER != flScopeType)
|
||
|
{
|
||
|
long lnNumPathElements = 0;
|
||
|
hr = spIADsPathname->GetNumElements( &lnNumPathElements );
|
||
|
RETURN_FALSE_IF_FAIL;
|
||
|
|
||
|
switch (lnNumPathElements)
|
||
|
{
|
||
|
case 1:
|
||
|
hr = spIADsPathname->Retrieve( ADS_FORMAT_SERVER, &sbstrDomain );
|
||
|
RETURN_HR_IF_FAIL;
|
||
|
break;
|
||
|
case 2:
|
||
|
hr = spIADsPathname->GetElement( 1, &sbstrDomain );
|
||
|
RETURN_HR_IF_FAIL;
|
||
|
break;
|
||
|
default:
|
||
|
ASSERT(FALSE);
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
strrefDomainUser.Format(L"%s\\%s", sbstrDomain, sbstrUser);
|
||
|
|
||
|
return hr;
|
||
|
} // ExtractDomainUserString
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
UiGetUser(
|
||
|
HWND hwndOwner, // IN: Owner window
|
||
|
BOOL /*fIsContainer*/, // IN: TRUE if invoked for a container
|
||
|
LPCTSTR pszServerName, // IN: Initial target machine name
|
||
|
OUT CString& strrefUser) // IN: Allocated buffer containing the user details
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
CComPtr<IDsObjectPicker> spDsObjectPicker;
|
||
|
hr = CoCreateInstance(CLSID_DsObjectPicker, NULL, CLSCTX_INPROC_SERVER,
|
||
|
IID_IDsObjectPicker, (PVOID *)&spDsObjectPicker);
|
||
|
RETURN_FALSE_IF_FAIL;
|
||
|
ASSERT( !!spDsObjectPicker );
|
||
|
|
||
|
hr = InitObjectPickerForUsers(spDsObjectPicker, pszServerName);
|
||
|
RETURN_FALSE_IF_FAIL;
|
||
|
|
||
|
CComPtr<IDataObject> spDataObject;
|
||
|
hr = spDsObjectPicker->InvokeDialog(hwndOwner, &spDataObject);
|
||
|
RETURN_FALSE_IF_FAIL;
|
||
|
if (S_FALSE == hr)
|
||
|
return FALSE; // user cancelled
|
||
|
ASSERT( !!spDataObject );
|
||
|
|
||
|
CString strADsPath;
|
||
|
ULONG flScopeType = DSOP_SCOPE_TYPE_TARGET_COMPUTER;
|
||
|
CComVariant svarObjectSid;
|
||
|
hr = ExtractADsPathAndUPN( spDataObject,
|
||
|
strADsPath,
|
||
|
strrefUser,
|
||
|
svarObjectSid,
|
||
|
&flScopeType );
|
||
|
RETURN_FALSE_IF_FAIL;
|
||
|
|
||
|
// JonN 11/15/00 188203 check for LocalService/NetworkService
|
||
|
if (svarObjectSid.vt == (VT_ARRAY|VT_UI1))
|
||
|
{
|
||
|
PSID pSid = svarObjectSid.parray->pvData;
|
||
|
if ( IsWellKnownSid(pSid, WinLocalServiceSid) )
|
||
|
{
|
||
|
strrefUser = TEXT("NT AUTHORITY\\LocalService");
|
||
|
return TRUE;
|
||
|
}
|
||
|
else if ( IsWellKnownSid(pSid, WinNetworkServiceSid) )
|
||
|
{
|
||
|
strrefUser = TEXT("NT AUTHORITY\\NetworkService");
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (strrefUser.IsEmpty())
|
||
|
{
|
||
|
if (strADsPath.IsEmpty())
|
||
|
{
|
||
|
ASSERT(FALSE);
|
||
|
return FALSE;
|
||
|
}
|
||
|
hr = ExtractDomainUserString( strADsPath, flScopeType, strrefUser );
|
||
|
RETURN_FALSE_IF_FAIL;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
} // UiGetUser()
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// DoHelp()
|
||
|
//
|
||
|
// This routine handles context help for the WM_HELP message.
|
||
|
//
|
||
|
// The return value is always TRUE.
|
||
|
//
|
||
|
BOOL DoHelp(
|
||
|
LPARAM lParam, // Pointer to HELPINFO structure
|
||
|
const DWORD rgzHelpIDs[]) // Array of HelpIDs
|
||
|
{
|
||
|
Assert(rgzHelpIDs != NULL);
|
||
|
const LPHELPINFO pHelpInfo = (LPHELPINFO)lParam;
|
||
|
|
||
|
if (pHelpInfo != NULL)
|
||
|
{
|
||
|
if (pHelpInfo->iContextType == HELPINFO_WINDOW)
|
||
|
{
|
||
|
const HWND hwnd = (HWND)pHelpInfo->hItemHandle;
|
||
|
Assert(IsWindow(hwnd));
|
||
|
// Display context help for a control
|
||
|
WinHelp(
|
||
|
hwnd,
|
||
|
g_szHelpFileFilemgmt,
|
||
|
HELP_WM_HELP,
|
||
|
(DWORD_PTR)rgzHelpIDs);
|
||
|
}
|
||
|
}
|
||
|
return TRUE;
|
||
|
} // DoHelp()
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// DoContextHelp()
|
||
|
//
|
||
|
// This routine handles context help for the WM_CONTEXTMENU message.
|
||
|
//
|
||
|
// The return value is always TRUE.
|
||
|
//
|
||
|
BOOL DoContextHelp(
|
||
|
WPARAM wParam, // Window requesting help
|
||
|
const DWORD rgzHelpIDs[]) // Array of HelpIDs
|
||
|
{
|
||
|
const HWND hwnd = (HWND)wParam;
|
||
|
Assert(IsWindow(hwnd));
|
||
|
Assert(rgzHelpIDs != NULL);
|
||
|
WinHelp(hwnd, g_szHelpFileFilemgmt, HELP_CONTEXTMENU, (DWORD_PTR)rgzHelpIDs);
|
||
|
return TRUE;
|
||
|
} // DoContextHelp()
|