//***********************************************************************
// remote.cpp
//
// This file contains code required to expand environment variables in the
// context of a remote machine. It does this by reading the environment variables
// from the remote machine's registry and caching them here.
//
// This file also contains code required to map local paths read out of the
// remote machine's registry into UNC paths such that c:\foo will be mapped
// to \\machine\c$\foo
//
// Author: Larry A. French
//
// History:
//      19-April-1996     Larry A. French
//          Wrote it.
//
// Copyright (C) 1995, 1996 Microsoft Corporation.  All rights reserved.
//
//************************************************************************


#include "stdafx.h"
#include "remote.h"
#include "trapreg.h"
#include "regkey.h"




CEnvCache::CEnvCache()
{
}


//*****************************************************************
// CEnvCache::GetEnvironmentVars
//
// Read the system environment variables for the remote machine out
// of its registry.
//
// Parameters:
//    LPCTSTR pszMachine
//          Pointer to the remote machine's name.
//
//    CMapStringToString* pmapVars
//          This string to string map is where the environment variables
//          for the machine are returned.
//
// Returns:
//    SCODE
//          S_OK if everything was successful, otherwise E_FAIL.
//
//****************************************************************
SCODE CEnvCache::GetEnvironmentVars(LPCTSTR pszMachine, CMapStringToString* pmapVars)
{
    CRegistryKey regkey;        // SYSTEM\CurrentControlSet\Services\EventLogs
    CRegistryValue regval;

    static TCHAR* apszNames1[] = {
        _T("SourcePath"),
        _T("SystemRoot")
    };

    if (regkey.Connect(pszMachine) != ERROR_SUCCESS) {
        goto CONNECT_FAILURE;
    }


    // First pick up the values for the SourcePath and SystemRoot environment variables and anything elese in
    // apszNames1.
    LONG nEntries;
    LONG iEntry;
    if (regkey.Open(_T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), KEY_READ ) == ERROR_SUCCESS) {
        nEntries = sizeof(apszNames1) / sizeof(TCHAR*);
        for (iEntry=0; iEntry<nEntries; ++iEntry) {
            if (regkey.GetValue(apszNames1[iEntry], regval)) {
                pmapVars->SetAt(apszNames1[iEntry], (LPCTSTR) regval.m_pData);
            }
        }
        regkey.Close();
    }

    // Now get the rest of the environment variables.
    if (regkey.Open(_T("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"), KEY_READ ) == ERROR_SUCCESS) {
        CStringArray* pasValues = regkey.EnumValues();
        if (pasValues != NULL) {
            nEntries = (LONG)pasValues->GetSize();
            for (iEntry=0; iEntry< nEntries; ++iEntry) {
                CString sValueName =  pasValues->GetAt(iEntry);
                if (regkey.GetValue(sValueName, regval)) {
                    pmapVars->SetAt(sValueName, (LPCTSTR) regval.m_pData);
                }
            }
        }
        regkey.Close();
    }
    return S_OK;

CONNECT_FAILURE:
	return E_FAIL;
}


SCODE CEnvCache::AddMachine(LPCTSTR pszMachine)
{
	CMapStringToString* pmapVars;
	if (m_mapMachine.Lookup(pszMachine, (CObject*&) pmapVars)) {
		// The machine already has an entry, so don't add another
		return E_FAIL;
	}

	pmapVars = new CMapStringToString;
	m_mapMachine.SetAt(pszMachine, pmapVars);

	SCODE sc = GetEnvironmentVars(pszMachine, pmapVars);

	return sc;
}


//******************************************************************
// CEnvCache::Lookup
//
// Lookup an environment variable on the specified machine.
//
// Parameters:
//		LPCTSTR pszMachineName
//			Pointer to the machine name string.
//
//		LPCTSTR pszName
//			Pointer to the name of the environment variable to lookup.
//
//		CString& sValue
//			This is a reference to the place where the environment varaible's
//			value is returned.
//
//
// Returns:
//		SCODE
//			S_OK if the environment variable was found.
//			E_FAIL if the environment varaible was not found.
//
//*******************************************************************
SCODE CEnvCache::Lookup(LPCTSTR pszMachineName, LPCTSTR pszName, CString& sResult)
{
	SCODE sc;
	CMapStringToString* pmapVars;
	// Get a pointer to the machine's cached map of environment variable values.
	// If the map hasn't been loaded yet, do so now and try to get its map again.
	if (!m_mapMachine.Lookup(pszMachineName, (CObject*&) pmapVars)) {
		sc = AddMachine(pszMachineName);
		if (FAILED(sc)) {
			return sc;
		}
		if (!m_mapMachine.Lookup(pszMachineName, (CObject*&) pmapVars)) {
			ASSERT(FALSE);
		}
	}

	// Look for the variable name in the environment name map
	if (pmapVars->Lookup(pszName, sResult)) {
		return S_OK;
	}
	else {
		return E_FAIL;
	}
}



//****************************************************************
// RemoteExpandEnvStrings
//
// Epand a string that may contain environment variables in the
// context of a remote machine.
//
// Parameters:
//		LPCTSTR pszComputerName
//			A pointer to the name of the remote machine.
//
//		CEnvCache& cache
//			The environment variable cache for all machines.  Note: the
//			cached values for a particular machine are loaded when there
//			is a reference to the machine.
//
//		CString& sValue
//			The string to expand.  This string is expanded in-place such
//			that on return, the string will contain the expanded values.
//
// Returns:
//		SCODE
//			S_OK if all strings were expanded
//
//******************************************************************
SCODE RemoteExpandEnvStrings(LPCTSTR pszComputerName, CEnvCache& cache, CString& sValue)
{
	SCODE sc = S_OK;
    LPCTSTR psz = sValue;
	TCHAR ch;
	CString sEnvVarName;
	CString sEnvVarValue;
	CString sResult;
	LPCTSTR pszPercent = NULL;
    while (ch = *psz++) {
        if (ch == _T('%')) {
			pszPercent = psz - 1;

			sEnvVarName = _T("");
            while (ch = *psz) {
				++psz;
                if (ch == _T('%')) {
					SCODE sc;
					sc = cache.Lookup(pszComputerName, sEnvVarName, sEnvVarValue);
					if (SUCCEEDED(sc)) {
						sResult += sEnvVarValue;
						pszPercent = NULL;
					}
					else {
						// If any environment variable is not found, then fail.
						sc = E_FAIL;
					}
					break;
                }
                if (iswspace(ch) || ch==_T(';')) {
                    break;
                }
                sEnvVarName += ch;
            }

			if (pszPercent != NULL) {
				// Control comes here if the opening percent was not matched by a closing
				// percent.
				while(pszPercent < psz) {
					sResult += *pszPercent++;
				}
			}

        }
		else {
			sResult += ch;
		}
    }

	sValue = sResult;		
	return sc;
	
}


//************************************************************
// SplitComplexPath
//
// Split a complex path consisting of several semicolon separated
// paths into separate paths and return them in a string array.
//
// Parameters:
//		LPCTSTR pszComplexPath
//			Pointer to the path that may or may not be composed of
//			several semicolon separated paths.
//
//		CStringArray& saPath
//			The place to return the split paths.
//
// Returns:
//		The individual paths are returned via saPath
//
//*************************************************************
void SplitComplexPath(LPCTSTR pszComplexPath, CStringArray& saPath)
{
	CString sPath;
	while (*pszComplexPath) {
		sPath.Empty();
		while (isspace(*pszComplexPath))  {
			++pszComplexPath;
		}

		while (*pszComplexPath &&
			   (*pszComplexPath != _T(';'))) {
			sPath += *pszComplexPath++;
		}

		if (!sPath.IsEmpty()) {
			saPath.Add(sPath);
		}

		if (*pszComplexPath==_T(';')) {
			++pszComplexPath;
		}
	}
}



//**************************************************************************
// MapPathToUNC
//
// Map a path to the UNC equivallent. Note that this method assumes that
// for each path containing a drive letter that the target machine will have
// the path shared out.  For example, if the path contains a "c:\foodir" prefix, then
// then you can get to "foodir" bygenerating the "\\machine\c$\foodir" path.
//
// Parameters:
//		LPCTSTR pszMachineName
//			Pointer to the machine name.
//
//		CString& sPath
//			Pointer to the path to map.  Upon return, this string will contain
//			the mapped path.
//
// Returns:
//		SCODE
//			S_OK if successful
//			E_FAIL if something went wrong.
//
//**************************************************************************
SCODE MapPathToUNC(LPCTSTR pszMachineName, CString& sPath)
{
	CStringArray saPaths;
	SplitComplexPath(sPath, saPaths);
	sPath.Empty();
	
	
	LPCTSTR pszPath = sPath.GetBuffer(sPath.GetLength() + 1);
	LONG nPaths = (LONG)saPaths.GetSize();
	SCODE sc = S_OK;
	for (LONG iPath=0; iPath < nPaths; ++iPath) {
		pszPath = saPaths[iPath];

		if (isalpha(pszPath[0]) && pszPath[1]==_T(':')) {
			CString sResult;
			sResult += _T("\\\\");
			sResult += pszMachineName;
			sResult += _T('\\');
			sResult += pszPath[0];		// Drive letter
			sResult += _T("$\\");		
			pszPath += 2;
			if (pszPath[0]==_T('\\')) {
				++pszPath;
			}
			sResult += pszPath;
			saPaths[iPath] = sResult;
		}
		else {
			sc = E_FAIL;
		}
		sPath += saPaths[iPath];
		if (iPath < nPaths - 1) {
			sPath += _T("; ");
		}
	}
	return sc;
}