#include "winnt.hxx"
#pragma hdrstop

HRESULT
ConvertSafeArrayToVariantArray(
    VARIANT varSafeArray,
    PVARIANT * ppVarArray,
    PDWORD pdwNumVariants
    );


HRESULT
ConvertByRefSafeArrayToVariantArray(
    VARIANT varSafeArray,
    PVARIANT * ppVarArray,
    PDWORD pdwNumVariants
    );

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


FILTERS Filters[] = {
                    {L"user", WINNT_USER_ID},
                    {L"group", WINNT_GROUP_ID},  // for backward compatibility
                    {L"localgroup", WINNT_LOCALGROUP_ID},
                    {L"globalgroup", WINNT_GLOBALGROUP_ID},
                    {L"printqueue", WINNT_PRINTER_ID},
                    {L"domain", WINNT_DOMAIN_ID},
                    {L"computer", WINNT_COMPUTER_ID},
                    {L"service", WINNT_SERVICE_ID},
                    {L"fileshare", WINNT_FILESHARE_ID},
                    {L"schema", WINNT_SCHEMA_ID},
                    {L"class", WINNT_CLASS_ID},
                    {L"syntax", WINNT_SYNTAX_ID},
                    {L"property", WINNT_PROPERTY_ID},
                    {L"FPNWfileshare", WINNT_FPNW_FILESHARE_ID}
                  };

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

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



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



HRESULT
BuildADsPath(
    LPWSTR Parent,
    LPWSTR Name,
    LPWSTR *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);


    if ( (wcslen(Parent) + wcslen(Name)) > MAX_PATH - 1) {
        RRETURN(E_FAIL);
    }

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

    if (!pszDisplayName || !*pszDisplayName) {
        //
        // The display name has to be valid.
        //
        BAIL_ON_FAILURE(hr = E_FAIL);
    }

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

    if (!_wcsicmp(Parent, L"ADs:")) {
        *pADsPath = AllocADsStr(pszDisplayName);
        if (*pADsPath == NULL) {
            BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
        }
        else {
            hr = S_OK;
            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"WinNT:" 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, pszDisplayName);

    *pADsPath = AllocADsStr(ADsPath);

    if (*pADsPath == NULL)
        BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);


cleanup:
error:

    if (pszDisplayName) {
        FreeADsMem(pszDisplayName);
    }

    RRETURN(hr);
}

HRESULT
BuildSchemaPath(
    LPWSTR Parent,
    LPWSTR Name,
    LPWSTR Schema,
    LPWSTR *pSchemaPath
    )
{
    WCHAR SchemaPath[MAX_PATH];
    WCHAR ProviderName[MAX_PATH];
    HRESULT hr = S_OK;

    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 ){

        *pSchemaPath = AllocADsStr(L"");
        RRETURN(*pSchemaPath ? S_OK: E_OUTOFMEMORY );
    }

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


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

    if (!pObjectInfo->NumComponents) {
        if( (wcslen(Name) + wcslen(szProviderName) + 4) > MAX_PATH ) {
            BAIL_ON_FAILURE(hr = E_INVALIDARG);
        }
        wcscat(SchemaPath, Name);
    }else{
        if( (wcslen(pObjectInfo->DisplayComponentArray[0]) +
                    wcslen(szProviderName) + 4) > MAX_PATH ) {
            BAIL_ON_FAILURE(hr = E_INVALIDARG);
        } 
        wcscat(SchemaPath, pObjectInfo->DisplayComponentArray[0]);
    }

    if( (wcslen(SchemaPath) + wcslen(SCHEMA_NAME) + wcslen(Schema) + 3) >
               MAX_PATH ) {
        BAIL_ON_FAILURE(hr = E_INVALIDARG);
    }
 
    wcscat( SchemaPath, L"/");
    wcscat( SchemaPath, SCHEMA_NAME );
    wcscat( SchemaPath, L"/");
    wcscat( SchemaPath, Schema );


    *pSchemaPath = AllocADsStr(SchemaPath);
    hr = pSchemaPath ? S_OK: E_OUTOFMEMORY ;

error:

    FreeObjectInfo( &ObjectInfo, TRUE );

    RRETURN(hr);
}



HRESULT
BuildADsGuid(
    REFCLSID clsid,
    LPWSTR *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);
    }

    *pADsClass = AllocADsStr(ADsClass);
    RRETURN (*pADsClass ? S_OK: E_OUTOFMEMORY);

}


HRESULT
MakeUncName(
    LPWSTR szSrcBuffer,
    LPWSTR szTargBuffer
    )
{
    ADsAssert(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
BuildObjectInfo(
    LPWSTR ADsParent,
    LPWSTR Name,
    POBJECTINFO * ppObjectInfo
    )
{
    WCHAR szBuffer[MAX_PATH];
    HRESULT hr;
    POBJECTINFO pObjectInfo = NULL;

    //
    // Both should be set in this call, cannot have a NULL parent.
    //
    if (!ADsParent || !*ADsParent) {
        RRETURN(E_ADS_BAD_PARAMETER);
    }
    //
    // We need to make sure that the path is not greater
    // than MAX_PATH + 2 = 1 for / and another for \0
    //
    if ((wcslen(ADsParent) + wcslen(Name) + 2) > MAX_PATH) {
        RRETURN(E_ADS_BAD_PARAMETER);
    }

    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(
    LPWSTR 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);
}


HRESULT
MakeWinNTAccountName(
    POBJECTINFO pObjectInfo,
    LPWSTR  szUserAccount,
    BOOL fConnectToReg
    )
{
    HRESULT hr = S_OK;
    DWORD dwNumComp = 0;
    DWORD dwProductType = PRODTYPE_INVALID;
    WCHAR szDomain[MAX_PATH];
    WCHAR szSAMName[MAX_ADS_PATH];
    BOOL fReplacedWithDC = FALSE;

    // The credentials are needed to pass into WinNTGetCachedComputerName
    CWinNTCredentials nullCredentials;

    // Need szSAMName as dummy param
    szSAMName[0] = L'\0';

    if (!pObjectInfo || !szUserAccount)
    {
        RRETURN(E_ADS_BAD_PARAMETER);
    }

    dwNumComp = pObjectInfo->NumComponents;

    switch (dwNumComp) {

    case 2:
    case 3:

        //
        // Check if machine is a dc
        //

        //
        // Going to try getComputerName first as the NetWkstaGetInfo call
        // times out faster than the RegConnect call we use in
        // GetMachineProductType - AjayR 11-06-98.
        //

        if (fConnectToReg) {

            if (dwNumComp==2) {
               //
               // we don't have domain name in pObjectInfo, let's try
               // to get it from the dc name (comp[0])
               //

               hr = WinNTGetCachedComputerName(
                   pObjectInfo->ComponentArray[0],
                   szUserAccount,
                   szSAMName,
                   nullCredentials
                   );

               if (SUCCEEDED(hr))
               {
                   fReplacedWithDC = TRUE;
               }
            }

            else // dwNumComp==3
            {
               //
               // We have domain name (comp[0]) in our objectInfo, let's use
               // it. Can call ValidateComputerName here, but not needed
               // since error will be caught next.
               //

               wcscpy(szUserAccount, pObjectInfo->ComponentArray[0]);

               fReplacedWithDC = TRUE;
            }

            if (fReplacedWithDC) {
                //
                // Now try connecting to make sure it is a DC
                // otherwise we should not do this replacement
                //
                hr = GetMachineProductType(
                         pObjectInfo->ComponentArray[dwNumComp-2],
                         &dwProductType
                         );
                BAIL_ON_FAILURE(hr);

                if (dwProductType != PRODTYPE_DC) {
                    //
                    // We cannot use szUserAccount as it has
                    // bad info
                    //
                    fReplacedWithDC = FALSE;
                    hr = E_FAIL;
                }

            }

        }// if fConnectToReg

        BAIL_ON_FAILURE(hr);
        //
        // Do not want to replace machine name with domain since not dc or
        // dc but can't replace - best efforts fail
        //

        if (fReplacedWithDC==FALSE)
        {
            wcscpy(szUserAccount, pObjectInfo->ComponentArray[dwNumComp-2]);
        }

        //
        // Add \UserName to account name
        //
        wcscat(szUserAccount, L"\\");
        wcscat(szUserAccount, pObjectInfo->ComponentArray[dwNumComp-1]);
        break;

    default:

        RRETURN(E_ADS_UNKNOWN_OBJECT);

    }


error:
    RRETURN(hr);
}



HRESULT
MakeWinNTDomainAndName(
    POBJECTINFO pObjectInfo,
    LPWSTR szDomName
    )
{
    DWORD dwNumComp = pObjectInfo->NumComponents;

    switch (dwNumComp) {
    case 2:
    case 3:
        wcscpy(szDomName, pObjectInfo->ComponentArray[dwNumComp - 2]);
        wcscat(szDomName, L"\\");
        wcscat(szDomName, pObjectInfo->ComponentArray[dwNumComp - 1]);
        break;

    default:
        RRETURN(E_ADS_UNKNOWN_OBJECT);

    }
    RRETURN(S_OK);
}

HRESULT
ValidateObject(
    DWORD dwObjectType,
    POBJECTINFO pObjectInfo,
    CWinNTCredentials& Credentials
    )
{
    ULONG uGroupType;
    DWORD dwParentId;

    switch (dwObjectType) {
      case WINNT_USER_ID:
        RRETURN(ValidateUserObject(pObjectInfo, &dwParentId, Credentials));

      case WINNT_GROUP_ID:
        RRETURN(ValidateGroupObject(
                    pObjectInfo,
                    &uGroupType,
                    &dwParentId,
                    Credentials
                    ));

      case WINNT_COMPUTER_ID:
        RRETURN(ValidateComputerObject(pObjectInfo, Credentials));

      case WINNT_PRINTER_ID:
        RRETURN(ValidatePrinterObject(pObjectInfo, Credentials));

      case WINNT_SERVICE_ID:
        RRETURN(ValidateServiceObject(pObjectInfo, Credentials));

      case WINNT_FILESHARE_ID:
        RRETURN(ValidateFileShareObject(pObjectInfo, Credentials));

      default:
        RRETURN(E_FAIL);
    }
}

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
CopyObjectInfo(
    POBJECTINFO pSrcObjectInfo,
    POBJECTINFO *pTargObjectInfo
    )
{
    POBJECTINFO pObjectInfo = NULL;
    HRESULT hr S_OK;
    DWORD i;

    if(!pSrcObjectInfo){
        RRETURN(S_OK);
    }
    pObjectInfo = (POBJECTINFO)AllocADsMem(sizeof(OBJECTINFO));

    if (!pObjectInfo) {
        hr = E_OUTOFMEMORY;
        BAIL_ON_FAILURE(hr);
    }

    memset(pObjectInfo, 0, sizeof(OBJECTINFO));

    pObjectInfo->ObjectType = pSrcObjectInfo->ObjectType;
    pObjectInfo->NumComponents = pSrcObjectInfo->NumComponents;
    pObjectInfo->ProviderName = AllocADsStr(pSrcObjectInfo->ProviderName);

    for(i=0; i<pSrcObjectInfo->NumComponents; i++){
        pObjectInfo->ComponentArray[i] =
          AllocADsStr(pSrcObjectInfo->ComponentArray[i]);
        pObjectInfo->DisplayComponentArray[i] =
          AllocADsStr(pSrcObjectInfo->DisplayComponentArray[i]);
    }
    *pTargObjectInfo = pObjectInfo;
    RRETURN(hr);

error:
    RRETURN(hr);
}

HRESULT
GetObjectType(
    PFILTERS pFilters,
    DWORD dwMaxFilters,
    LPWSTR 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_INVALIDARG);
}


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
GetDomainFromPath(
    LPTSTR ADsPath,
    LPTSTR szDomainName
    )
{
   OBJECTINFO ObjectInfo;
   POBJECTINFO pObjectInfo = &ObjectInfo;
   CLexer Lexer(ADsPath);
   HRESULT hr = S_OK;


   //assumption: Valid strings are passed to GetDomainFromPath

   ADsAssert(ADsPath);
   ADsAssert(szDomainName);

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


   if (pObjectInfo->NumComponents) {
       wcscpy(szDomainName, pObjectInfo->ComponentArray[0]);
   }else {
       hr = E_FAIL;
   }

error:

   FreeObjectInfo( &ObjectInfo, TRUE );

   RRETURN(hr);
}

HRESULT
GetServerFromPath(
    LPTSTR ADsPath,
    LPTSTR szServerName
    )
{
   OBJECTINFO ObjectInfo;
   POBJECTINFO pObjectInfo = &ObjectInfo;
   CLexer Lexer(ADsPath);
   HRESULT hr = S_OK;


   //assumption: Valid strings are passed to GetDomainFromPath

   ADsAssert(ADsPath);
   ADsAssert(szServerName);

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


   if (pObjectInfo->NumComponents > 1) {
       wcscpy(szServerName, pObjectInfo->ComponentArray[1]);
   }else {
       hr = E_FAIL;
   }

error:

   FreeObjectInfo( &ObjectInfo, TRUE );

   RRETURN(hr);
}



DWORD
TickCountDiff(
    DWORD dwTime1,
    DWORD dwTime2
    )
{
   //
   // does dwTime1 - dwTime2 and takes care of wraparound.
   // The first time must be later than the second
   // Restriction:: The two times must have been taken not more than
   // 49.7 days apart
   //

   DWORD dwRetval;

   if(dwTime1 >= dwTime2){
      dwRetval = dwTime1 - dwTime2;
   }

   else{
      dwRetval = dwTime2 - dwTime1;
      dwRetval =  MAX_DWORD - dwRetval;
   }
   return dwRetval;
}

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.
    //


    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
BuildComputerFromObjectInfo(
    POBJECTINFO pObjectInfo,
    LPTSTR pszADsPath
    )
{

    if(!pObjectInfo){
        RRETURN(E_FAIL);
    }

    if(pObjectInfo->NumComponents == 3) {

        wsprintf(
            pszADsPath,
            L"%s://%s/%s",
            pObjectInfo->ProviderName,
            pObjectInfo->ComponentArray[0],
            pObjectInfo->ComponentArray[1]
            );

    } else if (pObjectInfo->NumComponents == 2){

        wsprintf(
            pszADsPath,
            L"%s://%s",
            pObjectInfo->ProviderName,
            pObjectInfo->ComponentArray[0]
            );

    } else {
        RRETURN(E_FAIL);
    }

    RRETURN(S_OK);

}

HRESULT
FPNWSERVERADDRtoString(
    FPNWSERVERADDR WkstaAddress,
    LPWSTR * ppszString
    )
{

    HRESULT hr = S_OK;
    TCHAR  szNibble[2]; //one number and a null termination
    USHORT  usNibble;
    int i;
    TCHAR szWkstaAddr[MAX_PATH];

    //
    // assumption: valid input values are passed to this function.
    //

    //
    // First 4 bytes is network address, then a dot and then bytes 5-10
    // are physical node address. Each byte consumes 2 chars space.
    // Then a byte for TEXT('\0')
    //

    _tcscpy(szWkstaAddr, TEXT(""));

    for( i=0; i < 4; i++){

         usNibble = WkstaAddress[i] & 0xF0;
         usNibble = usNibble >> 4;
        _itot(usNibble, szNibble, 16 );
        _tcscat(szWkstaAddr, szNibble);
         usNibble = WkstaAddress[i] & 0xF;
        _itot(usNibble, szNibble, 16 );
        _tcscat(szWkstaAddr, szNibble);

    }

    _tcscat(szWkstaAddr, TEXT("."));

    for(i=4; i<10 ; i++){

         usNibble = WkstaAddress[i] & 0xF0;
         usNibble = usNibble >> 4;
        _itot(usNibble, szNibble, 16 );
        _tcscat(szWkstaAddr, szNibble);
         usNibble = WkstaAddress[i] & 0xF;
        _itot(usNibble, szNibble, 16 );
        _tcscat(szWkstaAddr, szNibble);
    }

    *ppszString = AllocADsStr(szWkstaAddr);

    if(!*ppszString){
        hr = E_OUTOFMEMORY;
    }

    RRETURN(hr);
}

PKEYDATA
CreateTokenList(
    LPWSTR   pKeyData
)
{
    DWORD       cTokens;
    DWORD       cb;
    PKEYDATA    pResult;
    LPWSTR       pDest;
    LPWSTR       psz = pKeyData;
    LPWSTR      *ppToken;

    if (!psz || !*psz)
        return NULL;

    cTokens=1;

    // Scan through the string looking for commas,
    // ensuring that each is followed by a non-NULL character:

    while ((psz = wcschr(psz, L',')) && psz[1]) {

        cTokens++;
        psz++;
    }

    cb = sizeof(KEYDATA) + (cTokens-1) * sizeof(LPWSTR) +
         wcslen(pKeyData)*sizeof(WCHAR) + sizeof(WCHAR);

    if (!(pResult = (PKEYDATA)AllocADsMem(cb)))
        return NULL;

    // Initialise pDest to point beyond the token pointers:

    pDest = (LPWSTR)((LPBYTE)pResult + sizeof(KEYDATA) +
                                      (cTokens-1) * sizeof(LPWSTR));

    // Then copy the key data buffer there:

    wcscpy(pDest, pKeyData);

    ppToken = pResult->pTokens;


    // Remember, wcstok has the side effect of replacing the delimiter
    // by NULL, which is precisely what we want:

    psz = wcstok (pDest, L",");

    while (psz) {

        *ppToken++ = psz;
        psz = wcstok (NULL, L",");
    }

    pResult->cTokens = cTokens;

    return( pResult );
}

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

    //
    // For those who know no not what they do
    //
    if (!pvProp) {
        BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
    }

    //
    // 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,
    BOOL fCheckSchemaWriteAccess
    )
{
    HRESULT hr = S_OK;
    DWORD dwSyntaxId  = 0;
    DWORD dwIndex = 0;
    LPNTOBJECT pNtDestObjects = NULL;

    VARIANT * pVarArray = NULL;
    VARIANT * pvProp = NULL;
    DWORD dwNumValues = 0;


    //
    // check if property in schema and get syntax of property
    //

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

    if(TRUE == fCheckSchemaWriteAccess) {
    //
    // check if this is a writeable property in the schema
    //

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

    //
    // 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);

        if( (0 == dwNumValues) && (NULL == pVarArray) ) {
        // return error if the safearray had no elements. Otherwise, the
        // NT object stored in the property cache is garbage.
            BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
        }

        pvProp = pVarArray;

    }else {

        dwNumValues = 1;
    }


    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,
                    dwNumValues,
                    pNtDestObjects
                    );
        //
        // 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);
}



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;
    LPNTOBJECT pNtDestObjects = NULL;

    VARIANT * pVarArray = NULL;
    VARIANT * pvProp = NULL;
    DWORD dwNumValues = 0;


    //
    // check if property in schema and get syntax of property
    //

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

    //
    // check if this is a writeable property in the schema
    //

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


    //
    // 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);

        if( (0 == dwNumValues) && (NULL == pVarArray) ) {
        // return error if the safearray had no elements. Otherwise, the
        // NT object stored in the property cache is garbage.
            BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
        }

        pvProp = pVarArray;

    }else {

        hr = E_FAIL;
        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,
                    dwNumValues,
                    pNtDestObjects
                    );
        //
        // 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);
}


DWORD
DelimitedStrSize(
    LPWSTR pszString,
    WCHAR  Delimiter
    )
{

    DWORD dwElements = (wcslen(pszString) == 0) ? 0: 1 ;
    LPTSTR pszCurrPos = pszString;

    if (!pszString || !*pszString) {
        return dwElements;
    }

    while(!(*pszCurrPos == TEXT('\0'))){

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

        pszCurrPos++;
    }

    return dwElements;

}




DWORD
NulledStrSize(
    LPWSTR pszString
    )
{


    DWORD dwElements = 0;
    LPTSTR pszCurrPos = pszString;
    BOOL foundNULL = FALSE;

    if (!pszString || !*pszString) {
        return dwElements;
    }

    while(!(*pszCurrPos == TEXT('\0') && foundNULL== TRUE)){

        if(*pszCurrPos == TEXT('\0')){
            dwElements++;
            foundNULL = TRUE;
        } else {
            foundNULL = FALSE;
        }

        pszCurrPos++;
    }

    return dwElements;

}



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] = L"";
    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) {
        BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
    }


    //
    // check if this is a writeable property in the schema
    //

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


    //
    // 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,
                    dwNumValues,
                    pNtDestObjects
                    );
        //
        // 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);

    // no control code

    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 DelimitedString)
        // in NTTypeToAdsTypeCopyConstruct() conversion
        // -> 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(&varData) == (VT_VARIANT|VT_ARRAY))) {

        hr  = ConvertSafeArrayToVariantArray(
                  *pVarData,
                  &pVarArray,
                  &dwNumValues
                  );
        BAIL_ON_FAILURE(hr);

    } else {

        hr = E_FAIL;
        BAIL_ON_FAILURE(hr);
    }

    *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);
    if(wcslen(bstrPropName) < MAX_PATH)
        wcscpy(szPropertyName, bstrPropName);
    else {
        BAIL_ON_FAILURE(hr = E_INVALIDARG);
    }

    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
                );
    }

    if (pPropEntry) {
        pPropEntry->Release();
    }
    VariantClear(&varValues);

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

    RRETURN(hr);
}


HRESULT
GetMachineProductType(
    IN  LPTSTR  pszServer,
    OUT PRODUCTTYPE *pdwProductType
    )
{

    HRESULT     hr = S_OK;
    LONG        dwStatus;
    HKEY        hkLocalMachine = NULL;
    HKEY        hkProductOptions = NULL;
    DWORD       dwValueType;
    WCHAR       szData[20];
    DWORD       dwDataSize = sizeof(szData);


    //
    // pszServer can be NULL for local server
    //
    if (!pdwProductType)
        RRETURN(E_ADS_BAD_PARAMETER);

    *pdwProductType = PRODTYPE_INVALID;


    //
    // Connect to remote's machine registry
    //
    dwStatus = RegConnectRegistry(
                    pszServer,
                    HKEY_LOCAL_MACHINE,
                    &hkLocalMachine
                    );

    if (dwStatus != ERROR_SUCCESS)
    {
        hr = HRESULT_FROM_WIN32(dwStatus);
        BAIL_ON_FAILURE(hr);
    }


    //
    // Open key ProductOptions
    //
    dwStatus = RegOpenKeyEx(
                    hkLocalMachine,
                    L"SYSTEM\\CurrentControlSet\\Control\\ProductOptions",
                    0,
                    KEY_QUERY_VALUE,
                    &hkProductOptions
                    );

    if (dwStatus != ERROR_SUCCESS)
    {
        hr = HRESULT_FROM_WIN32(dwStatus);
        BAIL_ON_FAILURE(hr);
    }


    //
    // Get Value of Product Type
    //
    dwStatus = RegQueryValueEx(
                    hkProductOptions,
                    L"ProductType",
                    NULL,
                    &dwValueType,
                    (LPBYTE) szData,
                    &dwDataSize
                    );


    //
    //  check server type
    //
    if (_wcsicmp(szData, WINNT_A_LANMANNT_W)==0)
    {
        *pdwProductType = PRODTYPE_DC;
    }
    else if (_wcsicmp(szData, WINNT_A_SERVERNT_W)==0)
    {
        *pdwProductType = PRODTYPE_STDALONESVR;
    }
    else if (_wcsicmp(szData, WINNT_A_WINNT_W)==0)
    {
        *pdwProductType = PRODTYPE_WKSTA;
    }
    else
    {
        hr = E_FAIL;
        BAIL_ON_FAILURE(hr);
    }


error:


    if ( hkLocalMachine )
        RegCloseKey(hkLocalMachine);

    if  ( hkProductOptions )
        RegCloseKey(hkProductOptions);

    RRETURN(hr);
}



//
// Get Sid of account name [lpszAccountName] from server [lpszServerName].
// Unmarshall the Sid into cache [pPropertyCache] if [fExplict] is TRUE.
// Use local machine if [lpszServerName] == NULL.
//

HRESULT
GetSidIntoCache(
    IN  LPTSTR lpszServerName,
    IN  LPTSTR lpszAccountName,
    IN  CPropertyCache * pPropertyCache,
    IN  BOOL fExplicit
    )
{
    HRESULT hr = S_OK;
    BOOL fGotSid = FALSE;
    DWORD dwErr = 0;
    PSID pSid = NULL;
    DWORD dwSidLength = 0;
    WCHAR szNewAccountName[MAX_PATH+UNLEN+2];
    LPTSTR lpSrvName;

    //
    // default cbSid size :
    //  - 1 (revision), 1 (authid), max sub(auth)
    //  - * 8 (rev, authid, subid all < 8), but use 8 in case of
    //    structure aligment because of compiler/machine (we want
    //    LookUpAccountName() to succeed at first attempt as much
    //    as possible to min this wired call)
    //

    const DWORD maxSid = (1+1+SID_MAX_SUB_AUTHORITIES) * 8;
    DWORD cbSid = maxSid;

    //
    // dummies
    //

    TCHAR szRefDomainName[MAX_PATH];
    DWORD cbRefDomainName = MAX_PATH;
    SID_NAME_USE eNameUse;


    if (!lpszAccountName)
        RRETURN(E_ADS_INVALID_USER_OBJECT);

    if (!pPropertyCache)
        RRETURN(E_ADS_BAD_PARAMETER);


    //
    // allocate sid and RefDomainName buffer
    //

    pSid = (PSID) AllocADsMem(
                    cbSid
                    );
    if (!pSid) {
        hr = E_OUTOFMEMORY;
        BAIL_ON_FAILURE(hr);
    }
    
    //
    // get sid and other unused info from server
    //

    fGotSid = LookupAccountName(
                lpszServerName,
                lpszAccountName,
                pSid,
                &cbSid,
                szRefDomainName,
                &cbRefDomainName,
                &eNameUse
                );

    if (!fGotSid) {

        if (cbSid>maxSid) {

            //
            // Fail becuase buffer size required > what we have allocated
            // for some reasons,  retry with correct buffer size
            //

            FreeADsMem(pSid);

            pSid = (PSID) AllocADsMem(
                        cbSid
                        );
            if (!pSid) {
                hr = E_OUTOFMEMORY;
                BAIL_ON_FAILURE(hr);
            }

            fGotSid = LookupAccountName(
                        lpszServerName,
                        lpszAccountName,
                        pSid,
                        &cbSid,
                        szRefDomainName,
                        &cbRefDomainName,
                        &eNameUse
                        );

            if (!fGotSid) {

                //
                // Fail on retry with proper buffer size, can do no more
                //

                dwErr = GetLastError();
                hr = HRESULT_FROM_WIN32(dwErr);
            }

        } else {

            //
            // Fail becuase of reasons other then buffer size, not need to
            // retry.
            //

            dwErr = GetLastError();
            hr = HRESULT_FROM_WIN32(dwErr);
        }
        
    } 
    
    if( fGotSid && (eNameUse == SidTypeDomain) ) {
            lpSrvName = lpszServerName;
            if (lpszServerName && (lpszServerName[0] == L'\\') && (lpszServerName[1] == L'\\')) {
                lpSrvName += 2;
            }

        #ifdef WIN95
                if (!_wcsicmp(lpszAccountName, lpSrvName)) {
        #else
                if (CompareStringW(
                        LOCALE_SYSTEM_DEFAULT,
                        NORM_IGNORECASE,
                        lpszAccountName,
                        -1,
                        lpSrvName,
                        -1
                        ) == CSTR_EQUAL ) {
        #endif
                    wcscpy(szNewAccountName, lpSrvName);
                    wcscat(szNewAccountName, L"\\");
                    wcscat(szNewAccountName, lpszAccountName);

                    cbSid = maxSid;
                    cbRefDomainName = MAX_PATH;
                
                    fGotSid = LookupAccountName(
                            lpszServerName,
                            szNewAccountName,
                            pSid,
                            &cbSid,
                            szRefDomainName,
                            &cbRefDomainName,
                            &eNameUse
                            );

                    if (!fGotSid) {

                        if (cbSid>maxSid) {

                        //
                        // Fail becuase buffer size required > what we have allocated
                        // for some reasons,  retry with correct buffer size
                        //

                            FreeADsMem(pSid);

                            pSid = (PSID) AllocADsMem(
                                    cbSid
                                    );
                            if (!pSid) {
                                hr = E_OUTOFMEMORY;
                                BAIL_ON_FAILURE(hr);
                            }

                            fGotSid = LookupAccountName(
                                        lpszServerName,
                                        szNewAccountName,
                                        pSid,
                                        &cbSid,
                                        szRefDomainName,
                                        &cbRefDomainName,
                                        &eNameUse
                                        );

                            if (!fGotSid) {

                                //
                                // Fail on retry with proper buffer size, can do no more
                                //

                                dwErr = GetLastError();
                                hr = HRESULT_FROM_WIN32(dwErr);
                            }

                        } else {

                                //
                                // Fail becuase of reasons other then buffer size, not need to
                                // retry.
                                //

                                dwErr = GetLastError();
                                hr = HRESULT_FROM_WIN32(dwErr);
                        }
        
                    }
                }
    }

    BAIL_ON_FAILURE(hr);


    //
    // On NT4 for some reason GetLengthSID does not set lasterror to 0
    //
    SetLastError(NO_ERROR);

    dwSidLength = GetLengthSid((PSID) pSid);

    dwErr = GetLastError();

    //
    // This is an extra check to make sure that we have the
    // correct length.
    //
    if (dwErr != NO_ERROR) {
        hr = HRESULT_FROM_WIN32(dwErr);
    }

    BAIL_ON_FAILURE(hr);
    //
    // store Sid in property cache
    //

    hr = SetOctetPropertyInCache(
                pPropertyCache,
                TEXT("objectSid"),
                (PBYTE) pSid,
                dwSidLength,
                fExplicit
                );
    BAIL_ON_FAILURE(hr);


error:

    if (pSid) {
        FreeADsMem(pSid);
    }

    RRETURN(hr);

}