Link to home
Start Free TrialLog in
Avatar of Jim Klocksin
Jim KlocksinFlag for United States of America

asked on

C-Language routine to manipulate Active Directory objects won't compile

I'm attempting to integrate some functionality into a C-Language DLL that I use with my PowerBuilder application in order to manipulate Active Directory users without having to buy an expensive package or do the work manually.  I basically copied the following code (found on more than one web site) and the results of the compile return errors that both the "CreateDSObject" and "Release" functions are not members of the "IDirectoryObject" or "IDispatch" structures.  Here's the code I'm using:

        HRESULT                  hr;
      IDirectoryObject              *pDirObject;
      ADSVALUE                  sAMValue;
      ADSVALUE                  uPNValue;
      ADSVALUE                  classValue;
      LPDISPATCH                  pDisp;
      REFIID                        IID_IDirectoryObject;
      
      ADS_ATTR_INFO  attrInfo[] =
      {  
            { L"objectClass",            ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &classValue,      1 },
            { L"sAMAccountName",      ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &sAMValue,            1 },
            { L"userPrincipalName", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &uPNValue,            1 },
      };
      DWORD dwAttrs = sizeof(attrInfo)/sizeof(ADS_ATTR_INFO);
      
      classValue.dwType                        =      ADSTYPE_CASE_IGNORE_STRING;
      classValue.CaseIgnoreString            =      L"user";
      
      sAMValue.dwType                              =      ADSTYPE_CASE_IGNORE_STRING;
      sAMValue.CaseIgnoreString            =      L"browvick";
      
      uPNValue.dwType                              =      ADSTYPE_CASE_IGNORE_STRING;
      uPNValue.CaseIgnoreString            =      L"Vicky_Brown@gmail.com";
      
      hr = ADsGetObject(      L"LDAP://DC=domainname,DC=com",
                                    IID_IDirectoryObject,
                                    (void**) &pDirObject );
      if ( SUCCEEDED(hr) )
      {
            hr = pDirObject->CreateDSObject(      L"CN=Vicky Brown",
                                                                  attrInfo,
                                                                  dwAttrs,
                                                                  &pDisp );
            if ( SUCCEEDED(hr) )
            {
                   // Use the DS object.
                   pDisp->Release();
            }
            pDirObject->Release();
      }
      return(0);

I'm thinking that I must be missing something like an "include" file, since this code apparently works as written according to at least 2 different sources on the web!?  If anyone knows where I'm going wrong or has any other sample code to share to help me out, I'd really appreciate it!
Avatar of sarabande
sarabande
Flag of Luxembourg image

perhaps you were using the wrong compiler.

IDirectoryObject::CreateDSObject only could be compiled with c++ compiler. the header to include is lads.h. minimum Windows client version is Vista. minimum server version is Server 2008.

check the lads.h whether it has code that fits to your compiler macros. if not you might use visual c++ compiler for the dll and embed all COM stuff there.

Sara
Avatar of Jim Klocksin

ASKER

I believe my compiler should work OK. I'm using Visual Studio 2005 and I've compiled other short programs in the past that were C++.  It is an old version of Visual Studio but that shouldn't be a problem.  I think my main problem is that I'm copying a code snippet I found on the internet and I don't really have a good understanding of COM and how it actually works.  I do have the "Iads.h" include file in my code. I just feel like I'm missing something (probably COM-related) that I need to make this work.
 It is an old version of Visual Studio but that shouldn't be a problem.
the error message you got is a c++ error. it simply says that the functions CreateDSObject and Release are not member functions of the IDirectoryObject interface. you could use f12 on IDirectoryObject and IntelliSense should open the header file (probably lads.h) where you could examine the interface definition and check whether the functions are there or not. if they are there they probably were not available because of some preprocessor conditions, something like #if defined WINVER == ....

but i would guess they are not there at all because the compiler is too old. i already told that the minimum server version needed is Server 2008. why do you think that a compiler could be older than the required Windows Version?

you could download the newest visual studio 2017 community for free. or, use at least visual studio 2008, where the differences to vs 2005 are small and most projects should convert without much efforts (but it is not 'no efforts').

Sara
Here's the definition in Iads.h:

typedef struct IDirectoryObjectVtbl
    {
        BEGIN_INTERFACE
       
        HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
            IDirectoryObject * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void **ppvObject);
       
        ULONG ( STDMETHODCALLTYPE *AddRef )(
            IDirectoryObject * This);
       
        ULONG ( STDMETHODCALLTYPE *Release )(
            IDirectoryObject * This);
       
        HRESULT ( STDMETHODCALLTYPE *GetObjectInformation )(
            IDirectoryObject * This,
            /* [out] */ PADS_OBJECT_INFO *ppObjInfo);
       
        HRESULT ( STDMETHODCALLTYPE *GetObjectAttributes )(
            IDirectoryObject * This,
            /* [in] */ LPWSTR *pAttributeNames,
            /* [in] */ DWORD dwNumberAttributes,
            /* [out] */ PADS_ATTR_INFO *ppAttributeEntries,
            /* [out] */ DWORD *pdwNumAttributesReturned);
       
        HRESULT ( STDMETHODCALLTYPE *SetObjectAttributes )(
            IDirectoryObject * This,
            /* [in] */ PADS_ATTR_INFO pAttributeEntries,
            /* [in] */ DWORD dwNumAttributes,
            /* [out] */ DWORD *pdwNumAttributesModified);
       
        HRESULT ( STDMETHODCALLTYPE *CreateDSObject )(
            IDirectoryObject * This,
            /* [in] */ LPWSTR pszRDNName,
            /* [in] */ PADS_ATTR_INFO pAttributeEntries,
            /* [in] */ DWORD dwNumAttributes,
            /* [out] */ IDispatch **ppObject);
       
        HRESULT ( STDMETHODCALLTYPE *DeleteDSObject )(
            IDirectoryObject * This,
            /* [in] */ LPWSTR pszRDNName);
       
        END_INTERFACE
    } IDirectoryObjectVtbl;

    interface IDirectoryObject
    {
        CONST_VTBL struct IDirectoryObjectVtbl *lpVtbl;
    };

   

#ifdef COBJMACROS


#define IDirectoryObject_QueryInterface(This,riid,ppvObject)      \
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IDirectoryObject_AddRef(This)      \
    (This)->lpVtbl -> AddRef(This)

#define IDirectoryObject_Release(This)      \
    (This)->lpVtbl -> Release(This)


#define IDirectoryObject_GetObjectInformation(This,ppObjInfo)      \
    (This)->lpVtbl -> GetObjectInformation(This,ppObjInfo)

#define IDirectoryObject_GetObjectAttributes(This,pAttributeNames,dwNumberAttributes,ppAttributeEntries,pdwNumAttributesReturned)      \
    (This)->lpVtbl -> GetObjectAttributes(This,pAttributeNames,dwNumberAttributes,ppAttributeEntries,pdwNumAttributesReturned)

#define IDirectoryObject_SetObjectAttributes(This,pAttributeEntries,dwNumAttributes,pdwNumAttributesModified)      \
    (This)->lpVtbl -> SetObjectAttributes(This,pAttributeEntries,dwNumAttributes,pdwNumAttributesModified)

#define IDirectoryObject_CreateDSObject(This,pszRDNName,pAttributeEntries,dwNumAttributes,ppObject)      \
    (This)->lpVtbl -> CreateDSObject(This,pszRDNName,pAttributeEntries,dwNumAttributes,ppObject)

#define IDirectoryObject_DeleteDSObject(This,pszRDNName)      \
    (This)->lpVtbl -> DeleteDSObject(This,pszRDNName)

#endif /* COBJMACROS */


#endif       /* C style interface */

So the functions (CreateDSObject and Release) are clearly member functions of IDirectoryObject.  I know I could try using a newer version of Visual Studio, but I think that, after going thru all that trouble, I'm still going to get the same results and then I'm right back where I started from.  I can try it if you clearly believe that it will make a difference, but, at this point, I'm not convinced it will!?
So the functions (CreateDSObject and Release) are clearly member functions of IDirectoryObject.

didn't you see that it is a c-style struct IDirectoryObjectVtbl and also a c-style interface IDirectoryObject which is not derived from IDirectoryObjectVtbl but has a pointer as (only) member.

none of them provides c++ member functions  but only c pointers and function pointers.

you would need to calling the defined macros like IDirectoryObject_CreateDSObject and IDirectoryObject_Release and look for a reference how those macros can be used.

i still recommend that you go to a newer compiler.

Sara
Admittedly, I'm no expert in any of this nor do I really want to be.  I have a ton of other programming and networking issues that I'm currently dealing with, so I don't really have enough time be even become conversant in this.  Bottom line, my goal is to have functionality that I can build into my existing Windows software package to allow my clients to be able to automatically be able to change their passwords in Active Directory without any need for me to be involved.  I also need to be able to "bulk-add" about 1,500 users into my Active Directory which I could do with a third-party package, but would rather do within my current software package.  My system is written primarily in PowerBuilder with a few routines that I wrote/borrowed in a small C-language DLL.  So, I am trying to incorporate this functionality using C rather than C++, since I never really worked with C++ and my C programming skills are, at this point, pretty basic.  It's obvious to me that you know much more than I do about both C and C++ programming and, frankly, I'd rather pay you to program a routine for my program as opposed to paying for any third-party package that I would have to make available to about 1,500 users (my obvious preference is to embed this functionality into my existing software so my clients only have to work with one system.  Anyway, I'm lost at this point and I barely understand everything you wrote in your last comment.  I'm going to attach my function as well as the compiler results, in the hope that you can figure out exactly what I'm missing (from a C-language context) and help me with this.  Also, I'm not joking about paying you for your help.

Thanks, Jim
C-Language-function---Add-new-user-t.txt
C-Language-function---Compiler-resul.txt
unfortunately i am neither an expert for  COM nor for Active Directory.

on my vs2010 system the Iads.h contains both the c-like interface and the c++ class Interface.

but i found a c sample for what you were trying to do:

#include <wchar.h>
#include <stdio.h>
#include <windows.h>

#include <iads.h>
#include <adshlp.h>

#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "Activeds.lib")

const IID iadsuser={0x3e37e320,0x17e2,0x11cf,{0xab,0xc4,0x02,0x60,0x8c,0x9e,0x75,0x53}};

int main(int argc,char *argv[])
{
	IADsUser *pObject;
	HRESULT hr,hr1;

	BSTR bstr;
	VARIANT var;

	// Initialize COM.
	CoInitialize(NULL);

	hr = ADsGetObject(L"LDAP://CN=Full Name,OU=XX,OU=YY,DC=ABC,DC=COM",&iadsuser,(void**)&pObject);
	//hr = ADsGetObject(L"WinNT://ABC.COM/Shortname,user",&iadsuser,(void**)&pObject);

	if(hr==S_OK)
	{
	    // Use the object.
		printf("Success -> ADsGetObject()\n");

		char text[100];
		memset(text,0,sizeof(text));

		hr1=pObject->lpVtbl->get_FullName(pObject,&bstr);
		if(hr1==S_OK){
			printf("Success -> get_FullName()\n");
			wcstombs(text,bstr,wcslen((wchar_t *)bstr));

			printf("%s\n",text);
			SysFreeString(bstr);
		}
		else printf("Fail code=%lx -> get_FullName()\n",hr1);

		memset(text,0,sizeof(text));
		hr1=pObject->lpVtbl->get_EmailAddress(pObject,&bstr);
		if(hr1==S_OK){
			printf("Success -> get_EmailAddress()\n");
			wcstombs(text,bstr,wcslen((wchar_t *)bstr));

			printf("%s\n",text);
			SysFreeString(bstr);
		}
		else printf("Fail code=%lx -> get_EmailAddress()\n",hr1);

		memset(text,0,sizeof(text));
		hr1=pObject->lpVtbl->get_TelephonePager(pObject,&var);
		if(hr1==S_OK){
			printf("Success -> get_TelephonePager()\n");
			wcstombs(text,V_BSTR(&var),wcslen((wchar_t *)V_BSTR(&var)));

			printf("%s\n",text);
			VariantClear(&var);
		}
		else printf("Fail code=%lx -> get_TelephonePager()\n",hr1);

	    // Release the object.
//	    pObject->Release();
		pObject->lpVtbl->Release(pObject);
	}
	else printf("Fail code=%lx -> Release()\n",hr);

	// Uninitialize COM.
	CoUninitialize();

	return 0;
}

Open in new window


in the above sample you see that the pointer returned by ADSGetObject (in your code it is pDirObject) is not a c++ pointer pointing to a class object of IDirectoryObject, but a pointer to the c struct defined by 'interface IDirectoryObject'.

so your call to c++ member function

  pDirObject->CreateDSObject(...)

Open in new window


must be changed to

pDirObject->lpVtb->CreateDSObject(pDirObject, ...)

Open in new window


the arguments are the same as in the sample code with one exception: you have to add pDirObject as first argument (that is the 'this' pointer which needs to be passed for a c function, because a c struct doesn't has a this pointer). also the Release function would be called by

pDirObject->lpVbtl->Release(pDirObject);

Open in new window


what should solve your Compiler errors.

Sara
I'm making progress (thanks to you!).  Your last comment got me past my compilation issues.  Now, I'm trying to get the function to actually work.  Now, when I attempt to execute this function, the following statement does not "succeed" and it's probably because I'm essentially guessing at the values needed for 1 or 2 of the parameters.  The statement is:

hr      =      CoCreateInstance(      (REFCLSID)CLSID_Object,
                                          NULL,
                                          CLSCTX_INPROC_SERVER,
                                          (REFIID)IID_IDirectoryObject,
                                          (void**) &pDirObject);

The parameters that are most likely causing this to fail are the CLSID_Object and/or the IID_IDirectoryObject parameters.  I've "#define"-d them as:

#define            IID_IDirectoryObject     L"E798DE2C-22E4-11D0-84FE-00C04FD8D503"
#define            CLSID_Object             L"bf967a82-0de6-11d0-a285-00aa003049e2"

The value for the IID_IDirectoryObject I took from the MSDN page that describes the IID_IDirectoryObject, so I'm thinking that this one may be correct.  The other (CLSID_Object) I'm basically guessing at, using a value I found somewhere on some web site!?

Point is that I don't really know what I'm doing here and there seems to be little explanation on any of the sites where the entire function that I "borrowed" is displayed.  If I'm right about the value of the one, then I have to assume that the other is also some GUID that everyone just assumes that programmers would know!?

Do you have any more suggestions for me?   Thanks, Jim
i already told you that COM isn't my expertise ;-)

the IID_IDirectoryObject     is the ID of the COM object which is constant. so if you anywhere found this definition it surely is true.

the class id is your id (i think). that means you must not use an existing class id or GUID but get you a new one. check visual studio Tools menu, it should have a GUID tool where you could get a new one.

if it doesn't work nevertheless, call GetLastError() and post the error code returned.

why did you call CoCreateInstance ? didn't you get your instance from ADsGetObject?

did you already check the docs to CoCreateInstance? (i would do and perhaps i will do even if i hate COM and their mysteries).

Sara
I commented out the call to CoCreateInstance (since I'm not really sure why it was in there, although I think at one point I thought I needed it to get a value for IID_IDirectoryObject, which I now know is probably not the case).  So, now I'm just calling ADsGetObject (which takes the value of IID_IDirectoryObject as one of it's parameters) but the result (HRESULT) that's getting returned is E_NOINTERFACE !?  I'm beginning to think this is impossible (or at least not possible for me) and I may just resort to buying a package that does this stuff...
i think you are pretty close ...

did you use

const IID iadsuser={0x3e37e320,0x17e2,0x11cf,{0xab,0xc4,0x02,0x60,0x8c,0x9e,0x75,0x53}};

Open in new window


like in the sample code i founc. it should be a constant for the interface.

then the name argument should not be a simple user name but like

LDAP://HostName[:PortNumber][/DistinguishedName]

Open in new window


where portnumber an distinguishedname could be obmitted in my opinion.

see https://msdn.microsoft.com/en-us/library/aa746384(v=vs.85).aspx for more info.

Sara
by the way i used the code sample i found as source for a win32 console project (empty project, no precompiled headers, multibyte characterset). i posted the code into a new item of type c++ file and named it tad.c. that way it uses ansi-c Compiler and not c++ compiler.

the only change i made was to move char text[100]; at top of the block since ansi c doesn't allow to define new variables somewhere in the middle of a {} block.

the call to ADsGetObject lasted about 15 seconds and returned with error what was not surpricing since i didn't use a valid name, but obviously it searched the AD available at my current network.

Sara
ASKER CERTIFIED SOLUTION
Avatar of sarabande
sarabande
Flag of Luxembourg image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I'm not giving up on this but time has become an overriding issue for me.  I'm most likely going to buy a software package that does the "bulk-add" for AD.  My other primary issue is that I need a way for users to reset their passwords (whether they know their existing password or not!), so I guess I'm going to have to buy that module as well (problem is that one is expensive).  I have to buy more memory for my server as well as additional CALs for Remote Access into my network.  Unfortunately, all of this needs to be done quickly.  My client will eventually pay for all of this, but I have to "front" the money which is not easy for me currently.  Anyway, my point is that I would like to keep this question open, but I just don't have time to work on this right now.  I'm currently putting together a quote for my client regarding all of the aforementioned items and I have to keep the network available to more and more users over the next few weeks.  Not sure what else to tell you other than that I appreciate all the time and interest you've put into my question, and I really do (long-term) want to program this even if it's only for my own satisfaction!
Sara, I really appreciate all of your help while I was trying to program something that I'm now realizing was not only beyond my capabilities, but, quite frankly, not worth the time and effort required, since I have to focus on my software application that I'm being paid to provide support for.  Bottom line is that I really needed 2 items regarding Active Directory, a "bulk-add" solution and a "password reset" solution.  I've decided that the prudent option is to simply purchase third-party packages that already provide this functionality.  So I'm officially "giving up" my attempt to program this stuff myself, but, again, thanks so much for all your time and assistance with my questions!