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.
1172 lines
41 KiB
1172 lines
41 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 2000
|
|
//
|
|
// File: dsrm.cpp
|
|
//
|
|
// Contents: Defines the main function and parser tables for the dsrm
|
|
// command line utility
|
|
//
|
|
// History: 07-Sep-2000 HiteshR Created dsmove
|
|
// 13-Sep-2000 JonN Templated dsrm from dsmove
|
|
// 26-Sep-2000 JonN Cleanup in several areas
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
/*
|
|
Error message strategy:
|
|
|
|
-- If errors occur before any particular directory object
|
|
is contacted, they will be reported as "dsrm failed: <error>".
|
|
|
|
-- For each target, either one or more errors will be reported,
|
|
or (if "quiet" is not specified) one success will be reported.
|
|
If "continue" is not specified, nothing will be reported on
|
|
targets after the first one to experience an error.
|
|
|
|
-- More than one error can be reported on a target, but only if
|
|
the "subtree", "exclude" and "continue" flags are all specified.
|
|
In this case, DSRM will continue to delete the other children
|
|
of that specified target object.
|
|
|
|
-- If a subtree is being deleted and the error actually relates to
|
|
a child object, the error reported will reference the particular
|
|
child object, rather than the specified target object.
|
|
|
|
-- Failure to delete a system object will be reported as
|
|
ERROR_DS_CANT_DELETE_DSA_OBJ or ERROR_DS_CANT_DELETE.
|
|
|
|
-- Failure to delete the logged-in user will be reported as
|
|
ERROR_DS_CANT_DELETE. This test will only be performed on the
|
|
specified target object, not on any of its child objects.
|
|
*/
|
|
|
|
#include "pch.h"
|
|
#include "stdio.h"
|
|
#include "cstrings.h"
|
|
#include "usage.h"
|
|
#include "rmtable.h"
|
|
#include "resource.h" // IDS_DELETE_PROMPT[_EXCLUDE]
|
|
#include <ntldap.h> // LDAP_MATCHING_RULE_BIT_AND_W
|
|
#define SECURITY_WIN32
|
|
#include <security.h> // GetUserNameEx
|
|
|
|
//
|
|
// Function Declarations
|
|
//
|
|
HRESULT ValidateSwitches();
|
|
HRESULT DoRm( PWSTR pszDoubleNullObjectDN );
|
|
HRESULT DoRmItem( CDSCmdCredentialObject& credentialObject,
|
|
CDSCmdBasePathsInfo& basePathsInfo,
|
|
PWSTR pszObjectDN,
|
|
bool* pfErrorReported );
|
|
HRESULT IsCriticalSystemObject( CDSCmdBasePathsInfo& basePathsInfo,
|
|
IADs* pIADs,
|
|
const BSTR pszClass,
|
|
const BSTR pszObjectDN,
|
|
bool* pfErrorReported );
|
|
HRESULT RetrieveStringColumn( IDirectorySearch* pSearch,
|
|
ADS_SEARCH_HANDLE SearchHandle,
|
|
LPWSTR szColumnName,
|
|
CComBSTR& sbstr );
|
|
HRESULT SetSearchPreference(IDirectorySearch* piSearch, ADS_SCOPEENUM scope);
|
|
HRESULT IsThisUserLoggedIn( const BSTR bstrUserDN );
|
|
HRESULT DeleteChildren( CDSCmdCredentialObject& credentialObject,
|
|
IADs* pIADs,
|
|
bool* pfErrorReported );
|
|
|
|
|
|
//
|
|
// Global variables
|
|
//
|
|
BOOL fSubtree = false; // BOOL is used in parser structure
|
|
BOOL fExclude = false;
|
|
BOOL fContinue = false;
|
|
BOOL fQuiet = false;
|
|
BOOL fNoPrompt = false;
|
|
LPWSTR g_lpszLoggedInUser = NULL;
|
|
|
|
//
|
|
//Usage Table
|
|
//
|
|
UINT USAGE_DSRM[] =
|
|
{
|
|
USAGE_DSRM_DESCRIPTION,
|
|
USAGE_DSRM_SYNTAX,
|
|
USAGE_DSRM_PARAMETERS,
|
|
USAGE_DSRM_REMARKS,
|
|
USAGE_DSRM_EXAMPLES,
|
|
USAGE_END,
|
|
};
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: _tmain
|
|
//
|
|
// Synopsis: Main function for command-line app
|
|
// beyond what parser can do.
|
|
//
|
|
// Returns: int : HRESULT to be returned from command-line app
|
|
//
|
|
// History: 07-Sep-2000 HiteshR Created dsmove
|
|
// 13-Sep-2000 JonN Templated from dsmove
|
|
// 26-Sep-2000 JonN Updated error reporting
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
int __cdecl _tmain( VOID )
|
|
{
|
|
|
|
int argc = 0;
|
|
LPTOKEN pToken = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
if (FAILED(hr))
|
|
{
|
|
DisplayErrorMessage(g_pszDSCommandName, NULL, hr);
|
|
goto exit_gracefully;
|
|
}
|
|
|
|
DWORD _dwErr = GetCommandInput(&argc,&pToken);
|
|
hr = HRESULT_FROM_WIN32( _dwErr );
|
|
if (FAILED(hr) || argc == 1)
|
|
{
|
|
if (FAILED(hr)) // JonN 3/27/01 344920
|
|
DisplayErrorMessage( g_pszDSCommandName, NULL, hr );
|
|
//if argc is 1, show usage in STDOUT
|
|
DisplayMessage(USAGE_DSRM,(argc == 1));
|
|
goto exit_gracefully;
|
|
}
|
|
|
|
|
|
PARSE_ERROR Error;
|
|
//Security Review:Correct Bound is passed.
|
|
::ZeroMemory( &Error, sizeof(Error) );
|
|
if(!ParseCmd(g_pszDSCommandName,
|
|
DSRM_COMMON_COMMANDS,
|
|
argc-1,
|
|
pToken+1,
|
|
USAGE_DSRM,
|
|
&Error,
|
|
TRUE))
|
|
{
|
|
//ParseCmd did not display any error. Error should
|
|
//be handled here. Check DisplayParseError for the
|
|
//cases where Error is not shown by ParseCmd
|
|
if(!Error.MessageShown)
|
|
{
|
|
hr = E_INVALIDARG;
|
|
DisplayErrorMessage(g_pszDSCommandName,
|
|
NULL,
|
|
hr);
|
|
|
|
goto exit_gracefully;
|
|
}
|
|
|
|
if(Error.ErrorSource == ERROR_FROM_PARSER
|
|
&& Error.Error == PARSE_ERROR_HELP_SWITCH)
|
|
{
|
|
hr = S_OK;
|
|
goto exit_gracefully;
|
|
}
|
|
|
|
hr = E_INVALIDARG;
|
|
goto exit_gracefully;
|
|
|
|
}
|
|
|
|
hr = ValidateSwitches();
|
|
if (FAILED(hr))
|
|
{
|
|
DisplayMessage(USAGE_DSRM);
|
|
goto exit_gracefully;
|
|
}
|
|
|
|
//
|
|
// Command line parsing succeeded
|
|
//
|
|
hr = DoRm( DSRM_COMMON_COMMANDS[eCommObjectDN].strValue );
|
|
|
|
exit_gracefully:
|
|
|
|
// Free Command Array
|
|
FreeCmd(DSRM_COMMON_COMMANDS);
|
|
// Free Token
|
|
if(pToken)
|
|
delete []pToken;
|
|
|
|
if (NULL != g_lpszLoggedInUser)
|
|
delete[] g_lpszLoggedInUser;
|
|
|
|
//
|
|
// Uninitialize COM
|
|
//
|
|
CoUninitialize();
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: ValidateSwitches
|
|
//
|
|
// Synopsis: Does advanced switch dependency validation
|
|
// beyond what parser can do.
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns: S_OK or E_INVALIDARG
|
|
//
|
|
// History: 07-Sep-2000 HiteshR Created dsmove
|
|
// 13-Sep-2000 JonN Templated from dsmove
|
|
// 26-Sep-2000 JonN Updated error reporting
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT ValidateSwitches()
|
|
{
|
|
// Check to be sure the server and domain switches
|
|
// are mutually exclusive
|
|
|
|
if (DSRM_COMMON_COMMANDS[eCommServer].bDefined &&
|
|
DSRM_COMMON_COMMANDS[eCommDomain].bDefined)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
|
|
// read subtree parameters
|
|
fSubtree = DSRM_COMMON_COMMANDS[eCommSubtree].bDefined;
|
|
fExclude = DSRM_COMMON_COMMANDS[eCommExclude].bDefined;
|
|
fContinue = DSRM_COMMON_COMMANDS[eCommContinue].bDefined;
|
|
fQuiet = DSRM_COMMON_COMMANDS[eCommQuiet].bDefined;
|
|
fNoPrompt = DSRM_COMMON_COMMANDS[eCommNoPrompt].bDefined;
|
|
|
|
if ( NULL == DSRM_COMMON_COMMANDS[eCommObjectDN].strValue
|
|
|| L'\0' == DSRM_COMMON_COMMANDS[eCommObjectDN].strValue[0]
|
|
|| (fExclude && !fSubtree) )
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING, L"ValidateSwitches: Invalid switches");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: DoRm
|
|
//
|
|
// Synopsis: Deletes the appropriate object(s)
|
|
// DoRm reports its own error and success messages
|
|
//
|
|
// Arguments: pszDoubleNullObjectDN: double-null-terminated stringlist
|
|
//
|
|
// Returns: HRESULT : error code to be returned from command-line app
|
|
// Could be almost any ADSI error
|
|
//
|
|
// History: 13-Sep-2000 JonN templated from DoMove
|
|
// 26-Sep-2000 JonN Updated error reporting
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
HRESULT DoRm( PWSTR pszDoubleNullObjectDN )
|
|
{
|
|
ASSERT( NULL != pszDoubleNullObjectDN
|
|
&& L'\0' != *pszDoubleNullObjectDN );
|
|
|
|
//
|
|
// Check to see if we are doing debug spew
|
|
//
|
|
#ifdef DBG
|
|
bool bDebugging = DSRM_COMMON_COMMANDS[eCommDebug].bDefined &&
|
|
DSRM_COMMON_COMMANDS[eCommDebug].nValue;
|
|
if (bDebugging)
|
|
{
|
|
ENABLE_DEBUG_OUTPUT(DSRM_COMMON_COMMANDS[eCommDebug].nValue);
|
|
}
|
|
#else
|
|
DISABLE_DEBUG_OUTPUT();
|
|
#endif
|
|
ENTER_FUNCTION(MINIMAL_LOGGING, DoRm);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
CDSCmdCredentialObject credentialObject;
|
|
if (DSRM_COMMON_COMMANDS[eCommUserName].bDefined &&
|
|
DSRM_COMMON_COMMANDS[eCommUserName].strValue)
|
|
{
|
|
credentialObject.SetUsername(
|
|
DSRM_COMMON_COMMANDS[eCommUserName].strValue);
|
|
credentialObject.SetUsingCredentials(true);
|
|
}
|
|
|
|
if (DSRM_COMMON_COMMANDS[eCommPassword].bDefined &&
|
|
DSRM_COMMON_COMMANDS[eCommPassword].strValue)
|
|
{
|
|
//Security Review:pCommandArgs[eCommPassword].strValue is encrypted.
|
|
//Decrypt pCommandArgs[eCommPassword].strValue and then pass it to the
|
|
//credentialObject.SetPassword.
|
|
//See NTRAID#NTBUG9-571544-2000/11/13-hiteshr
|
|
credentialObject.SetEncryptedPassword(&DSRM_COMMON_COMMANDS[eCommPassword].encryptedDataBlob);
|
|
credentialObject.SetUsingCredentials(true);
|
|
}
|
|
|
|
//
|
|
// Initialize the base paths info from the command line args
|
|
//
|
|
// CODEWORK should I just make this global?
|
|
CDSCmdBasePathsInfo basePathsInfo;
|
|
if (DSRM_COMMON_COMMANDS[eCommServer].bDefined &&
|
|
DSRM_COMMON_COMMANDS[eCommServer].strValue)
|
|
{
|
|
hr = basePathsInfo.InitializeFromName(
|
|
credentialObject,
|
|
DSRM_COMMON_COMMANDS[eCommServer].strValue,
|
|
true);
|
|
}
|
|
else if (DSRM_COMMON_COMMANDS[eCommDomain].bDefined &&
|
|
DSRM_COMMON_COMMANDS[eCommDomain].strValue)
|
|
{
|
|
hr = basePathsInfo.InitializeFromName(
|
|
credentialObject,
|
|
DSRM_COMMON_COMMANDS[eCommDomain].strValue);
|
|
}
|
|
else
|
|
{
|
|
hr = basePathsInfo.InitializeFromName(credentialObject, NULL);
|
|
}
|
|
if (FAILED(hr))
|
|
{
|
|
//
|
|
// Display error message and return
|
|
//
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRm: InitializeFromName failure: 0x%08x",
|
|
hr);
|
|
DisplayErrorMessage(g_pszDSCommandName, NULL, hr);
|
|
return hr;
|
|
}
|
|
|
|
// count through double-NULL-terminated string list
|
|
//Security Review:pszDoubleNullObjectDN is series of string separated by '\0'
|
|
//and last string is terminated by two '\0'.This code is fine.
|
|
for ( ;
|
|
L'\0' != *pszDoubleNullObjectDN;
|
|
pszDoubleNullObjectDN += (wcslen(pszDoubleNullObjectDN)+1) )
|
|
{
|
|
bool fErrorReported = false;
|
|
// return the error value for the first error encountered
|
|
HRESULT hrThisItem = DoRmItem( credentialObject,
|
|
basePathsInfo,
|
|
pszDoubleNullObjectDN,
|
|
&fErrorReported );
|
|
if (FAILED(hrThisItem))
|
|
{
|
|
if (!FAILED(hr))
|
|
hr = hrThisItem;
|
|
if (!fErrorReported)
|
|
DisplayErrorMessage(g_pszDSCommandName,
|
|
pszDoubleNullObjectDN,
|
|
hrThisItem);
|
|
if (fContinue)
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
|
|
// display success message for each individual deletion
|
|
if (!fQuiet && S_FALSE != hrThisItem)
|
|
{
|
|
DisplaySuccessMessage(g_pszDSCommandName,
|
|
pszDoubleNullObjectDN);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: DoRmItem
|
|
//
|
|
// Synopsis: Deletes a single target
|
|
//
|
|
// Arguments: credentialObject
|
|
// basePathsInfo
|
|
// pszObjectDN: X500 DN of object to delete
|
|
// *pfErrorReported: Will be set to true if DoRmItem takes
|
|
// care of reporting the error itself
|
|
//
|
|
// Returns: HRESULT : error code to be returned from command-line app
|
|
// Could be almost any ADSI error
|
|
// S_FALSE indicates the operation was cancelled
|
|
//
|
|
// History: 13-Sep-2000 JonN Created
|
|
// 26-Sep-2000 JonN Updated error reporting
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT DoRmItem( CDSCmdCredentialObject& credentialObject,
|
|
CDSCmdBasePathsInfo& basePathsInfo,
|
|
PWSTR pszObjectDN,
|
|
bool* pfErrorReported )
|
|
{
|
|
ASSERT( NULL != pszObjectDN
|
|
&& L'\0' != *pszObjectDN
|
|
&& NULL != pfErrorReported );
|
|
|
|
ENTER_FUNCTION(LEVEL3_LOGGING, DoRmItem);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
CComBSTR sbstrADsPath;
|
|
basePathsInfo.ComposePathFromDN(pszObjectDN,sbstrADsPath);
|
|
|
|
CComPtr<IADs> spIADsItem;
|
|
hr = DSCmdOpenObject(credentialObject,
|
|
sbstrADsPath,
|
|
IID_IADs,
|
|
(void**)&spIADsItem,
|
|
true);
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRmItem(%s): DsCmdOpenObject failure: 0x%08x",
|
|
sbstrADsPath, hr);
|
|
return hr;
|
|
}
|
|
ASSERT( !!spIADsItem );
|
|
|
|
// CODEWORK Is this a remote LDAP operation, or does the ADsOpenObject
|
|
// already retrieve the class? I can bundle the class retrieval into
|
|
// the IsCriticalSystemObject search if necessary.
|
|
CComBSTR sbstrClass;
|
|
hr = spIADsItem->get_Class( &sbstrClass );
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRmItem(%s): get_class failure: 0x%08x",
|
|
sbstrADsPath, hr);
|
|
return hr;
|
|
}
|
|
ASSERT( !!sbstrClass && L'\0' != sbstrClass[0] );
|
|
|
|
// Check whether this is a critical system object
|
|
// This method will report its own errors
|
|
hr = IsCriticalSystemObject( basePathsInfo,
|
|
spIADsItem,
|
|
sbstrClass,
|
|
CComBSTR(pszObjectDN),
|
|
pfErrorReported );
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
if (!fNoPrompt)
|
|
{
|
|
while (true)
|
|
{
|
|
// display prompt
|
|
// CODEWORK allow "a" for all?
|
|
CComBSTR sbstrPrompt;
|
|
if (!sbstrPrompt.LoadString(
|
|
::GetModuleHandle(NULL),
|
|
(fExclude) ? IDS_DELETE_PROMPT_EXCLUDE
|
|
: IDS_DELETE_PROMPT))
|
|
{
|
|
ASSERT(FALSE);
|
|
sbstrPrompt = (fExclude)
|
|
? L"Are you sure you wish to delete %1 (Y/N)? "
|
|
: L"Are you sure you wish to delete all children of %1 (Y/N)? ";
|
|
}
|
|
// 476225-2002/04/26-JonN escaped output
|
|
CComBSTR sbstrOutputDN;
|
|
hr = GetOutputDN( &sbstrOutputDN, pszObjectDN );
|
|
PWSTR pszOutputDN = (SUCCEEDED(hr)) ? sbstrOutputDN : pszObjectDN;
|
|
PTSTR ptzSysMsg = NULL;
|
|
//Security Review:FormatMessage is called with FORMAT_MESSAGE_ALLOCATE_BUFFER
|
|
//so the buffer is correctly allocated by the API.
|
|
DWORD cch = ::FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER
|
|
| FORMAT_MESSAGE_FROM_STRING
|
|
| FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
sbstrPrompt,
|
|
0,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR)&ptzSysMsg,
|
|
0,
|
|
(va_list*)&pszOutputDN );
|
|
if (0 == cch)
|
|
{
|
|
DWORD _dwErr = GetLastError();
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRmItem(%s): FormatMessage failure: %d",
|
|
sbstrADsPath, _dwErr);
|
|
return HRESULT_FROM_WIN32( _dwErr );
|
|
}
|
|
DisplayOutputNoNewline( ptzSysMsg );
|
|
(void) ::LocalFree( ptzSysMsg );
|
|
|
|
// read a line of console input
|
|
WCHAR ach[129];
|
|
//Security Review:ZermoMemory takes number of bytes. This is correct.
|
|
::ZeroMemory( ach, sizeof(ach) );
|
|
DWORD cchRead = 0;
|
|
//Security Review: Bound is fine here. User input is handled correctly.
|
|
if (!ReadConsole(GetStdHandle(STD_INPUT_HANDLE),
|
|
ach,
|
|
128,
|
|
&cchRead,
|
|
NULL))
|
|
{
|
|
DWORD dwErr = ::GetLastError();
|
|
if (ERROR_INSUFFICIENT_BUFFER == dwErr)
|
|
continue;
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRmItem(%s): ReadConsole failure: %d",
|
|
sbstrADsPath, dwErr);
|
|
return HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
if (cchRead < 1)
|
|
continue;
|
|
|
|
CComBSTR sbstrY;
|
|
if (!sbstrY.LoadString(
|
|
::GetModuleHandle(NULL),
|
|
IDS_DELETE_RESPONSE_Y ))
|
|
{
|
|
sbstrY = L"Y";
|
|
}
|
|
CComBSTR sbstrN;
|
|
if (!sbstrN.LoadString(
|
|
::GetModuleHandle(NULL),
|
|
IDS_DELETE_RESPONSE_N ))
|
|
{
|
|
sbstrN = L"N";
|
|
}
|
|
|
|
// return S_FALSE if user types 'n'
|
|
WCHAR wchUpper = (WCHAR)::CharUpper( (LPTSTR)(ach[0]) );
|
|
if (NULL != wcschr(sbstrN,wchUpper))
|
|
return S_FALSE;
|
|
else if (NULL != wcschr(sbstrY,wchUpper))
|
|
break;
|
|
|
|
// loop back to prompt
|
|
}
|
|
}
|
|
|
|
if (fExclude)
|
|
{
|
|
return DeleteChildren( credentialObject, spIADsItem, pfErrorReported );
|
|
}
|
|
else if (fSubtree)
|
|
{
|
|
// delete the whole subtree
|
|
CComQIPtr<IADsDeleteOps> spDeleteOps( spIADsItem );
|
|
ASSERT( !!spDeleteOps );
|
|
if ( !spDeleteOps )
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRmItem(%s): IADsDeleteOps init failure",
|
|
sbstrADsPath);
|
|
return E_FAIL;
|
|
}
|
|
hr = spDeleteOps->DeleteObject( NULL );
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRmItem(%s): DeleteObject failure: 0x%08x",
|
|
sbstrADsPath, hr);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_OUTPUT(FULL_LOGGING,
|
|
L"DoRmItem(%s): DeleteObject succeeds: 0x%08x",
|
|
sbstrADsPath, hr);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// Single-object deletion
|
|
|
|
// get IADsContainer for parent object
|
|
CComBSTR sbstrParentObjectPath;
|
|
hr = spIADsItem->get_Parent( &sbstrParentObjectPath );
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRmItem(%s): get_Parent failure: 0x%08x",
|
|
sbstrADsPath, hr);
|
|
return hr;
|
|
}
|
|
ASSERT( !!sbstrParentObjectPath
|
|
&& L'\0' != sbstrParentObjectPath[0] );
|
|
CComPtr<IADsContainer> spDsContainer;
|
|
hr = DSCmdOpenObject(credentialObject,
|
|
sbstrParentObjectPath,
|
|
IID_IADsContainer,
|
|
(void**)&spDsContainer,
|
|
true);
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DoRmItem(%s): DSCmdOpenObject failure: 0x%08x",
|
|
sbstrParentObjectPath, hr);
|
|
return hr;
|
|
}
|
|
ASSERT( !!spDsContainer );
|
|
|
|
// get leaf name
|
|
CComBSTR sbstrLeafWithDecoration; // will contain "CN="
|
|
CPathCracker pathCracker;
|
|
hr = pathCracker.Set(CComBSTR(pszObjectDN), ADS_SETTYPE_DN);
|
|
ASSERT(!FAILED(hr));
|
|
if (FAILED(hr))
|
|
return hr;
|
|
hr = pathCracker.GetElement(0, &sbstrLeafWithDecoration);
|
|
ASSERT(!FAILED(hr));
|
|
if (FAILED(hr))
|
|
return hr;
|
|
ASSERT( !!sbstrLeafWithDecoration
|
|
&& L'\0' != sbstrLeafWithDecoration[0] );
|
|
|
|
// delete just this object
|
|
hr = spDsContainer->Delete( sbstrClass, sbstrLeafWithDecoration );
|
|
DEBUG_OUTPUT((FAILED(hr)) ? MINIMAL_LOGGING : FULL_LOGGING,
|
|
L"DoRmItem(%s): Delete(%s, %s) returns 0x%08x",
|
|
sbstrADsPath, sbstrClass, sbstrLeafWithDecoration, hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: IsCriticalSystemObject
|
|
//
|
|
// Synopsis: Checks whether a single target is a critical system
|
|
// object or whether the subtree contains any such objects.
|
|
// The root object is tested on these criteria (if "exclude"
|
|
// is not specified):
|
|
// (1) is of class user and represents the logged-in user
|
|
// (2) is of class nTDSDSA
|
|
// (3) is of class trustedDomain
|
|
// (3.5) is of class interSiteTransport (212232 JonN 10/27/00)
|
|
// (4) is of class computer and userAccountControl indicates
|
|
// that this is a DC
|
|
// The entire subtree is tested on these criteria (if "subtree"
|
|
// is specified, excepting the root object if "exclude"
|
|
// is specified):
|
|
// (1) isCriticalSystemObject is true
|
|
// (2) is of class nTDSDSA
|
|
// (3) is of class trustedDomain
|
|
// (3.5) is of class interSiteTransport (212232 JonN 10/27/00)
|
|
// (4) is of class computer and userAccountControl indicates
|
|
// that this is a DC
|
|
//
|
|
// Arguments: credentialObject
|
|
// basePathsInfo
|
|
// pszObjectDN: X500 DN of object to delete
|
|
// *pfErrorReported: Will be set to true if DoRmItem takes
|
|
// care of reporting the error itself
|
|
//
|
|
// Returns: HRESULT : error code to be returned from command-line app
|
|
// Could be almost any ADSI error, although likely codes are
|
|
// ERROR_DS_CANT_DELETE
|
|
// ERROR_DS_CANT_DELETE_DSA_OBJ
|
|
// ERROR_DS_CANT_FIND_DSA_OBJ
|
|
//
|
|
// History: 13-Sep-2000 JonN Created
|
|
// 26-Sep-2000 JonN Updated error reporting
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
// CODEWORK This could use the pszMessage parameter to ReportErrorMessage
|
|
// to provide additional details on why the object is protected.
|
|
HRESULT IsCriticalSystemObject( CDSCmdBasePathsInfo& basePathsInfo,
|
|
IADs* pIADs,
|
|
const BSTR pszClass,
|
|
const BSTR pszObjectDN,
|
|
bool* pfErrorReported )
|
|
{
|
|
ASSERT( pIADs && pszClass && pszObjectDN && pfErrorReported );
|
|
if ( !pIADs || !pszClass || !pszObjectDN || !pfErrorReported )
|
|
return E_INVALIDARG;
|
|
|
|
ENTER_FUNCTION(LEVEL5_LOGGING, IsCriticalSystemObject);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Class-specific checks
|
|
// Let the parent report errors on the root object
|
|
if (fExclude)
|
|
{
|
|
// skip tests on root object, it won't be deleted anyhow
|
|
}
|
|
//Security Review:One of the string is constant in all the
|
|
//_wcsicmp below. They are all fine.
|
|
else if (!_wcsicmp(L"user",pszClass))
|
|
{
|
|
// CODEWORK we could do this check for the entire subtree
|
|
hr = IsThisUserLoggedIn(pszObjectDN);
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsCriticalSystemObject(%s): User is logged in: 0x%08x",
|
|
pszObjectDN, hr);
|
|
return hr;
|
|
}
|
|
}
|
|
else if (!_wcsicmp(L"nTDSDSA",pszClass))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsCriticalSystemObject(%s): Object is an nTDSDSA",
|
|
pszObjectDN);
|
|
return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE_DSA_OBJ);
|
|
}
|
|
else if (!_wcsicmp(L"trustedDomain",pszClass))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsCriticalSystemObject(%s): Object is a trustedDomain",
|
|
pszObjectDN);
|
|
return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE);
|
|
}
|
|
else if (!_wcsicmp(L"interSiteTransport",pszClass))
|
|
{
|
|
// 212232 JonN 10/27/00 Protect interSiteTransport objects
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsCriticalSystemObject(%s): Object is an interSiteTransport",
|
|
pszObjectDN);
|
|
return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE);
|
|
}
|
|
else if (!_wcsicmp(L"computer",pszClass))
|
|
{
|
|
// Figure out if the account is a DC
|
|
CComVariant Var;
|
|
hr = pIADs->Get(CComBSTR(L"userAccountControl"), &Var);
|
|
if ( SUCCEEDED(hr) && (Var.lVal & ADS_UF_SERVER_TRUST_ACCOUNT))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsCriticalSystemObject(%s): Object is a DC computer object",
|
|
pszObjectDN);
|
|
return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE_DSA_OBJ);
|
|
}
|
|
}
|
|
|
|
if (!fSubtree)
|
|
return S_OK;
|
|
|
|
// The user passed the "subtree" flag. Search the entire subtree.
|
|
// Note that the checks are not identical to the single-object
|
|
// checks, they generally conform to what DSADMIN/SITEREPL does.
|
|
|
|
CComQIPtr<IDirectorySearch,&IID_IDirectorySearch> spSearch( pIADs );
|
|
ASSERT( !!spSearch );
|
|
if ( !spSearch )
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsCriticalSystemObject(%s): Failed to load IDirectorySearch",
|
|
pszObjectDN);
|
|
return E_FAIL;
|
|
}
|
|
|
|
hr = SetSearchPreference(spSearch, ADS_SCOPE_SUBTREE);
|
|
ASSERT( !FAILED(hr) );
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
CComBSTR sbstrDSAObjectCategory = L"CN=NTDS-DSA,";
|
|
sbstrDSAObjectCategory += basePathsInfo.GetSchemaNamingContext();
|
|
CComBSTR sbstrComputerObjectCategory = L"CN=Computer,";
|
|
sbstrComputerObjectCategory += basePathsInfo.GetSchemaNamingContext();
|
|
CComBSTR sbstrFilter;
|
|
sbstrFilter = L"(|(isCriticalSystemObject=TRUE)(objectCategory=";
|
|
sbstrFilter += sbstrDSAObjectCategory;
|
|
sbstrFilter += L")(objectCategory=CN=Trusted-Domain,";
|
|
sbstrFilter += basePathsInfo.GetSchemaNamingContext();
|
|
|
|
// 212232 JonN 10/27/00 Protect interSiteTransport objects
|
|
sbstrFilter += L")(objectCategory=CN=Inter-Site-Transport,";
|
|
sbstrFilter += basePathsInfo.GetSchemaNamingContext();
|
|
|
|
sbstrFilter += L")(&(objectCategory=";
|
|
sbstrFilter += sbstrComputerObjectCategory;
|
|
sbstrFilter += L")(userAccountControl:";
|
|
sbstrFilter += LDAP_MATCHING_RULE_BIT_AND_W L":=8192)))";
|
|
|
|
LPWSTR pAttrs[2] = { L"aDSPath",
|
|
L"objectCategory"};
|
|
|
|
ADS_SEARCH_HANDLE SearchHandle = NULL;
|
|
hr = spSearch->ExecuteSearch (sbstrFilter,
|
|
pAttrs,
|
|
2,
|
|
&SearchHandle);
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsCriticalSystemObject(%s): Search with filter %s fails: 0x%08x",
|
|
pszObjectDN, sbstrFilter, hr);
|
|
return hr;
|
|
}
|
|
DEBUG_OUTPUT(LEVEL6_LOGGING,
|
|
L"IsCriticalSystemObject(%s): Search with filter %s succeeds: 0x%08x",
|
|
pszObjectDN, sbstrFilter, hr);
|
|
|
|
while ( hr = spSearch->GetNextRow( SearchHandle ),
|
|
SUCCEEDED(hr) && hr != S_ADS_NOMORE_ROWS )
|
|
{
|
|
CComBSTR sbstrADsPathThisItem;
|
|
hr = RetrieveStringColumn( spSearch,
|
|
SearchHandle,
|
|
pAttrs[0],
|
|
sbstrADsPathThisItem );
|
|
ASSERT( !FAILED(hr) );
|
|
if (FAILED(hr))
|
|
return hr;
|
|
// only compare DNs
|
|
CPathCracker pathcracker;
|
|
hr = pathcracker.Set( sbstrADsPathThisItem, ADS_SETTYPE_FULL );
|
|
ASSERT( !FAILED(hr) ); // 571371-JonN-2002/04/08
|
|
if (FAILED(hr))
|
|
return hr;
|
|
CComBSTR sbstrDN;
|
|
hr = pathcracker.Retrieve( ADS_FORMAT_X500_DN, &sbstrDN );
|
|
ASSERT( !FAILED(hr) ); // 571371-JonN-2002/04/08
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// ignore matches on root object if fExclude, it won't
|
|
// be deleted anyway
|
|
//Security Review:Check for the return value of pathcracker.Retrieve
|
|
//if it fails we may AV. 571371-2000/11/13-hiteshr
|
|
if (fExclude && !!sbstrDN && !_wcsicmp( pszObjectDN, sbstrDN ))
|
|
continue;
|
|
|
|
CComBSTR sbstrObjectCategory;
|
|
hr = RetrieveStringColumn( spSearch,
|
|
SearchHandle,
|
|
pAttrs[1],
|
|
sbstrObjectCategory );
|
|
ASSERT( !FAILED(hr) );
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
//Security Review:sbstrObjectCategory is coming from ADSI and is fine.
|
|
//sbstrDSAObjectCategory are formed by adding string from ADSI to hardcoded string.
|
|
//This is fine.
|
|
hr = ( !_wcsicmp(sbstrObjectCategory,sbstrDSAObjectCategory)
|
|
|| !_wcsicmp(sbstrObjectCategory,sbstrComputerObjectCategory) )
|
|
? HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE_DSA_OBJ)
|
|
: HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE);
|
|
|
|
DisplayErrorMessage(g_pszDSCommandName,
|
|
sbstrDN,
|
|
hr);
|
|
*pfErrorReported = TRUE;
|
|
return hr; // do not permit deletion
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: RetrieveStringColumn
|
|
//
|
|
// Synopsis: Extracts a string value from a SearchHandle
|
|
// beyond what parser can do.
|
|
//
|
|
// Arguments: IDirectorySearch*
|
|
// SearchHandle: must be current on an active record
|
|
// szColumnName: as passed to ExecuteSearch
|
|
// sbstr: returns contents of string value
|
|
//
|
|
// Returns: HRESULT : error code to be returned from command-line app
|
|
// errors should not occur here
|
|
//
|
|
// History: 26-Sep-2000 JonN Created
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
HRESULT RetrieveStringColumn( IDirectorySearch* pSearch,
|
|
ADS_SEARCH_HANDLE SearchHandle,
|
|
LPWSTR szColumnName,
|
|
CComBSTR& sbstr )
|
|
{
|
|
ASSERT( pSearch && szColumnName );
|
|
ADS_SEARCH_COLUMN col;
|
|
//Security Review:Correct size in byte is passed.
|
|
::ZeroMemory( &col, sizeof(col) );
|
|
HRESULT hr = pSearch->GetColumn( SearchHandle, szColumnName, &col );
|
|
ASSERT( !FAILED(hr) );
|
|
if (FAILED(hr))
|
|
return hr;
|
|
ASSERT( col.dwNumValues == 1 );
|
|
if ( col.dwNumValues != 1 )
|
|
{
|
|
(void) pSearch->FreeColumn( &col );
|
|
return E_FAIL;
|
|
}
|
|
switch (col.dwADsType)
|
|
{
|
|
case ADSTYPE_CASE_IGNORE_STRING:
|
|
sbstr = col.pADsValues[0].CaseIgnoreString;
|
|
break;
|
|
case ADSTYPE_DN_STRING:
|
|
sbstr = col.pADsValues[0].DNString;
|
|
break;
|
|
default:
|
|
ASSERT(FALSE);
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
(void) pSearch->FreeColumn( &col );
|
|
return hr;
|
|
}
|
|
|
|
|
|
#define QUERY_PAGESIZE 50
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: SetSearchPreference
|
|
//
|
|
// Synopsis: Sets default search parameters
|
|
//
|
|
// Arguments: IDirectorySearch*
|
|
// ADS_SCOPEENUM: scope of search
|
|
//
|
|
// Returns: HRESULT : error code to be returned from command-line app
|
|
// errors should not occur here
|
|
//
|
|
// History: 26-Sep-2000 JonN Created
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
HRESULT SetSearchPreference(IDirectorySearch* piSearch, ADS_SCOPEENUM scope)
|
|
{
|
|
if (NULL == piSearch)
|
|
{
|
|
ASSERT(FALSE);
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ADS_SEARCHPREF_INFO aSearchPref[4];
|
|
aSearchPref[0].dwSearchPref = ADS_SEARCHPREF_CHASE_REFERRALS;
|
|
aSearchPref[0].vValue.dwType = ADSTYPE_INTEGER;
|
|
aSearchPref[0].vValue.Integer = ADS_CHASE_REFERRALS_EXTERNAL;
|
|
aSearchPref[1].dwSearchPref = ADS_SEARCHPREF_PAGESIZE;
|
|
aSearchPref[1].vValue.dwType = ADSTYPE_INTEGER;
|
|
aSearchPref[1].vValue.Integer = QUERY_PAGESIZE;
|
|
aSearchPref[2].dwSearchPref = ADS_SEARCHPREF_CACHE_RESULTS;
|
|
aSearchPref[2].vValue.dwType = ADSTYPE_BOOLEAN;
|
|
aSearchPref[2].vValue.Integer = FALSE;
|
|
aSearchPref[3].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
|
|
aSearchPref[3].vValue.dwType = ADSTYPE_INTEGER;
|
|
aSearchPref[3].vValue.Integer = scope;
|
|
|
|
return piSearch->SetSearchPreference (aSearchPref, 4);
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: IsThisUserLoggedIn
|
|
//
|
|
// Synopsis: Checks whether the object with this DN represents
|
|
// the currently logged-in user
|
|
//
|
|
// Arguments: bstrUserDN: DN of object to check
|
|
//
|
|
// Returns: HRESULT : error code to be returned from command-line app
|
|
// ERROR_DS_CANT_DELETE indicates that this user is logged in
|
|
//
|
|
// History: 26-Sep-2000 JonN Created
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
HRESULT IsThisUserLoggedIn( const BSTR bstrUserDN )
|
|
{
|
|
ENTER_FUNCTION(LEVEL7_LOGGING, IsThisUserLoggedIn);
|
|
|
|
if (g_lpszLoggedInUser == NULL) {
|
|
// get the size passing null pointer
|
|
DWORD nSize = 0;
|
|
// this is expected to fail
|
|
if (GetUserNameEx(NameFullyQualifiedDN , NULL, &nSize))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsThisUserLoggedIn(%s): GetUserNameEx unexpected success",
|
|
bstrUserDN);
|
|
return E_FAIL;
|
|
}
|
|
|
|
if( nSize == 0 )
|
|
{
|
|
// JonN 3/16/01 344862
|
|
// dsrm from workgroup computer cannot remotely delete users from domain
|
|
// This probably failed because the local computer is in a workgroup
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsThisUserLoggedIn(%s): GetUserNameEx nSize==0",
|
|
bstrUserDN);
|
|
return S_OK; // allow user deletion
|
|
}
|
|
|
|
g_lpszLoggedInUser = new WCHAR[ nSize ];
|
|
if( g_lpszLoggedInUser == NULL )
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsThisUserLoggedIn(%s): out of memory",
|
|
bstrUserDN);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
//Security Review:Correct size in byte is passed.
|
|
::ZeroMemory( g_lpszLoggedInUser, nSize*sizeof(WCHAR) );
|
|
|
|
// this is expected to succeed
|
|
if (!GetUserNameEx(NameFullyQualifiedDN, g_lpszLoggedInUser, &nSize ))
|
|
{
|
|
// JonN 3/16/01 344862
|
|
// dsrm from workgroup computer cannot remotely delete users from domain
|
|
// This probably failed because the local computer is in a workgroup
|
|
DWORD dwErr = ::GetLastError();
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"IsThisUserLoggedIn(%s): GetUserNameEx unexpected failure: %d",
|
|
bstrUserDN, dwErr);
|
|
return S_OK; // allow user deletion
|
|
}
|
|
}
|
|
|
|
//Security Review:GetUserNameEx returns Null terminated user name.
|
|
if (!_wcsicmp (g_lpszLoggedInUser, bstrUserDN))
|
|
return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Function: DeleteChildren
|
|
//
|
|
// Synopsis: Deletes only the children of a single target
|
|
//
|
|
// Arguments: credentialObject
|
|
// basePathsInfo
|
|
// pIADs: IADs pointer to the object
|
|
// *pfErrorReported: Will be set to true if DeleteChildren
|
|
// takes care of reporting the error itself
|
|
//
|
|
// Returns: HRESULT : error code to be returned from command-line app
|
|
// Could be almost any ADSI error
|
|
// Returns S_OK if there are no children
|
|
//
|
|
// History: 26-Sep-2000 JonN Created
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
HRESULT DeleteChildren( CDSCmdCredentialObject& credentialObject,
|
|
IADs* pIADs,
|
|
bool* pfErrorReported )
|
|
{
|
|
ENTER_FUNCTION(LEVEL5_LOGGING, DeleteChildren);
|
|
|
|
ASSERT( pIADs && pfErrorReported );
|
|
if ( !pIADs || !pfErrorReported )
|
|
return E_POINTER;
|
|
|
|
CComQIPtr<IDirectorySearch,&IID_IDirectorySearch> spSearch( pIADs );
|
|
ASSERT( !!spSearch );
|
|
if ( !spSearch )
|
|
return E_FAIL;
|
|
HRESULT hr = SetSearchPreference(spSearch, ADS_SCOPE_ONELEVEL);
|
|
ASSERT( !FAILED(hr) );
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
LPWSTR pAttrs[1] = { L"aDSPath" };
|
|
ADS_SEARCH_HANDLE SearchHandle = NULL;
|
|
hr = spSearch->ExecuteSearch (L"(objectClass=*)",
|
|
pAttrs,
|
|
1,
|
|
&SearchHandle);
|
|
if (FAILED(hr))
|
|
{
|
|
DEBUG_OUTPUT(MINIMAL_LOGGING,
|
|
L"DeleteChildren: ExecuteSearch failure: 0x%08x",
|
|
hr);
|
|
return hr;
|
|
}
|
|
|
|
while ( hr = spSearch->GetNextRow( SearchHandle ),
|
|
SUCCEEDED(hr) && hr != S_ADS_NOMORE_ROWS )
|
|
{
|
|
CComBSTR sbstrADsPathThisItem;
|
|
hr = RetrieveStringColumn( spSearch,
|
|
SearchHandle,
|
|
pAttrs[0],
|
|
sbstrADsPathThisItem );
|
|
ASSERT( !FAILED(hr) );
|
|
if (FAILED(hr))
|
|
break;
|
|
|
|
CComPtr<IADsDeleteOps> spDeleteOps;
|
|
// return the error value for the first error encountered
|
|
HRESULT hrThisItem = DSCmdOpenObject(credentialObject,
|
|
sbstrADsPathThisItem,
|
|
IID_IADsDeleteOps,
|
|
(void**)&spDeleteOps,
|
|
true);
|
|
if (FAILED(hrThisItem))
|
|
{
|
|
DEBUG_OUTPUT(
|
|
MINIMAL_LOGGING,
|
|
L"DeleteChildren: DsCmdOpenObject(%s) failure: 0x%08x",
|
|
sbstrADsPathThisItem, hrThisItem);
|
|
}
|
|
else
|
|
{
|
|
ASSERT( !!spDeleteOps );
|
|
hrThisItem = spDeleteOps->DeleteObject( NULL );
|
|
if (FAILED(hrThisItem))
|
|
{
|
|
DEBUG_OUTPUT(
|
|
MINIMAL_LOGGING,
|
|
L"DeleteChildren: DeleteObject(%s) failure: 0x%08x",
|
|
sbstrADsPathThisItem, hrThisItem);
|
|
}
|
|
}
|
|
if (!FAILED(hrThisItem))
|
|
continue;
|
|
|
|
// an error occurred
|
|
|
|
if (!FAILED(hr))
|
|
hr = hrThisItem;
|
|
|
|
CComBSTR sbstrDN;
|
|
CPathCracker pathcracker;
|
|
HRESULT hr2 = pathcracker.Set( sbstrADsPathThisItem, ADS_SETTYPE_FULL );
|
|
ASSERT( !FAILED(hr2) );
|
|
if (FAILED(hr2))
|
|
break;
|
|
hr2 = pathcracker.Retrieve( ADS_FORMAT_X500_DN, &sbstrDN );
|
|
ASSERT( !FAILED(hr2) );
|
|
if (FAILED(hr2))
|
|
break;
|
|
|
|
// Report error message for the child which could not be deleted
|
|
DisplayErrorMessage(g_pszDSCommandName,
|
|
sbstrDN,
|
|
hrThisItem);
|
|
*pfErrorReported = true;
|
|
|
|
if (!fContinue)
|
|
break;
|
|
}
|
|
if (hr != S_ADS_NOMORE_ROWS)
|
|
{
|
|
DEBUG_OUTPUT(FULL_LOGGING,
|
|
L"DeleteChildren: abandoning search");
|
|
(void) spSearch->AbandonSearch( SearchHandle );
|
|
}
|
|
|
|
return (hr == S_ADS_NOMORE_ROWS) ? S_OK : hr;
|
|
}
|