1302 lines
30 KiB
C++
1302 lines
30 KiB
C++
|
|
||
|
#include "headers.hxx"
|
||
|
#pragma hdrstop
|
||
|
#include <compobj.h>
|
||
|
#include <initguid.h>
|
||
|
|
||
|
//
|
||
|
// Helpful debug macros
|
||
|
//
|
||
|
|
||
|
#define DBG_OUT_HRESULT(hr) printf("error 0x%x at line %u\n", hr, __LINE__)
|
||
|
|
||
|
|
||
|
#define BREAK_ON_FAIL_HRESULT(hr) \
|
||
|
if (FAILED(hr)) \
|
||
|
{ \
|
||
|
DBG_OUT_HRESULT(hr); \
|
||
|
break; \
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Forward references
|
||
|
//
|
||
|
|
||
|
VOID
|
||
|
DumpDsSelectedItemList(
|
||
|
PDSSELECTIONLIST pDsSelList,
|
||
|
ULONG cRequestedAttributes,
|
||
|
LPCTSTR *aptzRequestedAttributes);
|
||
|
|
||
|
VOID
|
||
|
Usage();
|
||
|
|
||
|
HRESULT
|
||
|
AnsiToUnicode(
|
||
|
LPWSTR pwszTo,
|
||
|
LPCSTR szFrom,
|
||
|
LONG cchTo);
|
||
|
|
||
|
BOOL
|
||
|
ParseFetchSwitch(
|
||
|
char *pzFetchSwitch,
|
||
|
PULONG pcRequestedAttributes,
|
||
|
LPCTSTR **paptzRequestedAttributes);
|
||
|
|
||
|
BOOL
|
||
|
ParseGroupSwitch(
|
||
|
char *pzGroupSwitch,
|
||
|
ULONG *pulFlags);
|
||
|
|
||
|
BOOL
|
||
|
ParseUserSwitch(
|
||
|
char *pzUserSwitch,
|
||
|
ULONG *pulFlags);
|
||
|
|
||
|
BOOL
|
||
|
ParseInitialScope(
|
||
|
char *pzScopeSwitch,
|
||
|
ULONG *pulFlags);
|
||
|
|
||
|
BOOL
|
||
|
ParseScope(
|
||
|
char *pzScopeSwitch,
|
||
|
ULONG *pulFlags,
|
||
|
PWSTR wzComputer,
|
||
|
PWSTR wzDomain);
|
||
|
|
||
|
BOOL
|
||
|
ParseProviders(
|
||
|
char *pzProviderSwitch,
|
||
|
PULONG cAcceptableProviders,
|
||
|
LPCWSTR **paptzAcceptableProviders);
|
||
|
|
||
|
BOOL
|
||
|
ParseRunCount(
|
||
|
char *pzRunCountSwitch,
|
||
|
PULONG pcRunCount);
|
||
|
|
||
|
LPWSTR
|
||
|
VariantString(
|
||
|
VARIANT *pvar);
|
||
|
|
||
|
void
|
||
|
VarArrayToStr(
|
||
|
VARIANT *pvar,
|
||
|
LPWSTR wzBuf,
|
||
|
ULONG cchbuf);
|
||
|
|
||
|
//
|
||
|
// Entry point
|
||
|
//
|
||
|
|
||
|
enum API_TO_CALL
|
||
|
{
|
||
|
NONE,
|
||
|
COMPUTER,
|
||
|
USER_GROUP
|
||
|
};
|
||
|
|
||
|
void _cdecl
|
||
|
main(int argc, char * argv[])
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
BOOL fBadArg = FALSE;
|
||
|
API_TO_CALL api = NONE;
|
||
|
|
||
|
ULONG flObjectPicker = 0;
|
||
|
ULONG flDsObjectPicker = 0;
|
||
|
ULONG flUserGroupObjectPickerSpecifiedDomain = 0;
|
||
|
ULONG flUserGroupObjectPickerOtherDomains = 0;
|
||
|
ULONG *pflUserGroup = &flUserGroupObjectPickerOtherDomains;
|
||
|
ULONG flComputerObjectPicker = 0;
|
||
|
ULONG flInitialScope = 0;
|
||
|
|
||
|
WCHAR wzComputer[MAX_PATH] = L"";
|
||
|
WCHAR wzDomain[MAX_PATH] = L"";
|
||
|
|
||
|
ULONG cAcceptableProviders = 0;
|
||
|
LPCWSTR *aptzAcceptableProviders = NULL;
|
||
|
|
||
|
ULONG cInvocations = 1;
|
||
|
ULONG cRequestedAttributes = 0;
|
||
|
LPCTSTR *aptzRequestedAttributes = NULL;
|
||
|
|
||
|
for (int idxArg = 1; idxArg < argc && !fBadArg; idxArg++)
|
||
|
{
|
||
|
switch (argv[idxArg][1])
|
||
|
{
|
||
|
case 'c':
|
||
|
case 'C':
|
||
|
if (argv[idxArg][2] == ':' &&
|
||
|
tolower(argv[idxArg][3]) == 'a')
|
||
|
{
|
||
|
if (api == USER_GROUP)
|
||
|
{
|
||
|
fBadArg = TRUE;
|
||
|
printf("Can't specify /C:Api with user/group switches\n");
|
||
|
}
|
||
|
api = COMPUTER;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (api == COMPUTER)
|
||
|
{
|
||
|
fBadArg = TRUE;
|
||
|
printf("Can't specify both /C and /C:Api\n");
|
||
|
}
|
||
|
api = USER_GROUP;
|
||
|
*pflUserGroup |= UGOP_COMPUTERS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'd':
|
||
|
case 'D':
|
||
|
if (argv[idxArg][2] != ':')
|
||
|
{
|
||
|
printf("Expected ':' after /D\n");
|
||
|
fBadArg = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (argv[idxArg][3])
|
||
|
{
|
||
|
case 's':
|
||
|
case 'S':
|
||
|
pflUserGroup = &flUserGroupObjectPickerSpecifiedDomain;
|
||
|
break;
|
||
|
|
||
|
case 'o':
|
||
|
case 'O':
|
||
|
case '0': // allow a typo
|
||
|
pflUserGroup = &flUserGroupObjectPickerOtherDomains;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
fBadArg = TRUE;
|
||
|
printf("Expected S or O after /D:\n");
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'f':
|
||
|
case 'F':
|
||
|
fBadArg = !ParseFetchSwitch(argv[idxArg], &cRequestedAttributes, &aptzRequestedAttributes);
|
||
|
break;
|
||
|
|
||
|
case 'g':
|
||
|
case 'G':
|
||
|
if (api != NONE && api != USER_GROUP)
|
||
|
{
|
||
|
fBadArg = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
api = USER_GROUP;
|
||
|
fBadArg = !ParseGroupSwitch(argv[idxArg], pflUserGroup);
|
||
|
break;
|
||
|
|
||
|
case 'h':
|
||
|
case 'H':
|
||
|
if (api != NONE && api != USER_GROUP)
|
||
|
{
|
||
|
fBadArg = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
api = USER_GROUP;
|
||
|
*pflUserGroup |= UGOP_INCLUDE_HIDDEN;
|
||
|
break;
|
||
|
|
||
|
case 'i':
|
||
|
case 'I':
|
||
|
fBadArg = !ParseInitialScope(argv[idxArg], &flInitialScope);
|
||
|
break;
|
||
|
|
||
|
case 'm':
|
||
|
case 'M':
|
||
|
flObjectPicker |= OP_MULTISELECT;
|
||
|
break;
|
||
|
|
||
|
case 'n':
|
||
|
case 'N':
|
||
|
flDsObjectPicker |= DSOP_RESTRICT_NAMES_TO_KNOWN_DOMAINS;
|
||
|
break;
|
||
|
|
||
|
case 'p':
|
||
|
case 'P':
|
||
|
fBadArg = !ParseProviders(argv[idxArg],
|
||
|
&cAcceptableProviders,
|
||
|
&aptzAcceptableProviders);
|
||
|
break;
|
||
|
|
||
|
case 'r':
|
||
|
case 'R':
|
||
|
fBadArg = !ParseRunCount(argv[idxArg], &cInvocations);
|
||
|
break;
|
||
|
|
||
|
case 's':
|
||
|
case 'S':
|
||
|
fBadArg = !ParseScope(argv[idxArg],
|
||
|
&flDsObjectPicker,
|
||
|
wzComputer,
|
||
|
wzDomain);
|
||
|
break;
|
||
|
|
||
|
case 'u':
|
||
|
case 'U':
|
||
|
if (api == COMPUTER)
|
||
|
{
|
||
|
fBadArg = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
api = USER_GROUP;
|
||
|
fBadArg = !ParseUserSwitch(argv[idxArg], pflUserGroup);
|
||
|
break;
|
||
|
|
||
|
case 'x':
|
||
|
case 'X':
|
||
|
flDsObjectPicker |= DSOP_CONVERT_EXTERNAL_PATHS_TO_SID;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
fBadArg = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fBadArg || api == NONE)
|
||
|
{
|
||
|
Usage();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
hr = CoInitialize(NULL);
|
||
|
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
printf("CoInitializeEx - 0x%x\n", hr);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Call the API
|
||
|
//
|
||
|
|
||
|
PDSSELECTIONLIST pDsSelList = NULL;
|
||
|
|
||
|
if (api == USER_GROUP)
|
||
|
{
|
||
|
GETUSERGROUPSELECTIONINFO ugsi;
|
||
|
|
||
|
ZeroMemory(&ugsi, sizeof ugsi);
|
||
|
ugsi.cbSize = sizeof(ugsi);
|
||
|
ugsi.ptzComputerName = *wzComputer ? wzComputer : NULL;
|
||
|
ugsi.ptzDomainName = *wzDomain ? wzDomain : NULL;
|
||
|
ugsi.flObjectPicker = flObjectPicker;
|
||
|
ugsi.flDsObjectPicker = flDsObjectPicker;
|
||
|
ugsi.flStartingScope = flInitialScope;
|
||
|
ugsi.flUserGroupObjectPickerSpecifiedDomain =
|
||
|
flUserGroupObjectPickerSpecifiedDomain;
|
||
|
ugsi.flUserGroupObjectPickerOtherDomains =
|
||
|
flUserGroupObjectPickerOtherDomains;
|
||
|
ugsi.ppDsSelList = &pDsSelList;
|
||
|
ugsi.cAcceptableProviders = cAcceptableProviders;
|
||
|
ugsi.aptzAcceptableProviders = aptzAcceptableProviders;
|
||
|
ugsi.cRequestedAttributes = cRequestedAttributes;
|
||
|
ugsi.aptzRequestedAttributes = aptzRequestedAttributes;
|
||
|
|
||
|
for (ULONG i = 0; i < cInvocations; i++)
|
||
|
{
|
||
|
hr = GetUserGroupSelection(&ugsi);
|
||
|
|
||
|
if (hr == S_FALSE)
|
||
|
{
|
||
|
printf("Invocation %u: User/Group picker dialog cancelled\n", i);
|
||
|
}
|
||
|
else if (SUCCEEDED(hr))
|
||
|
{
|
||
|
DumpDsSelectedItemList(pDsSelList,
|
||
|
cRequestedAttributes,
|
||
|
aptzRequestedAttributes);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
printf("Invocation %u: User/Group picker dialog failed 0x%x\n", i, hr);
|
||
|
}
|
||
|
|
||
|
FreeDsSelectionList(pDsSelList);
|
||
|
pDsSelList = NULL;
|
||
|
}
|
||
|
}
|
||
|
else if (api == COMPUTER)
|
||
|
{
|
||
|
GETCOMPUTERSELECTIONINFO csi;
|
||
|
|
||
|
ZeroMemory(&csi, sizeof csi);
|
||
|
csi.cbSize = sizeof(csi);
|
||
|
csi.ptzComputerName = *wzComputer ? wzComputer : NULL;
|
||
|
csi.ptzDomainName = *wzDomain ? wzDomain : NULL;
|
||
|
csi.flObjectPicker = flObjectPicker;
|
||
|
csi.flDsObjectPicker = flDsObjectPicker;
|
||
|
csi.flStartingScope = flInitialScope;
|
||
|
csi.flComputerObjectPicker = flComputerObjectPicker;
|
||
|
csi.ppDsSelList = &pDsSelList;
|
||
|
csi.cAcceptableProviders = cAcceptableProviders;
|
||
|
csi.aptzAcceptableProviders = aptzAcceptableProviders;
|
||
|
csi.cRequestedAttributes = cRequestedAttributes;
|
||
|
csi.aptzRequestedAttributes = aptzRequestedAttributes;
|
||
|
|
||
|
for (ULONG i = 0; i < cInvocations; i++)
|
||
|
{
|
||
|
hr = GetComputerSelection(&csi);
|
||
|
|
||
|
if (hr == S_FALSE)
|
||
|
{
|
||
|
printf("Invocation %u: Computer picker dialog cancelled\n", i);
|
||
|
}
|
||
|
else if (SUCCEEDED(hr))
|
||
|
{
|
||
|
DumpDsSelectedItemList(pDsSelList,
|
||
|
cRequestedAttributes,
|
||
|
aptzRequestedAttributes);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
printf("Invocation %u: Computer picker dialog failed 0x%x\n", i, hr);
|
||
|
}
|
||
|
|
||
|
FreeDsSelectionList(pDsSelList);
|
||
|
pDsSelList = NULL;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
printf("unexpected api setting\n");
|
||
|
}
|
||
|
|
||
|
CoUninitialize();
|
||
|
}
|
||
|
|
||
|
|
||
|
LPWSTR apwzAttr[100];
|
||
|
|
||
|
BOOL
|
||
|
ParseFetchSwitch(
|
||
|
char *pzFetchSwitch,
|
||
|
PULONG pcRequestedAttributes,
|
||
|
LPCTSTR **paptzRequestedAttributes)
|
||
|
{
|
||
|
BOOL fOk = TRUE;
|
||
|
|
||
|
pzFetchSwitch += 2; // advance past '/' (or '-') and 'G'
|
||
|
|
||
|
if (*pzFetchSwitch != ':')
|
||
|
{
|
||
|
printf("expected : after /Fetch\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
*pcRequestedAttributes = 0;
|
||
|
*paptzRequestedAttributes = NULL;
|
||
|
|
||
|
ULONG cRequestedAttributes = 0;
|
||
|
|
||
|
for (pzFetchSwitch++; *pzFetchSwitch && fOk; pzFetchSwitch++)
|
||
|
{
|
||
|
if (*pzFetchSwitch == ',')
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
PSTR pzComma = strchr(pzFetchSwitch, ',');
|
||
|
|
||
|
if (pzComma)
|
||
|
{
|
||
|
*pzComma = '\0';
|
||
|
}
|
||
|
|
||
|
ULONG cchFetchSwitch = strlen(pzFetchSwitch) + 1;
|
||
|
|
||
|
// BUGBUG this is leaked. (who cares?)
|
||
|
apwzAttr[cRequestedAttributes] = new WCHAR[cchFetchSwitch];
|
||
|
if (!apwzAttr[cRequestedAttributes])
|
||
|
{
|
||
|
printf("out of memory\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
AnsiToUnicode(apwzAttr[cRequestedAttributes],
|
||
|
pzFetchSwitch,
|
||
|
cchFetchSwitch);
|
||
|
cRequestedAttributes++;
|
||
|
|
||
|
if (pzComma)
|
||
|
{
|
||
|
*pzComma = ',';
|
||
|
pzFetchSwitch = pzComma;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pzFetchSwitch += strlen(pzFetchSwitch) - 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (cRequestedAttributes && fOk)
|
||
|
{
|
||
|
*pcRequestedAttributes = cRequestedAttributes;
|
||
|
*paptzRequestedAttributes = (LPCTSTR *)apwzAttr;
|
||
|
}
|
||
|
|
||
|
return fOk;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
ParseGroupSwitch(
|
||
|
char *pzGroupSwitch,
|
||
|
ULONG *pulFlags)
|
||
|
{
|
||
|
BOOL fOk = TRUE;
|
||
|
|
||
|
pzGroupSwitch += 2; // advance past '/' (or '-') and 'G'
|
||
|
|
||
|
if (*pzGroupSwitch != ':')
|
||
|
{
|
||
|
printf("expected : after /Groups\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
for (pzGroupSwitch++; *pzGroupSwitch && fOk; pzGroupSwitch++)
|
||
|
{
|
||
|
if (*pzGroupSwitch == ',')
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
PSTR pzComma = strchr(pzGroupSwitch, ',');
|
||
|
|
||
|
if (pzComma)
|
||
|
{
|
||
|
*pzComma = '\0';
|
||
|
}
|
||
|
|
||
|
BOOL fMatch = FALSE;
|
||
|
|
||
|
if (!lstrcmpiA(pzGroupSwitch, "all"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_ALL_GROUPS;
|
||
|
}
|
||
|
|
||
|
if (pzComma)
|
||
|
{
|
||
|
*pzComma = ',';
|
||
|
}
|
||
|
|
||
|
if (fMatch)
|
||
|
{
|
||
|
if (pzComma)
|
||
|
{
|
||
|
pzGroupSwitch = pzComma;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pzGroupSwitch += strlen(pzGroupSwitch) - 1;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (tolower(*pzGroupSwitch))
|
||
|
{
|
||
|
case 'u':
|
||
|
if (tolower(pzGroupSwitch[1]) == 's')
|
||
|
{
|
||
|
*pulFlags |= UGOP_UNIVERSAL_GROUPS_SE;
|
||
|
pzGroupSwitch++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*pulFlags |= UGOP_UNIVERSAL_GROUPS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'a':
|
||
|
if (tolower(pzGroupSwitch[1]) == 's')
|
||
|
{
|
||
|
*pulFlags |= UGOP_ACCOUNT_GROUPS_SE;
|
||
|
pzGroupSwitch++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*pulFlags |= UGOP_ACCOUNT_GROUPS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'r':
|
||
|
if (tolower(pzGroupSwitch[1]) == 's')
|
||
|
{
|
||
|
*pulFlags |= UGOP_RESOURCE_GROUPS_SE;
|
||
|
pzGroupSwitch++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*pulFlags |= UGOP_RESOURCE_GROUPS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'l':
|
||
|
*pulFlags |= UGOP_LOCAL_GROUPS;
|
||
|
break;
|
||
|
|
||
|
case 'g':
|
||
|
*pulFlags |= UGOP_GLOBAL_GROUPS;
|
||
|
break;
|
||
|
|
||
|
case 'b':
|
||
|
*pulFlags |= UGOP_BUILTIN_GROUPS;
|
||
|
break;
|
||
|
|
||
|
case 'w':
|
||
|
*pulFlags |= UGOP_WELL_KNOWN_PRINCIPALS_USERS;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
fOk = FALSE;
|
||
|
printf("unexpected character '%c' in group type switch\n",
|
||
|
*pzGroupSwitch);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
printf("flags = 0x%x\n", *pulFlags);
|
||
|
return fOk;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
ParseUserSwitch(
|
||
|
char *pzUserSwitch,
|
||
|
ULONG *pulFlags)
|
||
|
{
|
||
|
BOOL fOk = TRUE;
|
||
|
|
||
|
pzUserSwitch += 2; // advance past '/' (or '-') and 'U'
|
||
|
|
||
|
if (*pzUserSwitch != ':')
|
||
|
{
|
||
|
printf("expected : after /Users\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
for (pzUserSwitch++; *pzUserSwitch && fOk; pzUserSwitch++)
|
||
|
{
|
||
|
if (*pzUserSwitch == ',')
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
PSTR pzComma = strchr(pzUserSwitch, ',');
|
||
|
|
||
|
if (pzComma)
|
||
|
{
|
||
|
*pzComma = '\0';
|
||
|
}
|
||
|
|
||
|
BOOL fMatch = FALSE;
|
||
|
|
||
|
if (!lstrcmpiA(pzUserSwitch, "all"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_ALL_USERS;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "world"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_WORLD;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "authenticated"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_AUTHENTICATED_USER;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "anonymous"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_ANONYMOUS;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "dialup"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_DIALUP;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "network"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_NETWORK;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "Batch"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_BATCH;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "Interactive"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_INTERACTIVE;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "Service"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_SERVICE;
|
||
|
}
|
||
|
else if (!lstrcmpiA(pzUserSwitch, "System"))
|
||
|
{
|
||
|
fMatch = TRUE;
|
||
|
*pulFlags |= UGOP_USER_SYSTEM;
|
||
|
}
|
||
|
|
||
|
if (pzComma)
|
||
|
{
|
||
|
*pzComma = ',';
|
||
|
}
|
||
|
|
||
|
if (fMatch)
|
||
|
{
|
||
|
if (pzComma)
|
||
|
{
|
||
|
pzUserSwitch = pzComma;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pzUserSwitch += strlen(pzUserSwitch) - 1;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (tolower(*pzUserSwitch))
|
||
|
{
|
||
|
case 'u':
|
||
|
*pulFlags |= UGOP_USERS;
|
||
|
break;
|
||
|
|
||
|
case 'c':
|
||
|
*pulFlags |= UGOP_CONTACTS;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
fOk = FALSE;
|
||
|
printf("unexpected character '%c' in user type switch\n",
|
||
|
*pzUserSwitch);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pzComma)
|
||
|
{
|
||
|
pzUserSwitch = pzComma;
|
||
|
}
|
||
|
}
|
||
|
return fOk;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
ParseInitialScope(
|
||
|
char *pzScopeSwitch,
|
||
|
ULONG *pulFlags)
|
||
|
{
|
||
|
PSTR pzCur = strchr(pzScopeSwitch, ':');
|
||
|
|
||
|
if (!pzCur)
|
||
|
{
|
||
|
printf("expected : after /InitialScope switch\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// advance past colon
|
||
|
|
||
|
pzCur++;
|
||
|
|
||
|
switch (tolower(*pzCur))
|
||
|
{
|
||
|
case 'c':
|
||
|
*pulFlags |= DSOP_SCOPE_SPECIFIED_MACHINE;
|
||
|
break;
|
||
|
|
||
|
case 'd':
|
||
|
*pulFlags |= DSOP_SCOPE_SPECIFIED_DOMAIN;
|
||
|
break;
|
||
|
|
||
|
case 'g':
|
||
|
*pulFlags |= DSOP_SCOPE_DIRECTORY;
|
||
|
break;
|
||
|
|
||
|
case 't':
|
||
|
*pulFlags |= DSOP_SCOPE_DOMAIN_TREE;
|
||
|
break;
|
||
|
|
||
|
case 'x':
|
||
|
*pulFlags |= DSOP_SCOPE_EXTERNAL_TRUSTED_DOMAINS;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
printf("invalid /InitialScope switch\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
ParseProviders(
|
||
|
char *pzProviderSwitch,
|
||
|
PULONG pcAcceptableProviders,
|
||
|
LPCWSTR **paptzAcceptableProviders)
|
||
|
{
|
||
|
BOOL fOk = TRUE;
|
||
|
PSTR pzCur = strchr(pzProviderSwitch, ':');
|
||
|
static LPCWSTR apwzProviders[3];
|
||
|
|
||
|
*paptzAcceptableProviders = apwzProviders;
|
||
|
|
||
|
if (!pzCur)
|
||
|
{
|
||
|
printf("expected : after /Providers switch\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
ZeroMemory(apwzProviders, sizeof apwzProviders);
|
||
|
|
||
|
// advance past colon
|
||
|
|
||
|
pzCur++;
|
||
|
while (*pzCur)
|
||
|
{
|
||
|
switch (tolower(*pzCur))
|
||
|
{
|
||
|
case 'w':
|
||
|
apwzProviders[(*pcAcceptableProviders)++] = L"WinNT";
|
||
|
break;
|
||
|
|
||
|
case 'l':
|
||
|
apwzProviders[(*pcAcceptableProviders)++] = L"LDAP";
|
||
|
break;
|
||
|
|
||
|
case 'g':
|
||
|
apwzProviders[(*pcAcceptableProviders)++] = L"GC";
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
printf("invalid provider switch\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
for (; *pzCur && *pzCur != ','; pzCur++)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
if (*pzCur == ',')
|
||
|
{
|
||
|
pzCur++;
|
||
|
}
|
||
|
|
||
|
// ignore extras
|
||
|
if (*pcAcceptableProviders == 3)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fOk;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
ParseRunCount(
|
||
|
char *pzRunCountSwitch,
|
||
|
PULONG pcRunCount)
|
||
|
{
|
||
|
PSTR pzCur = strchr(pzRunCountSwitch, ':');
|
||
|
|
||
|
if (!pzCur)
|
||
|
{
|
||
|
printf("expected : after /Runcount switch\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
*pcRunCount = (ULONG) atol(pzCur + 1);
|
||
|
|
||
|
if (!*pcRunCount || *pcRunCount > 10)
|
||
|
{
|
||
|
printf("Invalid run count %u\n", *pcRunCount);
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
ParseScope(
|
||
|
char *pzScopeSwitch,
|
||
|
ULONG *pulFlags,
|
||
|
PWSTR wzComputer,
|
||
|
PWSTR wzDomain)
|
||
|
{
|
||
|
PSTR pzCur = strchr(pzScopeSwitch, ':');
|
||
|
|
||
|
if (!pzCur)
|
||
|
{
|
||
|
printf("expected : after /Scope switch\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// advance past colon
|
||
|
|
||
|
pzCur++;
|
||
|
|
||
|
while (*pzCur)
|
||
|
{
|
||
|
switch (tolower(*pzCur))
|
||
|
{
|
||
|
case 'c':
|
||
|
{
|
||
|
*pulFlags |= DSOP_SCOPE_SPECIFIED_MACHINE;
|
||
|
|
||
|
// find '='
|
||
|
|
||
|
for (PSTR pzEqual = pzCur;
|
||
|
*pzEqual && *pzEqual != ',' && *pzEqual != '=';
|
||
|
pzEqual++)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
if (*pzEqual == '=')
|
||
|
{
|
||
|
CHAR szTemp[MAX_PATH];
|
||
|
|
||
|
strcpy(szTemp, pzEqual + 1);
|
||
|
LPSTR pzComma = strchr(szTemp, ',');
|
||
|
|
||
|
if (pzComma)
|
||
|
{
|
||
|
*pzComma = '\0';
|
||
|
}
|
||
|
AnsiToUnicode(wzComputer, szTemp, MAX_PATH);
|
||
|
pzCur = pzEqual + 1;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 'd':
|
||
|
{
|
||
|
*pulFlags |= DSOP_SCOPE_SPECIFIED_DOMAIN;
|
||
|
|
||
|
for (PSTR pzEqual = pzCur;
|
||
|
*pzEqual && *pzEqual != ',' && *pzEqual != '=';
|
||
|
pzEqual++)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
if (*pzEqual == '=')
|
||
|
{
|
||
|
CHAR szTemp[MAX_PATH];
|
||
|
LPSTR pzOpenQuote = strchr(pzEqual, '\'');
|
||
|
|
||
|
if (!pzOpenQuote)
|
||
|
{
|
||
|
printf("Expected single quote after = in domain spec\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
LPSTR pzCloseQuote = strchr(pzOpenQuote + 1, '\'');
|
||
|
|
||
|
if (!pzCloseQuote)
|
||
|
{
|
||
|
printf("Expected closing single quote after = in domain spec\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
CopyMemory(szTemp, pzOpenQuote + 1, pzCloseQuote - pzOpenQuote - 1);
|
||
|
szTemp[pzCloseQuote - pzOpenQuote - 1] = '\0';
|
||
|
AnsiToUnicode(wzDomain, szTemp, MAX_PATH);
|
||
|
pzCur = pzCloseQuote + 1;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 'g':
|
||
|
*pulFlags |= DSOP_SCOPE_DIRECTORY;
|
||
|
break;
|
||
|
|
||
|
case 't':
|
||
|
*pulFlags |= DSOP_SCOPE_DOMAIN_TREE;
|
||
|
break;
|
||
|
|
||
|
case 'x':
|
||
|
*pulFlags |= DSOP_SCOPE_EXTERNAL_TRUSTED_DOMAINS;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
printf("invalid scope switch\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
for (; *pzCur && *pzCur != ','; pzCur++)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
if (*pzCur == ',')
|
||
|
{
|
||
|
pzCur++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+--------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: DumpDsSelectedItemList
|
||
|
//
|
||
|
// Synopsis: Dump the contents of the list of DS items the user selected
|
||
|
// to the console.
|
||
|
//
|
||
|
// Arguments: [pDsSelList] - list of Ds items
|
||
|
//
|
||
|
// History: 11-04-1997 DavidMun Created
|
||
|
//
|
||
|
//---------------------------------------------------------------------------
|
||
|
|
||
|
VOID
|
||
|
DumpDsSelectedItemList(
|
||
|
PDSSELECTIONLIST pDsSelList,
|
||
|
ULONG cRequestedAttributes,
|
||
|
LPCTSTR *aptzRequestedAttributes)
|
||
|
{
|
||
|
if (!pDsSelList)
|
||
|
{
|
||
|
printf("List is empty\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ULONG i;
|
||
|
PDSSELECTION pCur = &pDsSelList->aDsSelection[0];
|
||
|
|
||
|
for (i = 0; i < pDsSelList->cItems; i++, pCur++)
|
||
|
{
|
||
|
printf("Name: %ws\n", pCur->pwzName);
|
||
|
printf("Class: %ws\n", pCur->pwzClass);
|
||
|
printf("Path: %ws\n", pCur->pwzADsPath);
|
||
|
printf("UPN: %ws\n", pCur->pwzUPN);
|
||
|
|
||
|
for (ULONG j = 0; j < cRequestedAttributes; j++)
|
||
|
{
|
||
|
printf("Attr %02u: %ws = %ws\n",
|
||
|
j,
|
||
|
aptzRequestedAttributes[j],
|
||
|
VariantString(&pCur->pvarOtherAttributes[j]));
|
||
|
}
|
||
|
printf("\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
LPWSTR
|
||
|
VariantString(
|
||
|
VARIANT *pvar)
|
||
|
{
|
||
|
static WCHAR wzBuf[1024];
|
||
|
|
||
|
wzBuf[0] = L'\0';
|
||
|
|
||
|
switch (pvar->vt)
|
||
|
{
|
||
|
case VT_EMPTY:
|
||
|
lstrcpy(wzBuf, L"VT_EMPTY");
|
||
|
break;
|
||
|
|
||
|
case VT_NULL:
|
||
|
lstrcpy(wzBuf, L"VT_NULL");
|
||
|
break;
|
||
|
|
||
|
case VT_I2:
|
||
|
wsprintf(wzBuf, L"%d", V_I2(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_I4:
|
||
|
wsprintf(wzBuf, L"%d", V_I4(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_R4:
|
||
|
wsprintf(wzBuf, L"%f", V_R4(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_R8:
|
||
|
wsprintf(wzBuf, L"%f", V_R8(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_CY:
|
||
|
wsprintf(wzBuf, L"$%f", V_CY(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_DATE:
|
||
|
wsprintf(wzBuf, L"date %f", V_DATE(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_BSTR:
|
||
|
if (V_BSTR(pvar))
|
||
|
{
|
||
|
wsprintf(wzBuf, L"'%ws'", V_BSTR(pvar));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lstrcpy(wzBuf, L"VT_BSTR NULL");
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case VT_DISPATCH:
|
||
|
wsprintf(wzBuf, L"VT_DISPATCH 0x%x", V_DISPATCH(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_UNKNOWN:
|
||
|
wsprintf(wzBuf, L"VT_UNKNOWN 0x%x", V_UNKNOWN(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_ERROR:
|
||
|
case VT_HRESULT:
|
||
|
wsprintf(wzBuf, L"hr 0x%x", V_ERROR(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_BOOL:
|
||
|
wsprintf(wzBuf, L"%s", V_BOOL(pvar) ? "TRUE" : "FALSE");
|
||
|
break;
|
||
|
|
||
|
case VT_VARIANT:
|
||
|
wsprintf(wzBuf, L"variant 0x%x", V_VARIANTREF(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_DECIMAL:
|
||
|
lstrcpy(wzBuf, L"VT_DECIMAL");
|
||
|
break;
|
||
|
|
||
|
case VT_I1:
|
||
|
wsprintf(wzBuf, L"%d", V_I1(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_UI1:
|
||
|
wsprintf(wzBuf, L"%u", V_UI1(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_UI2:
|
||
|
wsprintf(wzBuf, L"%u", V_UI2(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_UI4:
|
||
|
wsprintf(wzBuf, L"%u", V_UI4(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_I8:
|
||
|
lstrcpy(wzBuf, L"VT_I8");
|
||
|
break;
|
||
|
|
||
|
case VT_UI8:
|
||
|
lstrcpy(wzBuf, L"VT_UI8");
|
||
|
break;
|
||
|
|
||
|
case VT_INT:
|
||
|
wsprintf(wzBuf, L"%d", V_INT(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_UINT:
|
||
|
wsprintf(wzBuf, L"%u", V_UINT(pvar));
|
||
|
break;
|
||
|
|
||
|
case VT_VOID:
|
||
|
lstrcpy(wzBuf, L"VT_VOID");
|
||
|
break;
|
||
|
|
||
|
case VT_UI1 | VT_ARRAY:
|
||
|
VarArrayToStr(pvar, wzBuf, ARRAYLEN(wzBuf));
|
||
|
break;
|
||
|
|
||
|
case VT_PTR:
|
||
|
case VT_SAFEARRAY:
|
||
|
case VT_CARRAY:
|
||
|
case VT_USERDEFINED:
|
||
|
case VT_LPSTR:
|
||
|
case VT_LPWSTR:
|
||
|
case VT_RECORD:
|
||
|
case VT_FILETIME:
|
||
|
case VT_BLOB:
|
||
|
case VT_STREAM:
|
||
|
case VT_STORAGE:
|
||
|
case VT_STREAMED_OBJECT:
|
||
|
case VT_STORED_OBJECT:
|
||
|
case VT_BLOB_OBJECT:
|
||
|
case VT_CF:
|
||
|
case VT_CLSID:
|
||
|
case VT_BSTR_BLOB:
|
||
|
default:
|
||
|
wsprintf(wzBuf, L"VT 0x%x", V_VT(pvar));
|
||
|
break;
|
||
|
}
|
||
|
return wzBuf;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void
|
||
|
VarArrayToStr(
|
||
|
VARIANT *pvar,
|
||
|
LPWSTR wzBuf,
|
||
|
ULONG cchBuf)
|
||
|
{
|
||
|
ULONG i;
|
||
|
LPWSTR pwzNext = wzBuf;
|
||
|
LPWSTR pwzEnd = wzBuf + cchBuf;
|
||
|
|
||
|
for (i = 0; i < pvar->parray->rgsabound[0].cElements && pwzNext < pwzEnd + 6; i++)
|
||
|
{
|
||
|
wsprintf(pwzNext, L"x%02x ", ((LPBYTE)(pvar->parray->pvData))[i]);
|
||
|
pwzNext += lstrlen(pwzNext);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
VOID
|
||
|
Usage()
|
||
|
{
|
||
|
printf("\n");
|
||
|
printf("Usage: opt <switches>\n");
|
||
|
printf("\n");
|
||
|
printf("Switches are:\n");
|
||
|
printf(" /Computers[:Api]\n");
|
||
|
printf(" If API is specified, GetComputerSelection is called, otherwise\n");
|
||
|
printf(" this means include computer objects in the query\n");
|
||
|
printf(" /Domain:{S|O}\n");
|
||
|
printf(" If S is specified, then any following /Users, /Groups, or\n");
|
||
|
printf(" /Computers switches apply only to the specified domain scope.\n");
|
||
|
printf(" Otherwise, those switches apply to all other scopes\n");
|
||
|
printf(" /Fetch:<attr>[,<attr>]...\n");
|
||
|
printf(" <attr> is the name of an attribute to retrieve\n");
|
||
|
printf(" /Groups:<group-type>[,<group-type>]...\n");
|
||
|
printf(" <group-type> is one of:\n");
|
||
|
printf(" ALL - same as specifiying each of the following\n");
|
||
|
printf(" U - Universal\n");
|
||
|
printf(" A - Account\n");
|
||
|
printf(" R - Resource\n");
|
||
|
printf(" US - Universal Security Enabled\n");
|
||
|
printf(" AS - Account Security Enabled\n");
|
||
|
printf(" RS - Resource Security Enabled\n");
|
||
|
printf(" L - Local\n");
|
||
|
printf(" G - Global\n");
|
||
|
printf(" B - Builtin\n");
|
||
|
printf(" W - Well-Known Security Principals\n");
|
||
|
printf(" /Hidden\n");
|
||
|
printf(" Include objects hidden from address book.\n");
|
||
|
printf(" /Initialscope:{Computer|Domain|Gc|Tree|Xternal}\n");
|
||
|
printf(" /Multiselect\n");
|
||
|
printf(" /Namerestrict - reject names in unknown domains\n");
|
||
|
printf(" /Providers:<provider>[,<provider>]\n");
|
||
|
printf(" <provider> is one of:\n");
|
||
|
printf(" WINNT\n");
|
||
|
printf(" LDAP\n");
|
||
|
printf(" GC\n");
|
||
|
printf(" /Runcount:<n>\n");
|
||
|
printf(" <n> is number of times to invoke object picker\n");
|
||
|
printf(" /Scopes:<scope-spec>[,<scope-spec>]...\n");
|
||
|
printf(" <scope-spec> is one of:\n");
|
||
|
printf(" Computer[=name]\n");
|
||
|
printf(" Domain[='<domain-spec>']\n");
|
||
|
printf(" Gc\n");
|
||
|
printf(" Tree\n");
|
||
|
printf(" Xternal\n");
|
||
|
printf(" <domain-spec> is one of:\n");
|
||
|
printf(" <path> - the ADs path of a DS namespace\n");
|
||
|
printf(" <name> - the flat name of a downlevel domain\n");
|
||
|
printf(" /Users:<user-type>[,<user-type>]...\n");
|
||
|
printf(" <user-type> is one of:\n");
|
||
|
printf(" ALL - same as specifying each of the following\n");
|
||
|
printf(" U - User\n");
|
||
|
printf(" C - Contact\n");
|
||
|
printf(" WORLD\n");
|
||
|
printf(" AUTHENTICATED\n");
|
||
|
printf(" ANONYMOUS\n");
|
||
|
printf(" DIALUP\n");
|
||
|
printf(" NETWORK\n");
|
||
|
printf(" BATCH\n");
|
||
|
printf(" INTERACTIVE\n");
|
||
|
printf(" SERVICE\n");
|
||
|
printf(" SYSTEM\n");
|
||
|
printf(" /Xternal - force conversion of external paths to SIDs\n");
|
||
|
printf("\n");
|
||
|
printf("You must specify the Scope switch and at least one of the Computers,\n");
|
||
|
printf("Groups, or Users switches.\n");
|
||
|
printf("The significant characters in the switches are shown in upper case.\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: AnsiToUnicode
|
||
|
//
|
||
|
// Synopsis: Convert ANSI string [szFrom] to Unicode string in buffer
|
||
|
// [pwszTo].
|
||
|
//
|
||
|
// Arguments: [pwszTo] - destination buffer
|
||
|
// [szFrom] - source string
|
||
|
// [cchTo] - size of destination buffer, in WCHARS
|
||
|
//
|
||
|
// Returns: S_OK - conversion succeeded
|
||
|
// HRESULT_FROM_WIN32 - MultiByteToWideChar failed
|
||
|
//
|
||
|
// Modifies: *[pwszTo]
|
||
|
//
|
||
|
// History: 10-29-96 DavidMun Created
|
||
|
//
|
||
|
// Notes: The string in [pwszTo] will be NULL terminated even on
|
||
|
// failure.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
HRESULT
|
||
|
AnsiToUnicode(
|
||
|
LPWSTR pwszTo,
|
||
|
LPCSTR szFrom,
|
||
|
LONG cchTo)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
ULONG cchWritten;
|
||
|
|
||
|
cchWritten = MultiByteToWideChar(CP_ACP, 0, szFrom, -1, pwszTo, cchTo);
|
||
|
|
||
|
if (!cchWritten)
|
||
|
{
|
||
|
pwszTo[cchTo - 1] = L'\0'; // ensure NULL termination
|
||
|
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
printf("AnsiToUnicode: MultiByteToWideChar hr=0x%x\n", hr);
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|