windows-nt/Source/XPSP1/NT/inetsrv/iis/ui/admin/mmc/menuex.cpp

956 lines
20 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1994-1998 Microsoft Corporation
Module Name :
menuex.cpp
Abstract:
Menu extension classes
Author:
Ronald Meijer (ronaldm)
Project:
Internet Services Manager
Revision History:
--*/
#include "stdafx.h"
#include "inetmgr.h"
#include "menuex.h"
#include "constr.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
//
// Static member initialization;
//
const TCHAR CISMShellExecutable::s_chField = _T(';');
const TCHAR CISMShellExecutable::s_chEscape = _T('$');
const TCHAR CISMShellExecutable::s_chService = _T('S');
const TCHAR CISMShellExecutable::s_chComputer = _T('C');
/* static */
HICON
CISMShellExecutable::GetShellIcon(
IN LPCTSTR lpszShellExecutable
)
/*++
Routine Description:
Extract icon from the given shell executable
Arguments:
LPCTSTR lpszShellExecutable : Executable name (or shell document)
Return Value:
Handle to the icon or NULL
--*/
{
SHFILEINFO shfi;
::SHGetFileInfo(
lpszShellExecutable,
0L,
&shfi,
sizeof(shfi),
SHGFI_ICON | SHGFI_SHELLICONSIZE | SHGFI_SMALLICON
);
//
// Will be NULL if SHGetFileInfo failed
//
return shfi.hIcon;
}
/* static */
HICON
CISMShellExecutable::ExtractIcon(
IN LPCTSTR lpszIconSource,
IN UINT nIconOffset
)
/*++
Routine Description:
Extract icon specified by index from the given file
Arguments:
LPCTSTR lpszIconSource : Source of the icon file
UINT nIconOffset : The 0-based icon index within the file
Return Value:
Handle to the icon or NULL
--*/
{
HICON hIconSmall = NULL;
::ExtractIconEx(lpszIconSource, nIconOffset, NULL, &hIconSmall, 1);
return hIconSmall;
}
/* static */
LPTSTR
CISMShellExecutable::GetField(
IN LPTSTR pchLine OPTIONAL
)
/*++
Routine Description:
Similar to strtok, this function reads fields from the source string
separated by semi-colons. When one is found, it is changed to a NULL,
and the pointer to the current string is returned. Subsequent calls
to this function may use pchLine as NULL, meaning continue where we
left off. This function differs from strtok, in that only the first
separator character is skipped. Having multiple ones in a row, e.g.
";;;", would return each field as an empty field, and not just skip
past all of them. Also, quoted fields are returned in their entirety,
and any separator characters in them are not treated as separators.
Arguments:
LPTSTR pchLine : Beginning string pointer or NULL
Return Value:
String pointer to the next field, or NULL if no further fields are
available.
--*/
{
static LPTSTR pchEnd = NULL;
static BOOL fEOL = FALSE;
LPTSTR pch;
//
// Initialize beginning line pointer
//
if (pchLine != NULL)
{
//
// Starting with a new string
//
fEOL = FALSE;
pch = pchLine;
}
else
{
//
// Continue where we left off if there was any
// thing more to read
//
if (fEOL)
{
return NULL;
}
//
// Must have been called at least once with non-NULL
// parameter
//
ASSERT(pchEnd != NULL);
pch = ++pchEnd;
}
pchEnd = pch;
//
// Quotes are handled seperately
//
if (*pchEnd == _T('\"') )
{
//
// Skip ahead to closing quote
//
++pch;
do
{
++pchEnd;
}
while (*pchEnd && *pchEnd != _T('\"') );
if (!*pchEnd)
{
fEOL = TRUE;
}
else
{
*(pchEnd++) = _T('\0');
}
return pch;
}
//
// Else look for the field separator
// Allowing for null fields
//
if (*pchEnd != CISMShellExecutable::s_chField)
{
do
{
++pchEnd;
}
while (*pchEnd && * pchEnd != CISMShellExecutable::s_chField);
}
if (!*pchEnd)
{
fEOL = TRUE;
}
else
{
*pchEnd = _T('\0');
}
return pch;
}
/* static */
DWORD
CISMShellExecutable::ExpandEscapeCodes(
OUT CString & strDest,
IN LPCTSTR lpSrc,
IN LPCTSTR lpszComputer OPTIONAL,
IN LPCTSTR lpszService OPTIONAL
)
/*++
Routine Description:
Expand the escape codes in the string with computer or service
strings. See note at constructor below
Arguments:
CString & strDest : Destination string
LPCTSTR lpSrc : Original source string
LPCTSTR lpszComputer : Computer name
LPCTSTR lpszService : Service name
Return Value:
Error return code
--*/
{
DWORD err = ERROR_SUCCESS;
try
{
strDest.Empty();
while(*lpSrc)
{
if (*lpSrc == CISMShellExecutable::s_chEscape)
{
TCHAR ch = *(++lpSrc);
CharUpper((LPTSTR)ch);
switch(ch)
{
case CISMShellExecutable::s_chComputer:
if (lpszComputer)
{
strDest += PURE_COMPUTER_NAME(lpszComputer);
}
break;
case CISMShellExecutable::s_chService:
if (lpszService)
{
strDest += lpszService;
}
break;
case CISMShellExecutable::s_chEscape:
strDest += CISMShellExecutable::s_chEscape;
break;
case _T('\0'):
default:
//
// Ignored
//
break;
}
}
else
{
strDest += *lpSrc;
}
++lpSrc;
}
}
catch(CMemoryException * e)
{
err = ERROR_NOT_ENOUGH_MEMORY;
e->Delete();
}
return err;
}
//
// Helper macros to parse description line
//
#define PARSE_STR_FIELD(src, dest) \
{ \
LPTSTR lp = CISMShellExecutable::GetField((LPTSTR)src);\
if (lp != NULL) \
{ \
dest = lp; \
TRACEEOLID("Field is " << dest); \
} \
}
#define PARSE_INT_FIELD(src, dest, def) \
{ \
LPTSTR lp = CISMShellExecutable::GetField((LPTSTR)src);\
dest = (lp != NULL ? ::_ttoi(lp) : def); \
TRACEEOLID("Numeric field is " << dest); \
}
//
// Helper macro for conditional expansion
//
#define SAFE_EXPAND(err, dest, src, server, service) \
{ \
err = ExpandEscapeCodes(dest, src, server, service); \
if (err != ERROR_SUCCESS) \
{ \
break; \
} \
}
CISMShellExecutable::CISMShellExecutable(
IN LPCTSTR lpszRegistryValue,
IN int nBitmapID,
IN int nCmd
)
/*++
Routine Description:
Constructor. Read the description from the registry and initialize the
fields.
Arguments:
LPCTSTR lpszRegistryValue : Registry value
int nButton : Toolbar ID
Return Value:
N/A
Notes:
Fields
=======
The registry description consists of fields separated by semi-colons
The fields are as follows:
1) executable name : Executable name or shell document
2) tool tips text : Text to appear in the toolbar
3) selection arguments : Command line options if there is a selection
4) non-selection arguments : Command line options if nothing is selected
5) working directory : CWD if not set
6) show in toolbar : If zero, not shown in toolbar, anything else
will show in toolbar.
CAVEAT: Empty is translated to mean 'yes'
7) icon path : Where to get the toolbar icon (if empty,
the executable name will be used
8) icon index : If the above is specified, the icon index
0 otherwise
1) Is mandatory, all other fields are optional.
Escape Codes
============
Any field may contain escape codes that are filled in at runtime. These
escape codes are as follows:
$S : Replaced with service name (if selected -- skipped otherwise)
$C : Replaced with computer name (if selected -- skipped otherwise)
$$ : Replaced with a single $
--*/
: m_strCommand(),
m_strParms(),
m_strNoSelectionParms(),
m_strToolTipsText(),
m_hIcon(NULL),
m_pBitmap(NULL),
m_pmmcButton(NULL),
m_nBitmapID(nBitmapID),
m_nCmd(nCmd),
m_fShowInToolbar(TRUE)
{
try
{
CString strIconFile;
UINT nIconOffset;
PARSE_STR_FIELD(lpszRegistryValue, m_strCommand);
PARSE_STR_FIELD(NULL, m_strToolTipsText);
PARSE_STR_FIELD(NULL, m_strParms);
PARSE_STR_FIELD(NULL, m_strNoSelectionParms);
PARSE_STR_FIELD(NULL, m_strDefaultDirectory);
PARSE_INT_FIELD(NULL, m_fShowInToolbar, TRUE);
PARSE_STR_FIELD(NULL, strIconFile);
PARSE_INT_FIELD(NULL, nIconOffset, 0);
//
// If no tooltips text, give it the executable name
//
if (m_strToolTipsText.IsEmpty())
{
m_strToolTipsText = m_strCommand;
}
if (!m_strCommand.IsEmpty() && m_fShowInToolbar)
{
//
// If no specific icon source file is specified,
// just use what the shell would use.
//
if (strIconFile.IsEmpty())
{
m_hIcon = GetShellIcon(m_strCommand);
}
else
{
m_hIcon = ExtractIcon(strIconFile, nIconOffset);
}
//
// Provide the ? icon if nothing is proviced in the binary
//
if (m_hIcon == NULL)
{
m_pBitmap = new CBitmap;
if (!m_pBitmap->LoadMappedBitmap(IDB_UNKNOWN))
{
TRACEEOLID("Can't load unknown toolbar button bitmap");
delete m_pBitmap;
m_pBitmap = NULL;
}
}
else
{
ExtractBitmapFromIcon(m_hIcon, m_pBitmap);
}
//
// Now build the MMC button structure
//
if (HasBitmap())
{
m_pmmcButton = (MMCBUTTON *)AllocMem(sizeof(MMCBUTTON));
if (m_pmmcButton != NULL)
{
m_pmmcButton->nBitmap = m_nBitmapID;
m_pmmcButton->idCommand = m_nCmd;
m_pmmcButton->fsState = TBSTATE_ENABLED;
m_pmmcButton->fsType = TBSTYLE_BUTTON;
m_pmmcButton->lpButtonText = _T(" ");
m_pmmcButton->lpTooltipText = (LPTSTR)(LPCTSTR)m_strToolTipsText;
}
}
}
}
catch(CMemoryException * e)
{
TRACEEOLID("!!!Exception initializing add-on-tool");
e->ReportError();
e->Delete();
}
}
CISMShellExecutable::~CISMShellExecutable()
/*++
Routine Description:
Destructor
Arguments:
N/A
Return Value:
N/A
--*/
{
if (m_pBitmap)
{
//
// I don't think I own this...
//
m_pBitmap->DeleteObject();
delete m_pBitmap;
}
if (m_pmmcButton)
{
FreeMem(m_pmmcButton);
}
}
void
CISMShellExecutable::ExtractBitmapFromIcon(
IN HICON hIcon,
OUT CBitmap *& pBitmap
)
/*++
Routine Description:
Extract bitmap information from icon
Arguments:
HICON hIcon : Input icon handle
CBitmap *& pBitmap : Returns bitmap info
Return Value:
None
--*/
{
try
{
pBitmap = new CBitmap();
//
// Get bitmap info from icon
//
ICONINFO ii;
ASSERT(hIcon != NULL);
::GetIconInfo(hIcon, &ii);
BITMAP bm;
//
// Determine size of the image
//
::GetObject(ii.hbmColor, sizeof(bm), &bm);
//
// Now create a new bitmap by drawing the icon on
// a button face background colour
//
CDC dcMem;
dcMem.CreateCompatibleDC(NULL);
pBitmap->CreateBitmapIndirect(&bm);
CBitmap * pOld = dcMem.SelectObject(pBitmap);
COLORREF crOld = dcMem.SetBkColor(::GetSysColor(COLOR_BTNFACE));
CRect rc(0, 0, bm.bmWidth, bm.bmHeight);
CBrush br(::GetSysColor(COLOR_BTNFACE));
dcMem.FillRect(&rc, &br);
::DrawIconEx(
dcMem.m_hDC,
0,
0,
hIcon,
bm.bmWidth,
bm.bmHeight,
0,
NULL,
DI_NORMAL
);
dcMem.SetBkColor(crOld);
dcMem.SelectObject(pOld);
}
catch(CException * e)
{
TRACEEOLID("!!! Exception adding icon based button");
e->ReportError();
e->Delete();
}
}
LPCTSTR
CISMShellExecutable::GetToolTipsText(
CString & str,
IN LPCTSTR lpszServer OPTIONAL,
IN LPCTSTR lpszService OPTIONAL
)
/*++
Routine Description:
Get the tooltips text. Optionally perform escape code
expansion
Arguments:
LPCTSTR lpstrServer : Currently selected server (or NULL)
LPCTSTR lpstrService : Currently selected service (or NULL)
Return Value:
Pointer to string
--*/
{
ExpandEscapeCodes(str, m_strToolTipsText, lpszServer, lpszService);
return (LPCTSTR)str;
}
DWORD
CISMShellExecutable::Execute(
IN LPCTSTR lpszServer OPTIONAL,
IN LPCTSTR lpszService OPTIONAL
)
/*++
Routine Description:
Execute the current module
Arguments:
LPCTSTR lpszServer : Currently selected server (or NULL)
LPCTSTR lpszService : Currently selected service (or NULL)
Return Value:
Error return code
--*/
{
DWORD err = ERROR_SUCCESS;
CString strCommand;
CString strParms;
CString strDefaultDirectory;
do
{
//
// Expand escape codes as appropriate
//
SAFE_EXPAND(err, strCommand, m_strCommand, lpszServer, lpszService);
SAFE_EXPAND(err, strParms, lpszServer
? m_strParms : m_strNoSelectionParms, lpszServer, lpszService);
SAFE_EXPAND(err, strDefaultDirectory, m_strDefaultDirectory,
lpszServer, lpszService);
if (::ShellExecute(
NULL,
_T("open"),
strCommand,
strParms,
strDefaultDirectory,
SW_SHOW
) <= (HINSTANCE)32)
{
err = ::GetLastError();
}
}
while(FALSE);
return err;
}
/* OBSOLETE
//
// Add Machine Page Procedure Name
//
#define ADDMACHINEPAGE_PROC "ISMAddMachinePages"
BOOL
AddISMPage(
IN HPROPSHEETPAGE hPage,
IN LPARAM lParam
)
/*++
Routine Description:
Callback function to be used for ISM machine property sheet
extention modules to add pages to the property sheet.
Arguments:
HPROPSHEETPAGE hPage : Handle to page to be added
LPARAM lParam : LParam given to the extention module.
Privately, this is a cast to the property
sheet structure.
Return Value:
TRUE for success, FALSE for failure. In case of failure, GetLastError
will reveal the reason.
--/
{
//
// The extention module has been given the propsheet
// cunningly disguised as an LPARAM. If they've messed
// with the lparam, this will propably crash.
//
PROPSHEETHEADER * ppsh = (PROPSHEETHEADER *)lParam;
HPROPSHEETPAGE * pOld = ppsh->phpage;
ppsh->phpage = (HPROPSHEETPAGE *)AllocMem(ppsh->nPages + 1);
if (ppsh->phpage == NULL)
{
//
// Memory failure
//
ppsh->nPages = 0;
TRACEEOLID("Memory failure building machine property sheet");
::SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
//
// Save the old handles
//
if (pOld)
{
for (UINT i = 0; i < ppsh->nPages; ++i)
{
ppsh->phpage[i] = pOld[i];
}
FreeMem(pOld);
}
//
// Add the new page
//
ppsh->phpage[ppsh->nPages++] = hPage;
TRACEEOLID("Succesfully built machine property sheet with "
<< ppsh->nPages << " pages.");
return TRUE;
}
CISMMachinePageExt::CISMMachinePageExt(
IN LPCTSTR lpstrDLLName
)
/*++
Routine Description:
Constructor for ISM Machine property sheet extender.
Arguments:
LPCTSTR lpstrDLLName : Name of the DLL.
Return Value:
N/A
Notes:
This will not attempt to load and resolve the DLL
--/
: m_strDLLName(lpstrDLLName),
m_hDLL(NULL),
m_lpfn(NULL)
{
}
CISMMachinePageExt::~CISMMachinePageExt()
/*++
Routine Description:
Destructor
Arguments:
N/A
Return Value:
N/A
Notes:
This will unload the DLL if it is still loaded, but this should
have been done with the UnLoad method beforehand.
--/
{
//
// Should have been unloaded by now
//
ASSERT(m_hDLL == NULL);
if (m_hDLL)
{
VERIFY(UnLoad());
}
}
//
// Prototype
//
DWORD
ISMAddMachinePages(
IN LPCTSTR lpstrMachineName,
IN LPFNADDPROPSHEETPAGE lpfnAddPage,
IN LPARAM lParam,
IN HINSTANCE hInstance
);
DWORD
CISMMachinePageExt::Load()
/*++
Routine Description:
Load the DLL, and resolve the exposed interface
Arguments:
None
Return Value:
Error return code;
--/
{
#ifdef _COMSTATIC
m_lpfn = &ISMAddMachinePages;
return ERROR_SUCCESS;
#else
//
// Make sure it's not already loaded
//
ASSERT(m_hDLL == NULL);
m_hDLL = ::AfxLoadLibrary(m_strDLLName);
if (m_hDLL)
{
m_lpfn = (LPFNADDMACHINEPAGE)::GetProcAddress(
m_hDLL, ADDMACHINEPAGE_PROC);
if (m_lpfn)
{
return ERROR_SUCCESS;
}
}
return ::GetLastError();
#endif // _COMSTATIC
}
BOOL
CISMMachinePageExt::UnLoad()
/*++
Routine Description:
Unload the extention DLL
Arguments:
None
Return Value:
TRUE for success, FALSE for failure
--/
{
#ifdef _COMSTATIC
return TRUE;
#else
BOOL fResult = FALSE;
if (m_hDLL)
{
fResult = ::AfxFreeLibrary(m_hDLL);
m_hDLL = NULL;
m_lpfn = NULL;
}
return fResult;
#endif // _COMSTATIC
}
*/