535 lines
18 KiB
C++
535 lines
18 KiB
C++
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
//
|
||
|
// Copyright (C) Microsoft Corporation, 1997 - 1999
|
||
|
//
|
||
|
// File: config.cpp
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
#include "pch.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
#include <shlwapi.h>
|
||
|
#include "config.h"
|
||
|
#include "util.h"
|
||
|
#include "strings.h"
|
||
|
|
||
|
//
|
||
|
// Determine if a character is a DBCS lead byte.
|
||
|
// If build is UNICODE, always returns false.
|
||
|
//
|
||
|
inline bool DBCSLEADBYTE(TCHAR ch)
|
||
|
{
|
||
|
if (sizeof(ch) == sizeof(char))
|
||
|
return boolify(IsDBCSLeadByte((BYTE)ch));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
LPCTSTR CConfig::s_rgpszSubkeys[] = { REGSTR_KEY_OFFLINEFILES,
|
||
|
REGSTR_KEY_OFFLINEFILESPOLICY };
|
||
|
|
||
|
LPCTSTR CConfig::s_rgpszValues[] = { REGSTR_VAL_DEFCACHESIZE,
|
||
|
REGSTR_VAL_CSCENABLED,
|
||
|
REGSTR_VAL_GOOFFLINEACTION,
|
||
|
REGSTR_VAL_NOCONFIGCACHE,
|
||
|
REGSTR_VAL_NOCACHEVIEWER,
|
||
|
REGSTR_VAL_NOMAKEAVAILABLEOFFLINE,
|
||
|
REGSTR_VAL_SYNCATLOGOFF,
|
||
|
REGSTR_VAL_SYNCATLOGON,
|
||
|
REGSTR_VAL_SYNCATSUSPEND,
|
||
|
REGSTR_VAL_NOREMINDERS,
|
||
|
REGSTR_VAL_REMINDERFREQMINUTES,
|
||
|
REGSTR_VAL_INITIALBALLOONTIMEOUTSECONDS,
|
||
|
REGSTR_VAL_REMINDERBALLOONTIMEOUTSECONDS,
|
||
|
REGSTR_VAL_EVENTLOGGINGLEVEL,
|
||
|
REGSTR_VAL_PURGEATLOGOFF,
|
||
|
REGSTR_VAL_PURGEONLYAUTOCACHEATLOGOFF,
|
||
|
REGSTR_VAL_FIRSTPINWIZARDSHOWN,
|
||
|
REGSTR_VAL_SLOWLINKSPEED,
|
||
|
REGSTR_VAL_ALWAYSPINSUBFOLDERS,
|
||
|
REGSTR_VAL_ENCRYPTCACHE,
|
||
|
REGSTR_VAL_NOFRADMINPIN
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Returns the single instance of the CConfig class.
|
||
|
// Note that by making the singleton instance a function static
|
||
|
// object it is not created until the first call to GetSingleton.
|
||
|
//
|
||
|
CConfig& CConfig::GetSingleton(
|
||
|
void
|
||
|
)
|
||
|
{
|
||
|
static CConfig TheConfig;
|
||
|
return TheConfig;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// This is the workhorse of the CSCUI policy code for scalar values.
|
||
|
// The caller passes in a value (iVAL_XXXXXX) identifier from the eValues
|
||
|
// enumeration to identify the policy/preference value of interest.
|
||
|
// Known keys in the registry are scanned until a value is found.
|
||
|
// The scanning order enforces the precedence of policy vs. default vs.
|
||
|
// preference and machine vs. user.
|
||
|
//
|
||
|
DWORD CConfig::GetValue(
|
||
|
eValues iValue,
|
||
|
bool *pbSetByPolicy
|
||
|
) const
|
||
|
{
|
||
|
//
|
||
|
// This table identifies each DWORD policy/preference item used by CSCUI.
|
||
|
// The entries MUST be ordered the same as the eValues enumeration.
|
||
|
// Each entry describes the possible sources for data and a default value
|
||
|
// to be used if no registry entries are present or if there's a problem reading
|
||
|
// the registry.
|
||
|
//
|
||
|
static const struct Item
|
||
|
{
|
||
|
DWORD fSrc; // Mask indicating the reg locations to read.
|
||
|
DWORD dwDefault; // Hard-coded default.
|
||
|
|
||
|
} rgItems[] = {
|
||
|
|
||
|
// Value ID eSRC_PREF_CU | eSRC_PREF_LM | eSRC_POL_CU | eSRC_POL_LM Default value
|
||
|
// -------------------------------------- ------------ ------------ ----------- ----------- -------------------
|
||
|
/* iVAL_DEFCACHESIZE */ { eSRC_POL_LM, 1000 },
|
||
|
/* iVAL_CSCENABLED */ { eSRC_POL_LM, 1 },
|
||
|
/* iVAL_GOOFFLINEACTION */ { eSRC_PREF_CU | eSRC_POL_CU | eSRC_POL_LM, eGoOfflineSilent },
|
||
|
/* iVAL_NOCONFIGCACHE */ { eSRC_POL_CU | eSRC_POL_LM, 0 },
|
||
|
/* iVAL_NOCACHEVIEWER */ { eSRC_POL_CU | eSRC_POL_LM, 0 },
|
||
|
/* iVAL_NOMAKEAVAILABLEOFFLINE */ { eSRC_POL_CU | eSRC_POL_LM, 0 },
|
||
|
/* iVAL_SYNCATLOGOFF */ { eSRC_PREF_CU | eSRC_POL_CU | eSRC_POL_LM, eSyncFull },
|
||
|
/* iVAL_SYNCATLOGON */ { eSRC_PREF_CU | eSRC_POL_CU | eSRC_POL_LM, eSyncNone },
|
||
|
/* iVAL_SYNCATSUSPEND */ { eSRC_POL_CU | eSRC_POL_LM, eSyncNone },
|
||
|
/* iVAL_NOREMINDERS */ { eSRC_PREF_CU | eSRC_POL_CU | eSRC_POL_LM, 0 },
|
||
|
/* iVAL_REMINDERFREQMINUTES */ { eSRC_PREF_CU | eSRC_POL_CU | eSRC_POL_LM, 60 },
|
||
|
/* iVAL_INITIALBALLOONTIMEOUTSECONDS */ { eSRC_POL_CU | eSRC_POL_LM, 30 },
|
||
|
/* iVAL_REMINDERBALLOONTIMEOUTSECONDS */ { eSRC_POL_CU | eSRC_POL_LM, 15 },
|
||
|
/* iVAL_EVENTLOGGINGLEVEL */ { eSRC_PREF_CU | eSRC_PREF_LM | eSRC_POL_CU | eSRC_POL_LM, 0 },
|
||
|
/* iVAL_PURGEATLOGOFF */ { eSRC_POL_LM, 0 },
|
||
|
/* iVAL_PURGEONLYAUTOCACHEATLOGOFF */ { eSRC_POL_LM, 0 },
|
||
|
/* iVAL_FIRSTPINWIZARDSHOWN */ { eSRC_PREF_CU , 0 },
|
||
|
/* iVAL_SLOWLINKSPEED */ { eSRC_PREF_CU | eSRC_PREF_LM | eSRC_POL_CU | eSRC_POL_LM, 640 },
|
||
|
/* iVAL_ALWAYSPINSUBFOLDERS */ { eSRC_POL_LM, 0 },
|
||
|
/* iVAL_ENCRYPTCACHE */ { eSRC_PREF_LM | eSRC_POL_LM, 0 },
|
||
|
/* iVAL_NOFRADMINPIN */ { eSRC_POL_CU | eSRC_POL_LM, 0 }
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// This table maps registry keys and subkey names to our
|
||
|
// source mask values. The array is ordered with the highest
|
||
|
// precedence sources first. A policy level mask is also
|
||
|
// associated with each entry so that we honor the "big switch"
|
||
|
// for enabling/disabling CSCUI policies.
|
||
|
//
|
||
|
static const struct Source
|
||
|
{
|
||
|
eSources fSrc; // Source for reg data.
|
||
|
HKEY hkeyRoot; // Root key in registry (hkcu, hklm).
|
||
|
eSubkeys iSubkey; // Index into s_rgpszSubkeys[]
|
||
|
|
||
|
} rgSrcs[] = { { eSRC_POL_LM, HKEY_LOCAL_MACHINE, iSUBKEY_POL },
|
||
|
{ eSRC_POL_CU, HKEY_CURRENT_USER, iSUBKEY_POL },
|
||
|
{ eSRC_PREF_CU, HKEY_CURRENT_USER, iSUBKEY_PREF },
|
||
|
{ eSRC_PREF_LM, HKEY_LOCAL_MACHINE, iSUBKEY_PREF }
|
||
|
};
|
||
|
|
||
|
|
||
|
const Item& item = rgItems[iValue];
|
||
|
DWORD dwResult = item.dwDefault; // Set default return value.
|
||
|
bool bSetByPolicy = false;
|
||
|
|
||
|
//
|
||
|
// Iterate over all of the sources until we find one that is specified
|
||
|
// for this item. For each iteration, if we're able to read the value,
|
||
|
// that's the one we return. If not we drop down to the next source
|
||
|
// in the precedence order (rgSrcs[]) and try to read it's value. If
|
||
|
// we've tried all of the sources without a successful read we return the
|
||
|
// hard-coded default.
|
||
|
//
|
||
|
for (int i = 0; i < ARRAYSIZE(rgSrcs); i++)
|
||
|
{
|
||
|
const Source& src = rgSrcs[i];
|
||
|
|
||
|
//
|
||
|
// Is this source valid for this item?
|
||
|
//
|
||
|
if (0 != (src.fSrc & item.fSrc))
|
||
|
{
|
||
|
//
|
||
|
// This source is valid for this item. Read it.
|
||
|
//
|
||
|
DWORD cbResult = sizeof(dwResult);
|
||
|
DWORD dwType;
|
||
|
|
||
|
if (ERROR_SUCCESS == SHGetValue(src.hkeyRoot,
|
||
|
s_rgpszSubkeys[src.iSubkey],
|
||
|
s_rgpszValues[iValue],
|
||
|
&dwType,
|
||
|
&dwResult,
|
||
|
&cbResult))
|
||
|
{
|
||
|
//
|
||
|
// We read a value from the registry so we're done.
|
||
|
//
|
||
|
bSetByPolicy = (0 != (eSRC_POL & src.fSrc));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (NULL != pbSetByPolicy)
|
||
|
*pbSetByPolicy = bSetByPolicy;
|
||
|
|
||
|
return dwResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Save a custom GoOfflineAction list to the registry.
|
||
|
// See comments for LoadCustomGoOfflineActions for formatting details.
|
||
|
//
|
||
|
HRESULT
|
||
|
CConfig::SaveCustomGoOfflineActions(
|
||
|
RegKey& key,
|
||
|
HDPA hdpaGOA
|
||
|
)
|
||
|
{
|
||
|
if (NULL == hdpaGOA)
|
||
|
{
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
HRESULT hr = NOERROR;
|
||
|
int cValuesNotDeleted = 0;
|
||
|
key.DeleteAllValues(&cValuesNotDeleted);
|
||
|
if (0 != cValuesNotDeleted)
|
||
|
{
|
||
|
Trace((TEXT("%d GoOfflineAction values not deleted from registry"),
|
||
|
cValuesNotDeleted));
|
||
|
}
|
||
|
|
||
|
TCHAR szServer[MAX_PATH];
|
||
|
TCHAR szAction[20];
|
||
|
const int cGOA = DPA_GetPtrCount(hdpaGOA);
|
||
|
for (int i = 0; i < cGOA; i++)
|
||
|
{
|
||
|
//
|
||
|
// Write each sharename-action pair to the registry.
|
||
|
// The action value must be converted to ASCII to be
|
||
|
// compatible with the values generated by poledit.
|
||
|
//
|
||
|
CustomGOA *pGOA = (CustomGOA *)DPA_GetPtr(hdpaGOA, i);
|
||
|
wnsprintf(szAction, ARRAYSIZE(szAction), TEXT("%d"), DWORD(pGOA->GetAction()));
|
||
|
pGOA->GetServerName(szServer, ARRAYSIZE(szServer));
|
||
|
hr = key.SetValue(szServer, szAction);
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
Trace((TEXT("Error 0x%08X saving GoOfflineAction for \"%s\" to registry."),
|
||
|
hr, szServer));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
bool
|
||
|
CConfig::CustomGOAExists(
|
||
|
HDPA hdpaGOA,
|
||
|
const CustomGOA& goa
|
||
|
)
|
||
|
{
|
||
|
if (NULL != hdpaGOA)
|
||
|
{
|
||
|
const int cEntries = DPA_GetPtrCount(hdpaGOA);
|
||
|
for (int i = 0; i < cEntries; i++)
|
||
|
{
|
||
|
CustomGOA *pGOA = (CustomGOA *)DPA_GetPtr(hdpaGOA, i);
|
||
|
if (NULL != pGOA)
|
||
|
{
|
||
|
if (0 == goa.CompareByServer(*pGOA))
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Builds an array of Go-offline actions.
|
||
|
// Each entry is a server-action pair.
|
||
|
//
|
||
|
void
|
||
|
CConfig::GetCustomGoOfflineActions(
|
||
|
HDPA hdpa,
|
||
|
bool *pbSetByPolicy // optional. Can be NULL.
|
||
|
)
|
||
|
{
|
||
|
TraceAssert(NULL != hdpa);
|
||
|
|
||
|
static const struct Source
|
||
|
{
|
||
|
eSources fSrc; // Source for reg data.
|
||
|
HKEY hkeyRoot; // Root key in registry (hkcu, hklm).
|
||
|
eSubkeys iSubkey; // Index into s_rgpszSubkeys[]
|
||
|
|
||
|
} rgSrcs[] = { { eSRC_POL_LM, HKEY_LOCAL_MACHINE, iSUBKEY_POL },
|
||
|
{ eSRC_POL_CU, HKEY_CURRENT_USER, iSUBKEY_POL },
|
||
|
{ eSRC_PREF_CU, HKEY_CURRENT_USER, iSUBKEY_PREF }
|
||
|
};
|
||
|
|
||
|
ClearCustomGoOfflineActions(hdpa);
|
||
|
|
||
|
TCHAR szName[MAX_PATH];
|
||
|
HRESULT hr;
|
||
|
bool bSetByPolicyAny = false;
|
||
|
bool bSetByPolicy = false;
|
||
|
|
||
|
//
|
||
|
// Iterate over all of the possible sources.
|
||
|
//
|
||
|
for (int i = 0; i < ARRAYSIZE(rgSrcs); i++)
|
||
|
{
|
||
|
const Source& src = rgSrcs[i];
|
||
|
RegKey key(src.hkeyRoot, s_rgpszSubkeys[src.iSubkey]);
|
||
|
|
||
|
if (SUCCEEDED(key.Open(KEY_READ)))
|
||
|
{
|
||
|
RegKey keyGOA(key, REGSTR_SUBKEY_CUSTOMGOOFFLINEACTIONS);
|
||
|
if (SUCCEEDED(keyGOA.Open(KEY_READ)))
|
||
|
{
|
||
|
TCHAR szValue[20];
|
||
|
DWORD dwType;
|
||
|
DWORD cbValue = sizeof(szValue);
|
||
|
RegKey::ValueIterator iter = keyGOA.CreateValueIterator();
|
||
|
|
||
|
|
||
|
while(S_OK == (hr = iter.Next(szName, ARRAYSIZE(szName), &dwType, (LPBYTE)szValue, &cbValue)))
|
||
|
{
|
||
|
if (REG_SZ == dwType)
|
||
|
{
|
||
|
//
|
||
|
// Convert from "0","1","2" to 0,1,2
|
||
|
//
|
||
|
DWORD dwValue = szValue[0] - TEXT('0');
|
||
|
if (IsValidGoOfflineAction(dwValue))
|
||
|
{
|
||
|
//
|
||
|
// Only add if value is of proper type and value.
|
||
|
// Protects against someone manually adding garbage
|
||
|
// to the registry.
|
||
|
//
|
||
|
// Server names can also be entered into the registry
|
||
|
// using poledit (and winnt.adm). This entry mechanism
|
||
|
// can't validate format so we need to ensure the entry
|
||
|
// doesn't have leading '\' or space characters.
|
||
|
//
|
||
|
LPCTSTR pszServer = szName;
|
||
|
while(*pszServer && (TEXT('\\') == *pszServer || TEXT(' ') == *pszServer))
|
||
|
pszServer++;
|
||
|
|
||
|
bSetByPolicy = (0 != (src.fSrc & eSRC_POL));
|
||
|
bSetByPolicyAny = bSetByPolicyAny || bSetByPolicy;
|
||
|
CustomGOA *pGOA = new CustomGOA(pszServer,
|
||
|
(CConfig::OfflineAction)dwValue,
|
||
|
bSetByPolicy);
|
||
|
if (NULL != pGOA)
|
||
|
{
|
||
|
if (CustomGOAExists(hdpa, *pGOA) || -1 == DPA_AppendPtr(hdpa, pGOA))
|
||
|
{
|
||
|
delete pGOA;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Trace((TEXT("GoOfflineAction value %d invalid for \"%s\""),
|
||
|
dwValue, szName));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Trace((TEXT("GoOfflineAction for \"%s\" has invalid reg type %d"),
|
||
|
szName, dwType));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (NULL != pbSetByPolicy)
|
||
|
*pbSetByPolicy = bSetByPolicyAny;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Delete all CustomGOA blocks attached to a DPA.
|
||
|
// When complete, the DPA is empty.
|
||
|
//
|
||
|
void
|
||
|
CConfig::ClearCustomGoOfflineActions( // [static]
|
||
|
HDPA hdpaGOA
|
||
|
)
|
||
|
{
|
||
|
if (NULL != hdpaGOA)
|
||
|
{
|
||
|
const int cEntries = DPA_GetPtrCount(hdpaGOA);
|
||
|
for (int i = cEntries - 1; 0 <= i; i--)
|
||
|
{
|
||
|
CustomGOA *pGOA = (CustomGOA *)DPA_GetPtr(hdpaGOA, i);
|
||
|
delete pGOA;
|
||
|
DPA_DeletePtr(hdpaGOA, i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// Retrieve the go-offline action for a specific server. If the server
|
||
|
// has a "customized" action defined by either system policy or user
|
||
|
// setting, that action is used. Otherwise, the "default" action is
|
||
|
// used.
|
||
|
//
|
||
|
int
|
||
|
CConfig::GoOfflineAction(
|
||
|
LPCTSTR pszServer
|
||
|
) const
|
||
|
{
|
||
|
int iAction = GoOfflineAction(); // Get default action.
|
||
|
|
||
|
if (NULL == pszServer)
|
||
|
return iAction;
|
||
|
|
||
|
TraceAssert(NULL != pszServer);
|
||
|
|
||
|
//
|
||
|
// Skip passed any leading backslashes for comparison.
|
||
|
// The values we store in the registry don't have a leading "\\".
|
||
|
//
|
||
|
while(*pszServer && TEXT('\\') == *pszServer)
|
||
|
pszServer++;
|
||
|
|
||
|
HRESULT hr;
|
||
|
CConfig::OfflineActionInfo info;
|
||
|
CConfig::OfflineActionIter iter = CreateOfflineActionIter();
|
||
|
while(S_OK == (hr = iter.Next(&info)))
|
||
|
{
|
||
|
if (0 == lstrcmpi(pszServer, info.szServer))
|
||
|
{
|
||
|
iAction = info.iAction; // Return custom action.
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// Guard against bogus reg data.
|
||
|
//
|
||
|
if (eNumOfflineActions <= iAction || 0 > iAction)
|
||
|
iAction = eGoOfflineSilent;
|
||
|
|
||
|
return iAction;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// CConfig::CustomGOA
|
||
|
// "GOA" is "Go Offline Action"
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool
|
||
|
CConfig::CustomGOA::operator < (
|
||
|
const CustomGOA& rhs
|
||
|
) const
|
||
|
{
|
||
|
int diff = CompareByServer(rhs);
|
||
|
if (0 == diff)
|
||
|
diff = m_action - rhs.m_action;
|
||
|
|
||
|
return diff < 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Compare two CustomGoOfflineAction objects by their
|
||
|
// server names. Comparison is case-insensitive.
|
||
|
// Returns: <0 = *this < rhs
|
||
|
// 0 = *this == rhs
|
||
|
// >0 = *this > rhs
|
||
|
//
|
||
|
int
|
||
|
CConfig::CustomGOA::CompareByServer(
|
||
|
const CustomGOA& rhs
|
||
|
) const
|
||
|
{
|
||
|
return lstrcmpi(GetServerName(), rhs.GetServerName());
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// CConfig::OfflineActionIter
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CConfig::OfflineActionIter::OfflineActionIter(
|
||
|
const CConfig *pConfig
|
||
|
) : m_pConfig(const_cast<CConfig *>(pConfig)),
|
||
|
m_iAction(-1),
|
||
|
m_hdpaGOA(DPA_Create(4))
|
||
|
{
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
CConfig::OfflineActionIter::~OfflineActionIter(
|
||
|
void
|
||
|
)
|
||
|
{
|
||
|
if (NULL != m_hdpaGOA)
|
||
|
{
|
||
|
CConfig::ClearCustomGoOfflineActions(m_hdpaGOA);
|
||
|
DPA_Destroy(m_hdpaGOA);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
CConfig::OfflineActionIter::Next(
|
||
|
OfflineActionInfo *pInfo
|
||
|
)
|
||
|
{
|
||
|
if (NULL == m_hdpaGOA)
|
||
|
{
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
HRESULT hr = S_FALSE;
|
||
|
|
||
|
if (-1 == m_iAction)
|
||
|
{
|
||
|
m_pConfig->GetCustomGoOfflineActions(m_hdpaGOA);
|
||
|
m_iAction = 0;
|
||
|
}
|
||
|
if (m_iAction < DSA_GetItemCount(m_hdpaGOA))
|
||
|
{
|
||
|
CustomGOA *pGOA = (CustomGOA *)DPA_GetPtr(m_hdpaGOA, m_iAction);
|
||
|
if (NULL != pGOA)
|
||
|
{
|
||
|
lstrcpyn(pInfo->szServer, pGOA->GetServerName(), ARRAYSIZE(pInfo->szServer));
|
||
|
pInfo->iAction = (DWORD)pGOA->GetAction();
|
||
|
m_iAction++;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|