#include "nwcompat.hxx"
#pragma hdrstop


FILTERS Filters[] = {
                    {L"computer", NWCOMPAT_COMPUTER_ID},
                    {L"user", NWCOMPAT_USER_ID},
                    {L"group", NWCOMPAT_GROUP_ID},
                    {L"service", NWCOMPAT_SERVICE_ID},
                    {L"printqueue", NWCOMPAT_PRINTER_ID},
                    {L"fileshare", NWCOMPAT_FILESHARE_ID},
                    {L"class", NWCOMPAT_CLASS_ID},
                    {L"syntax", NWCOMPAT_SYNTAX_ID},
                    {L"property", NWCOMPAT_PROPERTY_ID}
                  };

#define MAX_FILTERS  (sizeof(Filters)/sizeof(FILTERS))


HRESULT
CreatePropEntry(
    LPWSTR szPropName,
    ADSTYPE dwADsType,
    VARIANT varData,
    REFIID riid,
    LPVOID * ppDispatch
    );



PFILTERS  gpFilters = Filters;
DWORD gdwMaxFilters = MAX_FILTERS;
extern WCHAR * szProviderName;

//+------------------------------------------------------------------------
//
//  Class:      Common
//
//  Purpose:    Contains NWCOMPAT routines and properties that are common to
//              all NWCOMPAT objects. NWCOMPAT objects get the routines and
//              properties through C++ inheritance.
//
//-------------------------------------------------------------------------

HRESULT
MakeUncName(
    LPWSTR szSrcBuffer,
    LPWSTR szTargBuffer
    )
{
    ADsAssert(szSrcBuffer && *szSrcBuffer);
    wcscpy(szTargBuffer, L"\\\\");
    wcscat(szTargBuffer, szSrcBuffer);
    RRETURN(S_OK);
}

HRESULT
ValidateOutParameter(
    BSTR * retval
    )
{
    if (!retval) {
        RRETURN(E_ADS_BAD_PARAMETER);
    }
    RRETURN(S_OK);
}

HRESULT
BuildADsPath(
    BSTR Parent,
    BSTR Name,
    BSTR *pADsPath
    )
{
    WCHAR ADsPath[MAX_PATH];
    WCHAR ProviderName[MAX_PATH];
    HRESULT hr = S_OK;
    LPWSTR pszDisplayName = NULL;

    //
    // We will assert if bad parameters are passed to us.
    // This is because this should never be the case. This
    // is an internal call
    //

    ADsAssert(Parent && Name);
    ADsAssert(pADsPath);

    hr = GetDisplayName(
             Name,
             &pszDisplayName
             );
    BAIL_ON_FAILURE(hr);

    //
    // Special case the Namespace object; if
    // the parent is L"ADs:", then Name = ADsPath
    //

    if (!_wcsicmp(Parent, L"ADs:")) {
        hr = ADsAllocString(Name, pADsPath);
        BAIL_ON_FAILURE(hr);
        goto cleanup;
    }

    //
    // The rest of the cases we expect valid data,
    // Path, Parent and Name are read-only, the end-user
    // cannot modify this data
    //

    //
    // For the first object, the domain object we do not add
    // the first backslash; so we examine that the parent is
    // L"NWCOMPAT:" and skip the slash otherwise we start with
    // the slash
    //

    wsprintf(ProviderName, L"%s:", szProviderName);

    wcscpy(ADsPath, Parent);

    if (_wcsicmp(ADsPath, ProviderName)) {
        wcscat(ADsPath, L"/");
    }
    else {
       wcscat(ADsPath, L"//");
    }
    wcscat(ADsPath, Name);

    hr = ADsAllocString(ADsPath, pADsPath);

cleanup:
error:

    if (pszDisplayName) {
        FreeADsMem(pszDisplayName);
    }

    RRETURN(hr);
}

HRESULT
BuildSchemaPath(
    BSTR Parent,
    BSTR Name,
    BSTR Schema,
    BSTR *pSchemaPath
    )
{
    WCHAR SchemaPath[MAX_PATH];
    WCHAR ProviderName[MAX_PATH];
    HRESULT hr = S_OK;
    long i;
    ULONG cchSchemaPath = 0;

    OBJECTINFO ObjectInfo;
    POBJECTINFO pObjectInfo = &ObjectInfo;
    CLexer Lexer(Parent);


    memset(pObjectInfo, 0, sizeof(OBJECTINFO));
    //
    // We will assert if bad parameters are passed to us.
    // This is because this should never be the case. This
    // is an internal call
    //

    ADsAssert(Parent);
    ADsAssert(pSchemaPath);

    //
    // If no schema name is passed in, then there is no schema path
    //
    if ( Schema == NULL || *Schema == 0 ){

        RRETURN(ADsAllocString(L"", pSchemaPath));
    }

    memset(pObjectInfo, 0, sizeof(OBJECTINFO));
    hr = Object(&Lexer, pObjectInfo);
    BAIL_ON_FAILURE(hr);

    cchSchemaPath += wcslen(szProviderName) + 3;
    cchSchemaPath += ((!pObjectInfo->NumComponents) ? wcslen(Name) : wcslen(pObjectInfo->DisplayComponentArray[0]));
    cchSchemaPath += 2;
    cchSchemaPath += wcslen(SCHEMA_NAME);
    cchSchemaPath += wcslen(Schema);

    if (cchSchemaPath >= MAX_PATH) {

        hr = E_OUTOFMEMORY;
        BAIL_ON_FAILURE(hr);
    }

    wsprintf(SchemaPath, L"%s://", szProviderName);

    if (!pObjectInfo->NumComponents) {
        wcscat(SchemaPath, Name);
    }else{
        wcscat(SchemaPath, pObjectInfo->DisplayComponentArray[0]);
    }

    wcscat( SchemaPath, L"/");
    wcscat( SchemaPath, SCHEMA_NAME );
    wcscat( SchemaPath, L"/");
    wcscat( SchemaPath, Schema );

    hr = ADsAllocString(SchemaPath, pSchemaPath);

error:

    FreeObjectInfo( &ObjectInfo, TRUE );

    RRETURN(hr);
}

HRESULT
BuildADsGuid(
    REFCLSID clsid,
    BSTR *pADsClass
    )
{
    WCHAR ADsClass[MAX_PATH];

    if (!StringFromGUID2(clsid, ADsClass, MAX_PATH)) {
        //
        // MAX_PATH should be more than enough for the GUID.
        //
        ADsAssert(!"GUID too big !!!");
        RRETURN(E_FAIL);
    }
 
    RRETURN(ADsAllocString(ADsClass, pADsClass));
}

HRESULT
BuildObjectInfo(
    BSTR ADsParent,
    BSTR Name,
    POBJECTINFO * ppObjectInfo
    )
{
    WCHAR szBuffer[MAX_PATH];
    ULONG cchBuffer = 0;
    HRESULT hr;
    POBJECTINFO pObjectInfo = NULL;

    memset(szBuffer, 0, sizeof(szBuffer));

    cchBuffer += (wcslen(ADsParent) + 1 + wcslen(Name));
    if (cchBuffer >= MAX_PATH) {
        hr = E_OUTOFMEMORY;
        BAIL_ON_FAILURE(hr);
    }
    else {

        wcscpy(szBuffer, ADsParent);
        wcscat(szBuffer, L"/");
        wcscat(szBuffer, Name);

        CLexer Lexer(szBuffer);

        pObjectInfo = (POBJECTINFO)AllocADsMem(sizeof(OBJECTINFO));
        if (!pObjectInfo) {
            hr = E_OUTOFMEMORY;
            BAIL_ON_FAILURE(hr);
        }
        memset(pObjectInfo, 0, sizeof(OBJECTINFO));
        hr = Object(&Lexer, pObjectInfo);
        BAIL_ON_FAILURE(hr);

        *ppObjectInfo = pObjectInfo;

        RRETURN(hr);
    }

error:

    if (pObjectInfo) {
        FreeObjectInfo(pObjectInfo);
    }

    *ppObjectInfo = NULL;

    RRETURN(hr);
}

HRESULT
BuildObjectInfo(
    BSTR ADsPath,
    POBJECTINFO * ppObjectInfo
    )
{
    HRESULT hr;
    POBJECTINFO pObjectInfo = NULL;
    CLexer Lexer(ADsPath);

    pObjectInfo = (POBJECTINFO)AllocADsMem(sizeof(OBJECTINFO));
    if (!pObjectInfo) {
        hr = E_OUTOFMEMORY;
        BAIL_ON_FAILURE(hr);
    }
    memset(pObjectInfo, 0, sizeof(OBJECTINFO));
    hr = Object(&Lexer, pObjectInfo);
    BAIL_ON_FAILURE(hr);

    *ppObjectInfo = pObjectInfo;

    RRETURN(hr);

error:

    if (pObjectInfo) {
        FreeObjectInfo(pObjectInfo);
    }

    *ppObjectInfo = NULL;

    RRETURN(hr);
}

VOID
FreeObjectInfo(
    POBJECTINFO pObjectInfo,
    BOOL fStatic
    )
{
    DWORD i = 0;

    if (!pObjectInfo) {
        return;
    }

    FreeADsStr(pObjectInfo->ProviderName);

    for (i = 0; i < pObjectInfo->NumComponents; i++ ) {
        FreeADsStr(pObjectInfo->ComponentArray[i]);
        FreeADsStr(pObjectInfo->DisplayComponentArray[i]);
    }

    if ( !fStatic ) {
        FreeADsMem(pObjectInfo);
    }
}

HRESULT
ValidateObject(
    DWORD dwObjectType,
    POBJECTINFO pObjectInfo
    )
{
    switch (dwObjectType) {
    case NWCOMPAT_USER_ID:
        RRETURN(ValidateUserObject(pObjectInfo));

    case NWCOMPAT_GROUP_ID:
        RRETURN(ValidateGroupObject(pObjectInfo));

    case NWCOMPAT_PRINTER_ID:
        RRETURN(ValidatePrinterObject(pObjectInfo));

    default:
        RRETURN(E_FAIL);
    }
}

HRESULT
GetObjectType(
    PFILTERS pFilters,
    DWORD dwMaxFilters,
    BSTR ClassName,
    PDWORD pdwObjectType
    )
{
    DWORD i = 0;

    ADsAssert(pdwObjectType);

    for (i = 0; i < dwMaxFilters; i++) {
        if (!_wcsicmp(ClassName, (pFilters + i)->szObjectName)) {
            *pdwObjectType = (pFilters + i)->dwFilterId;
            RRETURN(S_OK);
        }
    }
    *pdwObjectType = 0;
    RRETURN(E_FAIL);
}

HRESULT
ValidateProvider(
    POBJECTINFO pObjectInfo
    )
{
    //
    // The provider name is case-sensitive.  This is a restriction that OLE
    // has put on us.
    //
    if (!(wcscmp(pObjectInfo->ProviderName, szProviderName))) {
        RRETURN(S_OK);
    }
    RRETURN(E_FAIL);
}

HRESULT
ConvertSystemTimeToDATE(
    SYSTEMTIME Time,
    DATE *     pdaTime
    )
{
    FILETIME ft;
    BOOL fRetval = FALSE;
    USHORT wDosDate;
    USHORT wDosTime;

    //
    // System Time To File Time.
    //

    fRetval = SystemTimeToFileTime(&Time,
                                   &ft);
    if(!fRetval){
      RRETURN(HRESULT_FROM_WIN32(GetLastError()));
    }

    //
    // File Time to DosDateTime.
    //

    fRetval = FileTimeToDosDateTime(&ft,
                                    &wDosDate,
                                    &wDosTime);
    if(!fRetval){
      RRETURN(HRESULT_FROM_WIN32(GetLastError()));
    }

    //
    // DosDateTime to VariantTime.
    //

    fRetval = DosDateTimeToVariantTime(wDosDate,
                                       wDosTime,
                                       pdaTime );
    if(!fRetval){
      RRETURN(HRESULT_FROM_WIN32(GetLastError()));
    }

    RRETURN(S_OK);
}

HRESULT
ConvertDATEToSYSTEMTIME(
    DATE  daDate,
    SYSTEMTIME *pSysTime
    )
{
    HRESULT hr;
    FILETIME ft;
    BOOL fRetval = FALSE;
    USHORT wDosDate;
    USHORT wDosTime;

    fRetval = VariantTimeToDosDateTime(daDate,
                                       &wDosDate,
                                       &wDosTime );

    if(!fRetval){
        hr = HRESULT_FROM_WIN32(GetLastError());
        RRETURN(hr);
    }

    fRetval = DosDateTimeToFileTime(wDosDate,
                                    wDosTime,
                                    &ft);



    if(!fRetval){
        hr = HRESULT_FROM_WIN32(GetLastError());
        RRETURN(hr);
    }

    fRetval = FileTimeToSystemTime(&ft,
                                   pSysTime );


    if(!fRetval){
        hr = HRESULT_FROM_WIN32(GetLastError());
        RRETURN(hr);
    }

    RRETURN(S_OK);
}

HRESULT
ConvertDATEToDWORD(
    DATE  daDate,
    DWORD *pdwDate
    )
{
    BOOL fBool = TRUE;
    WORD wDOSDate = 0;
    WORD wDOSTime = 0;
    WORD wHour = 0;
    WORD wMinute = 0;

    //
    // Break up Variant date.
    //

    fBool = VariantTimeToDosDateTime(
                (DOUBLE) daDate,
                &wDOSDate,
                &wDOSTime
                );
    if (fBool == FALSE) {
        goto error;
    }

    //
    // Convert DOS time into DWORD time which expresses time as the number of
    // minutes elapsed since mid-night.
    //

    wHour = wDOSTime >> 11;
    wMinute = (wDOSTime >> 5) - (wHour << 6);

    //
    // Return.
    //

    *pdwDate = wHour * 60 + wMinute;

error:

    RRETURN(S_OK);
}

HRESULT
ConvertDWORDToDATE(
    DWORD  dwTime,
    DATE * pdaTime
    )
{
    BOOL       fBool = TRUE;
    DOUBLE     vTime = 0;
    SYSTEMTIME SysTime = {1980,1,0,1,0,0,0,0};
    SYSTEMTIME LocalTime = {1980,1,0,1,0,0,0,0};
    WORD       wDOSDate = 0;
    WORD       wDOSTime = 0;
    WORD       wHour = 0;
    WORD       wMinute = 0;
    WORD       wSecond = 0;

    //
    // Get Hour and Minute from DWORD.
    //

    SysTime.wHour = (WORD) (dwTime / 60);
    SysTime.wMinute = (WORD) (dwTime % 60);

    //
    // Get Time-zone specific local time.
    //

    fBool = SystemTimeToTzSpecificLocalTime(
                NULL,
                &SysTime,
                &LocalTime
                );
    if (fBool == FALSE) {
        RRETURN(HRESULT_FROM_WIN32(GetLastError()));
    }

    wHour = LocalTime.wHour;
    wMinute = LocalTime.wMinute;
    wSecond = LocalTime.wSecond;

    //
    // Set a dummy date.
    //

    wDOSDate = DATE_1980_JAN_1;

    //
    // Shift data to correct bit as required by the DOS date & time format.
    //

    wHour = wHour << 11;
    wMinute = wMinute << 5;

    //
    // Put them in DOS format.
    //

    wDOSTime = wHour | wMinute | wSecond;

    //
    // Convert into VariantTime.
    //

    fBool = DosDateTimeToVariantTime(
                wDOSDate,
                wDOSTime,
                &vTime
                );
    //
    // Return.
    //

    if (fBool == TRUE) {

        *pdaTime = vTime;

        RRETURN(S_OK);
    }
    else {
        RRETURN(E_FAIL);
    }
}

HRESULT
DelimitedStringToVariant(
    LPTSTR pszString,
    VARIANT *pvar,
    TCHAR Delimiter
    )
{
    SAFEARRAYBOUND sabound[1];
    DWORD dwElements;
    LPTSTR pszCurrPos = pszString;
    LPTSTR *rgszStrings = NULL;
    SAFEARRAY *psa = NULL;
    VARIANT v;
    HRESULT hr = S_OK;
    LONG i;

    //
    // This function converts a delimited string into a VARIANT of
    // safe arrays.
    //
    // Assumption: a valid string are passed to this function
    // note that the input string gets destroyed in the process
    //

    //
    // scan the delimited string once to find out the dimension
    //

    //
    // in order to filter for NULL input values do a sanity check for
    // length of input string.
    //


    //
    // take care of null case first for sanity's sake
    //

    if (!pszString){
        sabound[0].cElements = 0;
        sabound[0].lLbound = 0;

        psa = SafeArrayCreate(VT_VARIANT, 1, sabound);

        if (psa == NULL){
            hr = E_OUTOFMEMORY;
            goto error;
        }

        VariantInit(pvar);
        V_VT(pvar) = VT_ARRAY|VT_VARIANT;
        V_ARRAY(pvar) = psa;
        goto error;
    }

    dwElements = (wcslen(pszString) == 0) ? 0: 1 ;

    while(!(*pszCurrPos == TEXT('\0'))){
        if(*pszCurrPos == Delimiter){
            dwElements++;
            *pszCurrPos = TEXT('\0');
        }
        pszCurrPos++;
    }

    rgszStrings = (LPTSTR *)AllocADsMem(sizeof(LPTSTR)*dwElements);

    if(!rgszStrings){
        hr = E_OUTOFMEMORY;
        goto error;
    }

    //
    // scan string again and put the appropriate pointers
    //

    pszCurrPos = pszString;
    if(rgszStrings != NULL){
        (*rgszStrings) = pszCurrPos;
    }
    i = 1;

    while(i < (LONG)dwElements){

        if(*pszCurrPos == TEXT('\0')){
            *(rgszStrings+i) = ++pszCurrPos;
            i++;
        }
        pszCurrPos++;
    }


    //
    // create the safearray
    //

    sabound[0].cElements = dwElements;
    sabound[0].lLbound = 0;

    psa = SafeArrayCreate(VT_VARIANT, 1, sabound);

    if (psa == NULL){
        hr = E_OUTOFMEMORY;
        goto error;
    }

    for(i=0; i<(LONG)dwElements; i++){

        VariantInit(&v);
        V_VT(&v) = VT_BSTR;

        hr = ADsAllocString(*(rgszStrings+i), &(V_BSTR(&v)));
        BAIL_ON_FAILURE(hr);

        //
        // Stick the caller provided data into the end of the SafeArray
        //

        hr = SafeArrayPutElement(psa, &i, &v);
        VariantClear(&v);
        BAIL_ON_FAILURE(hr);

    }

    //
    // convert this safearray into a VARIANT
    //

    VariantInit(pvar);
    V_VT(pvar) = VT_ARRAY|VT_VARIANT;
    V_ARRAY(pvar) = psa;

error:
    if(rgszStrings && dwElements != 0){
        FreeADsMem(rgszStrings);
    }
    RRETURN(hr);
}

HRESULT
VariantToDelimitedString(
    VARIANT var,
    LPTSTR *ppszString,
    TCHAR  Delimiter
    )
{

    LONG lIndices;
    ULONG cElements;
    ULONG  ulRequiredLength=0;
    SAFEARRAY  *psa = NULL;
    VARIANT vElement;
    LPTSTR  pszCurrPos = NULL;
    HRESULT hr = S_OK;
    ULONG i;

    //
    // converts the safearray in a variant to a delimited string
    //

    *ppszString = NULL;

    if(!(V_VT(&var) == (VT_VARIANT|VT_ARRAY))) {
        RRETURN(E_FAIL);
    }

    psa = V_ARRAY(&var);

    //
    // Check that there is only one dimension in this array
    //

    if (psa->cDims != 1) {
        hr = E_FAIL;
        BAIL_ON_FAILURE(hr);
    }

    //
    // Check that there is atleast one element in this array
    //

    cElements = psa->rgsabound[0].cElements;

    if (cElements == 0){
        hr = E_FAIL;
        goto error;
    }

    //
    // We know that this is a valid single dimension array
    //

    lIndices= 0;
    for(i=0; i< cElements; i++){
        lIndices = i;
        VariantInit(&vElement);
        hr = SafeArrayGetElement(psa, &lIndices, &vElement);
        BAIL_ON_FAILURE(hr);

        if(!(V_VT(&vElement) == VT_BSTR)){
            RRETURN(E_FAIL);
        }

        //
        // unpack the BSTR in the VARIANT
        //

        ulRequiredLength+= wcslen(vElement.bstrVal)+1;
        VariantClear(&vElement);
    }

    ulRequiredLength +=2;

    *ppszString = (LPTSTR)AllocADsMem( ulRequiredLength*sizeof(TCHAR));
    if(*ppszString == NULL){
        hr = E_OUTOFMEMORY;
        goto error;
    }
    lIndices= 0;

    pszCurrPos = *ppszString;

    for(i=0; i< cElements; i++){
        lIndices = i;
        VariantInit(&vElement);
        hr = SafeArrayGetElement(psa, &lIndices, &vElement);
        BAIL_ON_FAILURE(hr);

        if(!(V_VT(&vElement) == VT_BSTR)){
            RRETURN(E_FAIL);
        }

        //
        // unpack the BSTR in the VARIANT
        //


        wcscpy(pszCurrPos, (LPTSTR)vElement.bstrVal);
        pszCurrPos += wcslen(vElement.bstrVal);

        if(i < cElements-1){
            *pszCurrPos = Delimiter;
        }
        pszCurrPos++;
        VariantClear(&vElement);
    }

    *pszCurrPos = L'\0';
    RRETURN(S_OK);

 error:

    RRETURN(hr);
}
HRESULT
VariantToNulledString(
    VARIANT var,
    LPTSTR *ppszString
    )

{

    LONG lIndices;
    ULONG cElements;
    ULONG  ulRequiredLength=0;
    SAFEARRAY  *psa = NULL;
    VARIANT vElement;
    LPTSTR  szCurrPos = NULL;
    HRESULT hr = S_OK;
    ULONG i;
    //
    //converts the safearray in a variant to a double nulled string
    //

    VariantInit(&vElement);
    *ppszString = NULL;

    if (!(V_VT(&var) == (VT_VARIANT|VT_ARRAY))) {
        RRETURN(E_FAIL);
    }

    psa = V_ARRAY(&var);

    //
    // Check that there is only one dimension in this array
    //

    if (psa->cDims != 1) {
        hr = E_FAIL;
        BAIL_ON_FAILURE(hr);
    }
    //
    // Check that there is atleast one element in this array
    //

    cElements = psa->rgsabound[0].cElements;

    if (cElements == 0){
        hr = E_FAIL;
        BAIL_ON_FAILURE(hr);
    }

    //
    // We know that this is a valid single dimension array
    //

    lIndices= 0;
    for(i=0; i< cElements; i++){
        lIndices = i;
        VariantInit(&vElement);

        hr = SafeArrayGetElement(psa, &lIndices, &vElement);
        BAIL_ON_FAILURE(hr);

        if(!(V_VT(&vElement) == VT_BSTR)){
            RRETURN(E_FAIL);
        }

        //
        // unpack the BSTR in the VARIANT
        //


        ulRequiredLength+= wcslen(vElement.bstrVal)+1;
        VariantClear(&vElement);
    }

    ulRequiredLength +=2;

    *ppszString = (LPTSTR)AllocADsMem( ulRequiredLength*sizeof(TCHAR));
    if(*ppszString == NULL){
        hr = E_OUTOFMEMORY;
        goto error;
    }
    lIndices= 0;

    szCurrPos = *ppszString;

    for(i=0; i< cElements; i++){
        lIndices = i;
        VariantInit(&vElement);

        hr = SafeArrayGetElement(psa, &lIndices, &vElement);
        BAIL_ON_FAILURE(hr);

        if(!(V_VT(&vElement) == VT_BSTR)){
            RRETURN(E_FAIL);
        }

        //
        // unpack the BSTR in the VARIANT
        //


        wcscpy(szCurrPos, (LPTSTR)vElement.bstrVal);
        szCurrPos += wcslen(vElement.bstrVal)+1;
        VariantClear(&vElement);
    }

    *szCurrPos = L'\0';
    RRETURN(S_OK);

error:

    VariantClear(&vElement);

    RRETURN(hr);
}


HRESULT
NulledStringToVariant(
    LPTSTR pszString,
    VARIANT *pvar
    )
{
    SAFEARRAYBOUND sabound[1];
    DWORD dwElements = 0;
    LPTSTR pszCurrPos = pszString;
    BOOL foundNULL = FALSE;
    LPTSTR *rgszStrings = NULL;
    SAFEARRAY *psa = NULL;
    VARIANT v;
    HRESULT hr = S_OK;
    LONG i;

    //
    // This function converts a double nulled string into a VARIANT of
    // safe arrays.
    //
    // Assumption: Valid double nulled strings are passed to this function
    //


    //
    // scan the double nulled string once to find out the dimension
    //

    while(!(*pszCurrPos == L'\0' && foundNULL)){
        if(*pszCurrPos == L'\0'){
            dwElements++;
            foundNULL = TRUE;
        }
        else{
            foundNULL = FALSE;
        }
        pszCurrPos++;
    }

    if(dwElements){
        rgszStrings = (LPTSTR *)AllocADsMem(sizeof(LPTSTR)*dwElements);
    }

    //
    // scan string again and put the appropriate pointers
    //

    pszCurrPos = pszString;
    if(rgszStrings != NULL){
        (*rgszStrings) = pszCurrPos;
    }
    foundNULL = FALSE;
    i = 1;

    while(i < (LONG)dwElements){
        if(foundNULL){
            *(rgszStrings+i) = pszCurrPos;
            i++;
        }
        if(*pszCurrPos == L'\0'){
            foundNULL = TRUE;
        }
        else{
            foundNULL = FALSE;
        }
        pszCurrPos++;
    }


    //
    // create the safearray
    //

    sabound[0].cElements = dwElements;
    sabound[0].lLbound = 0;

    psa = SafeArrayCreate(VT_VARIANT, 1, sabound);

    if (psa == NULL){
        hr = E_OUTOFMEMORY;
        goto error;
    }

    for(i=0; i<(LONG)dwElements; i++){

        VariantInit(&v);
        V_VT(&v) = VT_BSTR;

        hr = ADsAllocString(*(rgszStrings+i), &(V_BSTR(&v)));
        BAIL_ON_FAILURE(hr);

        //
        // Stick the caller provided data into the end of the SafeArray
        //

        hr = SafeArrayPutElement(psa, &i, &v);
        VariantClear(&v);
        BAIL_ON_FAILURE(hr);

    }

    //
    // convert this safearray into a VARIANT
    //

    VariantInit(pvar);
    V_VT(pvar) = VT_ARRAY|VT_VARIANT;
    V_ARRAY(pvar) = psa;

error:
    if(rgszStrings){
        FreeADsMem(rgszStrings);
    }
    RRETURN(hr);
}



STDMETHODIMP
GenericGetPropertyManager(
    CPropertyCache * pPropertyCache,
    THIS_ BSTR bstrName,
    VARIANT FAR* pvProp
    )
{
    HRESULT hr = S_OK;
    DWORD dwSyntaxId;
    DWORD dwNumValues;
    DWORD dwInfoLevel;
    LPNTOBJECT pNtSrcObjects = NULL;

    //
    // retrieve data object from cache; if one exists

    hr = pPropertyCache->getproperty(
                bstrName,
                &dwSyntaxId,
                &dwNumValues,
                &pNtSrcObjects
                );

    BAIL_ON_FAILURE(hr);

    //
    // translate the Nt objects to variants
    //

    if (dwNumValues == 1) {

        hr = NtTypeToVarTypeCopy(
                    pNtSrcObjects,
                    pvProp
                    );

    }else {

        hr = NtTypeToVarTypeCopyConstruct(
                pNtSrcObjects,
                dwNumValues,
                pvProp
                );
    }

    BAIL_ON_FAILURE(hr);

error:

    if (pNtSrcObjects) {

        NTTypeFreeNTObjects(
            pNtSrcObjects,
            dwNumValues
            );
    }

    RRETURN(hr);
}

STDMETHODIMP
GenericPutPropertyManager(
    CPropertyCache * pPropertyCache,
    PPROPERTYINFO  pSchemaProps,
    DWORD dwSchemaPropSize,
    THIS_ BSTR bstrName,
    VARIANT vProp
    )
{
    HRESULT hr = S_OK;
    DWORD dwSyntaxId  = 0;
    DWORD dwIndex = 0;
    LPNTOBJECT pNtDestObjects = NULL;

    //
    // Issue: How do we handle multi-valued support
    //
    DWORD dwNumValues = 1;

    //
    // check if this is a legal property for this object,
    //
    //

    hr = ValidatePropertyinSchemaClass(
                pSchemaProps,
                dwSchemaPropSize,
                bstrName,
                &dwSyntaxId
                );
    BAIL_ON_FAILURE(hr);


    //
    // check if this is a writeable property
    //

    hr = ValidateIfWriteableProperty(
                pSchemaProps,
                dwSchemaPropSize,
                bstrName
                );
    BAIL_ON_FAILURE(hr);


    //
    // check if the variant maps to the syntax of this property
    //

    hr = VarTypeToNtTypeCopyConstruct(
                    dwSyntaxId,
                    &vProp,
                    1,
                    &pNtDestObjects
                    );
    BAIL_ON_FAILURE(hr);

    //
    // Find this property in the cache
    //

    hr = pPropertyCache->findproperty(
                        bstrName,
                        &dwIndex
                        );

    //
    // If this property does not exist in the
    // cache, add this property into the cache.
    //


    if (FAILED(hr)) {
        hr = pPropertyCache->addproperty(
                    bstrName,
                    dwSyntaxId
                    );
        //
        // If the operation fails for some reason
        // move on to the next property
        //
        BAIL_ON_FAILURE(hr);

    }

    //
    // Now update the property in the cache
    // Should use putproperty, not updateproperty -> unmarshalling from svr only
    //

    hr = pPropertyCache->putproperty(
                    bstrName,
                    dwSyntaxId,
                    dwNumValues,
                    pNtDestObjects
                    );
    BAIL_ON_FAILURE(hr);

error:

    if (pNtDestObjects) {
        NTTypeFreeNTObjects(
                pNtDestObjects,
                dwNumValues
                );

    }

    RRETURN(hr);
}



HRESULT
BuildPrinterNameFromADsPath(
    LPWSTR pszADsParent,
    LPWSTR pszPrinterName,
    LPWSTR pszUncPrinterName
    )
{
    POBJECTINFO pObjectInfo = NULL;
    CLexer Lexer(pszADsParent);
    HRESULT hr;

    pObjectInfo = (POBJECTINFO)AllocADsMem(sizeof(OBJECTINFO));
    if (!pObjectInfo)
        BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);

    memset(pObjectInfo, 0, sizeof(OBJECTINFO));
    hr = Object(&Lexer, pObjectInfo);
    BAIL_ON_FAILURE(hr);

    wsprintf(
        pszUncPrinterName,
        L"\\\\%s\\%s",
        pObjectInfo->ComponentArray[0],
        pszPrinterName
        );

error:
    if(pObjectInfo){
        FreeObjectInfo(pObjectInfo);
    }
    RRETURN(hr);

}

STDMETHODIMP
GenericGetExPropertyManager(
    DWORD dwObjectState,
    CPropertyCache * pPropertyCache,
    THIS_ BSTR bstrName,
    VARIANT FAR* pvProp
    )
{
    HRESULT hr = S_OK;
    DWORD dwSyntaxId;
    DWORD dwNumValues;
    LPNTOBJECT pNtSrcObjects = NULL;

    //
    // retrieve data object from cache; if one exis
    //

    if (dwObjectState == ADS_OBJECT_UNBOUND) {

        hr = pPropertyCache->unboundgetproperty(
                    bstrName,
                    &dwSyntaxId,
                    &dwNumValues,
                    &pNtSrcObjects
                    );
        BAIL_ON_FAILURE(hr);

    }else {

        hr = pPropertyCache->getproperty(
                    bstrName,
                    &dwSyntaxId,
                    &dwNumValues,
                    &pNtSrcObjects
                    );
        BAIL_ON_FAILURE(hr);
    }


    //
    // translate the Nds objects to variants
    //

    hr = NtTypeToVarTypeCopyConstruct(
                pNtSrcObjects,
                dwNumValues,
                pvProp
                );

    BAIL_ON_FAILURE(hr);

error:
    if (pNtSrcObjects) {

        NTTypeFreeNTObjects(
            pNtSrcObjects,
            dwNumValues
            );
    }

    RRETURN(hr);
}


STDMETHODIMP
GenericPutExPropertyManager(
    CPropertyCache * pPropertyCache,
    PPROPERTYINFO pSchemaProps,
    DWORD dwSchemaPropSize,
    THIS_ BSTR bstrName,
    VARIANT vProp
    )
{
    HRESULT hr = S_OK;
    DWORD dwSyntaxId  = 0;
    DWORD dwIndex = 0;
    DWORD dwNumValues = 0;
    LPNTOBJECT pNtDestObjects = NULL;

    VARIANT * pVarArray = NULL;
    VARIANT * pvProp = NULL;


    //
    // Issue: How do we handle multi-valued support
    //
    //
    // A VT_BYREF|VT_VARIANT may expand to a VT_VARIANT|VT_ARRAY.
    // We should dereference a VT_BYREF|VT_VARIANT once and see
    // what's inside.
    //
    pvProp = &vProp;
    if (V_VT(pvProp) == (VT_BYREF|VT_VARIANT)) {
        pvProp = V_VARIANTREF(&vProp);
    }


    if ((V_VT(pvProp) == (VT_VARIANT|VT_ARRAY)) ||
        (V_VT(&vProp) == (VT_VARIANT|VT_ARRAY|VT_BYREF))) {

        hr  = ConvertByRefSafeArrayToVariantArray(
                    *pvProp,
                    &pVarArray,
                    &dwNumValues
                    );
        BAIL_ON_FAILURE(hr);
        pvProp = pVarArray;

    }else {

        hr = E_FAIL;
        BAIL_ON_FAILURE(hr);
    }


    //
    // check if this is a legal property for this object,
    //
    //

    hr = ValidatePropertyinSchemaClass(
                pSchemaProps,
                dwSchemaPropSize,
                bstrName,
                &dwSyntaxId
                );
    BAIL_ON_FAILURE(hr);

    //
    // check if this is a writeable property
    //


    hr = ValidateIfWriteableProperty(
                pSchemaProps,
                dwSchemaPropSize,
                bstrName
                );
    BAIL_ON_FAILURE(hr);


    //
    // check if the variant maps to the syntax of this property
    //

    hr = VarTypeToNtTypeCopyConstruct(
                    dwSyntaxId,
                    pvProp,
                    dwNumValues,
                    &pNtDestObjects
                    );
    BAIL_ON_FAILURE(hr);

    //
    // Find this property in the cache
    //

    hr = pPropertyCache->findproperty(
                        bstrName,
                        &dwIndex
                        );

    //
    // If this property does not exist in the
    // cache, add this property into the cache.
    //


    if (FAILED(hr)) {
        hr = pPropertyCache->addproperty(
                    bstrName,
                    dwSyntaxId
                    );
        //
        // If the operation fails for some reason
        // move on to the next property
        //
        BAIL_ON_FAILURE(hr);

    }

    //
    // Now update the property in the cache
    //

    hr = pPropertyCache->putproperty(
                    bstrName,
                    dwSyntaxId,
                    dwNumValues,
                    pNtDestObjects
                    );
    BAIL_ON_FAILURE(hr);

error:

    if (pNtDestObjects) {
        NTTypeFreeNTObjects(
                pNtDestObjects,
                dwNumValues
                );

    }

    if (pVarArray) {

        DWORD i = 0;

        for (i = 0; i < dwNumValues; i++) {
            VariantClear(pVarArray + i);
        }
        FreeADsMem(pVarArray);
    }


    RRETURN(hr);
}

HRESULT
GenericPropCountPropertyManager(
    CPropertyCache * pPropertyCache,
    PLONG plCount
    )
{
    HRESULT hr = E_FAIL;

    if (pPropertyCache) {
        hr = pPropertyCache->get_PropertyCount((PDWORD)plCount);
    }
    RRETURN(hr);
}


HRESULT
GenericNextPropertyManager(
    CPropertyCache * pPropertyCache,
    VARIANT FAR *pVariant
    )
{
    HRESULT hr = E_FAIL;
    DWORD dwSyntaxId = 0;
    DWORD dwNumValues = 0;
    LPNTOBJECT pNtSrcObjects = NULL;
    VARIANT varData;
    IDispatch * pDispatch = NULL;

    VariantInit(&varData);

    hr = pPropertyCache->unboundgetproperty(
                pPropertyCache->get_CurrentIndex(),
                &dwSyntaxId,
                &dwNumValues,
                &pNtSrcObjects
                );
    BAIL_ON_FAILURE(hr);

    //
    // translate the Nt objects to variants
    //

    hr = ConvertNtValuesToVariant(
                pPropertyCache->get_CurrentPropName(),
                pNtSrcObjects,
                dwNumValues,
                pVariant
                );
    BAIL_ON_FAILURE(hr);


error:

    //
    // - goto next one even if error to avoid infinite looping at a property
    //   which we cannot convert (e.g. schemaless server property.)
    // - do not return the result of Skip() as current operation does not
    //   depend on the sucess of Skip().
    //

    pPropertyCache->skip_propindex(
                1
                );

    if (pNtSrcObjects) {

        NTTypeFreeNTObjects(
                pNtSrcObjects,
                dwNumValues
                );
    }

    RRETURN(hr);
}


HRESULT
GenericSkipPropertyManager(
    CPropertyCache * pPropertyCache,
    ULONG cElements
    )
{
    HRESULT hr = E_FAIL;

    hr = pPropertyCache->skip_propindex(
                cElements
                );
    RRETURN(hr);
}

HRESULT
GenericResetPropertyManager(
    CPropertyCache * pPropertyCache
    )
{
    pPropertyCache->reset_propindex();

    RRETURN(S_OK);
}


HRESULT
GenericDeletePropertyManager(
    CPropertyCache * pPropertyCache,
    VARIANT varEntry
    )
{
   HRESULT hr = S_OK;
   DWORD dwIndex = 0;

   switch (V_VT(&varEntry)) {

   case VT_BSTR:

       hr = pPropertyCache->findproperty(
                           V_BSTR(&varEntry),
                           &dwIndex
                           );
       BAIL_ON_FAILURE(hr);
       break;

   case VT_I4:
       dwIndex = V_I4(&varEntry);
       break;


   case VT_I2:
       dwIndex = V_I2(&varEntry);
       break;


   default:
       hr = E_FAIL;
       BAIL_ON_FAILURE(hr);
   }

   hr = pPropertyCache->deleteproperty(
                       dwIndex
                       );
error:
   RRETURN(hr);
}

HRESULT
GenericPutPropItemPropertyManager(
    CPropertyCache * pPropertyCache,
    PPROPERTYINFO pSchemaProps,
    DWORD dwSchemaPropSize,
    VARIANT varData
    )
{
    HRESULT hr = S_OK;
    DWORD dwSyntaxId  = 0;
    DWORD dwIndex = 0;
    WCHAR szPropertyName[MAX_PATH];
    LPNTOBJECT pNtDestObjects = NULL;
    DWORD dwNumValues = 0;
    DWORD dwControlCode = 0;

    hr = ConvertVariantToNtValues(
                varData,
                pSchemaProps,
                dwSchemaPropSize,
                szPropertyName,
                &pNtDestObjects,
                &dwNumValues,
                &dwSyntaxId,
                &dwControlCode
                );
    BAIL_ON_FAILURE(hr);

    if (dwControlCode != ADS_PROPERTY_UPDATE) {
        RRETURN(E_ADS_BAD_PARAMETER);
    }

    //
    // Find this property in the cache
    //

    hr = pPropertyCache->findproperty(
                        szPropertyName,
                        &dwIndex
                        );

    //
    // If this property does not exist in the
    // cache, add this property into the cache.
    //


    if (FAILED(hr)) {
        hr = pPropertyCache->addproperty(
                    szPropertyName,
                    dwSyntaxId
                    );
        //
        // If the operation fails for some reason
        // move on to the next property
        //
        BAIL_ON_FAILURE(hr);

    }

    //
    // Now update the property in the cache
    //

    hr = pPropertyCache->putproperty(
                    szPropertyName,
                    dwSyntaxId,
                    dwNumValues,
                    pNtDestObjects
                    );
    BAIL_ON_FAILURE(hr);

error:

    if (pNtDestObjects) {
        NTTypeFreeNTObjects(
                pNtDestObjects,
                dwNumValues
                );

    }

    RRETURN(hr);
}


HRESULT
GenericGetPropItemPropertyManager(
    CPropertyCache * pPropertyCache,
    DWORD dwObjectState,
    BSTR bstrName,
    LONG lnADsType,
    VARIANT * pVariant
    )
{
    HRESULT hr = S_OK;
    DWORD dwSyntaxId;
    DWORD dwNumValues;
    LPNTOBJECT pNtSrcObjects = NULL;

    //
    // retrieve data object from cache; if one exis
    //

    if (dwObjectState == ADS_OBJECT_UNBOUND) {

        hr = pPropertyCache->unboundgetproperty(
                    bstrName,
                    &dwSyntaxId,
                    &dwNumValues,
                    &pNtSrcObjects
                    );
        BAIL_ON_FAILURE(hr);

    }else {

        hr = pPropertyCache->getproperty(
                    bstrName,
                    &dwSyntaxId,
                    &dwNumValues,
                    &pNtSrcObjects
                    );
        BAIL_ON_FAILURE(hr);
    }


    //
    // translate the Nds objects to variants
    //

    hr = ConvertNtValuesToVariant(
                bstrName,
                pNtSrcObjects,
                dwNumValues,
                pVariant
                );
    BAIL_ON_FAILURE(hr);

error:
    if (pNtSrcObjects) {

        NTTypeFreeNTObjects(
            pNtSrcObjects,
            dwNumValues
            );
    }

    RRETURN(hr);
}


HRESULT
GenericItemPropertyManager(
    CPropertyCache * pPropertyCache,
    DWORD dwObjectState,
    VARIANT varIndex,
    VARIANT *pVariant
    )
{
    HRESULT hr = S_OK;
    DWORD dwSyntaxId;
    DWORD dwNumValues;
    LPNTOBJECT pNtSrcObjects = NULL;
    LPWSTR szPropName = NULL;
    VARIANT *pvVar = &varIndex;

    //
    // retrieve data object from cache; if one exis
    //

    if (V_VT(pvVar) == (VT_BYREF|VT_VARIANT)) {
        //
        // The value is being passed in byref so we need to
        // deref it for vbs stuff to work
        //
        pvVar = V_VARIANTREF(&varIndex);
    }

    switch (V_VT(pvVar)) {

    case VT_BSTR:
        if (dwObjectState == ADS_OBJECT_UNBOUND) {

            hr = pPropertyCache->unboundgetproperty(
                        V_BSTR(pvVar),
                        &dwSyntaxId,
                        &dwNumValues,
                        &pNtSrcObjects
                        );
            BAIL_ON_FAILURE(hr);

        }else {

            hr = pPropertyCache->getproperty(
                        V_BSTR(pvVar),
                        &dwSyntaxId,
                        &dwNumValues,
                        &pNtSrcObjects
                        );
            BAIL_ON_FAILURE(hr);
        }

        hr = ConvertNtValuesToVariant(
                    V_BSTR(pvVar),
                    pNtSrcObjects,
                    dwNumValues,
                    pVariant
                    );
        BAIL_ON_FAILURE(hr);
        break;


    case VT_I4:

        hr = pPropertyCache->unboundgetproperty(
                    V_I4(pvVar),
                    &dwSyntaxId,
                    &dwNumValues,
                    &pNtSrcObjects
                    );
        BAIL_ON_FAILURE(hr);

        szPropName = pPropertyCache->get_PropName(V_I4(pvVar));

        hr = ConvertNtValuesToVariant(
                    szPropName,
                    pNtSrcObjects,
                    dwNumValues,
                    pVariant
                    );
        BAIL_ON_FAILURE(hr);
        break;


    case VT_I2:

        hr = pPropertyCache->unboundgetproperty(
                    (DWORD)V_I2(pvVar),
                    &dwSyntaxId,
                    &dwNumValues,
                    &pNtSrcObjects
                    );
        BAIL_ON_FAILURE(hr);

        szPropName = pPropertyCache->get_PropName(V_I2(pvVar));

        hr = ConvertNtValuesToVariant(
                    szPropName,
                    pNtSrcObjects,
                    dwNumValues,
                    pVariant
                    );
        BAIL_ON_FAILURE(hr);

        break;





    default:
        hr = E_FAIL;
        BAIL_ON_FAILURE(hr);

    }

error:
    if (pNtSrcObjects) {

        NTTypeFreeNTObjects(
            pNtSrcObjects,
            dwNumValues
            );
    }

    RRETURN(hr);

}


HRESULT
GenericPurgePropertyManager(
    CPropertyCache * pPropertyCache
    )
{
    pPropertyCache->flushpropcache();
    RRETURN(S_OK);
}


HRESULT
CreatePropEntry(
    LPWSTR szPropName,
    ADSTYPE dwADsType,
    VARIANT varData,
    REFIID riid,
    LPVOID * ppDispatch
    )

{
    HRESULT hr = S_OK;
    IADsPropertyEntry * pPropEntry = NULL;

    hr = CoCreateInstance(
                CLSID_PropertyEntry,
                NULL,
                CLSCTX_INPROC_SERVER,
                IID_IADsPropertyEntry,
                (void **)&pPropEntry
                );
    BAIL_ON_FAILURE(hr);


    hr = pPropEntry->put_Name(szPropName);

    BAIL_ON_FAILURE(hr);

    hr = pPropEntry->put_Values(varData);

    BAIL_ON_FAILURE(hr);

    hr = pPropEntry->put_ADsType(dwADsType);

    BAIL_ON_FAILURE(hr);

    hr = pPropEntry->QueryInterface(
                        riid,
                        ppDispatch
                        );
    BAIL_ON_FAILURE(hr);


error:

    if (pPropEntry) {
        pPropEntry->Release();
    }

    RRETURN(hr);

}



HRESULT
ConvertNtValuesToVariant(
    LPWSTR szPropertyName,
    PNTOBJECT pNtSrcObject,
    DWORD dwNumValues,
    PVARIANT pVariant
    )
{
    HRESULT hr = S_OK;
    VARIANT varData;
    IDispatch * pDispatch = NULL;
    PADSVALUE pAdsValues = NULL;
    ADSTYPE dwADsType = ADSTYPE_INVALID;

    VariantInit(&varData);
    VariantInit(pVariant);

    if (dwNumValues>0) {

        hr = NTTypeToAdsTypeCopyConstruct(
                pNtSrcObject,
                dwNumValues,
                &pAdsValues
                );

        if (SUCCEEDED(hr)){

            hr = AdsTypeToPropVariant(
                    pAdsValues,
                    dwNumValues,
                    &varData
                    );
            BAIL_ON_FAILURE(hr);

            dwADsType = pAdsValues->dwType;
        }

        else if (hr==E_OUTOFMEMORY) {

            BAIL_ON_FAILURE(hr);
        }

        // failed because of NTType is not supported yet (e.g. NulledString)
        // in NTTypeToAdsTypeCopyConstruct() conversion yet
        // -> use empty variant now.
        else {

            VariantInit(&varData);
        }
    }

    hr = CreatePropEntry(
            szPropertyName,
            dwADsType,
            varData,
            IID_IDispatch,
            (void **)&pDispatch
            );
    BAIL_ON_FAILURE(hr);


    V_DISPATCH(pVariant) = pDispatch;
    V_VT(pVariant) = VT_DISPATCH;

error:

    VariantClear(&varData);

    if (pAdsValues) {
        AdsFreeAdsValues(
            pAdsValues,
            dwNumValues
            );
        FreeADsMem( pAdsValues);
    }

    RRETURN(hr);
}



HRESULT
ConvertVariantToVariantArray(
    VARIANT varData,
    VARIANT ** ppVarArray,
    DWORD * pdwNumValues
    )
{
    DWORD dwNumValues = 0;
    VARIANT * pVarArray = NULL;
    HRESULT hr = S_OK;
    VARIANT * pVarData = NULL;

    *ppVarArray = NULL;
    *pdwNumValues = 0;

    //
    // A VT_BYREF|VT_VARIANT may expand to a VT_VARIANT|VT_ARRAY.
    // We should dereference a VT_BYREF|VT_VARIANT once and see
    // what's inside.
    //
    pVarData = &varData;
    if (V_VT(pVarData) == (VT_BYREF|VT_VARIANT)) {
        pVarData = V_VARIANTREF(&varData);
    }

    if ((V_VT(pVarData) == (VT_VARIANT|VT_ARRAY|VT_BYREF)) ||
        (V_VT(pVarData) == (VT_VARIANT|VT_ARRAY))) {

        hr  = ConvertSafeArrayToVariantArray(
                  varData,
                  &pVarArray,
                  &dwNumValues
                  );
        BAIL_ON_FAILURE(hr);

    } else {

        pVarArray = NULL;
        dwNumValues = 0;
    }

    *ppVarArray = pVarArray;
    *pdwNumValues = dwNumValues;

error:
    RRETURN(hr);
}

void
FreeVariantArray(
    VARIANT * pVarArray,
    DWORD dwNumValues
    )
{
    if (pVarArray) {

        DWORD i = 0;

        for (i = 0; i < dwNumValues; i++) {
            VariantClear(pVarArray + i);
        }
        FreeADsMem(pVarArray);
    }
}

HRESULT
ConvertVariantToNtValues(
    VARIANT varData,
    PPROPERTYINFO pSchemaProps,
    DWORD dwSchemaPropSize,
    LPWSTR szPropertyName,
    PNTOBJECT *ppNtDestObjects,
    PDWORD pdwNumValues,
    PDWORD pdwSyntaxId,
    PDWORD pdwControlCode
    )
{
    HRESULT hr = S_OK;
    IADsPropertyEntry * pPropEntry = NULL;
    IDispatch * pDispatch = NULL;
    BSTR bstrPropName = NULL;
    DWORD dwControlCode = 0;
    DWORD dwAdsType = 0;
    VARIANT varValues;
    VARIANT * pVarArray = NULL;
    DWORD dwNumValues = 0;
    PADSVALUE pAdsValues = NULL;
    DWORD dwAdsValues  = 0;

    PNTOBJECT pNtDestObjects = 0;
    DWORD dwNumNtObjects = 0;
    DWORD dwNtSyntaxId = 0;

    if (V_VT(&varData) != VT_DISPATCH) {
        RRETURN (hr = DISP_E_TYPEMISMATCH);
    }

    pDispatch = V_DISPATCH(&varData);

    hr = pDispatch->QueryInterface(
                        IID_IADsPropertyEntry,
                        (void **)&pPropEntry
                        );
    BAIL_ON_FAILURE(hr);

    VariantInit(&varValues);
    VariantClear(&varValues);


    hr = pPropEntry->get_Name(&bstrPropName);
    BAIL_ON_FAILURE(hr);
    wcscpy(szPropertyName, bstrPropName);

    hr = pPropEntry->get_ControlCode((long *)&dwControlCode);
    BAIL_ON_FAILURE(hr);
    *pdwControlCode = dwControlCode;

    hr = pPropEntry->get_ADsType((long *)&dwAdsType);
    BAIL_ON_FAILURE(hr);

    hr = pPropEntry->get_Values(&varValues);
    BAIL_ON_FAILURE(hr);

    hr = ConvertVariantToVariantArray(
            varValues,
            &pVarArray,
            &dwNumValues
            );
    BAIL_ON_FAILURE(hr);

    if (dwNumValues) {
        hr = PropVariantToAdsType(
                    pVarArray,
                    dwNumValues,
                    &pAdsValues,
                    &dwAdsValues
                    );
        BAIL_ON_FAILURE(hr);

        hr = AdsTypeToNTTypeCopyConstruct(
                    pAdsValues,
                    dwAdsValues,
                    &pNtDestObjects,
                    &dwNumNtObjects,
                    &dwNtSyntaxId
                    );
        BAIL_ON_FAILURE(hr);

    }

    *pdwNumValues = dwNumValues;
    *ppNtDestObjects = pNtDestObjects;
    *pdwSyntaxId = dwNtSyntaxId;

error:

    if (pVarArray) {
        FreeVariantArray(
                pVarArray,
                dwNumValues
                );
    }

    RRETURN(hr);
}

HRESULT
ConvertNtValuesToVariant(
    BSTR bstrName,
    LPNTOBJECT pNtSrcObjects,
    DWORD dwNumValues,
    VARIANT * pVariant
    );


HRESULT
ConvertVariantToVariantArray(
    VARIANT varData,
    VARIANT ** ppVarArray,
    DWORD * pdwNumValues
    );

void
FreeVariantArray(
    VARIANT * pVarArray,
    DWORD dwNumValues
    );

HRESULT
ConvertVariantToNtValues(
    VARIANT varData,
    PPROPERTYINFO pSchemaProps,
    DWORD dwSchemaPropSize,
    LPWSTR szPropertyName,
    PNTOBJECT *ppNtDestObjects,
    PDWORD pdwNumValues,
    PDWORD pdwSyntaxId
    );
