//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 2000
//
//  File:      dsAdd.cpp
//
//  Contents:  Defines the main function and parser tables for the DSAdd
//             command line utility
//
//  History:   22-Sep-2000    JeffJon  Created
//             
//
//--------------------------------------------------------------------------

#include "pch.h"
#include "cstrings.h"
#include "usage.h"
#include "addtable.h"
#include "resource.h"

//
// Function Declarations
//
HRESULT DoAddValidation(PARG_RECORD pCommandArgs);
HRESULT DoAdd(PARG_RECORD pCommandArgs, PDSOBJECTTABLEENTRY pObjectEntry);


int __cdecl _tmain( VOID )
{

   int argc;
   LPTOKEN pToken = NULL;
   HRESULT hr = S_OK;

   //
   // Initialize COM
   //
   hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
   if (FAILED(hr))
   {
      DisplayErrorMessage(g_pszDSCommandName, 
                          NULL,
                          hr);
      return hr;
   }

   if( !GetCommandInput(&argc,&pToken) )
   {
      PARG_RECORD pNewCommandArgs = 0;

      //
      // False loop
      //
      do
      {
         if(argc == 1)
         {
            //
            // Display the error message and then break out of the false loop
            //
            DisplayMessage(USAGE_DSADD);
            hr = E_INVALIDARG;
            break;
         }

         //
         // Find which object table entry to use from
         // the second command line argument
         //
         PDSOBJECTTABLEENTRY pObjectEntry = NULL;
         UINT idx = 0;
         while (true)
         {
            pObjectEntry = g_DSObjectTable[idx];
            if (!pObjectEntry)
            {
               break;
            }

            PWSTR pszObjectType = (pToken+1)->GetToken();
            if (0 == _wcsicmp(pObjectEntry->pszCommandLineObjectType, pszObjectType))
            {
               break;
            }
            idx++;
         }

         if (!pObjectEntry)
         {
            //
            // Display the error message and then break out of the false loop
            //
            DisplayMessage(USAGE_DSADD);
            hr = E_INVALIDARG;
            break;
         }

         //
         // Now that we have the correct table entry, merge the command line table
         // for this object with the common commands
         //
         hr = MergeArgCommand(DSADD_COMMON_COMMANDS, 
                              pObjectEntry->pParserTable, 
                              &pNewCommandArgs);
         if (FAILED(hr))
         {
            //
            // Display the error message and then break out of the false loop
            //
            DisplayErrorMessage(g_pszDSCommandName, L"", hr);
            break;
         }

         if (!pNewCommandArgs)
         {
            //
            // Display the usage text and then break out of the false loop
            //
            DisplayMessage(pObjectEntry->nUsageID);
            hr = E_FAIL;
            break;
         }

         PARSE_ERROR Error;
         if(!ParseCmd(pNewCommandArgs,
                      argc-1, 
                      pToken+1,
                      pObjectEntry->nUsageID, 
                      &Error,
                      TRUE))
         {
            if (Error.Error != PARSE_ERROR_HELP_SWITCH &&
                Error.Error != ERROR_FROM_PARSER)
            {
               //
               // Display the usage text and then break out of the false loop
               //
               DisplayMessage(pObjectEntry->nUsageID);
            }
            hr = E_INVALIDARG;
            break;
         }
         else
         {
            //
            // Check to see if we are doing debug spew
            //
#ifdef DBG
            bool bDebugging = pNewCommandArgs[eCommDebug].bDefined && 
                              pNewCommandArgs[eCommDebug].nValue;
            if (bDebugging)
            {
               ENABLE_DEBUG_OUTPUT(pNewCommandArgs[eCommDebug].nValue);
            }
#else
            DISABLE_DEBUG_OUTPUT();
#endif
            //
            // Be sure that mutually exclusive and dependent switches are correct
            //
            hr = DoAddValidation(pNewCommandArgs);
            if (FAILED(hr))
            {
               DisplayErrorMessage(g_pszDSCommandName, 
                                   pNewCommandArgs[eCommObjectDNorName].strValue,
                                   hr);
               break;
            }

            //
            // Command line parsing succeeded
            //
            hr = DoAdd(pNewCommandArgs, pObjectEntry);
         }

      } while (false);

      //
      // Free the memory associated with the command values
      //
      if (pNewCommandArgs)
      {
         FreeCmd(pNewCommandArgs);
      }

      //
      // Free the tokens
      //
      if (pToken)
      {
         delete[] pToken;
         pToken = 0;
      }
   }

   //
   // Uninitialize COM
   //
   ::CoUninitialize();

   return hr;
}

//+--------------------------------------------------------------------------
//
//  Function:   DoGroupValidation
//
//  Synopsis:   Checks to be sure that command line switches for a group that 
//              are mutually exclusive are not both present and those that 
//              are dependent are both present
//
//  Arguments:  [pCommandArgs - IN] : the command line argument structure used
//                                    to retrieve the values for each switch
//
//  Returns:    HRESULT : S_OK if everything succeeded
//                        E_INVALIDARG if the object entry wasn't found
//                        Anything else is a failure code from an ADSI call
//
//  History:    04-Oct-2000   JeffJon   Created
//
//---------------------------------------------------------------------------
HRESULT DoGroupValidation(PARG_RECORD pCommandArgs)
{
   HRESULT hr = S_OK;

   do // false loop
   {
      //
      // Set the group scope to default (global) if not given
      //
      if (!pCommandArgs[eGroupScope].bDefined ||
          !pCommandArgs[eGroupScope].strValue)
      {
         size_t nScopeLen = _tcslen(g_bstrGroupScopeGlobal);
         pCommandArgs[eGroupScope].strValue = (LPTSTR)LocalAlloc(LPTR, (nScopeLen+2) * sizeof(TCHAR) );
         if (!pCommandArgs[eGroupScope].strValue)
         {
            DEBUG_OUTPUT(MINIMAL_LOGGING, L"Failed to allocate space for pCommandArgs[eGroupScope].strValue");
            hr = E_OUTOFMEMORY;
            break;
         }

         _tcscpy(pCommandArgs[eGroupScope].strValue, g_bstrGroupScopeGlobal);
         pCommandArgs[eGroupScope].bDefined = TRUE;
      }

      //
      // Set the group security to default (yes) if not given
      //
      if (!pCommandArgs[eGroupSecgrp].bDefined)
      {
         pCommandArgs[eGroupSecgrp].bValue = TRUE;
         pCommandArgs[eGroupSecgrp].bDefined = TRUE;

         //
         // Need to change the type to bool so that FreeCmd doesn't
         // try to free the string when the value is true
         //
         pCommandArgs[eGroupSecgrp].fType = ARG_TYPE_BOOL;
      }

   } while (false);

   return hr;
}


//+--------------------------------------------------------------------------
//
//  Function:   DoAddValidation
//
//  Synopsis:   Checks to be sure that command line switches that are mutually
//              exclusive are not both present and those that are dependent are
//              both presetn
//
//  Arguments:  [pCommandArgs - IN] : the command line argument structure used
//                                    to retrieve the values for each switch
//
//  Returns:    HRESULT : S_OK if everything succeeded
//                        E_INVALIDARG if the object entry wasn't found
//                        Anything else is a failure code from an ADSI call
//
//  History:    22-Sep-2000   JeffJon   Created
//
//---------------------------------------------------------------------------
HRESULT DoAddValidation(PARG_RECORD pCommandArgs)
{
   ENTER_FUNCTION_HR(MINIMAL_LOGGING, DoAddValidation, hr);

   do // false loop
   {
      //
      // Check the user switches
      //
      PWSTR pszObjectType = NULL;
      if (!pCommandArgs[eCommObjectType].bDefined &&
          !pCommandArgs[eCommObjectType].strValue)
      {
         hr = E_INVALIDARG;
         break;
      }

      pszObjectType = pCommandArgs[eCommObjectType].strValue;
      if (0 == _wcsicmp(g_pszUser, pszObjectType))
      {
         //
         // Can't have user must change password if user can change password is no
         //
         if ((pCommandArgs[eUserMustchpwd].bDefined &&
              pCommandArgs[eUserMustchpwd].bValue) &&
             (pCommandArgs[eUserCanchpwd].bDefined &&
              !pCommandArgs[eUserCanchpwd].bValue))
         {
            DEBUG_OUTPUT(MINIMAL_LOGGING, L"User must change password and user can change password = false was supplied");
            hr = E_INVALIDARG;
            break;
         }
      }
      else if (0 == _wcsicmp(g_pszGroup, pszObjectType))
      {
         hr = DoGroupValidation(pCommandArgs);
         break;
      }
   } while (false);

   return hr;
}


//+--------------------------------------------------------------------------
//
//  Function:   DoAdd
//
//  Synopsis:   Finds the appropriate object in the object table and fills in
//              the attribute values and then creates the object
//
//  Arguments:  [pCommandArgs - IN] : the command line argument structure used
//                                    to retrieve the values for each switch
//              [pObjectEntry - IN] : pointer to the object table entry for the
//                                    object type that will be modified
//
//  Returns:    HRESULT : S_OK if everything succeeded
//                        E_INVALIDARG if the object entry wasn't found
//                        Anything else is a failure code from an ADSI call
//
//  History:    22-Sep-2000   JeffJon   Created
//
//---------------------------------------------------------------------------
HRESULT DoAdd(PARG_RECORD pCommandArgs, PDSOBJECTTABLEENTRY pObjectEntry)
{
   ENTER_FUNCTION_HR(MINIMAL_LOGGING, DoAdd, hr);
   
   PADS_ATTR_INFO pCreateAttrs = NULL;
   PADS_ATTR_INFO pPostCreateAttrs = NULL;

   do // false loop
   {
      if (!pCommandArgs || !pObjectEntry)
      {
         ASSERT(pCommandArgs && pObjectEntry);
         hr = E_INVALIDARG;
         break;
      }

      CDSCmdCredentialObject credentialObject;
      if (pCommandArgs[eCommUserName].bDefined)
      {
         credentialObject.SetUsername(pCommandArgs[eCommUserName].strValue);
         credentialObject.SetUsingCredentials(true);
      }

      if (pCommandArgs[eCommPassword].bDefined)
      {
         credentialObject.SetPassword(pCommandArgs[eCommPassword].strValue);
         credentialObject.SetUsingCredentials(true);
      }

      //
      // Initialize the base paths info from the command line args
      // 
      CDSCmdBasePathsInfo basePathsInfo;
      if (pCommandArgs[eCommServer].bDefined)
      {
         hr = basePathsInfo.InitializeFromName(credentialObject, 
                                               pCommandArgs[eCommServer].strValue,
                                               true);
      }
      else if (pCommandArgs[eCommDomain].bDefined)
      {
         hr = basePathsInfo.InitializeFromName(credentialObject, 
                                               pCommandArgs[eCommDomain].strValue,
                                               false);
      }
      else
      {
         hr = basePathsInfo.InitializeFromName(credentialObject, NULL, false);
      }

      if (FAILED(hr))
      {
         //
         // Display error message and return
         //
         DisplayErrorMessage(g_pszDSCommandName, NULL, hr);
         break;
      }

      //
      // The DNs or Names should be given as a \0 separated list
      // So parse it and loop through each object
      //
      UINT nStrings = 0;
      PWSTR* ppszArray = NULL;
      ParseNullSeparatedString(pCommandArgs[eCommObjectDNorName].strValue,
                               &ppszArray,
                               &nStrings);
      if (nStrings < 1 ||
          !ppszArray)
      {
         //
         // Display the usage text and then fail
         //
         DisplayMessage(pObjectEntry->nUsageID);
         hr = E_INVALIDARG;
         break;
      }

      DWORD dwCount = pObjectEntry->dwAttributeCount; 

      //
      // Allocate the creation ADS_ATTR_INFO
      // Add an extra attribute for the object class
      //
      pCreateAttrs = new ADS_ATTR_INFO[dwCount + 1];

      if (!pCreateAttrs)
      {
         //
         // Display error message and return
         //
         DisplayErrorMessage(g_pszDSCommandName, NULL, E_OUTOFMEMORY);
         hr = E_OUTOFMEMORY;
         break;
      }

      //
      // Allocate the post create ADS_ATTR_INFO
      //
      pPostCreateAttrs = new ADS_ATTR_INFO[dwCount];
      if (!pPostCreateAttrs)
      {
         //
         // Display error message and return
         //
         DisplayErrorMessage(g_pszDSCommandName, NULL, E_OUTOFMEMORY);
         hr = E_OUTOFMEMORY;
         break;
      }

      //
      // Loop through each of the objects
      //
      for (UINT nNameIdx = 0; nNameIdx < nStrings; nNameIdx++)
      {
         do // false loop
         {
            //
            // Get the objects DN
            //
            PWSTR pszObjectDN = ppszArray[nNameIdx];
            if (!pszObjectDN)
            {
               //
               // Display the usage text and then fail
               //
               DisplayMessage(pObjectEntry->nUsageID);
               hr = E_INVALIDARG;
               break; // this breaks out of the false loop
            }
            DEBUG_OUTPUT(MINIMAL_LOGGING, L"Object DN = %s", pszObjectDN);

            CComBSTR sbstrObjectPath;
            basePathsInfo.ComposePathFromDN(pszObjectDN, sbstrObjectPath);

            //
            // Now that we have the table entry loop through the other command line
            // args and see which ones can be applied
            //
            DWORD dwCreateAttributeCount = 0;

            DEBUG_OUTPUT(MINIMAL_LOGGING, L"Starting processing DS_ATTRIBUTE_ONCREATE attributes");

            for (DWORD dwIdx = 0; dwIdx < dwCount; dwIdx++)
            {
               ASSERT(pObjectEntry->pAttributeTable[dwIdx]->pEvalFunc);

               UINT nAttributeIdx = pObjectEntry->pAttributeTable[dwIdx]->nAttributeID;

               if (pCommandArgs[nAttributeIdx].bDefined ||
                   pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_REQUIRED)
               {
                  //
                  // Call the evaluation function to get the appropriate ADS_ATTR_INFO set
                  // if this attribute entry has the DS_ATTRIBUTE_ONCREATE flag set
                  //
                  if ((pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_ONCREATE) &&
                      (!(pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_DIRTY) ||
                       pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_NOT_REUSABLE))
                  {
                     PADS_ATTR_INFO pNewAttr = NULL;
                     hr = pObjectEntry->pAttributeTable[dwIdx]->pEvalFunc(pszObjectDN,
                                                                          basePathsInfo,
                                                                          credentialObject,
                                                                          pObjectEntry, 
                                                                          pCommandArgs[nAttributeIdx],
                                                                          dwIdx, 
                                                                          &pNewAttr);

                     DEBUG_OUTPUT(MINIMAL_LOGGING, L"pEvalFunc returned hr = 0x%x", hr);
                     if (SUCCEEDED(hr) && hr != S_FALSE)
                     {
                        if (pNewAttr)
                        {
                           pCreateAttrs[dwCreateAttributeCount] = *pNewAttr;
                           dwCreateAttributeCount++;
                        }
                     }
                     else
                     {
                        //
                        // Don't show an error if the eval function returned S_FALSE
                        //
                        if (hr != S_FALSE)
                        {
                           //
                           // Display an error
                           //
                           DisplayErrorMessage(g_pszDSCommandName,
                                               pszObjectDN,
                                               hr);
                        }
            
                        if (hr == S_FALSE)
                        {
                           //
                           // Return a generic error code so that we don't print the success message
                           //
                           hr = E_FAIL;
                        }
                        break; // this breaks out of the attribute loop   
                     }
                  }
               }
            } // Attribute for loop

            //
            // The IDispatch interface of the new object
            //
            CComPtr<IDispatch> spDispatch;

            if (SUCCEEDED(hr))
            {
               //
               // Now that we have the attributes ready, lets create the object
               //

               //
               // Get the parent path of the new object
               //
               CComBSTR sbstrParentDN;
               hr = CPathCracker::GetParentDN(pszObjectDN, sbstrParentDN);
               if (FAILED(hr))
               {
                  //
                  // Display error message and return
                  //
                  DisplayErrorMessage(g_pszDSCommandName,
                                      pszObjectDN,
                                      hr);
                  break; // this breaks out of the false loop
               }

               CComBSTR sbstrParentPath;
               basePathsInfo.ComposePathFromDN(sbstrParentDN, sbstrParentPath);

               //
               // Open the parent of the new object
               //
               CComPtr<IDirectoryObject> spDirObject;
               hr = DSCmdOpenObject(credentialObject,
                                    sbstrParentPath,
                                    IID_IDirectoryObject,
                                    (void**)&spDirObject,
                                    true);

               if (FAILED(hr))
               {
                  //
                  // Display error message and return
                  //
                  DisplayErrorMessage(g_pszDSCommandName,
                                      pszObjectDN,
                                      hr);
                  break; // this breaks out of the false loop
               }

               //
               // Get the name of the new object
               //
               CComBSTR sbstrObjectName;
               hr = CPathCracker::GetObjectRDNFromDN(pszObjectDN, sbstrObjectName);
               if (FAILED(hr))
               {
                  //
                  // Display error message and return
                  //
                  DisplayErrorMessage(g_pszDSCommandName,
                                      pszObjectDN,
                                      hr);
                  break; // this breaks out of the false loop
               }

               //
               // Add the object class to the attributes before creating the object
               //
               PADSVALUE pADsObjectClassValue = new ADSVALUE;
               if (!pADsObjectClassValue)
               {
                  hr = E_OUTOFMEMORY;
                  //
                  // Display error message and return
                  //
                  DisplayErrorMessage(g_pszDSCommandName,
                                      pszObjectDN,
                                      hr);
                  break; // this breaks out of the false loop
               }

               pADsObjectClassValue->dwType = ADSTYPE_CASE_IGNORE_STRING;
               pADsObjectClassValue->CaseIgnoreString = (PWSTR)pObjectEntry->pszObjectClass;

               DEBUG_OUTPUT(MINIMAL_LOGGING, L"New object name = %s", pObjectEntry->pszObjectClass);

               ADS_ATTR_INFO adsClassAttrInfo =
                  { 
                     L"objectClass",
                     ADS_ATTR_UPDATE,
                     ADSTYPE_CASE_IGNORE_STRING,
                     pADsObjectClassValue,
                     1
                  };

               pCreateAttrs[dwCreateAttributeCount] = adsClassAttrInfo;
               dwCreateAttributeCount++;

      #ifdef DBG
               DEBUG_OUTPUT(FULL_LOGGING, L"Creation Attributes:");
               SpewAttrs(pCreateAttrs, dwCreateAttributeCount);
      #endif
         
               hr = spDirObject->CreateDSObject(sbstrObjectName,
                                                pCreateAttrs, 
                                                dwCreateAttributeCount,
                                                &spDispatch);

               DEBUG_OUTPUT(MINIMAL_LOGGING, L"CreateDSObject returned hr = 0x%x", hr);

               if (FAILED(hr))
               {
                  CComBSTR sbstrDuplicateErrorMessage;

                  if (ERROR_OBJECT_ALREADY_EXISTS == HRESULT_CODE(hr))
                  {
                     sbstrDuplicateErrorMessage.LoadString(::GetModuleHandle(NULL), 
                                                           IDS_MSG_DUPLICATE_NAME_ERROR);
                  }

                  //
                  // Display error message and return
                  //
                  DisplayErrorMessage(g_pszDSCommandName,
                                      pszObjectDN,
                                      hr,
                                      sbstrDuplicateErrorMessage);

                  if (pADsObjectClassValue)
                  {
                     delete pADsObjectClassValue;
                     pADsObjectClassValue = NULL;
                  }
                  break; // this breaks out of the false loop
               }

               if (pADsObjectClassValue)
               {
                  delete pADsObjectClassValue;
                  pADsObjectClassValue = NULL;
               }
            }

            if (SUCCEEDED(hr))
            {
               //
               // Now that we have created the object, set the attributes that are 
               // marked for Post Create
               //
               DWORD dwPostCreateAttributeCount = 0;
               DEBUG_OUTPUT(MINIMAL_LOGGING, L"Starting processing DS_ATTRIBUTE_POSTCREATE attributes");
               for (DWORD dwIdx = 0; dwIdx < dwCount; dwIdx++)
               {
                  ASSERT(pObjectEntry->pAttributeTable[dwIdx]->pEvalFunc);

                  UINT nAttributeIdx = pObjectEntry->pAttributeTable[dwIdx]->nAttributeID;

               if (pCommandArgs[nAttributeIdx].bDefined ||
                   pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_REQUIRED)
                  {
                     //
                     // Call the evaluation function to get the appropriate ADS_ATTR_INFO set
                     // if this attribute entry has the DS_ATTRIBUTE_POSTCREATE flag set
                     //
                     if ((pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_POSTCREATE) &&
                         (!(pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_DIRTY) ||
                          pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_NOT_REUSABLE))
                     {
                        PADS_ATTR_INFO pNewAttr = NULL;
                        hr = pObjectEntry->pAttributeTable[dwIdx]->pEvalFunc(pszObjectDN,
                                                                             basePathsInfo,
                                                                             credentialObject,
                                                                             pObjectEntry, 
                                                                             pCommandArgs[nAttributeIdx],
                                                                             dwIdx, 
                                                                             &pNewAttr);

                        DEBUG_OUTPUT(MINIMAL_LOGGING, L"pEvalFunc returned hr = 0x%x", hr);
                        if (SUCCEEDED(hr) && hr != S_FALSE)
                        {
                           if (pNewAttr)
                           {
                              pPostCreateAttrs[dwPostCreateAttributeCount] = *pNewAttr;
                              dwPostCreateAttributeCount++;
                           }
                        }
                        else
                        {
                           //
                           // Don't show an error if the eval function returned S_FALSE
                           //
                           if (hr != S_FALSE)
                           {
                              //
                              // Load the post create message
                              //
                              CComBSTR sbstrPostCreateMessage;
                              sbstrPostCreateMessage.LoadString(::GetModuleHandle(NULL),
                                                                IDS_POST_CREATE_FAILURE);

                              //
                              // Display an error
                              //
                              DisplayErrorMessage(g_pszDSCommandName,
                                                  pszObjectDN,
                                                  hr,
                                                  sbstrPostCreateMessage);
                           }
         
                           if (hr == S_FALSE)
                           {
                              //
                              // Return a generic error code so that we don't print the success message
                              //
                              hr = E_FAIL;
                           }
                           break; // attribute table loop        
                        }
                     }
                  }
               } // Attribute table for loop

               //
               // Now set the attributes if necessary
               //
               if (SUCCEEDED(hr) && dwPostCreateAttributeCount > 0)
               {
                  //
                  // Now that we have the attributes ready, lets set them in the DS
                  //
                  CComPtr<IDirectoryObject> spNewDirObject;
                  hr = spDispatch->QueryInterface(IID_IDirectoryObject, (void**)&spNewDirObject);
                  if (FAILED(hr))
                  {
                     //
                     // Display error message and return
                     //
                     DEBUG_OUTPUT(MINIMAL_LOGGING, L"QI for IDirectoryObject failed: hr = 0x%x", hr);
                     DisplayErrorMessage(g_pszDSCommandName,
                                         pszObjectDN,
                                         hr);
                     break; // this breaks out of the false loop
                  }

                  DEBUG_OUTPUT(MINIMAL_LOGGING, L"Setting %d attributes", dwPostCreateAttributeCount);
      #ifdef DBG
                  DEBUG_OUTPUT(FULL_LOGGING, L"Post Creation Attributes:");
                  SpewAttrs(pPostCreateAttrs, dwPostCreateAttributeCount);
      #endif

                  DWORD dwAttrsModified = 0;
                  hr = spNewDirObject->SetObjectAttributes(pPostCreateAttrs, 
                                                           dwPostCreateAttributeCount,
                                                           &dwAttrsModified);

                  DEBUG_OUTPUT(MINIMAL_LOGGING, L"SetObjectAttributes returned hr = 0x%x", hr);
                  if (FAILED(hr))
                  {
                     //
                     // Display error message and return
                     //
                     DisplayErrorMessage(g_pszDSCommandName,
                                         pszObjectDN,
                                         hr);
                     break; // this breaks out of the false loop
                  }
               }
            }
         } while (false);

         //
         // Loop through the attributes again, clearing any values for 
         // attribute entries that are marked DS_ATTRIBUTE_NOT_REUSABLE
         //
         DEBUG_OUTPUT(LEVEL5_LOGGING, L"Cleaning up memory and flags for object %d", nNameIdx);
         for (DWORD dwIdx = 0; dwIdx < dwCount; dwIdx++)
         {
            if (pObjectEntry->pAttributeTable[dwIdx]->dwFlags & DS_ATTRIBUTE_NOT_REUSABLE)
            {
               if (pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc &&
                   ((pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc->dwFlags & DS_ATTRIBUTE_READ) ||
                    (pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc->dwFlags & DS_ATTRIBUTE_DIRTY)))
               {
                  //
                  // Cleanup the memory associated with the value
                  //
                  if (pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc->adsAttrInfo.pADsValues)
                  {
                     delete[] pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc->adsAttrInfo.pADsValues;
                     pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc->adsAttrInfo.pADsValues = NULL;
                  }

                  //
                  // Cleanup the flags so that the attribute will be read for the next object
                  //
                  pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc->dwFlags &= ~(DS_ATTRIBUTE_READ);
                  pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc->dwFlags &= ~(DS_ATTRIBUTE_DIRTY);

                  DEBUG_OUTPUT(LEVEL5_LOGGING, 
                               L"Flags for attribute %s = %d",
                               pObjectEntry->pAttributeTable[dwIdx]->pszName,
                               pObjectEntry->pAttributeTable[dwIdx]->pAttrDesc->dwFlags);
               }
            }
         }

         //
         // Break if the continue flag is not specified
         //
         if (FAILED(hr) && !pCommandArgs[eCommContinue].bDefined)
         {
            break; // this breaks out of the name for loop
         }

         //
         // Display the success message
         //
         if (SUCCEEDED(hr) && !pCommandArgs[eCommQuiet].bDefined)
         {
            DisplaySuccessMessage(g_pszDSCommandName,
                                  pCommandArgs[eCommObjectDNorName].strValue);
         }
      } // Names for loop

   } while (false);

   //
   // Cleanup
   //
   if (pCreateAttrs)
   {
      delete[] pCreateAttrs;
      pCreateAttrs = NULL;
   }

   if (pPostCreateAttrs)
   {
      delete[] pPostCreateAttrs;
      pPostCreateAttrs = NULL;
   }

   return hr;
}