You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
698 lines
18 KiB
698 lines
18 KiB
/*++
|
|
|
|
Copyright (c) 1997-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
rnduser.cpp
|
|
|
|
Abstract:
|
|
|
|
This module contains implementation of CUser object.
|
|
|
|
Author:
|
|
|
|
Rajeevb,
|
|
|
|
Modification history:
|
|
|
|
Mu Han (muhan) 12-5-1997
|
|
|
|
--*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include "rnduser.h"
|
|
|
|
const WCHAR * const UserAttributeNames[] =
|
|
{
|
|
L"SamAccountName", // ZoltanS: was "cn" -- we need SamAccountName for ntds.
|
|
L"telephoneNumber",
|
|
L"IPPhone"
|
|
};
|
|
|
|
#define INC_ACCESS_ACL_SIZE(_SIZE_, _SID_) \
|
|
_SIZE_ += (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(_SID_));
|
|
|
|
#define BAIL_ON_BOOLFAIL(_FN_) \
|
|
if ( !_FN_ ) \
|
|
{ \
|
|
hr = HRESULT_FROM_WIN32(GetLastError()); \
|
|
goto failed; \
|
|
}
|
|
|
|
#define ACCESS_READ 0x10
|
|
#define ACCESS_WRITE 0x20
|
|
#define ACCESS_MODIFY (ACCESS_WRITE | WRITE_DAC)
|
|
#define ACCESS_DELETE DELETE
|
|
|
|
#define ACCESS_ALL (ACCESS_READ | ACCESS_MODIFY | ACCESS_DELETE)
|
|
|
|
|
|
|
|
HRESULT
|
|
ConvertStringToSid(
|
|
IN PWSTR string,
|
|
OUT PSID *sid,
|
|
OUT PDWORD pdwSidSize,
|
|
OUT PWSTR *end
|
|
);
|
|
|
|
HRESULT
|
|
ConvertACLToVariant(
|
|
PACL pACL,
|
|
LPVARIANT pvarACL
|
|
);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// non-interface class methods
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CUser::Init(BSTR bName)
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = SetSingleValue(UA_USERNAME, bName);
|
|
BAIL_IF_FAIL(hr, "can't set user name");
|
|
|
|
hr = SetDefaultSD();
|
|
BAIL_IF_FAIL(hr, "Init the security descriptor");
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
CUser::GetSingleValueBstr(
|
|
IN OBJECT_ATTRIBUTE Attribute,
|
|
OUT BSTR * AttributeValue
|
|
)
|
|
{
|
|
LOG((MSP_INFO, "CUser::GetSingleValueBstr - entered"));
|
|
|
|
BAIL_IF_BAD_WRITE_PTR(AttributeValue, E_POINTER);
|
|
|
|
if (!ValidUserAttribute(Attribute))
|
|
{
|
|
LOG((MSP_ERROR, "Invalid Attribute, %d", Attribute));
|
|
return E_FAIL;
|
|
}
|
|
|
|
CLock Lock(m_lock);
|
|
if(!m_Attributes[UserAttrIndex(Attribute)])
|
|
{
|
|
LOG((MSP_ERROR, "Attribute %S is not found",
|
|
UserAttributeName(Attribute)));
|
|
return E_FAIL;
|
|
}
|
|
|
|
*AttributeValue = SysAllocString(m_Attributes[UserAttrIndex(Attribute)]);
|
|
if (*AttributeValue == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
LOG((MSP_INFO, "CUser::get %S: %S",
|
|
UserAttributeName(Attribute), *AttributeValue));
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CUser::SetSingleValue(
|
|
IN OBJECT_ATTRIBUTE Attribute,
|
|
IN WCHAR * AttributeValue
|
|
)
|
|
{
|
|
LOG((MSP_INFO, "CUser::SetSingleValue - entered"));
|
|
|
|
if (!ValidUserAttribute(Attribute))
|
|
{
|
|
LOG((MSP_ERROR, "Invalid Attribute, %d", Attribute));
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (AttributeValue != NULL)
|
|
{
|
|
BAIL_IF_BAD_READ_PTR(AttributeValue, E_POINTER);
|
|
}
|
|
|
|
CLock Lock(m_lock);
|
|
if (!m_Attributes[UserAttrIndex(Attribute)].set(AttributeValue))
|
|
{
|
|
LOG((MSP_ERROR, "Can not add attribute %S",
|
|
UserAttributeName(Attribute)));
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
LOG((MSP_INFO, "CUser::set %S to %S",
|
|
UserAttributeName(Attribute), AttributeValue));
|
|
return S_OK;
|
|
}
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set the right security descriptor for the conference.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
HRESULT.
|
|
|
|
--*/
|
|
HRESULT
|
|
CUser::SetDefaultSD()
|
|
{
|
|
LOG((MSP_INFO, "CConference::SetDefaultSD - entered"));
|
|
|
|
//
|
|
// The security descriptor
|
|
//
|
|
|
|
IADsSecurityDescriptor* pSecDesc = NULL;
|
|
|
|
HRESULT hr = S_OK;
|
|
bool bOwner = false, bWorld = false;
|
|
PACL pACL = NULL;
|
|
PSID pSidWorld = NULL;
|
|
DWORD dwAclSize = sizeof(ACL), dwTemp;
|
|
BSTR bstrTemp = NULL;
|
|
LPWSTR pszTemp = NULL;
|
|
|
|
HANDLE hToken;
|
|
UCHAR *pInfoBuffer = NULL;
|
|
DWORD cbInfoBuffer = 512;
|
|
|
|
//
|
|
// Try to get the thread or process token
|
|
//
|
|
|
|
if( !OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken) )
|
|
{
|
|
//
|
|
// If there was a sever error we exit
|
|
//
|
|
|
|
if( GetLastError() != ERROR_NO_TOKEN )
|
|
{
|
|
LOG((MSP_ERROR, "CConference::SetDefaultSD - exit E_FAIL "
|
|
"OpenThreadToken failed!"));
|
|
return E_FAIL;
|
|
}
|
|
|
|
//
|
|
// Attempt to open the process token, since no thread token exists
|
|
//
|
|
|
|
if( !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken) )
|
|
{
|
|
LOG((MSP_ERROR, "CConference::SetDefaultSD - exit E_FAIL "
|
|
"OpenProcessToken failed"));
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop until we have a large enough structure
|
|
//
|
|
|
|
while ( (pInfoBuffer = new UCHAR[cbInfoBuffer]) != NULL )
|
|
{
|
|
if ( !GetTokenInformation(hToken, TokenUser, pInfoBuffer, cbInfoBuffer, &cbInfoBuffer) )
|
|
{
|
|
delete pInfoBuffer;
|
|
pInfoBuffer = NULL;
|
|
|
|
if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER )
|
|
return E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
CloseHandle(hToken);
|
|
|
|
//
|
|
// Did we get the owner ACL?
|
|
//
|
|
|
|
if ( pInfoBuffer )
|
|
{
|
|
INC_ACCESS_ACL_SIZE( dwAclSize, ((PTOKEN_USER) pInfoBuffer)->User.Sid );
|
|
bOwner = true;
|
|
}
|
|
|
|
//
|
|
// Make SID for "Everyone"
|
|
//
|
|
|
|
SysReAllocString( &bstrTemp, L"S-1-1-0" );
|
|
hr = ConvertStringToSid( bstrTemp, &pSidWorld, &dwTemp, &pszTemp );
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
INC_ACCESS_ACL_SIZE( dwAclSize, pSidWorld );
|
|
bWorld = true;
|
|
}
|
|
|
|
//
|
|
// Create a security descriptor
|
|
//
|
|
|
|
hr = CoCreateInstance(
|
|
CLSID_SecurityDescriptor,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IADsSecurityDescriptor,
|
|
(void **)&pSecDesc
|
|
);
|
|
if( FAILED(hr) )
|
|
{
|
|
|
|
LOG((MSP_ERROR, "CConference::SetDefaultSD - exit 0x%08x "
|
|
"Create security descriptor failed!", hr));
|
|
|
|
goto failed;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Create the ACL containing the Owner and World ACEs
|
|
//
|
|
|
|
pACL = (PACL) new BYTE[dwAclSize];
|
|
if ( pACL )
|
|
{
|
|
BAIL_ON_BOOLFAIL( InitializeAcl(pACL, dwAclSize, ACL_REVISION) );
|
|
|
|
// Add World Rights
|
|
if ( bWorld )
|
|
{
|
|
if ( bOwner )
|
|
{
|
|
BAIL_ON_BOOLFAIL( AddAccessAllowedAce(pACL, ACL_REVISION, ACCESS_READ, pSidWorld) );
|
|
}
|
|
else
|
|
{
|
|
BAIL_ON_BOOLFAIL( AddAccessAllowedAce(pACL, ACL_REVISION, ACCESS_ALL , pSidWorld) );
|
|
}
|
|
}
|
|
|
|
// Add Creator rights
|
|
if ( bOwner )
|
|
BAIL_ON_BOOLFAIL( AddAccessAllowedAce(pACL, ACL_REVISION, ACCESS_ALL, ((PTOKEN_USER) pInfoBuffer)->User.Sid) );
|
|
|
|
|
|
// Set the DACL onto our security descriptor
|
|
VARIANT varDACL;
|
|
VariantInit( &varDACL );
|
|
if ( SUCCEEDED(hr = ConvertACLToVariant((PACL) pACL, &varDACL)) )
|
|
{
|
|
if ( SUCCEEDED(hr = pSecDesc->put_DaclDefaulted(FALSE)) )
|
|
{
|
|
hr = pSecDesc->put_DiscretionaryAcl( V_DISPATCH(&varDACL) );
|
|
if( SUCCEEDED(hr) )
|
|
{
|
|
hr = put_SecurityDescriptor((IDispatch*)pSecDesc);
|
|
if( SUCCEEDED(hr) )
|
|
{
|
|
hr = this->put_SecurityDescriptorIsModified(TRUE);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
VariantClear( &varDACL );
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
// Clean up
|
|
failed:
|
|
SysFreeString( bstrTemp );
|
|
if ( pACL ) delete pACL;
|
|
if ( pSidWorld ) delete pSidWorld;
|
|
if ( pInfoBuffer ) delete pInfoBuffer;
|
|
if( pSecDesc ) pSecDesc->Release();
|
|
|
|
LOG((MSP_INFO, "CConference::SetDefaultSD - exit 0x%08x", hr));
|
|
return hr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// ITDirectoryObject
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
STDMETHODIMP CUser::get_Name(BSTR * ppVal)
|
|
{
|
|
return GetSingleValueBstr(UA_USERNAME, ppVal);
|
|
}
|
|
|
|
STDMETHODIMP CUser::put_Name(BSTR pVal)
|
|
{
|
|
return SetSingleValue(UA_USERNAME, pVal);
|
|
}
|
|
|
|
STDMETHODIMP CUser::get_DialableAddrs(
|
|
IN long dwAddressTypes, //defined in tapi.h
|
|
OUT VARIANT * pVariant
|
|
)
|
|
{
|
|
BAIL_IF_BAD_WRITE_PTR(pVariant, E_POINTER);
|
|
|
|
HRESULT hr;
|
|
|
|
BSTR *Addresses = new BSTR[1]; // only one for now.
|
|
BAIL_IF_NULL(Addresses, E_OUTOFMEMORY);
|
|
|
|
switch (dwAddressTypes)
|
|
{
|
|
case LINEADDRESSTYPE_DOMAINNAME:
|
|
hr = GetSingleValueBstr(UA_IPPHONE_PRIMARY, &Addresses[0]);
|
|
break;
|
|
|
|
case LINEADDRESSTYPE_IPADDRESS:
|
|
{
|
|
BSTR pDomainName;
|
|
DWORD dwIP;
|
|
|
|
hr = GetSingleValueBstr(UA_IPPHONE_PRIMARY, &pDomainName);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
hr = ResolveHostName(0, pDomainName, NULL, &dwIP);
|
|
|
|
SysFreeString(pDomainName);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
WCHAR wszIP[20];
|
|
|
|
ipAddressToStringW(wszIP, dwIP);
|
|
|
|
Addresses[0] = SysAllocString(wszIP);
|
|
|
|
if ( Addresses[0] == NULL )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LINEADDRESSTYPE_PHONENUMBER:
|
|
hr = GetSingleValueBstr(UA_TELEPHONE_NUMBER, &Addresses[0]);
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
DWORD dwCount = (FAILED(hr)) ? 0 : 1;
|
|
|
|
hr = ::CreateBstrCollection(dwCount, // count
|
|
&Addresses[0], // begin pointer
|
|
&Addresses[dwCount], // end pointer
|
|
pVariant, // return value
|
|
AtlFlagTakeOwnership); // flags
|
|
|
|
// the collection will destroy the Addresses array eventually.
|
|
// no need to free anything here. Even if we tell it to hand
|
|
// out zero objects, it will delete the array on construction.
|
|
// (ZoltanS verified.)
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CUser::EnumerateDialableAddrs(
|
|
IN DWORD dwAddressTypes, //defined in tapi.h
|
|
OUT IEnumDialableAddrs ** ppEnumDialableAddrs
|
|
)
|
|
{
|
|
BAIL_IF_BAD_WRITE_PTR(ppEnumDialableAddrs, E_POINTER);
|
|
|
|
HRESULT hr;
|
|
|
|
BSTR *Addresses = new BSTR[1]; // only one for now.
|
|
BAIL_IF_NULL(Addresses, E_OUTOFMEMORY);
|
|
|
|
switch (dwAddressTypes)
|
|
{
|
|
case LINEADDRESSTYPE_IPADDRESS:
|
|
{
|
|
BSTR pDomainName;
|
|
DWORD dwIP;
|
|
|
|
hr = GetSingleValueBstr(UA_IPPHONE_PRIMARY, &pDomainName);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
hr = ResolveHostName(0, pDomainName, NULL, &dwIP);
|
|
|
|
SysFreeString(pDomainName);
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
WCHAR wszIP[20];
|
|
|
|
ipAddressToStringW(wszIP, dwIP);
|
|
|
|
Addresses[0] = SysAllocString(wszIP);
|
|
|
|
if ( Addresses[0] == NULL )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LINEADDRESSTYPE_DOMAINNAME:
|
|
hr = GetSingleValueBstr(UA_IPPHONE_PRIMARY, &Addresses[0]);
|
|
break;
|
|
|
|
case LINEADDRESSTYPE_PHONENUMBER:
|
|
hr = GetSingleValueBstr(UA_TELEPHONE_NUMBER, &Addresses[0]);
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
DWORD dwCount = (FAILED(hr)) ? 0 : 1;
|
|
hr = ::CreateDialableAddressEnumerator(
|
|
&Addresses[0],
|
|
&Addresses[dwCount],
|
|
ppEnumDialableAddrs
|
|
);
|
|
|
|
// the enumerator will destroy the Addresses array eventually,
|
|
// so no need to free anything here. Even if we tell it to hand
|
|
// out zero objects, it will delete the array on destruction.
|
|
// (ZoltanS verified.)
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CUser::GetAttribute(
|
|
IN OBJECT_ATTRIBUTE Attribute,
|
|
OUT BSTR * ppAttributeValue
|
|
)
|
|
{
|
|
return GetSingleValueBstr(Attribute, ppAttributeValue);
|
|
}
|
|
|
|
STDMETHODIMP CUser::SetAttribute(
|
|
IN OBJECT_ATTRIBUTE Attribute,
|
|
IN BSTR pAttributeValue
|
|
)
|
|
{
|
|
return SetSingleValue(Attribute, pAttributeValue);
|
|
}
|
|
|
|
STDMETHODIMP CUser::GetTTL(
|
|
OUT DWORD * pdwTTL
|
|
)
|
|
{
|
|
BAIL_IF_BAD_WRITE_PTR(pdwTTL, E_POINTER);
|
|
|
|
*pdwTTL = 0;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// ITDirectoryObjectUser
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
STDMETHODIMP CUser::get_IPPhonePrimary(
|
|
OUT BSTR *pVal
|
|
)
|
|
{
|
|
return GetSingleValueBstr(UA_IPPHONE_PRIMARY, pVal);
|
|
}
|
|
|
|
STDMETHODIMP CUser::put_IPPhonePrimary(
|
|
IN BSTR newVal
|
|
)
|
|
{
|
|
// ZoltanS: we now need to check the BSTR, as the ResolveHostName
|
|
// call below doesn't check it before using it.
|
|
// Second argument is the maximum length of string to check -- we want
|
|
// to check the whole thing, so we say (UINT) -1, which is about 2^32.
|
|
|
|
if ( IsBadStringPtr(newVal, (UINT) -1) )
|
|
{
|
|
LOG((MSP_ERROR, "CUser::put_IPPhonePrimary: bad BSTR"));
|
|
return E_POINTER;
|
|
}
|
|
|
|
// ZoltanS: We shouldn't let the user set an IPPhonePrimary value that
|
|
// doesn't resolve to a known host / IP. Check here.
|
|
char * pchFullDNSName = NULL; // we don't really care what we get
|
|
DWORD dwIp = 0; // we don't really care what we get
|
|
|
|
// This is our utility function from rndutil.cpp.
|
|
HRESULT hr = ResolveHostName(0, newVal, &pchFullDNSName, &dwIp);
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "CUser::put_IPPhonePrimary: unresolvable name"));
|
|
return hr;
|
|
}
|
|
|
|
// Now actually set it.
|
|
return SetSingleValue(UA_IPPHONE_PRIMARY, newVal);
|
|
}
|
|
|
|
typedef IDispatchImpl<ITDirectoryObjectUserVtbl<CUser>, &IID_ITDirectoryObjectUser, &LIBID_RENDLib> CTDirObjUser;
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=
|
|
//
|
|
// CUser::GetIDsOfNames
|
|
//
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=
|
|
STDMETHODIMP CUser::GetIDsOfNames(REFIID riid,
|
|
LPOLESTR* rgszNames,
|
|
UINT cNames,
|
|
LCID lcid,
|
|
DISPID* rgdispid
|
|
)
|
|
{
|
|
LOG((MSP_TRACE, "CUser::GetIDsOfNames[%p] - enter. Name [%S]",this, *rgszNames));
|
|
|
|
|
|
HRESULT hr = DISP_E_UNKNOWNNAME;
|
|
|
|
|
|
|
|
//
|
|
// See if the requsted method belongs to the default interface
|
|
//
|
|
|
|
hr = CTDirObjUser::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LOG((MSP_TRACE, "CUser::GetIDsOfNames - found %S on CTDirObjUser", *rgszNames));
|
|
rgdispid[0] |= IDISPDIROBJUSER;
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// If not, then try the CDirectoryObject base class
|
|
//
|
|
|
|
hr = CDirectoryObject::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LOG((MSP_TRACE, "CUser::GetIDsOfNames - found %S on CDirectoryObject", *rgszNames));
|
|
rgdispid[0] |= IDISPDIROBJECT;
|
|
return hr;
|
|
}
|
|
|
|
LOG((MSP_ERROR, "CUser::GetIDsOfNames[%p] - finish. didn't find %S on our iterfaces",*rgszNames));
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=
|
|
//
|
|
// CUser::Invoke
|
|
//
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=
|
|
STDMETHODIMP CUser::Invoke(DISPID dispidMember,
|
|
REFIID riid,
|
|
LCID lcid,
|
|
WORD wFlags,
|
|
DISPPARAMS* pdispparams,
|
|
VARIANT* pvarResult,
|
|
EXCEPINFO* pexcepinfo,
|
|
UINT* puArgErr
|
|
)
|
|
{
|
|
LOG((MSP_TRACE, "CUser::Invoke[%p] - enter. dispidMember %lx",this, dispidMember));
|
|
|
|
HRESULT hr = DISP_E_MEMBERNOTFOUND;
|
|
DWORD dwInterface = (dispidMember & INTERFACEMASK);
|
|
|
|
|
|
//
|
|
// Call invoke for the required interface
|
|
//
|
|
|
|
switch (dwInterface)
|
|
{
|
|
case IDISPDIROBJUSER:
|
|
{
|
|
hr = CTDirObjUser::Invoke(dispidMember,
|
|
riid,
|
|
lcid,
|
|
wFlags,
|
|
pdispparams,
|
|
pvarResult,
|
|
pexcepinfo,
|
|
puArgErr
|
|
);
|
|
|
|
LOG((MSP_TRACE, "CUser::Invoke - ITDirectoryObjectUser"));
|
|
|
|
break;
|
|
}
|
|
|
|
case IDISPDIROBJECT:
|
|
{
|
|
hr = CDirectoryObject::Invoke(dispidMember,
|
|
riid,
|
|
lcid,
|
|
wFlags,
|
|
pdispparams,
|
|
pvarResult,
|
|
pexcepinfo,
|
|
puArgErr
|
|
);
|
|
|
|
LOG((MSP_TRACE, "CUser::Invoke - ITDirectoryObject"));
|
|
|
|
break;
|
|
}
|
|
|
|
} // end switch (dwInterface)
|
|
|
|
|
|
LOG((MSP_TRACE, "CUser::Invoke[%p] - finish. hr = %lx", hr));
|
|
|
|
return hr;
|
|
}
|
|
|