Are you a world traveler? ZoneTick is a cool utility that'll help you stay in touch over multiple time zones!
 
Modifying Properties of a Directory Object with DAPIWrite  
Nik Okuntseff  MS Exchange Server Programming 

Modifying Properties of a Directory Object with DAPIWrite

I have shown how attributes of a MS Exchange Directory item may be retrieved with DAPI functions, and dumped to the screen. Let us examine whether we could modify some attributes and write them to the directory.

The following two items are important when dealing with the DAPIWrite function.

  • You need to take care about proper initialization of the base point and the container - two members of DAPI_PARMS structure, which you pass to the DAPIStart. If these members are not initialized, the DAPIWrite function is likely to fail with "object class violation" message, which in this case means that you can't create objects of a particular type in the (default) location.
  • NULL terminators of DAPI_ENTRY arrays. The DAPIWrite function takes two pointers to DAPI_ENTRY arrays. One arrays specifies attribute names and the other - their values to be written into the directory. It appears that the last entry in each array must be set to NULL (for example, with ZeroMemory function as in the example below). Obviously, NULL entries identify array ends. During my original research, when I was not aware of it, the DAPIWrite function failed with 0xC0000081 error code (DAPI_E_BAD_HANDLE) when I once supplied these arrays without terminators. Apparently, it was trying to read what was following my last entry and make use of it.

Creating a New Mailbox with DAPIWrite

The following sample code (Dir/DAPIWrite) illustrates how you can create a new Windows NT user account and associate it with MS Exchange mailbox with help of the DAPIWrite function:

#include <afxwin.h>
#include <dapi.h>

int main( void )
{
    DAPI_PARMS parms = {0};
    parms.dwDAPISignature = DAPI_SIGNATURE;
    parms.dwFlags = DAPI_CREATE_NT_ACCOUNT;
    // Initialize base point and container
    parms.pszBasePoint = "/o=Rydex Industries Corporation/ou=DEV";
    parms.pszContainer = "/cn=Recipients";
 
 
    PDAPI_EVENT pDAPIEvent = NULL;
    DAPI_HANDLE hDAPISession = NULL;

    // Initialize DAPI
    pDAPIEvent = ::DAPIStart( &hDAPISession, &parms );
    if ( pDAPIEvent )
        throw ( -1 );

    // We have DAPI session.
    ATT_VALUE avAttrName[4];

    avAttrName[0].DapiType = DAPI_STRING8;
    avAttrName[0].Value.pszA = "Obj-Class";
    avAttrName[0].size = 9;
    avAttrName[0].pNextValue = NULL;
 
    avAttrName[1].DapiType = DAPI_STRING8;
    avAttrName[1].Value.pszA = "Directory Name";
    avAttrName[1].size = 14;
    avAttrName[1].pNextValue = NULL;

    avAttrName[2].DapiType = DAPI_STRING8;
    avAttrName[2].Value.pszA = "Home-Server";
    avAttrName[2].size = 11;
    avAttrName[2].pNextValue = NULL;

    // This terminates our attribute name array
    ZeroMemory( &avAttrName[3], sizeof(ATT_VALUE) );

    ATT_VALUE avAttrValue[4];

    avAttrValue[0].DapiType = DAPI_STRING8;
    avAttrValue[0].Value.pszA = "Mailbox";
    avAttrValue[0].size = 8;
    avAttrValue[0].pNextValue = NULL;

    avAttrValue[1].DapiType = DAPI_STRING8;
    avAttrValue[1].Value.pszA = "NewAccount";
    avAttrValue[1].size = 10;
    avAttrValue[1].pNextValue = NULL;

    avAttrValue[2].DapiType = DAPI_STRING8;
    avAttrValue[2].Value.pszA = "~SERVER"; // Or use explicit server name here
    avAttrValue[2].size = 7;
    avAttrValue[2].pNextValue = NULL;

    // This terminates our array of values
    ZeroMemory( &avAttrValue[3], sizeof(ATT_VALUE) );
 

    DAPI_ENTRY deAttributes;
    DAPI_ENTRY deValues;

    deAttributes.unAttributes = 3;
    deAttributes.ulEvalTag = TEXT_VALUE_ARRAY;
    deAttributes.rgEntryValues = &avAttrName[0];
 
    deValues.unAttributes = 3;
    deValues.ulEvalTag = VALUE_ARRAY;
    deValues.rgEntryValues = &avAttrValue[0];
 
    char * pAccount = NULL;
    char * pPassword = NULL;
    ULONG ulUSN = 0;
    pDAPIEvent = ::DAPIWrite( hDAPISession,
        DAPI_WRITE_CREATE,
        &deAttributes,
        &deValues,
        &ulUSN,
        &pAccount,  // Account
        &pPassword ); // Password

    if ( pDAPIEvent ) {

        LPVOID lpMsgBuf;
        ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_HMODULE |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            pDAPIEvent->hinstDAPI,
            pDAPIEvent->dwDAPIError,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
            (LPTSTR) &lpMsgBuf,
            0,
            NULL );

        // Display the string.
        ::MessageBox( NULL, (char *) lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
        // Free the buffer.
        ::LocalFree( lpMsgBuf );
    }

    // Terminate DAPI session
    ::DAPIEnd( &hDAPISession );
    return ( 0 );
}

Take a look at additional initialization of DAPI_PARMS structure before I pass it to the DAPIStart. Among other things I initialize the base point in the directory tree and a container (these two members tell DAPI where to look for entries). Note that base point in your case will be different because it is a distinguished name containing organization and site names. If you try to use DAPIWrite without properly initialized base point and container, the default values will be used. On Exchange server 5.5 this will create a mailbox on the home server, but not a global mailbox. On Exchange server 5.0 the DAPIWrite function fails with "object class violation" message. This message is accompanied by another suggesting restarting your app, MS Exchange server, or both. This message is misleading. Obviously, you can't create mailboxes and Exchange server 5.0 in the default location. See the description of the DAPI_PARMS structure for explanation of default values.

Also note the use of terminators and FormatMessage function as I have described previously to format error code into the text string. The "~SERVER" value identifies currently bound server. You can use computer name explicitly instead (such as "MIG", as in my particular case).
 

 

Modifying Existing Addr-Type Object

This last fragment in this section (Dir/DAPIWriteAddrType sample) demonstrates how you can modify the name of proxy generator DLL in existing Addr-Type object:

#include <afxwin.h>
#include <dapi.h>

int main( void )
{
    DAPI_PARMS parms = {0};
    parms.dwDAPISignature = DAPI_SIGNATURE;
    // Initialize base point and container
    parms.pszBasePoint = "/o=Rydex Industries Corporation/ou=DEV/cn=Configuration/cn=Addressing";
    parms.pszContainer = "/cn=Address-Types";
    PDAPI_EVENT pDAPIEvent = NULL;
    DAPI_HANDLE hDAPISession = NULL;

    // Initialize DAPI
    pDAPIEvent = ::DAPIStart( &hDAPISession, &parms );
    if ( pDAPIEvent )
        throw ( -1 );

    // We have DAPI session.
    ATT_VALUE avAttrName[5];

    avAttrName[0].DapiType = DAPI_STRING8;
    avAttrName[0].Value.pszA = "Obj-Class";
    avAttrName[0].size = 9;
    avAttrName[0].pNextValue = NULL;
 
    avAttrName[1].DapiType = DAPI_STRING8;
    avAttrName[1].Value.pszA = "Directory Name";
    avAttrName[1].size = 14;
    avAttrName[1].pNextValue = NULL;

    avAttrName[2].DapiType = DAPI_STRING8;
    avAttrName[2].Value.pszA = "Proxy-Generator-DLL";
    avAttrName[2].size = 19;
    avAttrName[2].pNextValue = NULL;

    avAttrName[3].DapiType = DAPI_STRING8;
    avAttrName[3].Value.pszA = "File-Version";
    avAttrName[3].size = 12;
    avAttrName[3].pNextValue = NULL;

    // This terminates our attribute name array
    ZeroMemory( &avAttrName[4], sizeof(ATT_VALUE) );

    ATT_VALUE avAttrValue[5];

    avAttrValue[0].DapiType = DAPI_STRING8;
    avAttrValue[0].Value.pszA = "Addr-Type";
    avAttrValue[0].size = 9;
    avAttrValue[0].pNextValue = NULL;

    avAttrValue[1].DapiType = DAPI_STRING8;
    avAttrValue[1].Value.pszA = "EDK:i386";
    avAttrValue[1].size = 8;
    avAttrValue[1].pNextValue = NULL;

    avAttrValue[2].DapiType = DAPI_STRING8;
    avAttrValue[2].Value.pszA = "qroxygen.dll";
    avAttrValue[2].size = 12;
    avAttrValue[2].pNextValue = NULL;

    avAttrValue[3].DapiType = DAPI_BINARY;
    BYTE binBuffer[8] = {0x00,0x00,0xE1,0x03,0x00,0x00,0x04,0x00};
    avAttrValue[3].Value.lpBinary = binBuffer;
    avAttrValue[3].size = 8;
    avAttrValue[3].pNextValue = NULL;

    // This terminates our array of values
    ZeroMemory( &avAttrValue[4], sizeof(ATT_VALUE) );
 

    DAPI_ENTRY deAttributes;
    DAPI_ENTRY deValues;

    deAttributes.unAttributes = 4;
    deAttributes.ulEvalTag = TEXT_VALUE_ARRAY;
    deAttributes.rgEntryValues = &avAttrName[0];
 
    deValues.unAttributes = 4;
    deValues.ulEvalTag = VALUE_ARRAY;
    deValues.rgEntryValues = &avAttrValue[0];
 
    char * pAccount = NULL;
    char * pPassword = NULL;
    ULONG ulUSN = 0;
    pDAPIEvent = ::DAPIWrite( hDAPISession,
        DAPI_WRITE_MODIFY,
        &deAttributes,
        &deValues,
        &ulUSN,
        &pAccount,  // Account
        &pPassword ); // Password

    if ( pDAPIEvent ) {

        LPVOID lpMsgBuf;
        ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_HMODULE |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            pDAPIEvent->hinstDAPI,
            pDAPIEvent->dwDAPIError,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
            (LPTSTR) &lpMsgBuf,
            0,
            NULL );

        // Display the string.
        ::MessageBox( NULL, (char *) lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
        // Free the buffer.
        ::LocalFree( lpMsgBuf );
    }

    // Terminate DAPI session
    ::DAPIEnd( &hDAPISession );
    return ( 0 );
}

A few comments must be made about the above program.

First, as in the previous sample, I properly initialize the base point in the directory tree and a container. If you try to use DAPIWrite without properly initialized base point and container, the default values will be used. Attempting to create an Addr-Type object in this default location is obviously prohibited (The DAPIWrite fails with "object class violation" message).

Second, I assume that sample gateway had been set up, and the EDK:i386 addressing object exists. In fact the code tries to modify existing object and should fail with error 0xC0000120 (target not found) if the object is not present. I am using the same File-Version value found in the original object (this is hard-coded, as well as all other values). The only thing, which is different is the proxy generation DLL name, which in my case is "qroxygen.dll".

If you successfully run the above program, it will modify one property of your Addr-Type object - its Proxy Generation DLL name. After that you will be able to see the change with Exchange Administrator. Note that you can successfully change this attribute using Exchange Administrator. In addition to this work it verifies whether new DLL in fact exists. It won't allow for change otherwise.
 

Deleting Attributes of Directory Objects

You may come across a need to remove one or more attributes from an existing directory object. Deleting directory objects as whole entities is covered in the next section. But how can I remove only one (or more) attribute of a directory object without removing the object itself? This is accomplished by specifying "~DEL" string in the attribute value. The following code example shows how you can delete the "Admin Note" attribute from EDK Addr-Type object. In order to test the code you need to create the attribute first (use Exchange Administrator). Then run the code and see what happens. Normally the Admin Note should disappear.
 

#include <afxwin.h>
#include <dapi.h>

int main( void )
{
    DAPI_PARMS parms = {0};
    parms.dwDAPISignature = DAPI_SIGNATURE;
    parms.pszDSAName = "RODES"; // Exchange computer name

    // Initialize base point and container
    parms.pszBasePoint = "/o=Infowave/ou=Borg/cn=Configuration/cn=Addressing";
    parms.pszContainer = "/cn=Address-Types";
    PDAPI_EVENT pDAPIEvent = NULL;
    DAPI_HANDLE hDAPISession = NULL;

    // Initialize DAPI
    pDAPIEvent = ::DAPIStart( &hDAPISession, &parms );
    if ( pDAPIEvent )
        throw ( -1 );

    // We have DAPI session.
    ATT_VALUE avAttrName[4];

    avAttrName[0].DapiType = DAPI_STRING8;
    avAttrName[0].Value.pszA = "Obj-Class";
    avAttrName[0].size = 9;
    avAttrName[0].pNextValue = NULL;
 
    avAttrName[1].DapiType = DAPI_STRING8;
    avAttrName[1].Value.pszA = "Directory Name";
    avAttrName[1].size = 14;
    avAttrName[1].pNextValue = NULL;

    avAttrName[2].DapiType = DAPI_STRING8;
    avAttrName[2].Value.pszA = "Admin Note";
    avAttrName[2].size = 10;
    avAttrName[2].pNextValue = NULL;

    // This terminates our attribute name array
    ZeroMemory( &avAttrName[3], sizeof(ATT_VALUE) );

    ATT_VALUE avAttrValue[4];

    avAttrValue[0].DapiType = DAPI_STRING8;
    avAttrValue[0].Value.pszA = "Addr-Type";
    avAttrValue[0].size = 9;
    avAttrValue[0].pNextValue = NULL;

    avAttrValue[1].DapiType = DAPI_STRING8;
    avAttrValue[1].Value.pszA = "EDK:i386";
    avAttrValue[1].size = 8;
    avAttrValue[1].pNextValue = NULL;

    avAttrValue[2].DapiType = DAPI_STRING8;
    avAttrValue[2].Value.pszA = "~del";  // This removes the attribute
    avAttrValue[2].size = 4;
    avAttrValue[2].pNextValue = NULL;

    // This terminates our array of values
    ZeroMemory( &avAttrValue[3], sizeof(ATT_VALUE) );

    DAPI_ENTRY deAttributes;
    DAPI_ENTRY deValues;

    deAttributes.unAttributes = 3;
    deAttributes.ulEvalTag = TEXT_VALUE_ARRAY;
    deAttributes.rgEntryValues = &avAttrName[0];
 
    deValues.unAttributes = 3;
    deValues.ulEvalTag = VALUE_ARRAY;
    deValues.rgEntryValues = &avAttrValue[0];
 
    char * pAccount = NULL;
    char * pPassword = NULL;
    ULONG ulUSN = 0;
    pDAPIEvent = ::DAPIWrite( hDAPISession,
        DAPI_WRITE_MODIFY,
        &deAttributes,
        &deValues,
        &ulUSN,
        &pAccount,  // Account
        &pPassword ); // Password

    if ( pDAPIEvent ) {

        LPVOID lpMsgBuf;
        ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_HMODULE |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            pDAPIEvent->hinstDAPI,
            pDAPIEvent->dwDAPIError,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
            (LPTSTR) &lpMsgBuf,
            0,
            NULL );

        // Display the string.
        ::MessageBox( NULL, (char *) lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
        // Free the buffer.
        ::LocalFree( lpMsgBuf );
    }

    // Terminate DAPI session
    ::DAPIEnd( &hDAPISession );
    return ( 0 );
}
 

[ Contents | Home ]

Send comments and suggestions to niko@wrconsulting.com
Copyright © 1997-1998 by Nik Okuntseff