I am writing a C native method to create an Active Directory user. I am getting a null pointer exception . This code is exactly the same as the code in the official Microsoft documentation.
I have mentioned in a comment on which line I get the error:
HRESULT CreateUserFromADs(
LPCWSTR pwszName,
LPCWSTR pwszSAMAccountName,
LPCWSTR pwszInitialPassword)
{
HRESULT hr;
CoInitialize(NULL);
IADsContainer* pUsers = NULL;
hr = ADsOpenObject(L"LDAP://WIN-F94H2MP3UJR.Test.local/CN=Users,DC=Test,DC=local", L"Administrator", L"Pass@12",
ADS_SECURE_AUTHENTICATION, // For secure authentication
IID_IADs,
(void**)&pUsers);
if (SUCCEEDED(hr))
{
IDispatch* pDisp = NULL;
CComBSTR sbstrName = "CN=";
sbstrName = pwszName;
// Create the new object in the User folder.
hr = pUsers->Create(CComBSTR("user"), sbstrName, &pDisp);
if (SUCCEEDED(hr))
{
IADsUser* padsUser = NULL;
// Get the IADs interface.
// Am getting null pointer exception here.
hr = pDisp->QueryInterface(IID_IADsUser,
(void**)&padsUser);
if (SUCCEEDED(hr))
{
CComBSTR sbstrProp;
/*
The sAMAccountName property is required on operating system
versions prior to Windows Server 2003.
The Windows Server 2003 operating system will create a
sAMAccountName value if one is not specified.
*/
CComVariant svar;
svar = pwszSAMAccountName;
sbstrProp = "sAMAccountName";
hr = padsUser->Put(sbstrProp, svar);
/*
Commit the new user to persistent memory.
The user does not exist until this is called.
*/
hr = padsUser->SetInfo();
/*
Set the initial password. This must be done after
SetInfo is called because the user object must
already exist on the server.
*/
hr = padsUser->SetPassword(CComBSTR(pwszInitialPassword));
/*
Set the pwdLastSet property to zero, which forces the
user to change the password the next time they log on.
*/
sbstrProp = "pwdLastSet";
svar = 0;
hr = padsUser->Put(sbstrProp, svar);
/*
Enable the user account by removing the
ADS_UF_ACCOUNTDISABLE flag from the userAccountControl
property. Also, remove the ADS_UF_PASSWD_NOTREQD and
ADS_UF_DONT_EXPIRE_PASSWD flags from the
userAccountControl property.
*/
svar.Clear();
sbstrProp = "userAccountControl";
hr = padsUser->Get(sbstrProp, &svar);
if (SUCCEEDED(hr))
{
svar = svar.lVal & ~(ADS_UF_ACCOUNTDISABLE |
ADS_UF_PASSWD_NOTREQD |
ADS_UF_DONT_EXPIRE_PASSWD);
hr = padsUser->Put(sbstrProp, svar);
hr = padsUser->SetInfo();
}
hr = padsUser->put_AccountDisabled(VARIANT_FALSE);
hr = padsUser->SetInfo();
padsUser->Release();
}
pDisp->Release();
}
pUsers->Release();
}
CoUninitialize();
return hr;
}
Error message is
Exception thrown: read access violation.
pDisp was nullptr.
CodePudding user response:
You are asking ADsOpenObject()
for an IADs*
interface pointer, but you are storing it in an IADsContainer*
variable. That is a type mismatch, IADs
and IADsContainer
are unrelated interfaces. So, when you call pUsers->Create()
, you are not actually calling IADsContainer::Create()
at all, you are actually calling IADs::SetInfo()
instead, which does not assign anything to pDisp
, which is why it remains NULL.
The Microsoft doc you linked to does not make that mistake, it asks ADsOpenObject()
for IADsContainer
instead of IADs
.
The IID
you request must match the variable type you receive into. So, change IID_IADs
to IID_IADsContainer
:
IADsContainer* pUsers = NULL;
hr = ADsOpenObject(...,
IID_IADsContainer,
(void**)&pUsers);
You should consider using the IID_PPV_ARGS()
macro to avoid making this mistake again:
IADsContainer* pUsers = NULL;
hr = ADsOpenObject(...,
IID_PPV_ARGS(&pUsers));
On a side note, your CreateUserFromADs()
function has no business calling CoInitialize()
directly, so you should remove that call. It is the responsibility of the calling thread to call CoInitialize/Ex()
to establish its concurrency model before performing any COM-related activities. It is not up to functions to decide what concurrency model to use on behalf of the calling thread.