//---------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 2000. // // File: cumiconn.cxx // // Contents: Contains the UMI connection object implementation // // History: 03-02-00 SivaramR Created. // //---------------------------------------------------------------------------- #include "winnt.hxx" //---------------------------------------------------------------------------- // Function: CreateConnection // // Synopsis: Creates a connection object. Called by class factory. // // Arguments: // // iid Interface requested. Only interface supported is IUmiConnection. // ppInterface Returns pointer to interface requested // // Returns: S_OK on success. Error code otherwise. // // Modifies: *ppInterface to return a pointer to the interface requested // //---------------------------------------------------------------------------- HRESULT CUmiConnection::CreateConnection( REFIID iid, LPVOID *ppInterface ) { CUmiConnection *pConn = NULL; HRESULT hr = S_OK; ADsAssert(ppInterface); pConn = new CUmiConnection(); if(NULL == pConn) BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); // initialize connection object hr = pConn->FInit(); BAIL_ON_FAILURE(hr); hr = pConn->QueryInterface(iid, ppInterface); BAIL_ON_FAILURE(hr); pConn->Release(); RRETURN(S_OK); error: if(pConn != NULL) delete pConn; RRETURN(hr); } //---------------------------------------------------------------------------- // Function: CUmiConnection // // Synopsis: Constructor. Initializes all member variables // // Arguments: // // None // // Returns: Nothing. // // Modifies: Nothing. // //---------------------------------------------------------------------------- CUmiConnection::CUmiConnection(void) { m_pIUmiPropList = NULL; m_pCUmiPropList = NULL; m_ulErrorStatus = 0; m_pIADsOpenDSObj = NULL; m_fAlreadyOpened = FALSE; m_pszComputerName = NULL; m_pszDomainName = NULL; } //---------------------------------------------------------------------------- // Function: ~CUmiConnection // // Synopsis: Destructor. Frees member variables // // Arguments: // // None // // Returns: Nothing. // // Modifies: Nothing. // //---------------------------------------------------------------------------- CUmiConnection::~CUmiConnection(void) { if(m_pIUmiPropList != NULL) m_pIUmiPropList->Release(); if(m_pszComputerName != NULL) FreeADsStr(m_pszComputerName); if(m_pszDomainName != NULL) FreeADsStr(m_pszDomainName); if(m_pIADsOpenDSObj != NULL) m_pIADsOpenDSObj->Release(); // m_pCUmiPropList does not have to be deleted since the Release() above // has already done it. } //---------------------------------------------------------------------------- // Function: FInit // // Synopsis: Initializes connection object. // // Arguments: // // None // // Returns: S_OK on success. Error code otherwise. // // Modifies: Nothing. // //---------------------------------------------------------------------------- HRESULT CUmiConnection::FInit(void) { HRESULT hr = S_OK; CUmiPropList *pPropList = NULL; pPropList = new CUmiPropList(ConnectionClass, g_dwConnectionTableSize); if(NULL == pPropList) BAIL_ON_FAILURE(hr = E_OUTOFMEMORY); hr = pPropList->FInit(NULL, g_UmiConUnImplProps); BAIL_ON_FAILURE(hr); hr = pPropList->QueryInterface( IID_IUmiPropList, (void **) &m_pIUmiPropList ); BAIL_ON_FAILURE(hr); // DECLARE_STD_REFCOUNTING initializes the refcount to 1. Call Release() // on the created object, so that releasing the interface pointer will // free the object. pPropList->Release(); m_pCUmiPropList = pPropList; hr = m_pCUmiPropList->SetDefaultConnProps(); BAIL_ON_FAILURE(hr); RRETURN(S_OK); error: if(m_pIUmiPropList != NULL) { m_pIUmiPropList->Release(); m_pIUmiPropList = NULL; m_pCUmiPropList = NULL; } else if(pPropList != NULL) delete pPropList; RRETURN(hr); } //---------------------------------------------------------------------------- // Function: QueryInterface // // Synopsis: Queries connection object for supported interfaces. Only // IUmiConnection is supported. // // Arguments: // // iid interface requested // ppInterface Returns pointer to interface requested. NULL if interface // is not supported. // // Returns: S_OK on success. Error code otherwise. // // Modifies: *ppInterface to return interface pointer // //---------------------------------------------------------------------------- STDMETHODIMP CUmiConnection::QueryInterface( REFIID iid, LPVOID *ppInterface ) { if(NULL == ppInterface) RRETURN(E_INVALIDARG); *ppInterface = NULL; if(IsEqualIID(iid, IID_IUnknown)) *ppInterface = (IUmiConnection *) this; else if(IsEqualIID(iid, IID_IUmiConnection)) *ppInterface = (IUmiConnection *) this; else if(IsEqualIID(iid, IID_IUmiBaseObject)) *ppInterface = (IUmiBaseObject *) this; else if(IsEqualIID(iid, IID_IUmiPropList)) *ppInterface = (IUmiPropList *) this; else RRETURN(E_NOINTERFACE); AddRef(); RRETURN(S_OK); } //---------------------------------------------------------------------------- // Function: GetLastStatus // // Synopsis: Returns status or error code from the last operation. Currently // only numeric status is returned i.e, no error objects are // returned. Implements IUmiBaseObject::GetLastStatus(). // // Arguments: // // uFlags Reserved. Must be 0 for now. // puSpecificStatus Returns status code // riid IID requested. Ignored currently. // pStatusObj Returns interface requested. Always returns NULL currently. // // Returns: UMI_S_NO_ERROR on success. Error code otherwise. // // Modifies: *puSpecificStatus to return status code. // //---------------------------------------------------------------------------- STDMETHODIMP CUmiConnection::GetLastStatus( ULONG uFlags, ULONG *puSpecificStatus, REFIID riid, LPVOID *pStatusObj ) { if(pStatusObj != NULL) *pStatusObj = NULL; if(puSpecificStatus != NULL) *puSpecificStatus = 0; if(uFlags != 0) RRETURN(UMI_E_INVALID_FLAGS); if(NULL == puSpecificStatus) RRETURN(UMI_E_INVALIDARG); *puSpecificStatus = m_ulErrorStatus; RRETURN(UMI_S_NO_ERROR); } //---------------------------------------------------------------------------- // Function: GetInterfacePropList // // Synopsis: Returns a pointer to the interface property list implementation // for the connection object. Implements // IUmiBaseObject::GetInterfacePropList(). // // Arguments: // // uFlags Reserved. Must be 0 for now. // pPropList Returns pointer to IUmiPropertyList interface // // Returns: UMI_S_NO_ERROR on success. Error code otherwise. // // Modifies: *pPropList to return interface pointer // //---------------------------------------------------------------------------- STDMETHODIMP CUmiConnection::GetInterfacePropList( ULONG uFlags, IUmiPropList **pPropList ) { HRESULT hr = UMI_S_NO_ERROR; SetLastStatus(0); if(uFlags != 0) BAIL_ON_FAILURE(hr = UMI_E_INVALID_FLAGS); if(NULL == pPropList) BAIL_ON_FAILURE(hr = UMI_E_INVALIDARG); ADsAssert(m_pIUmiPropList != NULL); hr = m_pIUmiPropList->QueryInterface(IID_IUmiPropList, (void **)pPropList); error: if(FAILED(hr)) SetLastStatus(hr); RRETURN(MapHrToUmiError(hr)); } //---------------------------------------------------------------------------- // Function: SetLastStatus // // Synopsis: Sets the status of the last operation. // // Arguments: // // ulStatus Status to be set // // Returns: Nothing // // Modifies: Nothing // //---------------------------------------------------------------------------- void CUmiConnection::SetLastStatus(ULONG ulStatus) { m_ulErrorStatus = ulStatus; return; } //---------------------------------------------------------------------------- // Function: Open // // Synopsis: Opens the object specified by a URL and gets the interface // requested on this object. Implements IUmiConnection::Open(). // // Arguments: // // pURL Pointer to an IUmiURL interface // uFlags Reserved. Must be 0 for now. // TargetIID Interface requested // ppInterface Returns pointer to interface requested // // Returns: UMI_S_NO_ERROR on success. Error code otherwise. // // Modifies: *ppInterface to return interface pointer // //---------------------------------------------------------------------------- STDMETHODIMP CUmiConnection::Open( IUmiURL *pURL, ULONG uFlags, REFIID TargetIID, LPVOID *ppInterface ) { HRESULT hr = UMI_S_NO_ERROR; LPWSTR pszUserName = NULL, pszPassword = NULL; DWORD dwBindFlags = 0, dwNumComponents = 0, dwIndex = 0; LPWSTR *ppszClasses = NULL; WCHAR pszUrl[MAX_URL+1]; WCHAR *pszLongUrl = pszUrl; ULONG ulUrlLen = MAX_URL; IUnknown *pIUnknown = NULL; CWinNTNamespaceCF tmpNamCF; ULONGLONG PathType = 0; BOOL fPrevAlreadyOpened = FALSE; LPWSTR pszPrevComputer = NULL, pszPrevDomain = NULL; SetLastStatus(0); if(uFlags != 0) BAIL_ON_FAILURE(hr = UMI_E_INVALID_FLAGS); if( (NULL == pURL) || (NULL == ppInterface) ) BAIL_ON_FAILURE(hr = UMI_E_INVALIDARG); *ppInterface = NULL; // Check if the user specified any interface properties for authentication hr = GetUserName(&pszUserName); BAIL_ON_FAILURE(hr); hr = GetPassword(&pszPassword); BAIL_ON_FAILURE(hr); hr = GetBindFlags(&dwBindFlags); BAIL_ON_FAILURE(hr); // check if this is a native path or UMI path hr = pURL->GetPathInfo(0, &PathType); BAIL_ON_FAILURE(hr); if(PathType & UMIPATH_INFO_NATIVE_STRING) { // Get the native path from the URL hr = pURL->Get(0, &ulUrlLen, pszUrl); if(WBEM_E_BUFFER_TOO_SMALL == hr) { // need to allocate more memory for URL pszLongUrl = (WCHAR *) AllocADsMem(ulUrlLen * sizeof(WCHAR)); if(NULL == pszLongUrl) BAIL_ON_FAILURE(hr = UMI_E_OUT_OF_MEMORY); hr = pURL->Get(0, &ulUrlLen, pszLongUrl); } BAIL_ON_FAILURE(hr); } else { // assume UMI path if not native hr = UmiToWinNTPath( pURL, &pszLongUrl, &dwNumComponents, &ppszClasses ); BAIL_ON_FAILURE(hr); } hr = tmpNamCF.CreateInstance( NULL, IID_IADsOpenDSObject, (void **) &m_pIADsOpenDSObj ); BAIL_ON_FAILURE(hr); // // we need a way to distinguish between calls to OpenDSObject from UMI // vs ADSI. We use the bind flags for this purpose. If ADS_AUTH_RESERVED // is set, then the call is from UMI. ADSI clients are not allowed to use // this flag - OLEDB relies on this. // hr = m_pIADsOpenDSObj->OpenDSObject( pszLongUrl, pszUserName, pszPassword, dwBindFlags | ADS_AUTH_RESERVED, (IDispatch **) &pIUnknown ); BAIL_ON_FAILURE(hr); // save off state in case we need to restore it later fPrevAlreadyOpened = m_fAlreadyOpened; pszPrevComputer = m_pszComputerName; pszPrevDomain = m_pszDomainName; // ensure that the returned object is what the user requested and that the // object is on the same domain/server that this connection is for hr = CheckObject( pIUnknown, dwNumComponents, ppszClasses ); BAIL_ON_FAILURE(hr); hr = pIUnknown->QueryInterface( TargetIID, ppInterface ); if(FAILED(hr)) { // restore state of connection m_fAlreadyOpened = fPrevAlreadyOpened; if(m_pszComputerName != pszPrevComputer) { if(m_pszComputerName != NULL) FreeADsStr(m_pszComputerName); m_pszComputerName = pszPrevComputer; } if(m_pszDomainName != pszPrevDomain) { if(m_pszDomainName != NULL) FreeADsStr(m_pszDomainName); m_pszDomainName = pszPrevDomain; } goto error; } // make interface properties read-only m_pCUmiPropList->DisableWrites(); error: if(pszUserName != NULL) FreeADsMem(pszUserName); if(pszPassword != NULL) FreeADsMem(pszPassword); if(pIUnknown != NULL) pIUnknown->Release(); if( (pszLongUrl != NULL) && (pszLongUrl != pszUrl) ) FreeADsMem(pszLongUrl); if(ppszClasses != NULL) { for(dwIndex = 0; dwIndex < dwNumComponents; dwIndex++) { if(ppszClasses[dwIndex] != NULL) FreeADsStr(ppszClasses[dwIndex]); } FreeADsMem(ppszClasses); } if(FAILED(hr)) { SetLastStatus(hr); if(m_pIADsOpenDSObj != NULL) { m_pIADsOpenDSObj->Release(); m_pIADsOpenDSObj = NULL; } } RRETURN(MapHrToUmiError(hr)); } //---------------------------------------------------------------------------- // Function: GetUserName // // Synopsis: Gets the username from the interface property cache. If the // interface property was not set, the default username is // returned. // // Arguments: // // ppszUserName Returns pointer to the username // // Returns: UMI_S_NO_ERROR on success. Error code otherwise. // // Modifies: *ppszUserName to return the username. // //---------------------------------------------------------------------------- HRESULT CUmiConnection::GetUserName(LPWSTR *ppszUserName) { HRESULT hr = UMI_S_NO_ERROR; UMI_PROPERTY_VALUES *pUmiProp = NULL; LPWSTR pszUserName = NULL; ADsAssert(ppszUserName != NULL); *ppszUserName = NULL; hr = m_pIUmiPropList->Get( TEXT(CONN_INTF_PROP_USERNAME), 0, &pUmiProp ); if(FAILED(hr)) { // shouldn't happen BAIL_ON_FAILURE(hr = UMI_E_FAIL); } ADsAssert(UMI_TYPE_LPWSTR == pUmiProp->pPropArray->uType); ADsAssert(pUmiProp->pPropArray->pUmiValue != NULL); pszUserName = pUmiProp->pPropArray->pUmiValue->pszStrValue[0]; if(pszUserName != NULL) { *ppszUserName = AllocADsStr(pszUserName); if(NULL == *ppszUserName) BAIL_ON_FAILURE(hr = UMI_E_OUT_OF_MEMORY); } else *ppszUserName = NULL; error: if(pUmiProp != NULL) m_pIUmiPropList->FreeMemory(0, pUmiProp); // ignore error return RRETURN(hr); } //---------------------------------------------------------------------------- // Function: GetPassword // // Synopsis: Gets the password from the interface property cache. If the // interface property was not set, the default password is // returned. // // Arguments: // // ppszPassword Returns pointer to the password // // Returns: UMI_S_NO_ERROR on success. Error code otherwise. // // Modifies: *ppszPassword to return the password. // //---------------------------------------------------------------------------- HRESULT CUmiConnection::GetPassword(LPWSTR *ppszPassword) { HRESULT hr = UMI_S_NO_ERROR; UMI_PROPERTY_VALUES *pUmiProp = NULL; LPWSTR pszPassword = NULL; ADsAssert(ppszPassword != NULL); *ppszPassword = NULL; hr = m_pCUmiPropList->GetHelper( TEXT(CONN_INTF_PROP_PASSWORD), 0, &pUmiProp, UMI_TYPE_NULL, // no-op TRUE // this is an internal call to GetHelper ); if(FAILED(hr)) { // shouldn't happen BAIL_ON_FAILURE(hr = UMI_E_FAIL); } ADsAssert(UMI_TYPE_LPWSTR == pUmiProp->pPropArray->uType); ADsAssert(pUmiProp->pPropArray->pUmiValue != NULL); pszPassword = pUmiProp->pPropArray->pUmiValue->pszStrValue[0]; if(pszPassword != NULL) { *ppszPassword = AllocADsStr(pszPassword); if(NULL == *ppszPassword) BAIL_ON_FAILURE(hr = UMI_E_OUT_OF_MEMORY); } else *ppszPassword = NULL; error: if(pUmiProp != NULL) m_pCUmiPropList->FreeMemory(0, pUmiProp); // ignore error return RRETURN(hr); } //---------------------------------------------------------------------------- // Function: GetBindFlags // // Synopsis: Gets the bind flags from the interface property cache. If the // interface properties were not set, the default bind flags are // returned. // // Arguments: // // pdwBindFlags Returns the bind flags. // // Returns: UMI_S_NO_ERROR on success. Error code otherwise. // // Modifies: *pdwBindFlags to return the bind flags. // //---------------------------------------------------------------------------- HRESULT CUmiConnection::GetBindFlags(DWORD *pdwBindFlags) { HRESULT hr = UMI_S_NO_ERROR; UMI_PROPERTY_VALUES *pUmiProp = NULL; DWORD dwUmiBindFlags = 0; ADsAssert(pdwBindFlags != NULL); hr = m_pIUmiPropList->Get( TEXT(CONN_INTF_PROP_SECURE_AUTH), 0, &pUmiProp ); if(SUCCEEDED(hr)) { ADsAssert(UMI_TYPE_BOOL == pUmiProp->pPropArray->uType); ADsAssert(pUmiProp->pPropArray->pUmiValue != NULL); if(TRUE == pUmiProp->pPropArray->pUmiValue->bValue[0]) dwUmiBindFlags |= ADS_SECURE_AUTHENTICATION; m_pIUmiPropList->FreeMemory(0, pUmiProp); // ignore error return pUmiProp = NULL; } else // shouldn't happen BAIL_ON_FAILURE(hr = UMI_E_FAIL); hr = m_pIUmiPropList->Get( TEXT(CONN_INTF_PROP_READONLY_SERVER), 0, &pUmiProp ); if(SUCCEEDED(hr)) { ADsAssert(UMI_TYPE_BOOL == pUmiProp->pPropArray->uType); ADsAssert(pUmiProp->pPropArray->pUmiValue != NULL); if(TRUE == pUmiProp->pPropArray->pUmiValue->bValue[0]) dwUmiBindFlags |= ADS_READONLY_SERVER; m_pIUmiPropList->FreeMemory(0, pUmiProp); // ignore error return pUmiProp = NULL; } else // shouldn't happen BAIL_ON_FAILURE(hr = UMI_E_FAIL); *pdwBindFlags = dwUmiBindFlags; error: if(pUmiProp != NULL) m_pIUmiPropList->FreeMemory(0, pUmiProp); // ignore error return RRETURN(hr); } //---------------------------------------------------------------------------- // Function: CheckObject // // Synopsis: Checks that the returned object is the same as what the user // requested, if the user passed in a UMI path i.e the classes // of the components in the UMI path to the object retrieved // should be the same as what the user requested. // This function also checks to make sure that subsequent calls to // Open(), after the first call, specify the same server/domain // as the first call. If the path used in the first call to Open() // specifies only a domain name, then all subsequent Open() calls // should also specify only a domain name and no computer name. // If the first call to Open() specified only a computer name, then // all subsequent calls should specify the same computer name. If // the first call to Open specified the WinNT namespace path // (umi:///winnt or WinNT:), then subsequent Open() calls can // connect to any domain/computer. Also, the namespace object can // be opened successfully even if we already connected to a // computer/domain. // // Arguments: // // pUnknown Pointer to the IUnknown of object retrieved // dwNumComps Number of components if the path is a UMI path. 0 otherwise. // ppszClasses Array containing the class of each component, if the path is // a UMI path to an object other than the namespace obejct. // NULL otherwise. // // Returns: S_OK on success. Error code otherwise. // // Modifies: Nothing // //---------------------------------------------------------------------------- HRESULT CUmiConnection::CheckObject( IUnknown *pUnknown, DWORD dwNumComps, LPWSTR *ppszClasses ) { HRESULT hr = S_OK; IUmiADSIPrivate *pUmiPrivate = NULL; CCoreADsObject *pCoreObj = NULL; LPWSTR pszComputerName = NULL, pszDomainName = NULL; DWORD dwIndex = 0, dwCoreIndex = 0; ADsAssert(pUnknown != NULL); hr = pUnknown->QueryInterface( IID_IUmiADSIPrivate, (LPVOID *) &pUmiPrivate ); BAIL_ON_FAILURE(hr); hr = pUmiPrivate->GetCoreObject((void **) &pCoreObj); BAIL_ON_FAILURE(hr); if(ppszClasses != NULL) { // user specified a UMI path and it was not umi:///winnt. Make sure the // classes are the same, as mentioned above. // walk the list of classes in reverse order. Reason for reverse order // is that the WinNT provider may tack on an additional component to // the ADsPath stored in the core object. For example, // Open("WinNT://ntdsdc1") would return an ADsPath of // "WinNT://ntdev/ntdsdc1". dwCoreIndex = pCoreObj->_dwNumComponents - 1; for(dwIndex = dwNumComps - 1; ((long) dwIndex) >= 0; dwIndex--) { if( _wcsicmp( ppszClasses[dwIndex], pCoreObj->_CompClasses[dwCoreIndex]) ) { if( (0 == dwIndex) && (dwNumComps > 1) ) { if(0 == _wcsicmp(pCoreObj->_CompClasses[1], SCHEMA_CLASS_NAME)) { // if the first component of a schema path doesn't match, // make sure it is "Domain". Need this special case because // of a bug in the WinNT provider. First component of a // schema path is ignored and hence the UMI path always // returns "Computer" as the class for this component. This // special case allows binding using a path like // umi://winnt/domain=ntdev/schema=schema. if(0 == _wcsicmp(ppszClasses[dwIndex], DOMAIN_CLASS_NAME)) { dwCoreIndex--; continue; } } } BAIL_ON_FAILURE(hr = UMI_E_INVALID_PATH); } dwCoreIndex--; } } // if(ppszClasses...) // get the domain/computer name specified in the path if(pCoreObj->_dwNumComponents > 0) { for(dwIndex = pCoreObj->_dwNumComponents - 1; ((long) dwIndex) >= 0; dwIndex--) { if(0 == (_wcsicmp( pCoreObj->_CompClasses[dwIndex], SCHEMA_CLASS_NAME)) ) { // schema container is a special case. We can connect to the // schema on any computer/domain irrespective of where we are // connected currently. This is to allow for CIMOM to connect to // the schema container for a computer. Currently, the WinNT // provider returns WinNT://ntdev/schema as the schema path on the // object WinNT://ntdsdc1. Hence we need to allow CIMOM to connect // to ntdev even after connecting to ntdsdc1. break; // pszComputerName and pszDomainName are both NULL } if(0 == (_wcsicmp( pCoreObj->_CompClasses[dwIndex], COMPUTER_CLASS_NAME)) ) { pszComputerName = pCoreObj->_ObjectInfo.DisplayComponentArray[dwIndex]; break; } else if(0 == (_wcsicmp( pCoreObj->_CompClasses[dwIndex], DOMAIN_CLASS_NAME)) ) { pszDomainName = pCoreObj->_ObjectInfo.DisplayComponentArray[dwIndex]; break; } } // for(..) } // if(pCoreObj...) if(FALSE == m_fAlreadyOpened) { // first call to Open() if(pszComputerName != NULL) { m_pszComputerName = AllocADsStr(pszComputerName); if(NULL == m_pszComputerName) { BAIL_ON_FAILURE(hr = UMI_E_OUT_OF_MEMORY); } } else if(pszDomainName != NULL) { m_pszDomainName = AllocADsStr(pszDomainName); if(NULL == m_pszDomainName) { BAIL_ON_FAILURE(hr = UMI_E_OUT_OF_MEMORY); } } m_fAlreadyOpened = TRUE; } else if( (pszComputerName != NULL) || (pszDomainName != NULL) ) { // Already opened connection and this is not the namespace object. // Make sure that the domain/computer is same as before. if(m_pszComputerName != NULL) { if( (NULL == pszComputerName) || (_wcsicmp(m_pszComputerName, pszComputerName)) ) { BAIL_ON_FAILURE(hr = UMI_E_MISMATCHED_SERVER); } } else if(m_pszDomainName != NULL) { if( (NULL == pszDomainName) || (_wcsicmp(m_pszDomainName, pszDomainName)) ) { BAIL_ON_FAILURE(hr = UMI_E_MISMATCHED_DOMAIN); } } else { // both m_pszComputerName and m_pszDomainName are NULL. Previous // open() must have been for a namespace object. if(pszComputerName != NULL) { m_pszComputerName = AllocADsStr(pszComputerName); if(NULL == m_pszComputerName) { BAIL_ON_FAILURE(hr = UMI_E_OUT_OF_MEMORY); } } else if(pszDomainName != NULL) { m_pszDomainName = AllocADsStr(pszDomainName); if(NULL == m_pszDomainName) { BAIL_ON_FAILURE(hr = UMI_E_OUT_OF_MEMORY); } } } // else { } // else if(pszComputer...) error: if(pUmiPrivate != NULL) pUmiPrivate->Release(); RRETURN(hr); }