Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

848 lines
21 KiB

  1. //---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 1995
  5. //
  6. // File: cenumdom.cxx
  7. //
  8. // Contents: LDAP Object Enumeration Code
  9. //
  10. // CLDAPGenObjectEnum::CLDAPGenObjectEnum()
  11. // CLDAPGenObjectEnum::CLDAPGenObjectEnum
  12. //
  13. // History:
  14. //----------------------------------------------------------------------------
  15. #include "ldap.hxx"
  16. #pragma hdrstop
  17. HRESULT
  18. BuildLDAPFilterArray(
  19. VARIANT vFilter,
  20. LPTSTR *ppszFilter
  21. );
  22. #define FILTER_BUFFER_LEN 256
  23. //+---------------------------------------------------------------------------
  24. //
  25. // Function: CLDAPEnumVariant::Create
  26. //
  27. // Synopsis:
  28. //
  29. // Arguments: [pCollection]
  30. // [ppEnumVariant]
  31. //
  32. // Returns: HRESULT
  33. //
  34. // Modifies:
  35. //
  36. // History: 01-30-95 krishnag Created.
  37. //
  38. //----------------------------------------------------------------------------
  39. HRESULT
  40. CLDAPGenObjectEnum::Create(
  41. CLDAPGenObjectEnum FAR* FAR* ppenumvariant,
  42. BSTR ADsPath,
  43. PADSLDP pLdapHandle,
  44. IADs *pADs,
  45. VARIANT vFilter,
  46. CCredentials& Credentials,
  47. DWORD dwOptReferral,
  48. DWORD dwPageSize
  49. )
  50. {
  51. HRESULT hr = NOERROR;
  52. CLDAPGenObjectEnum FAR* penumvariant = NULL;
  53. TCHAR *pszLDAPServer = NULL;
  54. DWORD dwModificationTime = 0L;
  55. DWORD dwNumberOfEntries = 0L;
  56. LPTSTR aStrings[2];
  57. DWORD dwPort = 0;
  58. WCHAR **aValues = NULL;
  59. int nCount = 0;
  60. DWORD totalCount = 0;
  61. DWORD dwcEntriesReturned = 0;
  62. PLDAPControl pPagedControl = NULL;
  63. LDAPControl referralControl =
  64. {
  65. LDAP_CONTROL_REFERRALS_W,
  66. {
  67. sizeof( DWORD ), (PCHAR) &dwOptReferral
  68. },
  69. TRUE
  70. };
  71. PLDAPControl clientControls[2] =
  72. {
  73. &referralControl,
  74. NULL
  75. };
  76. PLDAPControl serverControls[2] = {NULL, NULL};
  77. PLDAPControl *serverReturnedControls = NULL;
  78. penumvariant = new CLDAPGenObjectEnum();
  79. if (!penumvariant) {
  80. hr = E_OUTOFMEMORY;
  81. BAIL_ON_FAILURE(hr);
  82. }
  83. hr = ADsAllocString( ADsPath, &penumvariant->_ADsPath);
  84. BAIL_ON_FAILURE(hr);
  85. penumvariant->_dwOptReferral = dwOptReferral;
  86. penumvariant->_dwPageSize = dwPageSize;
  87. hr = BuildLDAPFilterArray(
  88. vFilter,
  89. &penumvariant->_pszFilter
  90. );
  91. if ( FAILED(hr) || !penumvariant->_pszFilter ) // this might happen when vFilter is empty
  92. {
  93. hr = S_OK;
  94. penumvariant->_pszFilter = AllocADsStr(TEXT("(objectClass=*)"));
  95. if ( penumvariant->_pszFilter == NULL )
  96. {
  97. hr = E_OUTOFMEMORY;
  98. BAIL_ON_FAILURE(hr);
  99. }
  100. }
  101. penumvariant->_Credentials = Credentials;
  102. hr = BuildLDAPPathFromADsPath2(
  103. ADsPath,
  104. &pszLDAPServer,
  105. &penumvariant->_pszLDAPDn,
  106. &dwPort
  107. );
  108. BAIL_ON_FAILURE(hr);
  109. penumvariant->_pLdapHandle = pLdapHandle;
  110. // Need to AddRef since we are storing the ld that is owned by the object
  111. pADs->AddRef();
  112. penumvariant->_pADs = pADs;
  113. aStrings[0] = TEXT("objectClass");
  114. aStrings[1] = NULL;
  115. //
  116. // Check if paged search control is supported; if so, we will use it.
  117. //
  118. hr = ReadPagingSupportedAttr(
  119. pszLDAPServer,
  120. &penumvariant->_fPagedSearch,
  121. Credentials,
  122. dwPort
  123. ) ;
  124. if (penumvariant->_fPagedSearch) {
  125. //
  126. // In this case we wont to use the ldap paging API
  127. // wrapper rather than use cookies directly.
  128. //
  129. hr = LdapSearchInitPage(
  130. penumvariant->_pLdapHandle,
  131. penumvariant->_pszLDAPDn,
  132. LDAP_SCOPE_ONELEVEL,
  133. penumvariant->_pszFilter,
  134. aStrings,
  135. 0,
  136. NULL,
  137. clientControls,
  138. 0,
  139. 0,
  140. NULL,
  141. &penumvariant->_phPagedSearch
  142. );
  143. if (FAILED(hr) || (penumvariant->_phPagedSearch == NULL)) {
  144. //
  145. // Some error in setting up the paged control, default to non
  146. // paged search.
  147. //
  148. penumvariant->_fPagedSearch = FALSE;
  149. }
  150. }
  151. if (!penumvariant->_fPagedSearch) {
  152. hr = LdapSearchExtS(
  153. penumvariant->_pLdapHandle,
  154. penumvariant->_pszLDAPDn,
  155. LDAP_SCOPE_ONELEVEL,
  156. penumvariant->_pszFilter,
  157. aStrings,
  158. 0,
  159. NULL,
  160. clientControls,
  161. NULL,
  162. 0,
  163. &penumvariant->_res
  164. );
  165. //
  166. // Error out only if there are no entries returned.
  167. //
  168. dwcEntriesReturned = LdapCountEntries(
  169. penumvariant->_pLdapHandle,
  170. penumvariant->_res
  171. );
  172. if (FAILED(hr) && !dwcEntriesReturned) {
  173. BAIL_ON_FAILURE(hr);
  174. }
  175. } // if not do the paged search case
  176. else {
  177. if (penumvariant->_fPagedSearch) {
  178. //
  179. // Get the first page full of results.
  180. //
  181. hr = LdapGetNextPageS(
  182. penumvariant->_pLdapHandle,
  183. penumvariant->_phPagedSearch,
  184. NULL,
  185. penumvariant->_dwPageSize,
  186. &totalCount,
  187. &penumvariant->_res
  188. );
  189. if (hr == HRESULT_FROM_WIN32(ERROR_DS_NO_RESULTS_RETURNED)) {
  190. penumvariant->_fLastPage = TRUE;
  191. goto error;
  192. } else if ((hr == HRESULT_FROM_WIN32(ERROR_DS_SIZELIMIT_EXCEEDED))
  193. && totalCount != 0) {
  194. penumvariant->_fLastPage = TRUE;
  195. hr = S_OK;
  196. }
  197. BAIL_ON_FAILURE(hr);
  198. }
  199. } // the paged search case
  200. error:
  201. if ( pszLDAPServer )
  202. FreeADsStr( pszLDAPServer);
  203. if (pPagedControl) {
  204. LdapControlFree(pPagedControl);
  205. }
  206. if (serverReturnedControls) {
  207. LdapControlsFree(serverReturnedControls);
  208. }
  209. if (FAILED(hr) && !dwcEntriesReturned) {
  210. if (penumvariant) delete penumvariant;
  211. *ppenumvariant = NULL;
  212. } else {
  213. //
  214. // entries successfully retrieved from server, return them
  215. // to client even if error and let client decide
  216. //
  217. *ppenumvariant = penumvariant;
  218. }
  219. RRETURN_EXP_IF_ERR(hr);
  220. }
  221. CLDAPGenObjectEnum::CLDAPGenObjectEnum():
  222. _ADsPath(NULL),
  223. _fAllEntriesReturned(FALSE),
  224. _fPagedSearch(FALSE),
  225. _fLastPage(FALSE),
  226. _dwOptReferral(LDAP_CHASE_EXTERNAL_REFERRALS),
  227. _pszFilter(NULL),
  228. _pszLDAPDn(NULL),
  229. _phPagedSearch(NULL)
  230. {
  231. _pObjList = NULL;
  232. _pLdapHandle = NULL;
  233. _pADs = NULL;
  234. _res = NULL;
  235. _entry = NULL;
  236. }
  237. CLDAPGenObjectEnum::~CLDAPGenObjectEnum()
  238. {
  239. if ( _res )
  240. LdapMsgFree( _res );
  241. if (_pszFilter) {
  242. FreeADsMem(_pszFilter);
  243. }
  244. if (_pszLDAPDn) {
  245. FreeADsMem(_pszLDAPDn);
  246. }
  247. if (_fPagedSearch) {
  248. LdapSearchAbandonPage(_pLdapHandle, _phPagedSearch);
  249. }
  250. if ( _pADs )
  251. _pADs->Release();
  252. if ( _ADsPath )
  253. ADsFreeString( _ADsPath );
  254. if ( _pObjList )
  255. delete _pObjList;
  256. _pLdapHandle = NULL;
  257. _phPagedSearch = NULL;
  258. }
  259. HRESULT
  260. CLDAPGenObjectEnum::EnumGenericObjects(
  261. ULONG cElements,
  262. VARIANT FAR* pvar,
  263. ULONG FAR* pcElementFetched
  264. )
  265. {
  266. HRESULT hr = S_OK;
  267. IDispatch *pDispatch = NULL;
  268. DWORD i = 0;
  269. BOOL fRepeat = FALSE;
  270. DWORD dwFailureCount = 0;
  271. DWORD dwPermitFailure = 1000;
  272. while (i < cElements) {
  273. hr = GetGenObject(&pDispatch);
  274. if (hr == S_FALSE) {
  275. break;
  276. }
  277. else if (FAILED(hr)) {
  278. //
  279. // Got an error while retrieving the object, ignore the
  280. // error and continue with the next object.
  281. // If continuously getting error more than dwPermitFailure,
  282. // make the return value S_FALSE, leave the loop.
  283. //
  284. if (fRepeat) {
  285. dwFailureCount++;
  286. if(dwFailureCount > dwPermitFailure) {
  287. hr = S_FALSE;
  288. break;
  289. }
  290. }
  291. else {
  292. fRepeat = TRUE;
  293. dwFailureCount = 1;
  294. }
  295. hr = S_OK;
  296. continue;
  297. }
  298. if (fRepeat) {
  299. fRepeat = FALSE;
  300. }
  301. VariantInit(&pvar[i]);
  302. pvar[i].vt = VT_DISPATCH;
  303. pvar[i].pdispVal = pDispatch;
  304. (*pcElementFetched)++;
  305. i++;
  306. }
  307. return(hr);
  308. }
  309. HRESULT
  310. CLDAPGenObjectEnum::GetGenObject(
  311. IDispatch ** ppDispatch
  312. )
  313. {
  314. HRESULT hr = S_OK;
  315. LPTSTR pszObjectName = NULL;
  316. TCHAR **aValues = NULL;
  317. int nCount = 0;
  318. DWORD totalCount = 0;
  319. TCHAR szADsClassName[64];
  320. LPWSTR aStrings[2] = {TEXT("objectClass"), NULL};
  321. DWORD dwPort;
  322. LPWSTR pszLDAPServer = NULL;
  323. LPWSTR pszLDAPDn = NULL;
  324. LPWSTR pszADsPathParent = NULL;
  325. BOOL fGCNameSpace = FALSE;
  326. PLDAPControl pPagedControl = NULL;
  327. PLDAPControl serverControls[2] = {NULL, NULL};
  328. PLDAPControl *serverReturnedControls = NULL;
  329. LDAPControl referralControl =
  330. {
  331. LDAP_CONTROL_REFERRALS_W,
  332. {
  333. sizeof( DWORD ), (PCHAR) &_dwOptReferral
  334. },
  335. TRUE
  336. };
  337. PLDAPControl clientControls[2] =
  338. {
  339. &referralControl,
  340. NULL
  341. };
  342. OBJECTINFO ObjectInfoLocal;
  343. memset(&ObjectInfoLocal, 0, sizeof(OBJECTINFO));
  344. *ppDispatch = NULL;
  345. if ( _fAllEntriesReturned )
  346. {
  347. hr = S_FALSE;
  348. goto error;
  349. }
  350. if ( _entry == NULL )
  351. hr = LdapFirstEntry( _pLdapHandle, _res, &_entry );
  352. else
  353. hr = LdapNextEntry( _pLdapHandle, _entry, &_entry );
  354. if (FAILED(hr) ) {
  355. _fAllEntriesReturned = TRUE;
  356. BAIL_ON_FAILURE(hr);
  357. }
  358. else if ( _entry == NULL ) {
  359. if (!_fPagedSearch || _fLastPage) { // reached the end of enumeration
  360. hr = S_FALSE;
  361. _fAllEntriesReturned = TRUE;
  362. goto error;
  363. }
  364. else { // more pages are remaining
  365. //
  366. // release this page
  367. //
  368. if (_res) {
  369. LdapMsgFree(
  370. _res
  371. );
  372. }
  373. //
  374. // Get the next page of results
  375. //
  376. //
  377. // Get the first page full of results.
  378. //
  379. hr = LdapGetNextPageS(
  380. _pLdapHandle,
  381. _phPagedSearch,
  382. NULL,
  383. _dwPageSize,
  384. &totalCount,
  385. &_res
  386. );
  387. if (hr == HRESULT_FROM_WIN32(ERROR_DS_NO_RESULTS_RETURNED)) {
  388. _fLastPage = TRUE;
  389. hr = S_FALSE;
  390. goto error;
  391. }
  392. BAIL_ON_FAILURE(hr);
  393. hr = LdapFirstEntry(_pLdapHandle, _res, &_entry );
  394. if (FAILED(hr) ) {
  395. _fAllEntriesReturned = TRUE;
  396. BAIL_ON_FAILURE(hr);
  397. }
  398. else if ( (_entry == NULL) ) { // reached the end of enumeration
  399. hr = S_FALSE;
  400. _fAllEntriesReturned = TRUE;
  401. goto error;
  402. }
  403. }
  404. }
  405. hr = LdapGetDn( _pLdapHandle, _entry, &pszObjectName );
  406. if (FAILED(hr) ) {
  407. BAIL_ON_FAILURE(hr);
  408. }
  409. hr = LdapGetValues( _pLdapHandle, _entry, TEXT("objectClass"),
  410. &aValues, &nCount );
  411. BAIL_ON_FAILURE(hr);
  412. if ( nCount == 0 )
  413. {
  414. // This object exists but does not contain objectClass attribute
  415. // which is required for all DS objects. Hence, ignore the object
  416. // and return error.
  417. hr = E_ADS_PROPERTY_NOT_FOUND;
  418. BAIL_ON_FAILURE(hr);
  419. }
  420. //
  421. // Now send back the current object
  422. //
  423. {
  424. CLexer Lexer(pszObjectName);
  425. hr = InitObjectInfo(pszObjectName,
  426. &ObjectInfoLocal);
  427. BAIL_ON_FAILURE(hr);
  428. Lexer.SetAtDisabler(TRUE);
  429. Lexer.SetFSlashDisabler(TRUE);
  430. hr = PathName(&Lexer,
  431. &ObjectInfoLocal);
  432. BAIL_ON_FAILURE(hr);
  433. Lexer.SetFSlashDisabler(FALSE);
  434. if (ObjectInfoLocal.ComponentArray[0].szValue == NULL) {
  435. BAIL_ON_FAILURE(hr=E_ADS_BAD_PATHNAME);
  436. }
  437. DWORD len;
  438. len = wcslen(ObjectInfoLocal.ComponentArray[0].szComponent) +
  439. wcslen(ObjectInfoLocal.ComponentArray[0].szValue) +
  440. 1; // For equal to sign
  441. pszObjectName[len] = '\0';
  442. //
  443. // We form the ADsPath to the parent by taking the _ADsPath of this
  444. // object (which is the parent ADsPath), chopping off the DN portion,
  445. // and attaching the parent DN portion of the object retrieved. This
  446. // is so that the enumerated child objects get a proper parent ADsPath
  447. // even if the parent object was bound using a GUID.
  448. //
  449. hr = BuildLDAPPathFromADsPath2(
  450. _ADsPath,
  451. &pszLDAPServer,
  452. &pszLDAPDn,
  453. &dwPort
  454. );
  455. BAIL_ON_FAILURE(hr);
  456. // _ADsPath could not be NULL
  457. if(!_wcsnicmp(L"GC:", _ADsPath, wcslen(L"GC:")))
  458. {
  459. fGCNameSpace = TRUE;
  460. }
  461. if (ObjectInfoLocal.NumComponents > 1) {
  462. //
  463. // pszObjectName[0 ... len] = object name (e.g., "CN=child")
  464. // pszObjectName[len+1 ....] = parent name (e.g., "OU=parent, DC=....")
  465. //
  466. hr = BuildADsPathFromLDAPPath2(
  467. (pszLDAPServer ? TRUE : FALSE),
  468. fGCNameSpace ? L"GC:" : L"LDAP:",
  469. pszLDAPServer,
  470. dwPort,
  471. &(pszObjectName[len+1]),
  472. &pszADsPathParent
  473. );
  474. }
  475. else {
  476. //
  477. // If the count is zero or less, then the objectName will
  478. // need to be created with a parent path of NULL. In this case
  479. // pszObjectName[len+1] is not necessarily valid. This is
  480. // espescially true when we have a defaultNamingContext
  481. // that is NULL or no defaultNamingContext with an object
  482. // directly underneath it (say dn is just o=testObject.
  483. //
  484. hr = BuildADsPathFromLDAPPath2(
  485. (pszLDAPServer ? TRUE : FALSE),
  486. fGCNameSpace ? L"GC:" : L"LDAP:",
  487. pszLDAPServer,
  488. dwPort,
  489. NULL,
  490. &pszADsPathParent
  491. );
  492. }
  493. BAIL_ON_FAILURE(hr);
  494. }
  495. hr = CLDAPGenObject::CreateGenericObject(
  496. pszADsPathParent,
  497. pszObjectName,
  498. aValues,
  499. nCount,
  500. _Credentials,
  501. ADS_OBJECT_BOUND,
  502. IID_IDispatch,
  503. (void **) ppDispatch
  504. );
  505. BAIL_ON_FAILURE(hr);
  506. error:
  507. FreeObjectInfo(&ObjectInfoLocal);
  508. if ( pszObjectName )
  509. LdapMemFree( pszObjectName );
  510. if ( aValues )
  511. LdapValueFree( aValues );
  512. if (pPagedControl) {
  513. LdapControlFree(pPagedControl);
  514. }
  515. if (serverReturnedControls) {
  516. LdapControlsFree(serverReturnedControls);
  517. }
  518. if (pszADsPathParent) {
  519. FreeADsMem(pszADsPathParent);
  520. }
  521. if (pszLDAPServer) {
  522. FreeADsMem(pszLDAPServer);
  523. }
  524. if (pszLDAPDn) {
  525. FreeADsMem(pszLDAPDn);
  526. }
  527. RRETURN_EXP_IF_ERR(hr);
  528. }
  529. //+---------------------------------------------------------------------------
  530. //
  531. // Function: CLDAPGenObjectEnum::Next
  532. //
  533. // Synopsis: Returns cElements number of requested NetOle objects in the
  534. // array supplied in pvar.
  535. //
  536. // Arguments: [cElements] -- The number of elements requested by client
  537. // [pvar] -- ptr to array of VARIANTs to for return objects
  538. // [pcElementFetched] -- if non-NULL, then number of elements
  539. // -- actually returned is placed here
  540. //
  541. // Returns: HRESULT -- S_OK if number of elements requested are returned
  542. // -- S_FALSE if number of elements is < requested
  543. //
  544. // Modifies:
  545. //
  546. // History: 11-3-95 krishnag Created.
  547. //
  548. //----------------------------------------------------------------------------
  549. STDMETHODIMP
  550. CLDAPGenObjectEnum::Next(
  551. ULONG cElements,
  552. VARIANT FAR* pvar,
  553. ULONG FAR* pcElementFetched
  554. )
  555. {
  556. ULONG cElementFetched = 0;
  557. HRESULT hr = S_OK;
  558. hr = EnumGenericObjects(
  559. cElements,
  560. pvar,
  561. &cElementFetched
  562. );
  563. if (pcElementFetched) {
  564. *pcElementFetched = cElementFetched;
  565. }
  566. RRETURN_EXP_IF_ERR(hr);
  567. }
  568. HRESULT
  569. BuildLDAPFilterArray(
  570. VARIANT var,
  571. LPTSTR *ppszFilter
  572. )
  573. {
  574. HRESULT hr = S_OK;
  575. LONG dwSLBound = 0;
  576. LONG dwSUBound = 0;
  577. VARIANT v;
  578. LONG i;
  579. LONG dwBracketCount = 0;
  580. DWORD dwFilterBufLen = 0;
  581. DWORD dwCurrentFilterLen = 0;
  582. LPTSTR pszFilter = NULL, pszTempFilter = NULL;
  583. *ppszFilter = NULL;
  584. if(!(V_VT(&var) == (VT_VARIANT|VT_ARRAY))) {
  585. RRETURN(E_FAIL);
  586. }
  587. //
  588. // Check that there is only one dimension in this array
  589. //
  590. if ((V_ARRAY(&var))->cDims != 1) {
  591. hr = E_FAIL;
  592. BAIL_ON_FAILURE(hr);
  593. }
  594. //
  595. // Check that there is at least one element in this array
  596. //
  597. if ((V_ARRAY(&var))->rgsabound[0].cElements == 0){
  598. hr = E_FAIL;
  599. BAIL_ON_FAILURE(hr);
  600. }
  601. //
  602. // We know that this is a valid single dimension array
  603. //
  604. hr = SafeArrayGetLBound(V_ARRAY(&var),
  605. 1,
  606. (long FAR *)&dwSLBound
  607. );
  608. BAIL_ON_FAILURE(hr);
  609. hr = SafeArrayGetUBound(V_ARRAY(&var),
  610. 1,
  611. (long FAR *)&dwSUBound
  612. );
  613. BAIL_ON_FAILURE(hr);
  614. dwCurrentFilterLen = 0;
  615. dwFilterBufLen = FILTER_BUFFER_LEN;
  616. pszFilter = (LPTSTR) AllocADsMem( (dwFilterBufLen + 1) * sizeof(WCHAR));
  617. if ( pszFilter == NULL )
  618. {
  619. hr = E_OUTOFMEMORY;
  620. BAIL_ON_FAILURE(hr);
  621. }
  622. for (i = dwSLBound; i <= dwSUBound; i++) {
  623. VariantInit(&v);
  624. hr = SafeArrayGetElement(V_ARRAY(&var),
  625. (long FAR *)&i,
  626. &v
  627. );
  628. BAIL_ON_FAILURE(hr);
  629. // The last length below is the string length of
  630. // "(| (objectClass=))" which is 18.
  631. //
  632. while ( dwCurrentFilterLen + _tcslen(V_BSTR(&v)) + 18 > dwFilterBufLen)
  633. {
  634. pszTempFilter = (LPTSTR) ReallocADsMem(
  635. pszFilter,
  636. (dwFilterBufLen + 1) * sizeof(WCHAR),
  637. (dwFilterBufLen*2 + 1) * sizeof(WCHAR));
  638. if ( pszTempFilter == NULL )
  639. {
  640. hr = E_OUTOFMEMORY;
  641. BAIL_ON_FAILURE(hr);
  642. }
  643. pszFilter = pszTempFilter;
  644. dwFilterBufLen *= 2;
  645. }
  646. if ( i == dwSUBound )
  647. {
  648. _tcscat( pszFilter, TEXT("(objectClass="));
  649. }
  650. else
  651. {
  652. dwBracketCount++;
  653. _tcscat( pszFilter, TEXT("(| (objectClass="));
  654. }
  655. _tcscat( pszFilter, V_BSTR(&v));
  656. _tcscat( pszFilter, TEXT(")"));
  657. dwCurrentFilterLen = _tcslen(pszFilter);
  658. VariantClear(&v);
  659. }
  660. for ( i = 0; i < dwBracketCount; i++ )
  661. _tcscat( pszFilter, TEXT(")"));
  662. *ppszFilter = pszFilter;
  663. RRETURN(S_OK);
  664. error:
  665. VariantClear(&v);
  666. if ( pszFilter != NULL)
  667. FreeADsMem( pszFilter );
  668. RRETURN(hr);
  669. }