Leaked source code of windows server 2003
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.
 
 
 
 
 
 

946 lines
25 KiB

#include "pch.h"
#pragma hdrstop
#include "nccom.h"
#include "ncnetcon.h"
#include "ncreg.h"
#include "saconob.h"
static const CLSID CLSID_SharedAccessConnectionUi =
{0x7007ACD5,0x3202,0x11D1,{0xAA,0xD2,0x00,0x80,0x5F,0xC1,0x27,0x0E}};
static const WCHAR c_szConnName[] = L"Name";
static const WCHAR c_szShowIcon[] = L"ShowIcon";
static const WCHAR c_szSharedAccessClientKeyPath[] = L"System\\CurrentControlSet\\Control\\Network\\SharedAccessConnection";
#define UPNP_ACTION_HRESULT(lError) (UPNP_E_ACTION_SPECIFIC_BASE + (lError - FAULT_ACTION_SPECIFIC_BASE))
CSharedAccessConnection::CSharedAccessConnection()
{
m_pSharedAccessBeacon = NULL;
m_pWANConnectionService = NULL;
}
HRESULT CSharedAccessConnection::FinalConstruct()
{
HRESULT hr = S_OK;
ISharedAccessBeaconFinder* pBeaconFinder;
hr = HrCreateInstance(CLSID_SharedAccessConnectionManager, CLSCTX_SERVER, &pBeaconFinder);
if(SUCCEEDED(hr))
{
hr = pBeaconFinder->GetSharedAccessBeacon(NULL, &m_pSharedAccessBeacon);
if(SUCCEEDED(hr))
{
NETCON_MEDIATYPE MediaType;
hr = m_pSharedAccessBeacon->GetMediaType(&MediaType);
if(SUCCEEDED(hr))
{
hr = m_pSharedAccessBeacon->GetService(NCM_SHAREDACCESSHOST_LAN == MediaType ? SAHOST_SERVICE_WANIPCONNECTION : SAHOST_SERVICE_WANPPPCONNECTION, &m_pWANConnectionService);
}
}
pBeaconFinder->Release();
}
return hr;
}
HRESULT CSharedAccessConnection::FinalRelease()
{
HRESULT hr = S_OK;
if(NULL != m_pSharedAccessBeacon)
{
m_pSharedAccessBeacon->Release();
}
if(NULL != m_pWANConnectionService)
{
m_pWANConnectionService->Release();
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::GetConnectionName
//
// Purpose: Initializes the connection object for the first time.
//
// Arguments:
// (none)
//
// Returns: S_OK if success, Win32 or OLE error code otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes: This function is only called when the object is created for
// the very first time and has no identity.
//
HRESULT CSharedAccessConnection::GetConnectionName(LPWSTR* pName)
{
HRESULT hr = S_OK;
HKEY hKey;
// first get the user assigned name
hr = HrRegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szSharedAccessClientKeyPath, KEY_READ, &hKey);
if(SUCCEEDED(hr))
{
tstring strName;
hr = HrRegQueryString(hKey, c_szConnName, &strName);
if (SUCCEEDED(hr))
{
hr = HrCoTaskMemAllocAndDupSz (strName.c_str(), pName, NETCON_MAX_NAME_LEN);
}
RegCloseKey(hKey);
}
// if that doesn't exist, construct the name
if(FAILED(hr))
{
IUPnPService* pOSInfoService;
hr = m_pSharedAccessBeacon->GetService(SAHOST_SERVICE_OSINFO, &pOSInfoService);
if(SUCCEEDED(hr))
{
BSTR MachineName;
hr = GetStringStateVariable(pOSInfoService, L"OSMachineName", &MachineName);
if(SUCCEEDED(hr))
{
BSTR SharedAdapterName;
hr = GetStringStateVariable(m_pWANConnectionService, L"X_Name", &SharedAdapterName);
if(SUCCEEDED(hr))
{
LPWSTR szNameString;
LPCWSTR szTemplateString = SzLoadIds(IDS_SHAREDACCESS_CONN_NAME);
Assert(NULL != szTemplateString);
LPOLESTR pszParams[] = {SharedAdapterName, MachineName};
if(0 != FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szTemplateString, 0, 0, reinterpret_cast<LPWSTR>(&szNameString), 0, reinterpret_cast<va_list *>(pszParams)))
{
HrCoTaskMemAllocAndDupSz (szNameString, pName, NETCON_MAX_NAME_LEN);
LocalFree(szNameString);
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
SysFreeString(SharedAdapterName);
}
SysFreeString(MachineName);
}
pOSInfoService->Release();
}
}
// if that fails, use the default
if(FAILED(hr))
{
hr = HrCoTaskMemAllocAndDupSz (SzLoadIds(IDS_SHAREDACCESS_DEFAULT_CONN_NAME), pName, NETCON_MAX_NAME_LEN);
}
TraceError("CSharedAccessConnection::HrInitialize", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::GetStatus
//
// Purpose: Returns the status of this ICS connection
//
// Arguments:
// pStatus [out] Returns status value
//
// Returns: S_OK if success, OLE or Win32 error code otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
HRESULT CSharedAccessConnection::GetStatus(NETCON_STATUS *pStatus)
{
HRESULT hr = S_OK;
if (!pStatus)
{
hr = E_POINTER;
}
else
{
BSTR ConnectionStatus;
hr = GetStringStateVariable(m_pWANConnectionService, L"ConnectionStatus", &ConnectionStatus);
if(SUCCEEDED(hr))
{
if(0 == lstrcmp(ConnectionStatus, L"Connected"))
{
*pStatus = NCS_CONNECTED;
}
else if(0 == lstrcmp(ConnectionStatus, L"Disconnected"))
{
*pStatus = NCS_DISCONNECTED;
}
else if(0 == lstrcmp(ConnectionStatus, L"Unconfigured"))
{
*pStatus = NCS_HARDWARE_DISABLED; // REVIEW: better state?
}
else if(0 == lstrcmp(ConnectionStatus, L"Connecting"))
{
*pStatus = NCS_CONNECTING;
}
else if(0 == lstrcmp(ConnectionStatus, L"Authenticating"))
{
*pStatus = NCS_CONNECTING;
}
else if(0 == lstrcmp(ConnectionStatus, L"PendingDisconnect"))
{
*pStatus = NCS_DISCONNECTING;
}
else if(0 == lstrcmp(ConnectionStatus, L"Disconnecting"))
{
*pStatus = NCS_DISCONNECTING;
}
else
{
*pStatus = NCS_HARDWARE_DISABLED;
}
SysFreeString(ConnectionStatus);
}
}
TraceError("CSharedAccessConnection::GetStatus", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::GetCharacteristics
//
// Purpose: Returns the characteristics of this connection type
//
// Arguments:
// pdwFlags [out] Returns characteristics flags
//
// Returns: S_OK if successful, OLE or Win32 error code otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
HRESULT CSharedAccessConnection::GetCharacteristics(DWORD* pdwFlags)
{
Assert (pdwFlags);
// TODO when get have a place to save the name, allow rename
HRESULT hr = S_OK;
*pdwFlags = NCCF_ALL_USERS | NCCF_ALLOW_RENAME; // REVIEW always ok, group policy?
HKEY hKey;
hr = HrRegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szSharedAccessClientKeyPath, KEY_QUERY_VALUE, &hKey);
if(SUCCEEDED(hr))
{
DWORD dwShowIcon = 0;
DWORD dwSize = sizeof(dwShowIcon);
DWORD dwType;
hr = HrRegQueryValueEx(hKey, c_szShowIcon, &dwType, reinterpret_cast<LPBYTE>(&dwShowIcon), &dwSize);
if(SUCCEEDED(hr) && REG_DWORD == dwType)
{
if(0 != dwShowIcon)
{
*pdwFlags |= NCCF_SHOW_ICON;
}
}
RegCloseKey(hKey);
}
hr = S_OK; // it's ok if the key doesn't exist
TraceError("CSharedAccessConnection::GetCharacteristics", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::Connect
//
// Purpose: Connects the remote ICS host
//
// Arguments:
// (none)
//
// Returns: S_OK if success, OLE or Win32 error code otherwise
//
// Author: kenwic 8 Aug 2000
//
HRESULT CSharedAccessConnection::Connect()
{
HRESULT hr = S_OK;
VARIANT OutArgs;
hr = InvokeVoidAction(m_pWANConnectionService, L"RequestConnection", &OutArgs);
if(UPNP_ACTION_HRESULT(800) == hr)
{
hr = E_ACCESSDENIED;
VariantClear(&OutArgs);
}
TraceError("CSharedAccessConnection::Connect", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::Disconnect
//
// Purpose: Disconnects the remote ICS host
//
// Arguments:
// (none)
//
// Returns: S_OK if success, OLE or Win32 error code otherwise
//
// Author: kenwic 8 Aug 2000
//
HRESULT CSharedAccessConnection::Disconnect()
{
HRESULT hr = S_OK;
VARIANT OutArgs;
hr = InvokeVoidAction(m_pWANConnectionService, L"ForceTermination", &OutArgs);
if(UPNP_ACTION_HRESULT(800) == hr)
{
hr = E_ACCESSDENIED;
VariantClear(&OutArgs);
}
TraceError("CSharedAccessConnection::Disconnect", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::Delete
//
// Purpose: Delete the remote ICS connection. This not allowed.
//
// Arguments:
// (none)
//
// Returns: E_FAIL;
//
// Author: kenwic 8 Aug 2000
//
// Notes: This function is not expected to ever be called.
//
HRESULT CSharedAccessConnection::Delete()
{
return E_FAIL; // can't delete the beacon
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::Duplicate
//
// Purpose: Duplicates the remote ICS connection. This not allowed.
//
// Arguments:
// (none)
//
// Returns: E_UNEXPECTED;
//
// Author: kenwic 8 Aug 2000
//
// Notes: This function is not expected to ever be called.
//
STDMETHODIMP CSharedAccessConnection::Duplicate (
PCWSTR pszDuplicateName,
INetConnection** ppCon)
{
return E_NOTIMPL;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::GetProperties
//
// Purpose: Get all of the properties associated with the connection.
// Returning all of them at once saves us RPCs vs. returning
// each one individually.
//
// Arguments:
// ppProps [out] Returned block of properties.
//
// Returns: S_OK or an error.
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
STDMETHODIMP CSharedAccessConnection::GetProperties (
NETCON_PROPERTIES** ppProps)
{
HRESULT hr = S_OK;
// Validate parameters.
//
if (!ppProps)
{
hr = E_POINTER;
}
else
{
// Initialize the output parameter.
//
*ppProps = NULL;
NETCON_PROPERTIES* pProps;
hr = HrCoTaskMemAlloc (sizeof (NETCON_PROPERTIES), reinterpret_cast<void**>(&pProps));
if (SUCCEEDED(hr))
{
HRESULT hrT;
ZeroMemory (pProps, sizeof (NETCON_PROPERTIES));
// guidId
//
pProps->guidId = CLSID_SharedAccessConnection; // there is only ever one beacon icon, so we'll just use our class id.
// we can't use all zeroes because the add connection wizard does that
// pszwName
//
hrT = GetConnectionName(&pProps->pszwName);
if (FAILED(hrT))
{
hr = hrT;
}
// pszwDeviceName
//
hrT = HrCoTaskMemAllocAndDupSz (pProps->pszwName, &pProps->pszwDeviceName, NETCON_MAX_NAME_LEN); // TODO the spec says the same as pszwName here, is that right
if (FAILED(hrT))
{
hr = hrT;
}
// Status
//
hrT = GetStatus (&pProps->Status);
if (FAILED(hrT))
{
hr = hrT;
}
if(NULL != m_pSharedAccessBeacon)
{
hr = m_pSharedAccessBeacon->GetMediaType(&pProps->MediaType);
}
else
{
hr = E_UNEXPECTED;
}
hrT = GetCharacteristics (&pProps->dwCharacter);
if (FAILED(hrT))
{
hr = hrT;
}
// clsidThisObject
//
pProps->clsidThisObject = CLSID_SharedAccessConnection;
// clsidUiObject
//
pProps->clsidUiObject = CLSID_SharedAccessConnectionUi;
// Assign the output parameter or cleanup if we had any failures.
//
if (SUCCEEDED(hr))
{
*ppProps = pProps;
}
else
{
Assert (NULL == *ppProps);
FreeNetconProperties (pProps);
}
}
}
TraceError ("CLanConnection::GetProperties", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::GetUiObjectClassId
//
// Purpose: Returns the CLSID of the object that handles UI for this
// connection type
//
// Arguments:
// pclsid [out] Returns CLSID of UI object
//
// Returns: S_OK if success, OLE error code otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
STDMETHODIMP CSharedAccessConnection::GetUiObjectClassId(CLSID *pclsid)
{
HRESULT hr = S_OK;
// Validate parameters.
//
if (!pclsid)
{
hr = E_POINTER;
}
else
{
*pclsid = CLSID_SharedAccessConnectionUi;
}
TraceError("CLanConnection::GetUiObjectClassId", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::Rename
//
// Purpose: Changes the name of the connection
//
// Arguments:
// pszName [in] New connection name (must be valid)
//
// Returns: S_OK if success, OLE error code otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
STDMETHODIMP CSharedAccessConnection::Rename(PCWSTR pszName)
{
HRESULT hr = S_OK;
if (!pszName)
{
hr = E_POINTER;
}
else if (!FIsValidConnectionName(pszName))
{
// Bad connection name
hr = E_INVALIDARG;
}
else
{
HKEY hKey;
hr = HrRegCreateKeyEx(HKEY_LOCAL_MACHINE, c_szSharedAccessClientKeyPath, NULL, KEY_SET_VALUE, NULL, &hKey, NULL);
if(SUCCEEDED(hr))
{
hr = HrRegSetSz(hKey, c_szConnName, pszName);
if (S_OK == hr)
{
INetConnectionRefresh* pNetConnectionRefresh;
hr = CoCreateInstance(CLSID_ConnectionManager, NULL, CLSCTX_SERVER, IID_INetConnectionRefresh, reinterpret_cast<void**>(&pNetConnectionRefresh));
if(SUCCEEDED(hr))
{
pNetConnectionRefresh->ConnectionRenamed(this);
pNetConnectionRefresh->Release();
}
}
}
}
TraceError("CLanConnection::Rename", hr);
return hr;
}
//+---------------------------------------------------------------------------
// IPersistNetConnection
//
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::GetClassID
//
// Purpose: Returns the CLSID of connection objects
//
// Arguments:
// pclsid [out] Returns CLSID to caller
//
// Returns: S_OK if success, OLE error otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
STDMETHODIMP CSharedAccessConnection::GetClassID(CLSID* pclsid)
{
HRESULT hr = S_OK;
// Validate parameters.
//
if (!pclsid)
{
hr = E_POINTER;
}
else
{
*pclsid = CLSID_SharedAccessConnection; // we just use our guid since there is only ever one saconob
}
TraceError("CSharedAccessConnection::GetClassID", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::GetSizeMax
//
// Purpose: Returns the maximum size of the persistence data
//
// Arguments:
// pcbSize [out] Returns size
//
// Returns: S_OK if success, OLE error otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
STDMETHODIMP CSharedAccessConnection::GetSizeMax(ULONG *pcbSize)
{
HRESULT hr = S_OK;
// Validate parameters.
//
if (!pcbSize)
{
hr = E_POINTER;
}
else
{
*pcbSize = sizeof(GUID);
}
TraceError("CLanConnection::GetSizeMax", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::Load
//
// Purpose: Allows the connection object to initialize (restore) itself
// from previously persisted data
//
// Arguments:
// pbBuf [in] Private data to use for restoring
// cbSize [in] Size of data
//
// Returns: S_OK if success, OLE error otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
STDMETHODIMP CSharedAccessConnection::Load(const BYTE *pbBuf, ULONG cbSize)
{
HRESULT hr = E_INVALIDARG;
// Validate parameters.
//
if (!pbBuf)
{
hr = E_POINTER;
}
else if (cbSize != sizeof(GUID))
{
hr = E_INVALIDARG;
}
else
{
hr = S_OK; // we don't need this guid, but we have to implemenet IPersistNetConnection
}
TraceError("CLanConnection::Load", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::Save
//
// Purpose: Provides the caller with data to use in restoring this object
// at a later time.
//
// Arguments:
// pbBuf [out] Returns data to use for restoring
// cbSize [in] Size of data buffer
//
// Returns: S_OK if success, OLE error otherwise
//
// Author: kenwic 8 Aug 2000
//
// Notes:
//
STDMETHODIMP CSharedAccessConnection::Save(BYTE *pbBuf, ULONG cbSize)
{
HRESULT hr;
// Validate parameters.
//
if (!pbBuf)
{
hr = E_POINTER;
}
else
{
CopyMemory(pbBuf, &CLSID_SharedAccessConnection, cbSize); // REVIEW can we eliminate this?
}
TraceError("CLanConnection::Save", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::GetInfo
//
// Purpose: Returns information about this connection
//
// Arguments:
// dwMask [in] Flags that control which fields to return. Use
// SACIF_ALL to get all fields.
// pLanConInfo [out] Structure that holds returned information
//
// Returns: S_OK if success, OLE error code otherwise
//
// Author: kenwic 6 Sep 2000
//
// Notes: Caller should delete the szwConnName value.
//
STDMETHODIMP CSharedAccessConnection::GetInfo(DWORD dwMask, SHAREDACCESSCON_INFO* pConInfo)
{
HRESULT hr = S_OK;
if (!pConInfo)
{
hr = E_POINTER;
}
else
{
ZeroMemory(pConInfo, sizeof(SHAREDACCESSCON_INFO));
if (dwMask & SACIF_ICON)
{
if (SUCCEEDED(hr))
{
DWORD dwValue;
// OK if value not there. Default to FALSE always.
//
HKEY hKey;
hr = HrRegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szSharedAccessClientKeyPath, KEY_QUERY_VALUE, &hKey);
if(SUCCEEDED(hr))
{
if (S_OK == HrRegQueryDword(hKey, c_szShowIcon, &dwValue))
{
pConInfo->fShowIcon = !!(dwValue);
}
RegCloseKey(hKey);
}
}
}
}
// Mask S_FALSE if it slipped thru.
if (S_FALSE == hr)
{
hr = S_OK;
}
TraceError("CSharedAccessConnection::GetInfo", hr);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CSharedAccessConnection::SetInfo
//
// Purpose: Sets information about this connection.
//
// Arguments:
// dwMask [in] Flags that control which fields to set
// pConInfo [in] Structure containing information to set
//
// Returns: S_OK if success, OLE or Win32 error code otherwise
//
// Author: kenwic 6 Sep 2000
//
STDMETHODIMP CSharedAccessConnection::SetInfo(DWORD dwMask,
const SHAREDACCESSCON_INFO* pConInfo)
{
HRESULT hr = S_OK;
if (!pConInfo)
{
hr = E_POINTER;
}
else
{
if (dwMask & SACIF_ICON)
{
if (SUCCEEDED(hr))
{
// Set ShowIcon value
HKEY hKey;
hr = HrRegCreateKeyEx(HKEY_LOCAL_MACHINE, c_szSharedAccessClientKeyPath, NULL, KEY_SET_VALUE, NULL, &hKey, NULL);
if(SUCCEEDED(hr))
{
hr = HrRegSetDword(hKey, c_szShowIcon, pConInfo->fShowIcon);
RegCloseKey(hKey);
}
}
}
if (SUCCEEDED(hr))
{
INetConnectionRefresh* pNetConnectionRefresh;
hr = CoCreateInstance(CLSID_ConnectionManager, NULL, CLSCTX_SERVER, IID_INetConnectionRefresh, reinterpret_cast<void**>(&pNetConnectionRefresh));
if(SUCCEEDED(hr))
{
pNetConnectionRefresh->ConnectionModified(this);
pNetConnectionRefresh->Release();
}
}
}
TraceError("CSharedAccessConnection::SetInfo", hr);
return hr;
}
HRESULT CSharedAccessConnection::GetLocalAdapterGUID(GUID* pGuid)
{
return m_pSharedAccessBeacon->GetLocalAdapterGUID(pGuid);
}
HRESULT CSharedAccessConnection::GetService(SAHOST_SERVICES ulService, IUPnPService** ppService)
{
return m_pSharedAccessBeacon->GetService(ulService, ppService);
}
HRESULT CSharedAccessConnection::GetStringStateVariable(IUPnPService* pService, LPWSTR pszVariableName, BSTR* pString)
{
HRESULT hr = S_OK;
VARIANT Variant;
VariantInit(&Variant);
BSTR VariableName;
VariableName = SysAllocString(pszVariableName);
if(NULL != VariableName)
{
hr = pService->QueryStateVariable(VariableName, &Variant);
if(SUCCEEDED(hr))
{
if(V_VT(&Variant) == VT_BSTR)
{
*pString = V_BSTR(&Variant);
}
else
{
hr = E_UNEXPECTED;
}
}
if(FAILED(hr))
{
VariantClear(&Variant);
}
SysFreeString(VariableName);
}
else
{
hr = E_OUTOFMEMORY;
}
TraceHr (ttidError, FAL, hr, FALSE, "CSharedAccessConnection::GetStringStateVariable");
return hr;
}
HRESULT InvokeVoidAction(IUPnPService * pService, LPTSTR pszCommand, VARIANT* pOutParams)
{
HRESULT hr;
BSTR bstrActionName;
bstrActionName = SysAllocString(pszCommand);
if (NULL != bstrActionName)
{
SAFEARRAYBOUND rgsaBound[1];
SAFEARRAY * psa = NULL;
rgsaBound[0].lLbound = 0;
rgsaBound[0].cElements = 0;
psa = SafeArrayCreate(VT_VARIANT, 1, rgsaBound);
if (psa)
{
LONG lStatus;
VARIANT varInArgs;
VARIANT varReturnVal;
VariantInit(&varInArgs);
VariantInit(pOutParams);
VariantInit(&varReturnVal);
varInArgs.vt = VT_VARIANT | VT_ARRAY;
V_ARRAY(&varInArgs) = psa;
hr = pService->InvokeAction(bstrActionName,
varInArgs,
pOutParams,
&varReturnVal);
if(SUCCEEDED(hr))
{
VariantClear(&varReturnVal);
}
SafeArrayDestroy(psa);
}
else
{
hr = E_OUTOFMEMORY;
}
SysFreeString(bstrActionName);
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}