windows-nt/Source/XPSP1/NT/termsrv/sessdir/tssdsql/tssdsql.cpp
2020-09-26 16:20:57 +08:00

2381 lines
80 KiB
C++

/****************************************************************************/
// tssdsql.cpp
//
// Terminal Server Session Directory Interface common component code.
//
// Copyright (C) 2000 Microsoft Corporation
/****************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <process.h>
#include <ole2.h>
#include <objbase.h>
#include <comdef.h>
#include <adoid.h>
#include <adoint.h>
#include <regapi.h>
#include "tssdsql.h"
#include "trace.h"
#include "resource.h"
/****************************************************************************/
// Types
/****************************************************************************/
// Shortcut VARIANT class to handle cleanup on destruction and common code
// inlining.
class CVar : public VARIANT
{
public:
CVar() { VariantInit(this); }
CVar(VARTYPE vt, SCODE scode = 0) {
VariantInit(this);
this->vt = vt;
this->scode = scode;
}
CVar(VARIANT var) { *this = var; }
~CVar() { VariantClear(this); }
void InitNull() { this->vt = VT_NULL; }
void InitFromLong(long L) { this->vt = VT_I4; this->lVal = L; }
void InitNoParam() {
this->vt = VT_ERROR;
this->lVal = DISP_E_PARAMNOTFOUND;
}
HRESULT InitFromWSTR(PCWSTR WStr) {
this->bstrVal = SysAllocString(WStr);
if (this->bstrVal != NULL) {
this->vt = VT_BSTR;
return S_OK;
}
else {
return E_OUTOFMEMORY;
}
}
// Inits from a non-NULL-terminated set of WCHARs.
HRESULT InitFromWChars(WCHAR *WChars, unsigned Len) {
this->bstrVal = SysAllocStringLen(WChars, Len);
if (this->bstrVal != NULL) {
this->vt = VT_BSTR;
return S_OK;
}
else {
return E_OUTOFMEMORY;
}
}
HRESULT InitEmptyBSTR(unsigned Size) {
this->bstrVal = SysAllocStringLen(L"", Size);
if (this->bstrVal != NULL) {
this->vt = VT_BSTR;
return S_OK;
}
else {
return E_OUTOFMEMORY;
}
}
HRESULT Clear() { return VariantClear(this); }
};
/****************************************************************************/
// Prototypes
/****************************************************************************/
INT_PTR CALLBACK CustomUIDlg(HWND, UINT, WPARAM, LPARAM);
void FindSqlValue(LPTSTR, LPTSTR, LPTSTR);
LPTSTR ModifySqlValue( LPTSTR * , LPTSTR , LPTSTR );
LPTSTR FindField( LPTSTR pszString , LPTSTR pszKeyName );
VOID strtrim( TCHAR **pszStr);
/****************************************************************************/
// Globals
/****************************************************************************/
extern HINSTANCE g_hInstance;
// The COM object counter (declared in server.cpp)
extern long g_lObjects;
/****************************************************************************/
// CTSSessionDirectory::CTSSessionDirectory
// CTSSessionDirectory::~CTSSessionDirectory
//
// Constructor and destructor
/****************************************************************************/
CTSSessionDirectory::CTSSessionDirectory() :
m_RefCount(0), m_pConnection(NULL)
{
InterlockedIncrement(&g_lObjects);
m_LocalServerAddress[0] = L'\0';
m_DBConnectStr = NULL;
m_DBPwdStr = NULL;
m_DBUserStr = NULL;
m_fEnabled = 0;
m_pszOpaqueString = NULL;
}
CTSSessionDirectory::~CTSSessionDirectory()
{
HRESULT hr;
// If the database connection exists, release it.
if (m_pConnection != NULL) {
hr = ExecServerOffline();
if (FAILED(hr)) {
ERR((TB,"Destr: ExecSvrOffline failed, hr=0x%X", hr));
}
hr = m_pConnection->Close();
if (FAILED(hr)) {
ERR((TB,"pConn->Close() failed, hr=0x%X", hr));
}
m_pConnection->Release();
m_pConnection = NULL;
}
// Decrement the global COM object counter
InterlockedDecrement(&g_lObjects);
if (m_DBConnectStr != NULL)
SysFreeString(m_DBConnectStr);
if (m_DBPwdStr != NULL)
SysFreeString(m_DBPwdStr);
if (m_DBUserStr != NULL)
SysFreeString(m_DBUserStr);
}
/****************************************************************************/
// CTSSessionDirectory::QueryInterface
//
// Standard COM IUnknown function.
/****************************************************************************/
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::QueryInterface(
REFIID riid,
void **ppv)
{
if (riid == IID_IUnknown) {
*ppv = (LPVOID)(IUnknown *)(ITSSessionDirectory *)this;
}
else if (riid == IID_ITSSessionDirectory) {
*ppv = (LPVOID)(ITSSessionDirectory *)this;
}
else if (riid == IID_IExtendServerSettings) {
*ppv = (LPVOID)(IExtendServerSettings *)this;
}
else {
ERR((TB,"QI: Unknown interface"));
return E_NOINTERFACE;
}
((IUnknown *)*ppv)->AddRef();
return S_OK;
}
/****************************************************************************/
// CTSSessionDirectory::AddRef
//
// Standard COM IUnknown function.
/****************************************************************************/
ULONG STDMETHODCALLTYPE CTSSessionDirectory::AddRef()
{
return InterlockedIncrement(&m_RefCount);
}
/****************************************************************************/
// CTSSessionDirectory::Release
//
// Standard COM IUnknown function.
/****************************************************************************/
ULONG STDMETHODCALLTYPE CTSSessionDirectory::Release()
{
long lRef = InterlockedDecrement(&m_RefCount);
if (lRef == 0)
delete this;
return lRef;
}
/****************************************************************************/
// CTSSessionDirectory::Initialize
//
// ITSSessionDirectory function. Called soon after object instantiation to
// intiialize the directory. LocalServerAddress provides a text representation
// of the local server's load balance IP address. This information should be
// used as the server IP address in the session directory for client
// redirection by other pool servers to this server. StoreServerName,
// ClusterName, and OpaqueSettings are generic reg entries known to TermSrv
// which cover config info across any type of session directory
// implementation. The contents of these strings are designed to be parsed
// by the session directory providers.
/****************************************************************************/
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::Initialize(
LPWSTR LocalServerAddress,
LPWSTR StoreServerName,
LPWSTR ClusterName,
LPWSTR OpaqueSettings,
DWORD Flags,
DWORD (*repopfn)())
{
HRESULT hr = S_OK;
unsigned Len;
WCHAR *pSearch;
WCHAR ConnectString[384];
ASSERT((LocalServerAddress != NULL),(TB,"Init: LocalServerAddr null!"));
ASSERT((StoreServerName != NULL),(TB,"Init: StoreServerName null!"));
ASSERT((ClusterName != NULL),(TB,"Init: ClusterName null!"));
ASSERT((OpaqueSettings != NULL),(TB,"Init: OpaqueSettings null!"));
// Copy off the server address and cluster name for later use.
wcsncpy(m_LocalServerAddress, LocalServerAddress,
sizeof(m_LocalServerAddress) / sizeof(WCHAR) - 1);
m_LocalServerAddress[sizeof(m_LocalServerAddress) / sizeof(WCHAR) - 1] =
L'\0';
wcsncpy(m_ClusterName, ClusterName,
sizeof(m_ClusterName) / sizeof(WCHAR) - 1);
m_ClusterName[sizeof(m_ClusterName) / sizeof(WCHAR) - 1] = L'\0';
// Create the SQL connect string using the OpaqueSettings string
// (which should contain some of the conn str including SQL security
// username and password, sub-table names, provider type, etc.).
// We add onto the end a semicolon (if not already present) and the
// data source (from StoreServerName), if the "data source" substring
// is not already in the connect string.
pSearch = OpaqueSettings;
while (*pSearch != L'\0') {
if (*pSearch == L'D' || *pSearch == L'd') {
if (!_wcsnicmp(pSearch, L"data source", wcslen(L"data source"))) {
// Transfer the OpaqueSettings string as a whole to become
// the connect str.
wcscpy(ConnectString, OpaqueSettings);
goto PostConnStrSetup;
}
}
pSearch++;
}
Len = wcslen(OpaqueSettings);
if (Len == 0 || OpaqueSettings[Len - 1] == L';')
wsprintfW(ConnectString, L"%sData Source=%s", OpaqueSettings,
StoreServerName);
else
wsprintfW(ConnectString, L"%s;Data Source=%s", OpaqueSettings,
StoreServerName);
PostConnStrSetup:
TRC1((TB,"Initialize: Svr addr=%S, StoreSvrName=%S, ClusterName=%S, "
"OpaqueSettings=%S, final connstr=%S",
m_LocalServerAddress, StoreServerName, m_ClusterName,
OpaqueSettings, ConnectString));
// Alloc the BSTRs for the connection strings.
m_DBConnectStr = SysAllocString(ConnectString);
if (m_DBConnectStr != NULL) {
m_DBUserStr = SysAllocString(L"");
if (m_DBUserStr != NULL) {
m_DBPwdStr = SysAllocString(L"");
if (m_DBPwdStr == NULL) {
ERR((TB,"Failed alloc bstr for pwdstr"));
goto ExitFunc;
}
}
else {
ERR((TB,"Failed alloc bstr for userstr"));
goto ExitFunc;
}
}
else {
ERR((TB,"Failed alloc bstr for connstr"));
goto ExitFunc;
}
// Create an ADO connection instance and connect.
hr = CoCreateInstance(CLSID_CADOConnection, NULL,
CLSCTX_INPROC_SERVER, IID_IADOConnection,
(LPVOID *)&m_pConnection);
if (SUCCEEDED(hr)) {
// Set the connection timeout to only 8 seconds. Standard is 15
// but we don't want to be holding up TermSrv's initialization.
m_pConnection->put_ConnectionTimeout(8);
// Do the open.
hr = OpenConnection();
if (SUCCEEDED(hr)) {
// Signal the server is online.
hr = ExecServerOnline();
}
else {
m_pConnection->Release();
m_pConnection = NULL;
}
}
else {
ERR((TB,"CoCreate(ADOConn) returned 0x%X", hr));
}
ExitFunc:
return hr;
}
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::Update(
LPWSTR LocalServerAddress,
LPWSTR StoreServerName,
LPWSTR ClusterName,
LPWSTR OpaqueSettings,
DWORD Flags)
{
return E_NOTIMPL;
}
/****************************************************************************/
// CTSSessionDirectory::OpenConnection
//
// Opens the connection to the SQL server based on the pre-existing
// connect string and allocated connection. This is called at init time,
// plus whenever the database connection times out and gets closed, but is
// still required
/****************************************************************************/
HRESULT CTSSessionDirectory::OpenConnection()
{
HRESULT hr;
ASSERT((m_pConnection != NULL),(TB,"OpenConn: NULL pconn"));
ASSERT((m_DBConnectStr != NULL),(TB,"OpenConn: NULL connstr"));
ASSERT((m_DBUserStr != NULL),(TB,"OpenConn: NULL userstr"));
ASSERT((m_DBPwdStr != NULL),(TB,"OpenConn: NULL pwdstr"));
hr = m_pConnection->Open(m_DBConnectStr, m_DBUserStr, m_DBPwdStr,
adOpenUnspecified);
if (FAILED(hr)) {
ERR((TB,"OpenConn: Failed open DB, connstring=%S, hr=0x%X",
m_DBConnectStr, hr));
}
return hr;
}
/****************************************************************************/
// GetRowArrayStringField
//
// Retrieves a WSTR from a specified row and field of the given SafeArray.
// Returns failure if the target field is not a string. MaxOutStr is max
// WCHARs not including NULL.
/****************************************************************************/
HRESULT GetRowArrayStringField(
SAFEARRAY *pSA,
unsigned RowIndex,
unsigned FieldIndex,
WCHAR *OutStr,
unsigned MaxOutStr)
{
HRESULT hr;
CVar varField;
long DimIndices[2];
DimIndices[0] = FieldIndex;
DimIndices[1] = RowIndex;
SafeArrayGetElement(pSA, DimIndices, &varField);
if (varField.vt == VT_BSTR) {
wcsncpy(OutStr, varField.bstrVal, MaxOutStr);
hr = S_OK;
}
else if (varField.vt == VT_NULL) {
OutStr[0] = L'\0';
hr = S_OK;
}
else {
ERR((TB,"GetRowStrField: Row %u Col %u value %d is not a string",
RowIndex, FieldIndex, varField.vt));
hr = E_FAIL;
}
return hr;
}
/****************************************************************************/
// GetRowArrayDWORDField
//
// Retrieves a DWORD from a specified row and field of the given SafeArray.
// Returns failure if the target field is not a 4-byte integer.
/****************************************************************************/
HRESULT GetRowArrayDWORDField(
SAFEARRAY *pSA,
unsigned RowIndex,
unsigned FieldIndex,
DWORD *pOutValue)
{
HRESULT hr;
CVar varField;
long DimIndices[2];
DimIndices[0] = FieldIndex;
DimIndices[1] = RowIndex;
SafeArrayGetElement(pSA, DimIndices, &varField);
if (varField.vt == VT_I4) {
*pOutValue = (DWORD)varField.lVal;
hr = S_OK;
}
else if (varField.vt == VT_NULL) {
*pOutValue = 0;
hr = S_OK;
}
else {
ERR((TB,"GetRowDWField: Row %u Col %u value %d is not a VT_I4",
RowIndex, FieldIndex, varField.vt));
hr = E_FAIL;
}
return hr;
}
/****************************************************************************/
// CTSSessionDirectory::GetUserDisconnectedSessions
//
// Called to perform a query against the session directory, to provide the
// list of disconnected sessions for the provided username and domain.
// Returns zero or more TSSD_DisconnectedSessionInfo blocks in SessionBuf.
// *pNumSessionsReturned receives the number of blocks.
/****************************************************************************/
#define NumOutputFields 11
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::GetUserDisconnectedSessions(
LPWSTR UserName,
LPWSTR Domain,
DWORD __RPC_FAR *pNumSessionsReturned,
TSSD_DisconnectedSessionInfo __RPC_FAR SessionBuf[
TSSD_MaxDisconnectedSessions])
{
DWORD NumSessions = 0;
long State;
long NumRecords;
HRESULT hr;
unsigned i, j;
unsigned NumFailed;
TSSD_DisconnectedSessionInfo *pInfo;
ADOCommand *pCommand;
ADOParameters *pParameters;
ADORecordset *pResultRecordSet;
ADOFields *pFields;
CVar varRows;
CVar varFields;
CVar varStart;
HRESULT hrFields[NumOutputFields];
TRC2((TB,"GetUserDisconnectedSessions"));
ASSERT((pNumSessionsReturned != NULL),(TB,"NULL pNumSess"));
ASSERT((SessionBuf != NULL),(TB,"NULL SessionBuf"));
hr = CreateADOStoredProcCommand(L"SP_TSSDGetUserDisconnectedSessions",
&pCommand, &pParameters);
if (SUCCEEDED(hr)) {
hr = AddADOInputStringParam(UserName, L"UserName", pCommand,
pParameters, FALSE);
if (SUCCEEDED(hr)) {
hr = AddADOInputStringParam(Domain, L"Domain", pCommand,
pParameters, FALSE);
if (SUCCEEDED(hr)) {
hr = AddADOInputDWORDParam(m_ClusterID, L"ClusterID",
pCommand, pParameters);
if (SUCCEEDED(hr)) {
// Execute the command.
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc,
&pResultRecordSet);
if (FAILED(hr)) {
// If we've not used the connection for awhile, it
// might have been disconnected and the connection
// object will be invalid. Attempt a reopen then
// reissue the command.
TRC2((TB,"GetUserDisc: Failed cmd, hr=0x%X, retrying",
hr));
m_pConnection->Close();
hr = OpenConnection();
if (SUCCEEDED(hr)) {
hr = pCommand->Execute(NULL, NULL,
adCmdStoredProc, &pResultRecordSet);
if (FAILED(hr)) {
ERR((TB,"GetUserDisc: Failed cmd, hr=0x%X",
hr));
}
}
else {
ERR((TB,"GetUserDisc: Failed reopen conn, hr=0x%X",
hr));
}
}
}
else {
ERR((TB,"GetUserDisc: Failed add cluster, hr=0x%X", hr));
}
}
else {
ERR((TB,"GetUserDisc: Failed add sessid, hr=0x%X", hr));
}
}
else {
ERR((TB,"GetUserDisc: Failed add svraddr, hr=0x%X", hr));
}
pParameters->Release();
pCommand->Release();
}
else {
ERR((TB,"GetUserDisc: Failed create cmd, hr=0x%X", hr));
}
// At this point we have a result recordset containing the server rows
// corresponding to all of the disconnected sessions.
if (SUCCEEDED(hr)) {
long State;
NumSessions = 0;
hr = pResultRecordSet->get_State(&State);
if (SUCCEEDED(hr)) {
if (!(State & adStateClosed)) {
VARIANT_BOOL VB;
// If EOF the recordset is empty.
hr = pResultRecordSet->get_EOF(&VB);
if (SUCCEEDED(hr)) {
if (VB) {
TRC1((TB,"GetUserDisc: Result recordset EOF, 0 rows"));
goto PostUnpackResultSet;
}
}
else {
ERR((TB,"GetUserDisc: Failed get_EOF, hr=0x%X", hr));
goto PostUnpackResultSet;
}
}
else {
ERR((TB,"GetUserDisc: Closed result recordset"));
goto PostUnpackResultSet;
}
}
else {
ERR((TB,"GetUserDisc: get_State failed, hr=0x%X", hr));
goto PostUnpackResultSet;
}
// Grab the result data into a safearray, starting with the default
// current row and all fields.
varStart.InitNoParam();
varFields.InitNoParam();
hr = pResultRecordSet->GetRows(TSSD_MaxDisconnectedSessions, varStart,
varFields, &varRows);
if (SUCCEEDED(hr)) {
NumRecords = 0;
hr = SafeArrayGetUBound(varRows.parray, 2, &NumRecords);
if (SUCCEEDED(hr)) {
// 0-based array bound was returned, num rows is that + 1.
NumRecords++;
ASSERT((NumRecords <= TSSD_MaxDisconnectedSessions),
(TB,"GetUserDisc: NumRecords %u greater than expected %u",
NumRecords, TSSD_MaxDisconnectedSessions));
TRC1((TB,"%d rows retrieved from safearray", NumRecords));
}
else {
ERR((TB,"GetUserDisc: Failed safearray getubound, hr=0x%X", hr));
goto PostUnpackResultSet;
}
}
else {
ERR((TB,"GetUserDisc: Failed to get rows, hr=0x%X", hr));
goto PostUnpackResultSet;
}
// Loop through and get the contents of each row, translating into
// the output DiscSession structs.
pInfo = SessionBuf;
for (i = 0; i < (unsigned)NumRecords; i++) {
// Stack up the hr's for each field before checking them all.
hrFields[0] = GetRowArrayStringField(varRows.parray, i, 0,
pInfo->ServerAddress, sizeof(pInfo->ServerAddress) /
sizeof(TCHAR) - 1);
hrFields[1] = GetRowArrayDWORDField(varRows.parray, i, 1,
&pInfo->SessionID);
hrFields[2] = GetRowArrayDWORDField(varRows.parray, i, 2,
&pInfo->TSProtocol);
hrFields[3] = GetRowArrayStringField(varRows.parray, i, 7,
pInfo->ApplicationType, sizeof(pInfo->ApplicationType) /
sizeof(TCHAR) - 1);
hrFields[4] = GetRowArrayDWORDField(varRows.parray, i, 8,
&pInfo->ResolutionWidth);
hrFields[5] = GetRowArrayDWORDField(varRows.parray, i, 9,
&pInfo->ResolutionHeight);
hrFields[6] = GetRowArrayDWORDField(varRows.parray, i, 10,
&pInfo->ColorDepth);
hrFields[7] = GetRowArrayDWORDField(varRows.parray, i, 3,
&pInfo->CreateTime.dwLowDateTime);
hrFields[8] = GetRowArrayDWORDField(varRows.parray, i, 4,
&pInfo->CreateTime.dwHighDateTime);
hrFields[9] = GetRowArrayDWORDField(varRows.parray, i, 5,
&pInfo->DisconnectionTime.dwLowDateTime);
hrFields[10] = GetRowArrayDWORDField(varRows.parray, i, 6,
&pInfo->DisconnectionTime.dwHighDateTime);
NumFailed = 0;
for (j = 0; j < NumOutputFields; j++) {
if (SUCCEEDED(hrFields[j])) {
continue;
}
else {
ERR((TB,"GetUserDisc: Row %u field %u returned hr=0x%X",
i, j, hrFields[j]));
NumFailed++;
}
}
if (!NumFailed) {
NumSessions++;
pInfo++;
}
}
PostUnpackResultSet:
pResultRecordSet->Release();
}
else {
ERR((TB,"GetUserDisc: Failed exec, hr=0x%X", hr));
}
*pNumSessionsReturned = NumSessions;
return hr;
}
/****************************************************************************/
// CTSSessionDirectory::NotifyCreateLocalSession
//
// ITSSessionDirectory function. Called when a session is created to add the
// session to the session directory. Note that other interface functions
// access the session directory by either the username/domain or the
// session ID; the directory schema should take this into account for
// performance optimization.
/****************************************************************************/
#define NumCreateParams 11
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::NotifyCreateLocalSession(
TSSD_CreateSessionInfo __RPC_FAR *pCreateInfo)
{
unsigned i, NumFailed;
HRESULT hr;
HRESULT hrParam[NumCreateParams];
ADOCommand *pCommand;
ADOParameters *pParameters;
ADORecordset *pResultRecordSet;
TRC2((TB,"NotifyCreateLocalSession, SessID=%u", pCreateInfo->SessionID));
ASSERT((pCreateInfo != NULL),(TB,"NotifyCreate: NULL CreateInfo"));
hr = CreateADOStoredProcCommand(L"SP_TSSDCreateSession", &pCommand,
&pParameters);
if (SUCCEEDED(hr)) {
// Create and add the params in one fell swoop. We'll check all
// of the return values in a batch later.
hrParam[0] = AddADOInputStringParam(pCreateInfo->UserName,
L"UserName", pCommand, pParameters, FALSE);
hrParam[1] = AddADOInputStringParam(pCreateInfo->Domain,
L"Domain", pCommand, pParameters, FALSE);
hrParam[2] = AddADOInputDWORDParam(m_ServerID,
L"ServerID", pCommand, pParameters);
hrParam[3] = AddADOInputDWORDParam(pCreateInfo->SessionID,
L"SessionID", pCommand, pParameters);
hrParam[4] = AddADOInputDWORDParam(pCreateInfo->TSProtocol,
L"TSProtocol", pCommand, pParameters);
hrParam[5] = AddADOInputStringParam(pCreateInfo->ApplicationType,
L"AppType", pCommand, pParameters);
hrParam[6] = AddADOInputDWORDParam(pCreateInfo->ResolutionWidth,
L"ResolutionWidth", pCommand, pParameters);
hrParam[7] = AddADOInputDWORDParam(pCreateInfo->ResolutionHeight,
L"ResolutionHeight", pCommand, pParameters);
hrParam[8] = AddADOInputDWORDParam(pCreateInfo->ColorDepth,
L"ColorDepth", pCommand, pParameters);
hrParam[9] = AddADOInputDWORDParam(pCreateInfo->CreateTime.dwLowDateTime,
L"CreateTimeLow", pCommand, pParameters);
hrParam[10] = AddADOInputDWORDParam(pCreateInfo->CreateTime.dwHighDateTime,
L"CreateTimeHigh", pCommand, pParameters);
NumFailed = 0;
for (i = 0; i < NumCreateParams; i++) {
if (SUCCEEDED(hrParam[i])) {
continue;
}
else {
ERR((TB,"NotifyCreate: Failed param create %u", i));
NumFailed++;
hr = hrParam[i];
}
}
if (NumFailed == 0) {
// Execute the command.
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
// If we've not used the connection for awhile, it might
// have been disconnected and the connection object will
// be invalid. Attempt a reopen then reissue the command.
TRC2((TB,"NotifyCreate: Failed cmd, hr=0x%X, retrying",
hr));
m_pConnection->Close();
hr = OpenConnection();
if (SUCCEEDED(hr)) {
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
ERR((TB,"NotifyCreate: Failed exec, hr=0x%X", hr));
}
}
else {
ERR((TB,"NotifyCreate: Failed reopen conn, hr=0x%X",
hr));
}
}
}
pParameters->Release();
pCommand->Release();
}
else {
ERR((TB,"NotifyCreate: Failed create cmd, hr=0x%X", hr));
}
return hr;
}
/****************************************************************************/
// CTSSessionDirectory::NotifyDestroyLocalSession
//
// ITSSessionDirectory function. Removes a session from the session database.
/****************************************************************************/
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::NotifyDestroyLocalSession(
DWORD SessionID)
{
HRESULT hr;
ADOCommand *pCommand;
ADOParameters *pParameters;
ADORecordset *pResultRecordSet;
TRC2((TB,"NotifyDestroyLocalSession, SessionID=%u", SessionID));
hr = CreateADOStoredProcCommand(L"SP_TSSDDeleteSession", &pCommand,
&pParameters);
if (SUCCEEDED(hr)) {
hr = AddADOInputDWORDParam(m_ServerID, L"ServerID",
pCommand, pParameters);
if (SUCCEEDED(hr)) {
hr = AddADOInputDWORDParam(SessionID, L"SessionID", pCommand,
pParameters);
if (SUCCEEDED(hr)) {
// Execute the command.
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
// If we've not used the connection for awhile, it might
// have been disconnected and the connection object will
// be invalid. Attempt a reopen then reissue the command.
TRC2((TB,"NotifyDestroy: Failed cmd, hr=0x%X, retrying",
hr));
m_pConnection->Close();
hr = OpenConnection();
if (SUCCEEDED(hr)) {
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
ERR((TB,"NotifyDestroy: Failed exec, hr=0x%X", hr));
}
}
else {
ERR((TB,"NotifyDestroy: Failed reopen conn, hr=0x%X",
hr));
}
}
}
else {
ERR((TB,"NotifyDestroy: Failed add sessid, hr=0x%X", hr));
}
}
else {
ERR((TB,"NotifyDestroy: Failed add svraddr, hr=0x%X", hr));
}
pParameters->Release();
pCommand->Release();
}
else {
ERR((TB,"NotifyDestroy: Failed create cmd, hr=0x%X", hr));
}
return hr;
}
/****************************************************************************/
// CTSSessionDirectory::NotifyDisconnectLocalSession
//
// ITSSessionDirectory function. Changes the state of an existing session to
// disconnected. The provided time should be returned in disconnected session
// queries performed by any machine in the server pool.
/****************************************************************************/
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::NotifyDisconnectLocalSession(
DWORD SessionID,
FILETIME DiscTime)
{
HRESULT hr;
ADOCommand *pCommand;
ADOParameters *pParameters;
ADORecordset *pResultRecordSet;
TRC2((TB,"NotifyDisconnectLocalSession, SessionID=%u", SessionID));
hr = CreateADOStoredProcCommand(L"SP_TSSDSetSessionDisconnected",
&pCommand, &pParameters);
if (SUCCEEDED(hr)) {
hr = AddADOInputDWORDParam(m_ServerID, L"ServerID",
pCommand, pParameters);
if (SUCCEEDED(hr)) {
hr = AddADOInputDWORDParam(SessionID, L"SessionID", pCommand,
pParameters);
if (SUCCEEDED(hr)) {
hr = AddADOInputDWORDParam(DiscTime.dwLowDateTime,
L"DiscTimeLow", pCommand, pParameters);
if (SUCCEEDED(hr)) {
hr = AddADOInputDWORDParam(DiscTime.dwHighDateTime,
L"DiscTimeHigh", pCommand, pParameters);
if (SUCCEEDED(hr)) {
// Execute the command.
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
// If we've not used the connection for awhile, it
// might have been disconnected and the connection
// object will be invalid. Attempt a reopen then
// reissue the command.
TRC2((TB,"NotifyDisc: Failed cmd, hr=0x%X, "
"retrying", hr));
m_pConnection->Close();
hr = OpenConnection();
if (SUCCEEDED(hr)) {
hr = pCommand->Execute(NULL, NULL,
adCmdStoredProc | adExecuteNoRecords,
&pResultRecordSet);
if (FAILED(hr)) {
ERR((TB,"NotifyDisc: Failed exec, hr=0x%X",
hr));
}
}
else {
ERR((TB,"NotifyDisc: Failed reopen conn, "
"hr=0x%X", hr));
}
}
}
else {
ERR((TB,"NotifyDisconn: Failed add disctimehigh, "
"hr=0x%X", hr));
}
}
else {
ERR((TB,"NotifyDisconn: Failed add disctimelow, hr=0x%X",
hr));
}
}
else {
ERR((TB,"NotifyDisconn: Failed add sessid, hr=0x%X", hr));
}
}
else {
ERR((TB,"NotifyDisconn: Failed add svraddr, hr=0x%X", hr));
}
pParameters->Release();
pCommand->Release();
}
else {
ERR((TB,"NotifyDisconn: Failed create cmd, hr=0x%X", hr));
}
return hr;
}
/****************************************************************************/
// CTSSessionDirectory::NotifyReconnectLocalSession
//
// ITSSessionDirectory function. Changes the state of an existing session
// from disconnected to connected.
/****************************************************************************/
#define NumReconnParams 6
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::NotifyReconnectLocalSession(
TSSD_ReconnectSessionInfo __RPC_FAR *pReconnInfo)
{
HRESULT hr;
HRESULT hrParam[NumReconnParams];
unsigned i, NumFailed;
ADOCommand *pCommand;
ADOParameters *pParameters;
ADORecordset *pResultRecordSet;
TRC2((TB,"NotifyReconnectLocalSession, SessionID=%u",
pReconnInfo->SessionID));
hr = CreateADOStoredProcCommand(L"SP_TSSDSetSessionReconnected",
&pCommand, &pParameters);
if (SUCCEEDED(hr)) {
// Add the 5 parameters.
hrParam[0] = AddADOInputDWORDParam(m_ServerID,
L"ServerID", pCommand, pParameters);
hrParam[1] = AddADOInputDWORDParam(pReconnInfo->SessionID,
L"SessionID", pCommand, pParameters);
hrParam[2] = AddADOInputDWORDParam(pReconnInfo->TSProtocol,
L"TSProtocol", pCommand, pParameters);
hrParam[3] = AddADOInputDWORDParam(pReconnInfo->ResolutionWidth,
L"ResWidth", pCommand, pParameters);
hrParam[4] = AddADOInputDWORDParam(pReconnInfo->ResolutionHeight,
L"ResHeight", pCommand, pParameters);
hrParam[5] = AddADOInputDWORDParam(pReconnInfo->ColorDepth,
L"ColorDepth", pCommand, pParameters);
NumFailed = 0;
for (i = 0; i < NumReconnParams; i++) {
if (SUCCEEDED(hrParam[i])) {
continue;
}
else {
ERR((TB,"NotifyReconn: Failed param create %u", i));
NumFailed++;
hr = hrParam[i];
}
}
if (NumFailed == 0) {
// Execute the command.
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
// If we've not used the connection for awhile, it might
// have been disconnected and the connection object will
// be in a bad state. Close, reopen, and reissue the
// command.
TRC2((TB,"NotifyReconn: Failed exec, hr=0x%X, retrying",
hr));
m_pConnection->Close();
hr = OpenConnection();
if (SUCCEEDED(hr)) {
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
ERR((TB,"NotifyReconn: Failed exec, hr=0x%X", hr));
}
}
else {
ERR((TB,"NotifyReconn: Failed reopen conn, hr=0x%X",
hr));
}
}
}
pParameters->Release();
pCommand->Release();
}
else {
ERR((TB,"NotifyReconn: Failed create cmd, hr=0x%X", hr));
}
return hr;
}
/****************************************************************************/
// CTSSessionDirectory::NotifyReconnectPending
//
// ITSSessionDirectory function. Informs session directory that a reconnect
// is pending soon because of a revectoring. Used by DIS to determine
// when a server might have gone down. (DIS is the Directory Integrity
// Service, which runs on the machine with the session directory.)
//
// This is a two-phase procedure--we first check the fields, and then we
// add the timestamp only if there is no outstanding timestamp already (i.e.,
// the two Almost-In-Time fields are 0). This prevents constant revectoring
// from updating the timestamp fields, which would prevent the DIS from
// figuring out that a server is down.
//
// These two steps are done in the stored procedure to make the operation
// atomic.
/****************************************************************************/
#define NumReconPendParams 3
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::NotifyReconnectPending(
WCHAR *ServerName)
{
HRESULT hr;
HRESULT hrParam[NumReconPendParams];
unsigned NumFailed, i;
FILETIME ft;
SYSTEMTIME st;
ADOCommand *pCommand;
ADOParameters *pParameters;
ADORecordset *pResultRecordSet;
TRC2((TB,"NotifyReconnectPending"));
ASSERT((ServerName != NULL),(TB,"NotifyReconnectPending: NULL ServerName"));
// Get the current system time.
GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);
// Call the stored procedure, which will update the fields if they are 0.
hr = CreateADOStoredProcCommand(L"SP_TSSDSetServerReconnectPending",
&pCommand, &pParameters);
if (SUCCEEDED(hr)) {
// Add the 3 parameters.
hrParam[0] = AddADOInputStringParam(ServerName,
L"ServerAddress", pCommand, pParameters, FALSE);
hrParam[1] = AddADOInputDWORDParam(ft.dwLowDateTime,
L"AlmostTimeLow", pCommand, pParameters);
hrParam[2] = AddADOInputDWORDParam(ft.dwHighDateTime,
L"AlmostTimeHigh", pCommand, pParameters);
NumFailed = 0;
for (i = 0; i < NumReconPendParams; i++) {
if (SUCCEEDED(hrParam[i])) {
continue;
}
else {
ERR((TB,"NotifyReconPending: Failed param create %u", i));
NumFailed++;
hr = hrParam[i];
}
}
if (NumFailed == 0) {
// Execute the command.
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
// If we've not used the connection for awhile, it might
// have been disconnected and the connection object will
// be in a bad state. Close, reopen, and reissue the
// command.
TRC2((TB,"NotifyReconPending: Failed exec, hr=0x%X, retrying",
hr));
m_pConnection->Close();
hr = OpenConnection();
if (SUCCEEDED(hr)) {
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (FAILED(hr)) {
ERR((TB,"NotifyReconPending: Failed exec, hr=0x%X", hr));
}
}
else {
ERR((TB,"NotifyReconPending: Failed reopen conn, hr=0x%X",
hr));
}
}
}
pParameters->Release();
pCommand->Release();
}
else {
ERR((TB,"NotifyReconnectPending: Failed create cmd, hr=0x%X", hr));
}
return hr;
}
HRESULT STDMETHODCALLTYPE CTSSessionDirectory::Repopulate(
DWORD WinStationCount,
TSSD_RepopulateSessionInfo *rsi)
{
return E_NOTIMPL;
}
/****************************************************************************/
// CreateADOStoredProcCommand
//
// Creates and returns a stored proc ADOCommand, plus a ref to its
// associated Parameters.
/****************************************************************************/
HRESULT CTSSessionDirectory::CreateADOStoredProcCommand(
PWSTR CmdName,
ADOCommand **ppCommand,
ADOParameters **ppParameters)
{
HRESULT hr;
BSTR CmdStr;
ADOCommand *pCommand;
ADOParameters *pParameters;
CmdStr = SysAllocString(CmdName);
if (CmdStr != NULL) {
hr = CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_INPROC_SERVER,
IID_IADOCommand25, (LPVOID *)&pCommand);
if (SUCCEEDED(hr)) {
// Set the connection.
hr = pCommand->putref_ActiveConnection(m_pConnection);
if (SUCCEEDED(hr)) {
// Set the command text.
hr = pCommand->put_CommandText(CmdStr);
if (SUCCEEDED(hr)) {
// Set the command type.
hr = pCommand->put_CommandType(adCmdStoredProc);
if (SUCCEEDED(hr)) {
// Get the Parameters pointer from the Command to
// allow appending params.
hr = pCommand->get_Parameters(&pParameters);
if (FAILED(hr)) {
ERR((TB,"Failed getParams for command, "
"hr=0x%X", hr));
goto PostCreateCommand;
}
}
else {
ERR((TB,"Failed set cmdtype for command, hr=0x%X",
hr));
goto PostCreateCommand;
}
}
else {
ERR((TB,"Failed set cmdtext for command, hr=0x%X", hr));
goto PostCreateCommand;
}
}
else {
ERR((TB,"Command::putref_ActiveConnection hr=0x%X", hr));
goto PostCreateCommand;
}
}
else {
ERR((TB,"CoCreate(Command) returned 0x%X", hr));
goto PostAllocCmdStr;
}
SysFreeString(CmdStr);
}
else {
ERR((TB,"Failed to alloc cmd str"));
hr = E_OUTOFMEMORY;
goto ExitFunc;
}
*ppCommand = pCommand;
*ppParameters = pParameters;
return hr;
// Error handling.
PostCreateCommand:
pCommand->Release();
PostAllocCmdStr:
SysFreeString(CmdStr);
ExitFunc:
*ppCommand = NULL;
*ppParameters = NULL;
return hr;
}
/****************************************************************************/
// AddADOInputDWORDParam
//
// Creates and adds to the given ADOParameters object a DWORD-initialized
// parameter value.
/****************************************************************************/
HRESULT CTSSessionDirectory::AddADOInputDWORDParam(
DWORD Param,
PWSTR ParamName,
ADOCommand *pCommand,
ADOParameters *pParameters)
{
HRESULT hr;
CVar varParam;
BSTR ParamStr;
ADOParameter *pParam;
ParamStr = SysAllocString(ParamName);
if (ParamStr != NULL) {
varParam.vt = VT_I4;
varParam.lVal = Param;
hr = pCommand->CreateParameter(ParamStr, adInteger, adParamInput, -1,
varParam, &pParam);
if (SUCCEEDED(hr)) {
hr = pParameters->Append(pParam);
if (FAILED(hr)) {
ERR((TB,"InDWParam: Failed append param %S, hr=0x%X",
ParamName, hr));
}
// ADO will have its own ref for the param.
pParam->Release();
}
else {
ERR((TB,"InDWParam: Failed CreateParam %S, hr=0x%X",
ParamName, hr));
}
SysFreeString(ParamStr);
}
else {
ERR((TB,"InDWParam: Failed alloc paramname"));
hr = E_OUTOFMEMORY;
}
return hr;
}
/****************************************************************************/
// AddADOInputStringParam
//
// Creates and adds to the given ADOParameters object a WSTR-initialized
// parameter value.
/****************************************************************************/
HRESULT CTSSessionDirectory::AddADOInputStringParam(
PWSTR Param,
PWSTR ParamName,
ADOCommand *pCommand,
ADOParameters *pParameters,
BOOL bNullOnNull)
{
HRESULT hr;
CVar varParam;
BSTR ParamStr;
ADOParameter *pParam;
int Len;
ParamStr = SysAllocString(ParamName);
if (ParamStr != NULL) {
// ADO does not seem to like accepting string params that are zero
// length. So, if the string we have is zero length and bNullOnNull says
// we can, we send a null VARIANT type, resulting in a null value at
// the SQL server.
if (wcslen(Param) > 0 || !bNullOnNull) {
hr = varParam.InitFromWSTR(Param);
Len = wcslen(Param);
}
else {
varParam.vt = VT_NULL;
varParam.bstrVal = NULL;
Len = -1;
hr = S_OK;
}
if (SUCCEEDED(hr)) {
hr = pCommand->CreateParameter(ParamStr, adVarWChar, adParamInput,
Len, varParam, &pParam);
if (SUCCEEDED(hr)) {
hr = pParameters->Append(pParam);
if (FAILED(hr)) {
ERR((TB,"InStrParam: Failed append param %S, hr=0x%X",
ParamName, hr));
}
// ADO will have its own ref for the param.
pParam->Release();
}
else {
ERR((TB,"InStrParam: Failed CreateParam %S, hr=0x%X",
ParamName, hr));
}
}
else {
ERR((TB,"InStrParam: Failed alloc variant bstr, "
"param %S, hr=0x%X", ParamName, hr));
}
SysFreeString(ParamStr);
}
else {
ERR((TB,"InStrParam: Failed alloc paramname"));
hr = E_OUTOFMEMORY;
}
return hr;
}
/****************************************************************************/
// CTSSessionDirectory::ExecServerOnline
//
// Encapsulates creation and execution of the SP_TSSDServerOnline
// stored procedure on the server. Assumes that m_ClusterName is already set.
/****************************************************************************/
HRESULT CTSSessionDirectory::ExecServerOnline()
{
HRESULT hr;
ADOCommand *pCommand;
ADOParameters *pParameters;
ADORecordset *pResultRecordSet;
CVar varRows;
CVar varFields;
CVar varStart;
long NumRecords;
if (m_pConnection != NULL) {
// Create the command.
hr = CreateADOStoredProcCommand(L"SP_TSSDServerOnline", &pCommand,
&pParameters);
if (SUCCEEDED(hr)) {
// Server name param.
hr = AddADOInputStringParam(m_LocalServerAddress,
L"ServerAddress", pCommand, pParameters, FALSE);
if (SUCCEEDED(hr)) {
// Cluster name param.
hr = AddADOInputStringParam(m_ClusterName,
L"ClusterName", pCommand, pParameters, TRUE);
if (SUCCEEDED(hr)) {
// Execute the command.
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc,
&pResultRecordSet);
if (SUCCEEDED(hr)) {
TRC2((TB,"ExecOn: Success"));
}
else {
ERR((TB,"Failed exec ServerOnline, hr=0x%X", hr));
}
}
else {
ERR((TB,"ExecOn: Failed adding ClusterName, hr=0x%X", hr));
}
}
else {
ERR((TB,"ExecOn: Failed adding ServerAddress, hr=0x%X",
hr));
}
pParameters->Release();
pCommand->Release();
}
else {
ERR((TB,"ExecOn: Failed create command, hr=0x%X", hr));
}
}
else {
ERR((TB,"ExecOn: Connection invalid"));
hr = E_FAIL;
}
// Parse out the ServerID and ClusterID from the result recordset.
if (SUCCEEDED(hr)) {
long State;
hr = pResultRecordSet->get_State(&State);
if (SUCCEEDED(hr)) {
if (!(State & adStateClosed)) {
VARIANT_BOOL VB;
// If EOF the recordset is empty.
hr = pResultRecordSet->get_EOF(&VB);
if (SUCCEEDED(hr)) {
if (VB) {
TRC1((TB,"ExecOnline: Result recordset EOF"));
hr = E_FAIL;
goto PostUnpackResultSet;
}
}
else {
ERR((TB,"GetUserDisc: Failed get_EOF, hr=0x%X", hr));
goto PostUnpackResultSet;
}
}
else {
ERR((TB,"GetUserDisc: Closed result recordset"));
hr = E_FAIL;
goto PostUnpackResultSet;
}
}
else {
ERR((TB,"GetUserDisc: get_State failed, hr=0x%X", hr));
goto PostUnpackResultSet;
}
// Grab the result data into a safearray, starting with the default
// current row and all fields.
varStart.InitNoParam();
varFields.InitNoParam();
hr = pResultRecordSet->GetRows(1, varStart, varFields, &varRows);
if (SUCCEEDED(hr)) {
NumRecords = 0;
hr = SafeArrayGetUBound(varRows.parray, 2, &NumRecords);
if (SUCCEEDED(hr)) {
// 0-based array bound was returned, num rows is that + 1.
NumRecords++;
ASSERT((NumRecords == 1),
(TB,"ExecOnline: NumRecords %u != expected %u",
NumRecords, 1));
TRC1((TB,"%d rows retrieved from safearray", NumRecords));
}
else {
ERR((TB,"ExecOnline: Failed safearray getubound, hr=0x%X", hr));
goto PostUnpackResultSet;
}
}
else {
ERR((TB,"ExecOnline: Failed to get rows, hr=0x%X", hr));
goto PostUnpackResultSet;
}
// Get the fields.
hr = GetRowArrayDWORDField(varRows.parray, 0, 0, &m_ServerID);
if (SUCCEEDED(hr)) {
hr = GetRowArrayDWORDField(varRows.parray, 0, 1, &m_ClusterID);
if (FAILED(hr)) {
ERR((TB,"ExecOnline: Failed retrieve ClusterID, hr=0x%X", hr));
}
}
else {
ERR((TB,"ExecOnline: Failed retrieve ServerID, hr=0x%X", hr));
}
PostUnpackResultSet:
pResultRecordSet->Release();
}
return hr;
}
/****************************************************************************/
// CTSSessionDirectory::ExecServerOffline
//
// Encapsulates creation and execution of the SP_TSSDServerOffline
// stored procedure on the server.
/****************************************************************************/
HRESULT CTSSessionDirectory::ExecServerOffline()
{
HRESULT hr;
ADOCommand *pCommand;
ADOParameters *pParameters;
ADORecordset *pResultRecordSet;
if (m_pConnection != NULL) {
// Create the command.
hr = CreateADOStoredProcCommand(L"SP_TSSDServerOffline", &pCommand,
&pParameters);
if (SUCCEEDED(hr)) {
// On an offline request, we need fast turn-around since we're
// likely being called when the system is going down. Set the
// timeout value for the command to 2 seconds.
pCommand->put_CommandTimeout(2);
hr = AddADOInputDWORDParam(m_ServerID,
L"ServerID", pCommand, pParameters);
if (SUCCEEDED(hr)) {
// Execute the command.
hr = pCommand->Execute(NULL, NULL, adCmdStoredProc |
adExecuteNoRecords, &pResultRecordSet);
if (SUCCEEDED(hr)) {
TRC2((TB,"ExecOff: Success"));
}
else {
ERR((TB,"Failed exec ServerOffline, hr=0x%X", hr));
}
}
else {
ERR((TB,"ExecOnOff: Failed adding ServerAddress, hr=0x%X",
hr));
}
pParameters->Release();
pCommand->Release();
}
else {
ERR((TB,"ExecOff: Failed create command, hr=0x%X", hr));
}
}
else {
ERR((TB,"ExecOff: Connection invalid"));
hr = E_FAIL;
}
return hr;
}
/* ------------------------------------------------------------------------
Plug-in UI interface for TSCC
------------------------------------------------------------------------*/
/* -------------------------------------------------------------------------------
* describes the name of this entry in server settins
* -------------------------------------------------------------------------------
*/
STDMETHODIMP CTSSessionDirectory::GetAttributeName(/* out */ WCHAR *pwszAttribName)
{
TCHAR szAN[256];
ASSERT((pwszAttribName != NULL),(TB,"NULL attrib ptr"));
LoadString(g_hInstance, IDS_ATTRIBUTE_NAME, szAN, sizeof(szAN) / sizeof(TCHAR));
lstrcpy(pwszAttribName, szAN);
return S_OK;
}
/* -------------------------------------------------------------------------------
* for this component the attribute value would indicate if its enabled or not
* -------------------------------------------------------------------------------
*/
STDMETHODIMP CTSSessionDirectory::GetDisplayableValueName(
/* out */WCHAR *pwszAttribValueName)
{
TCHAR szAvn[256];
ASSERT((pwszAttribValueName != NULL),(TB,"NULL attrib ptr"));
m_fEnabled = IsSessionDirectoryEnabled();
if (m_fEnabled)
LoadString(g_hInstance, IDS_ENABLE, szAvn, sizeof(szAvn) / sizeof(TCHAR));
else
LoadString(g_hInstance, IDS_DISABLE, szAvn, sizeof(szAvn) / sizeof(TCHAR));
lstrcpy(pwszAttribValueName, szAvn);
return S_OK;
}
/* -------------------------------------------------------------------------------
* Custom UI provided here
* pdwStatus informs Terminal Service Config to update termsrv
* -------------------------------------------------------------------------------
*/
STDMETHODIMP CTSSessionDirectory::InvokeUI( /* in */ HWND hParent , /* out */ PDWORD pdwStatus )
{
INT_PTR iRet = DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG_SDS),
hParent, (DLGPROC)CustomUIDlg, (LPARAM)this);
TRC1((TB,"DialogBox returned 0x%x", iRet));
*pdwStatus = ( DWORD )iRet;
return S_OK;
}
/* -------------------------------------------------------------------------------
* Custom menu items -- must be freed by LocalFree
* this is called everytime the user right clicks the listitem
* so you can alter the settings ( i.e. enable to disable and vice versa )
* -------------------------------------------------------------------------------
*/
STDMETHODIMP CTSSessionDirectory::GetMenuItems(
/* out */ int *pcbItems,
/* out */ PMENUEXTENSION *pMex)
{
ASSERT((pcbItems != NULL),(TB,"NULL items ptr"));
*pcbItems = 2;
*pMex = ( PMENUEXTENSION )LocalAlloc( LMEM_FIXED, *pcbItems * sizeof( MENUEXTENSION ) );
if( *pMex != NULL )
{
// display enable or disable
if( m_fEnabled )
{
LoadString(g_hInstance, IDS_DISABLE, (*pMex)[0].MenuItemName,
sizeof((*pMex)[0].MenuItemName) / sizeof(WCHAR));
}
else
{
LoadString(g_hInstance, IDS_ENABLE, (*pMex)[0].MenuItemName,
sizeof((*pMex)[0].MenuItemName) / sizeof(WCHAR));
}
LoadString(g_hInstance, IDS_DESCRIP_ENABLE, (*pMex)[0].StatusBarText,
sizeof((*pMex)[0].StatusBarText) / sizeof(WCHAR));
// menu items id -- this id will be passed back to u in ExecMenuCmd
(*pMex)[0].cmd = IDM_MENU_ENABLE;
LoadString(g_hInstance, IDS_PROPERTIES, (*pMex)[1].MenuItemName,
sizeof((*pMex)[1].MenuItemName) / sizeof(WCHAR));
LoadString(g_hInstance, IDS_DESCRIP_PROPS, (*pMex)[1].StatusBarText,
sizeof((*pMex)[1].StatusBarText) / sizeof(WCHAR));
// menu items id -- this id will be passed back to u in ExecMenuCmd
(*pMex)[1].cmd = IDM_MENU_PROPS;
return S_OK;
}
else
{
return E_OUTOFMEMORY;
}
}
/* -------------------------------------------------------------------------------
* When the user selects a menu item the cmd id is passed to this component.
* the provider ( which is us )
* -------------------------------------------------------------------------------
*/
STDMETHODIMP CTSSessionDirectory::ExecMenuCmd(
/* in */ UINT cmd,
/* in */ HWND hParent ,
/* out*/ PDWORD pdwStatus )
{
switch (cmd) {
case IDM_MENU_ENABLE:
m_fEnabled = m_fEnabled ? 0 : 1;
TRC1((TB,"%ws was selected", m_fEnabled ? L"Disable" : L"Enable"));
if( SetSessionDirectoryState( m_fEnabled ) == ERROR_SUCCESS )
{
*pdwStatus = UPDATE_TERMSRV_SESSDIR;
}
break;
case IDM_MENU_PROPS:
INT_PTR iRet = DialogBoxParam(g_hInstance,
MAKEINTRESOURCE(IDD_DIALOG_SDS),
hParent,
(DLGPROC)CustomUIDlg,
(LPARAM)this);
*pdwStatus = ( DWORD )iRet;
}
return S_OK;
}
/* -------------------------------------------------------------------------------
* Tscc provides a default help menu item, when selected this method is called
* if we want tscc to handle ( or provide ) help return any value other than zero
* for those u can't follow logic return zero if you're handling help.
* -------------------------------------------------------------------------------
*/
STDMETHODIMP CTSSessionDirectory::OnHelp( /* out */ int *piRet)
{
ASSERT((piRet != NULL),(TB,"NULL ret ptr"));
*piRet = 0;
return S_OK;
}
/* -------------------------------------------------------------------------------
* IsSessionDirectoryEnabled returns a bool
* -------------------------------------------------------------------------------
*/
BOOL CTSSessionDirectory::IsSessionDirectoryEnabled()
{
LONG lRet;
HKEY hKey;
DWORD dwEnabled = 0;
DWORD dwSize = sizeof(DWORD);
lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
REG_CONTROL_TSERVER,
0,
KEY_READ,
&hKey);
if (lRet == ERROR_SUCCESS)
{
lRet = RegQueryValueEx( hKey ,
REG_TS_SESSDIRACTIVE,
NULL ,
NULL ,
( LPBYTE )&dwEnabled ,
&dwSize );
RegCloseKey( hKey );
}
return ( BOOL )dwEnabled;
}
/* -------------------------------------------------------------------------------
* SetSessionDirectoryState - sets SessionDirectoryActive regkey to bVal
* -------------------------------------------------------------------------------
*/
DWORD CTSSessionDirectory::SetSessionDirectoryState( BOOL bVal )
{
LONG lRet;
HKEY hKey;
DWORD dwSize = sizeof( DWORD );
lRet = RegOpenKeyEx(
HKEY_LOCAL_MACHINE ,
REG_CONTROL_TSERVER ,
0,
KEY_WRITE,
&hKey );
if (lRet == ERROR_SUCCESS)
{
lRet = RegSetValueEx( hKey ,
REG_TS_SESSDIRACTIVE,
0,
REG_DWORD ,
( LPBYTE )&bVal ,
dwSize );
RegCloseKey( hKey );
}
else
{
ErrorMessage( NULL , IDS_ERROR_TEXT3 , ( DWORD )lRet );
}
return ( DWORD )lRet;
}
/* -------------------------------------------------------------------------------
* ErrorMessage --
* -------------------------------------------------------------------------------
*/
void CTSSessionDirectory::ErrorMessage( HWND hwnd , UINT res , DWORD dwStatus )
{
TCHAR tchTitle[ 64 ];
TCHAR tchText[ 64 ];
TCHAR tchErrorMessage[ 256 ];
LPTSTR pBuffer = NULL;
// report error
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, //ignored
( DWORD )dwStatus, //message ID
MAKELANGID( LANG_NEUTRAL, SUBLANG_NEUTRAL ), //message language
(LPTSTR)&pBuffer, //address of buffer pointer
0, //minimum buffer size
NULL);
LoadString(g_hInstance, IDS_ERROR_TITLE, tchTitle, sizeof(tchTitle) / sizeof(TCHAR));
LoadString(g_hInstance, res, tchText, sizeof(tchText) / sizeof(TCHAR));
wsprintf( tchErrorMessage , tchText , pBuffer );
::MessageBox(hwnd, tchErrorMessage, tchTitle, MB_OK | MB_ICONINFORMATION);
}
/* -------------------------------------------------------------------------------
* Custom UI msg handler dealt with here
* -------------------------------------------------------------------------------
*/
INT_PTR CALLBACK CustomUIDlg(HWND hwnd, UINT umsg, WPARAM wp, LPARAM lp)
{
static BOOL s_fServerNameChanged;
static BOOL s_fClusterNameChanged;
static BOOL s_fOpaqueStringChanged;
static BOOL s_fPreviousButtonState;
CTSSessionDirectory *pCTssd;
switch (umsg)
{
case WM_INITDIALOG:
{
pCTssd = ( CTSSessionDirectory * )lp;
SetWindowLongPtr( hwnd , DWLP_USER , ( LONG_PTR )pCTssd );
SendMessage( GetDlgItem( hwnd , IDC_EDIT_SERVERNAME ) ,
EM_LIMITTEXT ,
( WPARAM )64 ,
0 );
SendMessage( GetDlgItem( hwnd , IDC_EDIT_CLUSTERNAME ) ,
EM_LIMITTEXT ,
( WPARAM )64 ,
0 );
SendMessage( GetDlgItem( hwnd , IDC_EDIT_ACCOUNTNAME ) ,
EM_LIMITTEXT ,
( WPARAM )64 ,
0 );
SendMessage( GetDlgItem( hwnd , IDC_EDIT_PASSWORD ) ,
EM_LIMITTEXT ,
( WPARAM )64 ,
0 );
LONG lRet;
HKEY hKey;
TCHAR szString[ 256 ];
DWORD cbData = sizeof( szString );
lRet = RegOpenKeyEx( HKEY_LOCAL_MACHINE ,
REG_TS_CLUSTERSETTINGS ,
0,
KEY_READ | KEY_WRITE ,
&hKey );
if( lRet == ERROR_SUCCESS )
{
lRet = RegQueryValueEx(hKey ,
REG_TS_CLUSTER_STORESERVERNAME,
NULL ,
NULL ,
( LPBYTE )szString ,
&cbData );
if( lRet == ERROR_SUCCESS )
{
SetWindowText( GetDlgItem( hwnd , IDC_EDIT_SERVERNAME ) , szString );
}
cbData = sizeof( szString );
lRet = RegQueryValueEx(hKey,
REG_TS_CLUSTER_CLUSTERNAME,
NULL,
NULL,
(LPBYTE)szString,
&cbData);
if( lRet == ERROR_SUCCESS )
{
SetWindowText(GetDlgItem(hwnd, IDC_EDIT_CLUSTERNAME), szString);
}
cbData = 0;
lRet = RegQueryValueEx( hKey ,
REG_TS_CLUSTER_OPAQUESETTINGS,
NULL ,
NULL ,
(LPBYTE)NULL,
&cbData);
if( lRet == ERROR_SUCCESS )
{
pCTssd->m_pszOpaqueString = ( LPTSTR )LocalAlloc( LMEM_FIXED , cbData );
if( pCTssd->m_pszOpaqueString != NULL )
{
lRet = RegQueryValueEx( hKey ,
REG_TS_CLUSTER_OPAQUESETTINGS,
NULL ,
NULL ,
(LPBYTE)pCTssd->m_pszOpaqueString ,
&cbData );
}
else
{
lRet = ERROR_OUTOFMEMORY;
}
}
if( lRet == ERROR_SUCCESS )
{
// jump to user_id
TCHAR tchUserId[64] = { 0 };
TCHAR tchPassword[64] = { 0 };
LPTSTR pszUserId = tchUserId;
LPTSTR pszPassword = tchPassword;
FindSqlValue( pCTssd->m_pszOpaqueString , TEXT("User Id"), pszUserId );
strtrim( &pszUserId );
FindSqlValue( pCTssd->m_pszOpaqueString , TEXT("Password"), pszPassword );
strtrim( &pszPassword );
SetWindowText( GetDlgItem( hwnd , IDC_EDIT_ACCOUNTNAME ) , pszUserId );
SetWindowText( GetDlgItem( hwnd , IDC_EDIT_PASSWORD ) , pszPassword );
}
RegCloseKey(hKey);
}
else
{
if( pCTssd != NULL )
{
pCTssd->ErrorMessage( hwnd , IDS_ERROR_TEXT , ( DWORD )lRet );
}
EndDialog(hwnd, lRet);
}
if( pCTssd != NULL )
{
BOOL bEnable;
bEnable = pCTssd->IsSessionDirectoryEnabled();
CheckDlgButton( hwnd , IDC_CHECK_ENABLE , bEnable ? BST_CHECKED : BST_UNCHECKED );
s_fPreviousButtonState = bEnable;
EnableWindow(GetDlgItem(hwnd, IDC_EDIT_SERVERNAME), bEnable);
EnableWindow(GetDlgItem(hwnd, IDC_EDIT_CLUSTERNAME), bEnable);
EnableWindow(GetDlgItem(hwnd, IDC_EDIT_ACCOUNTNAME), bEnable);
EnableWindow(GetDlgItem(hwnd, IDC_EDIT_PASSWORD), bEnable);
EnableWindow(GetDlgItem(hwnd, IDC_STATIC_SQLNAME), bEnable);
EnableWindow(GetDlgItem(hwnd, IDC_STATIC_CLUSTERNAME), bEnable);
EnableWindow(GetDlgItem(hwnd, IDC_STATIC_SQLACCOUNT), bEnable);
EnableWindow(GetDlgItem(hwnd, IDC_STATIC_SQLPWD), bEnable);
}
s_fServerNameChanged = FALSE;
s_fClusterNameChanged = FALSE;
s_fOpaqueStringChanged = FALSE;
}
break;
case WM_COMMAND:
if( LOWORD( wp ) == IDCANCEL )
{
pCTssd = ( CTSSessionDirectory * )GetWindowLongPtr( hwnd , DWLP_USER );
if( pCTssd->m_pszOpaqueString != NULL )
{
LocalFree( pCTssd->m_pszOpaqueString );
}
EndDialog(hwnd , 0);
}
else if( LOWORD( wp ) == IDOK )
{
BOOL bEnabled;
DWORD dwRetStatus = 0;
pCTssd = ( CTSSessionDirectory * )GetWindowLongPtr(hwnd, DWLP_USER);
bEnabled = IsDlgButtonChecked( hwnd , IDC_CHECK_ENABLE ) == BST_CHECKED;
if( bEnabled != s_fPreviousButtonState )
{
DWORD dwStatus;
TRC1((TB,"EnableButtonChanged"));
dwStatus = pCTssd->SetSessionDirectoryState( bEnabled );
if( dwStatus != ERROR_SUCCESS )
{
return 0;
}
dwRetStatus = UPDATE_TERMSRV_SESSDIR;
}
if( s_fServerNameChanged || s_fClusterNameChanged || s_fOpaqueStringChanged )
{
HKEY hKey;
LONG lRet = RegOpenKeyEx( HKEY_LOCAL_MACHINE ,
REG_TS_CLUSTERSETTINGS ,
0,
KEY_READ | KEY_WRITE ,
&hKey );
if( lRet == ERROR_SUCCESS )
{
TCHAR szName[ 64 ];
if( s_fServerNameChanged )
{
TRC1((TB,"SQLServerNameChanged" )) ;
GetWindowText( GetDlgItem( hwnd , IDC_EDIT_SERVERNAME ) , szName , sizeof( szName ) / sizeof( TCHAR ) );
RegSetValueEx( hKey ,
REG_TS_CLUSTER_STORESERVERNAME,
0,
REG_SZ,
( CONST LPBYTE )szName ,
sizeof( szName ) );
}
if( s_fClusterNameChanged )
{
TRC1((TB,"ClusterNameChanged"));
GetWindowText( GetDlgItem( hwnd , IDC_EDIT_CLUSTERNAME ) , szName , sizeof( szName ) / sizeof( TCHAR ) );
RegSetValueEx( hKey ,
REG_TS_CLUSTER_CLUSTERNAME,
0,
REG_SZ,
( CONST LPBYTE )szName ,
sizeof( szName ) );
}
if( s_fOpaqueStringChanged )
{
TRC1((TB,"OpaqueStringChanged" )) ;
LPTSTR pszNewOpaqueString = NULL;
LPTSTR pszName = NULL;
GetWindowText( GetDlgItem( hwnd , IDC_EDIT_ACCOUNTNAME ) , szName , sizeof( szName ) / sizeof( TCHAR ) );
pszName = szName;
strtrim( &pszName );
ModifySqlValue( &pCTssd->m_pszOpaqueString , L"User Id" , pszName );
GetWindowText( GetDlgItem( hwnd , IDC_EDIT_PASSWORD ) , szName , sizeof( szName ) / sizeof( TCHAR ) );
pszName = szName;
strtrim( &pszName );
if( ModifySqlValue( &pCTssd->m_pszOpaqueString , L"Password" , pszName ) != NULL )
{
RegSetValueEx( hKey ,
REG_TS_CLUSTER_OPAQUESETTINGS,
0,
REG_SZ,
( CONST LPBYTE )pCTssd->m_pszOpaqueString ,
lstrlen( pCTssd->m_pszOpaqueString ) * sizeof( TCHAR ) );
}
}
RegCloseKey(hKey);
dwRetStatus = UPDATE_TERMSRV_SESSDIR;
}
else
{
pCTssd->ErrorMessage(hwnd , IDS_ERROR_TEXT2 , (DWORD)lRet);
return 0;
}
}
if( pCTssd->m_pszOpaqueString != NULL )
{
LocalFree( pCTssd->m_pszOpaqueString );
}
EndDialog( hwnd , ( INT_PTR )dwRetStatus );
}
else
{
switch (HIWORD(wp))
{
case EN_CHANGE:
if( LOWORD( wp ) == IDC_EDIT_SERVERNAME )
{
s_fServerNameChanged = TRUE;
}
else if( LOWORD( wp ) == IDC_EDIT_CLUSTERNAME )
{
s_fClusterNameChanged = TRUE;
}
else if( LOWORD( wp ) == IDC_EDIT_ACCOUNTNAME || LOWORD( wp ) == IDC_EDIT_PASSWORD )
{
s_fOpaqueStringChanged = TRUE;
}
break;
case BN_CLICKED:
if( LOWORD( wp ) == IDC_CHECK_ENABLE)
{
BOOL bEnable;
if( IsDlgButtonChecked( hwnd , IDC_CHECK_ENABLE ) == BST_CHECKED )
{
// enabled all controls
bEnable = TRUE;
}
else
{
// disable all controls
bEnable = FALSE;
}
// set flags
s_fServerNameChanged = bEnable;
s_fClusterNameChanged = bEnable;
s_fOpaqueStringChanged = bEnable;
EnableWindow( GetDlgItem( hwnd , IDC_EDIT_SERVERNAME ) , bEnable );
EnableWindow( GetDlgItem( hwnd , IDC_EDIT_CLUSTERNAME ) , bEnable );
EnableWindow( GetDlgItem( hwnd , IDC_EDIT_ACCOUNTNAME ) , bEnable );
EnableWindow( GetDlgItem( hwnd , IDC_EDIT_PASSWORD ) , bEnable );
EnableWindow( GetDlgItem( hwnd , IDC_STATIC_SQLNAME ) , bEnable );
EnableWindow( GetDlgItem( hwnd , IDC_STATIC_CLUSTERNAME ) , bEnable );
EnableWindow( GetDlgItem( hwnd , IDC_STATIC_SQLACCOUNT ) , bEnable );
EnableWindow( GetDlgItem( hwnd , IDC_STATIC_SQLPWD ) , bEnable );
}
break;
}
}
break;
}
return 0;
}
/********************************************************************************************
[in ] lpString is the buffer containing the OpaqueSettings
[in ] lpKeyName is the field name within the OpaqueSettings string
[out] pszValue is a buffer that will contain the field name value
Ret: None
*******************************************************************************************/
void FindSqlValue(LPTSTR lpString, LPTSTR lpKeyName, LPTSTR pszValue)
{
int i;
LPTSTR lpszStart = lpString;
LPTSTR lpszTemp;
UINT nKeyName;
if( lpString != NULL && lpKeyName != NULL )
{
// find field name
lpString = FindField( lpString , lpKeyName );
if( *lpString != 0 )
{
i = 0;
while( *lpString != 0 && *lpString != ( TCHAR )';' )
{
pszValue[i] = *lpString;
i++;
lpString++;
}
pszValue[ i ] = 0;
}
}
}
/********************************************************************************************
[in/out ] lpszOpaqueSettings is the buffer containing the OpaqueSettings
[in ] lpKeyName is the field name within the OpaqueSettings string
[in ] lpszNewValue contains the value that will replace the original value in the field
Ret: A new OpaqueSetting string is constructed and must be freed with LocalFree
********************************************************************************************/
LPTSTR ModifySqlValue( LPTSTR* lppszOpaqueSettings , LPTSTR lpszKeyName , LPTSTR lpszNewValue )
{
LPTSTR szEndPos = NULL;
LPTSTR szSecondPos = NULL;
LPTSTR pszNewSettings = NULL;
LPTSTR lpszOpaqueSettings = *lppszOpaqueSettings;
LPTSTR pszTempSettings = lpszOpaqueSettings;
UINT cbSize = 0;
//a ) find value
//b ) set pos2 after ';'
//c ) set endpos1 after '=' to null
//d ) create a buffer the length of first string + value + ; + second string
//e ) strcpy first string + value + ; + second string
//f ) return buffer
if( lpszKeyName != NULL && lpszOpaqueSettings != NULL )
{
szEndPos = FindField( lpszOpaqueSettings , lpszKeyName );
if( *szEndPos != 0 )
{
lpszOpaqueSettings = szEndPos;
while( *lpszOpaqueSettings != 0 )
{
if( *lpszOpaqueSettings == ( TCHAR )';' )
{
szSecondPos = lpszOpaqueSettings + 1;
break;
}
lpszOpaqueSettings++;
}
*szEndPos = 0;
cbSize = lstrlen( pszTempSettings );
cbSize += lstrlen( lpszNewValue );
cbSize += 2; // for the semicolon and null
if( szSecondPos != NULL && *szSecondPos != 0 )
{
cbSize += lstrlen( szSecondPos );
}
pszNewSettings = ( LPTSTR )LocalAlloc( LMEM_FIXED , cbSize * sizeof( TCHAR ) );
if( pszNewSettings != NULL )
{
lstrcpy( pszNewSettings , pszTempSettings );
lstrcat( pszNewSettings , lpszNewValue );
lstrcat( pszNewSettings , TEXT( ";" ) );
if( szSecondPos != NULL )
{
lstrcat( pszNewSettings , szSecondPos );
}
LocalFree( pszTempSettings );
*lppszOpaqueSettings = pszNewSettings;
}
}
else
{
// we're here because either the field name didnot exist or is unattainable
// so we're slapping the field name and value at the end.
cbSize = lstrlen( pszTempSettings );
// add the size of the keyname and = and ;
cbSize += lstrlen( lpszKeyName ) + 2;
// add the new value
cbSize += lstrlen( lpszNewValue ) + 1;
pszNewSettings = ( LPTSTR )LocalAlloc( LMEM_FIXED , cbSize * sizeof( TCHAR ) );
if( pszNewSettings != NULL )
{
lstrcpy( pszNewSettings , pszTempSettings );
lstrcat( pszNewSettings , lpszKeyName );
lstrcat( pszNewSettings , TEXT( "=" ) );
lstrcat( pszNewSettings , lpszNewValue );
lstrcat( pszNewSettings , TEXT( ";" ) );
LocalFree( pszTempSettings );
*lppszOpaqueSettings = pszNewSettings;
}
}
}
return pszNewSettings;
}
/********************************************************************************************
FindField -- greps the OpaqueString passed in
pszString and searches for field name in pszKeyName
[ in ] pszString - OpaqueString
[ in ] pszKeyName - field name
ret: the position of the field value ( after the " = " )
*******************************************************************************************/
LPTSTR FindField( LPTSTR pszString , LPTSTR pszKeyName )
{
LPTSTR lpszStart = pszString;
LPTSTR lpszTemp;
LPTSTR lpszFieldName;
UINT nKeyName;
// find field name
nKeyName = lstrlen( pszKeyName );
while( *pszString != 0 )
{
while( *pszString != 0 && *pszString != ( TCHAR )'=' )
{
pszString++;
}
// ok move backwards to check for name
if( *pszString != 0 )
{
lpszTemp = pszString - 1;
while( lpszStart <= lpszTemp )
{
if( IsCharAlphaNumeric( *lpszTemp ) )
{
break;
}
lpszTemp--;
}
lpszFieldName = ( lpszTemp - nKeyName + 1 );
if( lpszStart <= lpszFieldName && _tcsncicmp( lpszFieldName , pszKeyName , nKeyName ) == 0 )
{
// found the name skip '='
pszString++;
break;
}
}
pszString++;
}
return pszString;
}
/*********************************************************************************************
* borrowed from Ting Cai (tingcai) with slight modifications
* net\upnp\ssdp\common\ssdpparser\parser.cpp
*
********************************************************************************************/
VOID strtrim( TCHAR **pszStr)
{
TCHAR *end;
TCHAR *begin;
begin = *pszStr;
end = begin + lstrlen( *pszStr ) - 1;
while (*begin == ( TCHAR )' ' || *begin == ( TCHAR )'\t')
{
begin++;
}
*pszStr = begin;
while (*end == ( TCHAR )' ' || *end == ( TCHAR )'\t')
{
end--;
}
*(end+1) = '\0';
}